介紹如何透過 n8n 製作一個自動語音天氣預報機器人,我會抓取氣象署的 API,接著透過 AI 轉換成語音後發送到 Line。
Step 1: 建立初始節點
這邊可以用 Schedule Trigger,這樣之後就可以自動生成了。
Step 2: 抓取氣象資訊
接著建立 Http Request 節點,我是抓 36 小時天氣預報,並依序填入:
- Method: POST
- URL: https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001
- Send Query Parameters
- Authorization: 你的氣象署 Open Data API
- locationName: 目標地區
- format: JSON
Step 3: 整理氣象資訊
要發送給 TTS 的話要先把內容給整理好,由於氣象資訊可以直接組合成一個語句,所以就不透過大語言模型,我直接建一個 Code 節點來處理資訊:
const location = items[0].json.records.location[0]; const elements = Object.fromEntries(location.weatherElement.map(e => [e.elementName, e])); // 找出所有要素中最早的時間段 const allStarts = location.weatherElement .flatMap(e => (e.time || []).map(t => t.startTime)) .filter(Boolean); const targetStart = allStarts.sort()[0]; // 取指定時間段參數 function pickByStart(el, start) { if (!el || !Array.isArray(el.time)) return null; const hit = el.time.find(t => t.startTime === start) || el.time[0]; return hit ? { value: hit.parameter?.parameterName ?? null, start: hit.startTime, end: hit.endTime } : null; } function pickPoP(start) { const candidates = [elements.PoP, elements.PoP6h, elements.PoP12h]; for (const el of candidates) { const res = pickByStart(el, start); if (res) return res; } return null; } const wx = pickByStart(elements.Wx, targetStart); const pop = pickPoP(targetStart); const minT = pickByStart(elements.MinT, targetStart); const maxT = pickByStart(elements.MaxT, targetStart); // 安全解析時間字串(避免 new Date(YYYY-MM-DD HH:mm:ss) 解析誤差) function parseDateStr(str) { if (!str) return null; const [date, time] = str.split(' '); const [y, m, d] = date.split('-').map(Number); const [hh='0', mm='0', ss='0'] = (time || '').split(':'); return new Date(Number(y), Number(m)-1, Number(d), Number(hh), Number(mm), Number(ss)); } // 格式:M月D日 + (凌晨/早上/下午/晚上) + 12小時制 function formatTime(str) { if (!str) return ""; const d = parseDateStr(str); const M = d.getMonth() + 1; const D = d.getDate(); const h = d.getHours(); let label = ""; if (h < 6) label = "凌晨"; else if (h < 12) label = "早上"; else if (h < 18) label = "下午"; else label = "晚上"; // 12 小時制(18 -> 6,13 -> 1;特別處理 0 點顯示為 0 點) let h12 = h % 12 || 12; if (h === 0) h12 = 0; return `${M}月${D}日 ${label}${h12}點`; } const startStr = formatTime(wx?.start || targetStart); const endStr = formatTime(wx?.end); // 只輸出口語化 message return [{ json: { message: `${startStr} 到 ${endStr},${location.locationName}的天氣預報是 ${wx?.value || "—"},氣溫在 ${minT?.value || "—"} 到 ${maxT?.value || "—"} 度之間,降雨機率大約 ${pop?.value || "—"}%。` } }];
Step 4: 透過 TTS 轉成語音
這裡你可以用 OpenAI 節點,但我發現預設節點沒有比較新的 TTS 模型,所以我就改用自訂 API 的方式去呼叫,我這邊建立一個 Http Request 節點,並設定:
- Method: POST
- URL: https://api.openai.com/v1/audio/speech
- Send Header 打開
- Authorization: Bearer 你的OpenAI API token
- Content-Type: application/json
- Send Body 打開
- Body Content Type: JSON
- Specify Body: Using JSON
{ "model": "gpt-4o-mini-tts", "input": "{{ $json.message }}", "instructions": "請用台灣本地口音的標準國語發音,語氣自然親切,避免兒化音,使用台灣慣用詞。", "audio": { "voice": "verse", "format": "mp3" } }
這裡我就是把前一個節點整理的文字丟給 OpenAI,我使用的是 gpt-4o-mini-tts 模型,並指定他用台灣腔。
Step 5: 整理音訊檔
因為要傳到雲端空間有時候傳過去的格式有問題就會失敗,我這邊先新增一個 Code 節點去處理上一個節點來的音訊檔。
// 把 binary 的 key 整到 data,並補上檔名與 MIME for (const item of items) { if (!item.binary) throw new Error('這個 item 沒有 binary'); const keys = Object.keys(item.binary); const srcKey = keys.includes('data') ? 'data' : keys[0]; const b = item.binary[srcKey]; if (!b?.data) throw new Error('binary 沒內容'); item.binary.data = b; // 讓 key 一律叫 data item.binary.data.fileName = 'speech.mp3'; // 一定要有 .mp3 item.binary.data.mimeType = 'audio/mpeg'; // 對應 mp3 if (srcKey !== 'data') delete item.binary[srcKey]; } return items;
Step 6: 上傳至雲端空間
由於要發送給 Line 需要給他一個在網路上的連結,所以需要把檔案傳到伺服器上,我這邊示範傳到一個雲端暫存空間,因為比較單純大家都能做到,不過如果是真正的服務就一定要有自己的空間且不會刪檔會比較好。
新增 Http Request 節點,並設定:
- Method: Post
- URL: https://tmpfiles.org/api/v1/upload
- Send Body 打開
- Body Content Type: Form-Data
- Name: file
- Input Data Field Name: data
Step 7: 處理連結網址
接著處理上傳到雲端後的網址,我這邊先處理 tmpfiles 去抓到檔案真正的路徑,另外由於要發送給 Line 的連結一定要是 https,而 tmpfiles 回傳的路徑預設會是 http,所以我就用一個 Code 節點來整理這些資訊。
const pageUrl = $json.data?.url || ''; // 確保使用 https + dl 直連 const link = pageUrl .replace('http://', 'https://') .replace('://tmpfiles.org/', '://tmpfiles.org/dl/'); return [{ json: { link } }];
Step 8: 發送至 Line
最後就是發送到 Line 啦,新增一個 Http Requst 節點,然後設定:
- Method: POST
- URL: https://api.line.me/v2/bot/message/push
- Send Header 打開
- Authorization: Bearer Channel_token
- Content-Type: application/json
- Send Body 打開
- Body Content Type: JSON
- Specify Body: Using JSON
{ "to": 接收者 Line ID, "messages": [ { "type":"audio", "originalContentUrl":"{{$json.link}}", "duration":4000 } ] }
這裡因為一定要給 duration 資訊,不過不一定要正確,所以我就都給一個估計的值,如果希望很精準再去處理就好。
最後成功就可以在 Line 看到處理完傳過來的語音訊息啦。
完整工作流會長這樣:
另外我在天地人有開一門「3 小時掌握自動化工作新手應用實作 – n8n AI Agent」,輸入折扣碼 TC1600UY 還可以額外獲得 NT$500 優惠喔。