介紹如何透過 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 優惠喔。
