Node-RED TTS 與訊息/照片整合教學(2026/3 版)
作者:紅軍
2024/10分享第一篇Telegram開始到現在已經過去很久也改版過很多次 今天將目前能用的設定方式統一整合起來
一 安裝需要的整合以及服務
1.1 安裝通知軟體
Synology Chat設定方式請參考下方連結 [分享] Synology Chat設定 取得Telgram相關ID請參考下列之前的分享文章 [分享]Telegram 機器人創建token ID 以及群組創建ID 取得方法 因之前改版已經取消yaml設定方式 所以上面的舊文章只要創建好Telegram並取得Token以及ID即可 或者直接看下面內容是目前版本的設定方式 到系統 整合 搜尋Telegram
進去後選擇Telegram bot

依照內容先到botfather去新增機器人 便可以取得api token

輸入後便可以按傳送創建

沒問題的話就可以看到新建好的機器人了

至於怎麼創建群組跟取得群組ID請參考舊文章
再來就可以新增群組的ID

直接新增允許的聊天室ID

你創建好的機器人都會列出 選你的群組是屬於哪個機器人 我這邊沒有創群組所以是直接丟給機器人用的就是機器人的ID

也可以依照說明取得chat_id 注意 跟機器人的聊天ID是正數 如果是群組的會是負數 輸入後傳送即可 便可新增完成
可以看到群組或者是直接跟機器人聊天的ID是有差別的 在使用上要特別注意
1.2 安裝TTS軟體
因我都是用Edge 所以下面都是用Edge TTS來說明 Edge安裝可以參考我之前寫的舊文 Edge TTS Node-Red 亂數語音+亂數訊息分享
裝完這些就可以開始來設定了 如果都已經安裝可以直接跳過
二 NodeRed設定
因前段時間Telegram配合HA的更新改版一直在改發送的格式 相信各位應該都有收到通知
而我之前發放通知Flow都是個別處理導致要改的地方會一大堆

現在把格式統一整合到2個Function來處理 第1個Function負責處理要傳送格式 看是要傳送純文字,還是圖片, 還有TTS是否播放, 正常音量設定, 夜間時段音量設定。 第2個處理要傳送的格式內容

前Function 內容 請依照格式設定好相關內容
// ===== 0. Speaker 清單 =====
// 格式:entity_id / 白天音量 / 深夜音量
// 如果有多顆直接複製貼上修改 Speaker? 增加上去
//var Speaker? = {
// entity_id: "實體ID", // 喇叭位置
// normal_vol: 0.7,
// nite_vol: 0.3
//};
// 自動組合 AllSpeakers
// 依照你的喇叭數量添加修改
var SpeakerMap = {
Speaker1,
// Speaker2,
// Speaker3,
// Speaker4,
// Speaker5
};
// 此段不需動
var AllSpeakers = Object.values(SpeakerMap).filter(s =>
s &&
typeof s.entity_id === "string" &&
s.entity_id.trim() !== ""
);
// ===== 0-1. Telegram 設定 =====
// 沒填實體資料為空有多組照格式加上即可
var Telegram1 = "notify.telegram_bot_你的實體ID";
var Telegram2 = "";
var Telegram3 = "";
// 判斷喇叭數量不需動
var Telegram_Entity_List = [Telegram1, Telegram2, Telegram3]
.filter(v => typeof v === "string" && v.trim() !== "");
// ===== 0-2. 深夜時段設定 =====
// 例如 23:00 ~ 06:59 算深夜
var night_start_hour = 23;
var night_end_hour = 7;
// ===== 1. 設定抓取時間格式 =====
const now = new Date();
const pad = (num, len = 2) => num.toString().padStart(len, "0");
// date: yyyy-mm-dd_HH_MM_SS
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}`;
// date2: yyyy/mm/dd HH:MM:SS
const date2 = `${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
// ===== 2. 隨機訊息內容 =====
var myArray = [
//純文字訊息使用下方格式
// "純文字訊息1",
// "純文字訊息2"
// 傳送的文字如果需要帶日期格式改用下列方式
// `${date2}\n宿舍攝影機偵測到移動!`
];
var randomItem = myArray[Math.floor(Math.random() * myArray.length)];
// ===== 3. 隨機 TTS 語音 =====
var myVoice = [
//個人覺得edge tts好聽的語音,可依照自己喜好修改
"zh-TW-HsiaoChenNeural",
"zh-CN-XiaoxiaoNeural"
];
var randomVoice = myVoice[Math.floor(Math.random() * myVoice.length)];
// ===== 4. 這次通知設定 =====
var image_send = "no"; // 傳送照片 yes / no
var tts_send = "no"; // 使用TTS yes / no
// ===== 5. 圖片資料 =====
// 要用圖片就直接填完整路徑與網址
// 不用就留空字串
// var image_path = "";
// var image_url = "";
var image_path = `/config/www/image/snapshot_${date}.jpg`;
var image_url = `http://你的HAip:8123/local/image/snapshot_${date}.jpg`;
// ===== 6. TTS 設定 =====
// on = 全時 normal 音量
// nite = 深夜使用 nite_vol
// off = 深夜不播
var tts_night_mode = "on";
// tts_message 可和 message 不同
var tts_message = randomItem;
// 這裡可自由指定喇叭
// 全部播放:AllSpeakers
// 單顆播放:[Speaker1]
// 多顆指定:[Speaker1, Speaker2]
var tts_speakers = AllSpeakers;
// ===== 7. 若需要圖片,提供給 camera.snapshot / 舊流程相容用 =====
if (image_send === "yes" && image_path !== "") {
msg.payload_snapshot = {
filename: image_path
};
}
// ===== 8. 統一輸出 notify 物件 =====
msg.notify = {
message: randomItem,
telegram_entity_list: Telegram_Entity_List,
image_send: image_send,
image_path: image_path,
image_url: image_url,
tts_send: tts_send,
tts_voice: (tts_send === "yes") ? randomVoice : "",
tts_speakers: (tts_send === "yes") ? tts_speakers : [],
tts_night_mode: tts_night_mode,
tts_message: (tts_send === "yes") ? tts_message : "",
night_start_hour: night_start_hour,
night_end_hour: night_end_hour
};
return msg;後Function內容
// ===== 1. 讀取前面 Function 傳來的 notify 物件 =====
const notify = msg.notify || {};
const message = notify.message || "";
const telegram_entity_list = Array.isArray(notify.telegram_entity_list)
? notify.telegram_entity_list.filter(v => typeof v === "string" && v.trim() !== "")
: [];
const image_send = notify.image_send || "no";
const image_path = notify.image_path || "";
const image_url = notify.image_url || "";
const tts_send = notify.tts_send || "no";
const tts_voice = notify.tts_voice || "";
const tts_speakers = Array.isArray(notify.tts_speakers) ? notify.tts_speakers : [];
const tts_night_mode = notify.tts_night_mode || "on";
const tts_message = notify.tts_message || message;
const night_start_hour = Number(notify.night_start_hour ?? 23);
const night_end_hour = Number(notify.night_end_hour ?? 7);
// ===== 2. 準備 5 個輸出 =====
let msg_telegram_photo = null;
let msg_telegram_text = null;
let msg_synology_chat = null;
let msg_set_volume = null;
let msg_tts = null;
// ===== 3. Telegram =====
// 多組 Telegram 會用 push 方式逐筆送出
if (telegram_entity_list.length > 0) {
if (image_send === "yes" && image_path !== "") {
let telegramPhotoMsgs = [];
telegram_entity_list.forEach(id => {
telegramPhotoMsgs.push({
...msg,
payload_telegram_photo: {
entity_id: id,
file: image_path,
caption: message
}
});
});
if (telegramPhotoMsgs.length > 0) {
msg_telegram_photo = telegramPhotoMsgs;
}
} else {
let telegramTextMsgs = [];
telegram_entity_list.forEach(id => {
telegramTextMsgs.push({
...msg,
payload_telegram_text: {
entity_id: id,
message: message
}
});
});
if (telegramTextMsgs.length > 0) {
msg_telegram_text = telegramTextMsgs;
}
}
}
// ===== 4. Synology Chat =====
if (image_send === "yes" && image_url !== "") {
msg_synology_chat = {
...msg,
payload_synology_chat: {
data: {
file_url: image_url
},
message: message.replace(/%/g, "%25").replace(/&/g, "%26")
}
};
} else {
msg_synology_chat = {
...msg,
payload_synology_chat: {
message: message
}
};
}
// ===== 5. TTS + 設定音量 =====
if (tts_send === "yes" && tts_speakers.length > 0) {
const now = new Date();
const hour = now.getHours();
let isNight = false;
if (night_start_hour > night_end_hour) {
// 例如 23 -> 7,跨日
isNight = (hour >= night_start_hour || hour < night_end_hour);
} else if (night_start_hour < night_end_hour) {
// 例如 1 -> 6,不跨日
isNight = (hour >= night_start_hour && hour < night_end_hour);
} else {
// 相同代表整天都算夜間,通常不會這樣設
isNight = true;
}
// off = 深夜不播
if (!(tts_night_mode === "off" && isNight)) {
// 5-1. 先產生多筆音量設定訊息
let volumeMsgs = [];
let ttsEntityList = [];
tts_speakers.forEach(sp => {
if (!sp || typeof sp.entity_id !== "string" || sp.entity_id.trim() === "") return;
ttsEntityList.push(sp.entity_id);
// 音量選擇邏輯
let finalVol = null;
if (isNight && tts_night_mode === "nite") {
finalVol = sp.nite_vol;
} else {
finalVol = sp.normal_vol;
}
if (finalVol !== "" && finalVol !== null && finalVol !== undefined) {
volumeMsgs.push({
...msg,
payload_setvol: {
entity_id: sp.entity_id,
volume_level: Number(finalVol)
}
});
}
});
if (volumeMsgs.length > 0) {
msg_set_volume = volumeMsgs;
}
// 5-2. 再產生 TTS 訊息(只會有一筆)
if (ttsEntityList.length > 0) {
msg_tts = {
...msg,
payload_tts: {
media_player_entity_id: ttsEntityList,
message: tts_message,
cache: true,
options: {
volume: "+100%",
voice: tts_voice !== "" ? tts_voice : "zh-TW-HsiaoChenNeural",
pitch: "+0Hz",
rate: "+0%"
}
}
};
}
}
}
// ===== 6. 回傳 5 個輸出 =====
// Output 1: Telegram Photo(多筆)
// Output 2: Telegram Text(多筆)
// Output 3: Synology Chat
// Output 4: 設定音量(多筆)
// Output 5: TTS(單筆)
return [msg_telegram_photo, msg_telegram_text, msg_synology_chat, msg_set_volume, msg_tts];相關節點設定 後Function因為有5個Output 所以輸出需要改成5

傳送Telegram圖片 Action 用 telegram_bot.send_photo data 用 msg.payload_telegram_photo

傳送Telegram文字訊息 Action 用 notify.send_message data 用 msg.payload_telegram_text

傳送Synology chat通知 Action 用 notify.synology_chat_bot data 用 msg.payload_synology_chat

設定音量 Action 用 media_player.volume_set data 用 msg.payload_setvol

設定TTS Action 用 tts.speak Target 需要選擇 Edge TTS data 用 msg.payload_tts

這邊需要注意的是TTS前面有加一個延遲

主要是因為要先設定音量再去播放TTS所以要注意自己喇叭數量修改延遲的時間 我5顆喇叭設定1s 沒遇到問題 如果你比我多顆那就要注意觀察是否會造成音量還沒改就先播放了
以上為這次分享 個人認為還不是很完整 下版我將會針對傳送訊息的部分做修改 應該會納入HA Notify 傳送訊息的部分是否啟用 比如 是否啟用 Telegram 通知 是否啟用 Synology Chat 通知 是否啟用 HA Notify 通知 或者你有想要加的功能也可以留言 可以的話我會盡量加進來
本次都是透過 ChatGPT 一步一步弄出來的
2026/3 版更新:HA Notify + iMessage
新增 Ha Notify + iMessage 部分 並修改部分設定 新增 各類訊息是否傳送開關

iMessage傳送號碼設定及轉發關鍵詞

設定用Function
// ===== 0. 設定區 =====
// ===== 0-1. 通知 / 圖片 / TTS 設定 =====
// on = 啟用
// off = 停用
var Telegram = "on";
var Synology_Chat = "off";
var HA_Notify = "off";
var iMessage = "off";
var TTS = "off";
var image_send = "off"; // on / off
// ===== 0-2. 深夜時段設定 =====
// 例如 23:00 ~ 06:59 算深夜
var night_start_hour = 23;
var night_end_hour = 7;
// ===== 0-3. 播放時段設定 =====
// on = 全時 normal 音量
// nite = 深夜使用 nite_vol
// off = 深夜不播
var tts_night_mode = "on";
// ===== 0-4. Telegram 設定 =====
// 沒填實體資料為空,有多組照格式加上即可
var Telegram1 = "notify.telegram_bot_xxxxxxxxxx_xxxxxxxxx";
var Telegram2 = "";
var Telegram3 = "";
// 判斷實體數量不需動
var Telegram_Entity_List = [Telegram1, Telegram2, Telegram3]
.filter(v => typeof v === "string" && v.trim() !== "");
// ===== 0-5. iMessage 設定 =====
// 直接填手機號碼即可,例如 09xxxxxxxx
var iMessage1 = "09xxxxxx";
var iMessage2 = "";
var iMessage3 = "";
// iMessage 關鍵字
// 實際送出時會變成:關鍵字 + 換行 + 訊息內容
var iMessage_Keyword = "HA通知";
// 判斷電話數量不需動
var iMessage_List = [iMessage1, iMessage2, iMessage3]
.filter(v => typeof v === "string" && v.trim() !== "");
// ===== 0-6. Speaker 清單 =====
// 格式:entity_id / 白天音量 / 深夜音量
var Speaker1 = {
entity_id: "media_player.你的喇叭1",
normal_vol: 0.7,
nite_vol: 0.3
};
//var Speaker2 = {
// entity_id: "media_player.你的喇叭2",
// normal_vol: 0.7,
// nite_vol: 0.3
//};
//var Speaker3 = {
// entity_id: "media_player.你的喇叭3",
// normal_vol: 0.7,
// nite_vol: 0.3
//};
//var Speaker4 = {
// entity_id: "media_player.你的喇叭4",
// normal_vol: 0.7,
// nite_vol: 0.3
//};
//var Speaker5 = {
// entity_id: "media_player.你的喇叭5",
// normal_vol: 0.7,
// nite_vol: 0.3
//};
// 如果有多顆直接複製貼上修改 Speaker? 增加上去
// var Speaker6 = {
// entity_id: "media_player.你的喇叭6",
// normal_vol: 0.7,
// nite_vol: 0.3
// };
// ===== 自動組合 AllSpeakers =====
// 依照你的喇叭數量添加 / 刪除
// 注意最後一顆喇叭不要加 ,
var SpeakerMap = {
Speaker1
// Speaker2,
// Speaker3,
// Speaker4,
// Speaker5
// Speaker6
};
// 此段不需動
var AllSpeakers = Object.values(SpeakerMap).filter(s =>
s &&
typeof s.entity_id === "string" &&
s.entity_id.trim() !== ""
);
// ===== 1. 設定抓取時間格式 =====
const now = new Date();
const pad = (num, len = 2) => num.toString().padStart(len, "0");
// date: yyyy-mm-dd_HH_MM_SS
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}_${pad(now.getMinutes())}_${pad(now.getSeconds())}`;
// date2: yyyy/mm/dd HH:MM:SS
const date2 = `${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
// ===== 2. 隨機訊息內容 =====
var msgArray = [
// 純文字訊息使用下方格式
// "純文字訊息1",
// "純文字訊息2"
// 傳送的文字如果需要帶日期格式可改用下列方式
// `${date2}\n攝影機偵測到移動!`
"請在這裡填入你的訊息內容"
];
var randomMSG = msgArray[Math.floor(Math.random() * msgArray.length)];
// ===== 3. 隨機 TTS 語音 =====
// 個人覺得 Edge TTS 好聽的語音,可依自己喜好修改
var myVoice = [
"zh-TW-HsiaoChenNeural",
"zh-CN-XiaoxiaoNeural"
];
var randomVoice = myVoice[Math.floor(Math.random() * myVoice.length)];
// ===== 4. 圖片資料 =====
// 要用圖片就直接填完整路徑與網址
// 不用就留空字串
// var image_path = "";
// var image_url = "";
var image_path = `/config/www/image/snapshot_${date}.jpg`;
var image_url = `http://你的HA_IP:8123/local/image/snapshot_${date}.jpg`;
// ===== 5. TTS 內容 / 喇叭 =====
// tts_message 可和 message 不同
var tts_message = randomMSG;
// 這裡可自由指定喇叭
// 全部播放:AllSpeakers
// 單顆播放:[Speaker1]
// 多顆指定:[Speaker1, Speaker2]
var tts_speakers = AllSpeakers;
// ===== 6. 若需要圖片,提供給 camera.snapshot / 舊流程相容用 =====
if (image_send === "on" && image_path !== "") {
msg.payload_snapshot = {
filename: image_path
};
}
// ===== 7. 統一輸出 notify 物件 =====
msg.notify = {
message: randomMSG,
telegram_send: Telegram,
telegram_entity_list: Telegram_Entity_List,
synology_chat_send: Synology_Chat,
ha_notify_send: HA_Notify,
imessage_send: iMessage,
imessage_list: iMessage_List,
imessage_keyword: iMessage_Keyword,
image_send: image_send,
image_path: image_path,
image_url: image_url,
tts_send: TTS,
tts_voice: (TTS === "on") ? randomVoice : "",
tts_speakers: (TTS === "on") ? tts_speakers : [],
tts_night_mode: tts_night_mode,
tts_message: (TTS === "on") ? tts_message : "",
night_start_hour: night_start_hour,
night_end_hour: night_end_hour
};
return msg;處理發送Funcion

因多加了 iMessage , HA notify所以output要改成7個
Switch 節點設定

屬性設為 notifyimage_send 判斷是否傳送照片套用不同的 iMessage 格式
iMessage action節點設定差別在 傳送照片還是文字而已
傳送照片部分會再改 等我找出正確方式 應該會改成傳送url 而不是傳送檔案 主要是 iPhone 捷徑不支援傳送檔案到 Line
HA notfiy 的 action 節點要注意如果是多部手機 請在後面加上去同樣設定的 action 主要原因是 Action 只能固定 沒辦法從 Function 帶入參數

我沒在用HA Notify 所以不要問我怎麼查歷史訊息 這我真不會
設定輸出方式 Function 內容
// ===== 1. 讀取 notify =====
const notify = msg.notify || {};
const message = notify.message || "";
// ===== 通知開關 =====
const telegram_send = notify.telegram_send || "off";
const synology_chat_send = notify.synology_chat_send || "off";
const ha_notify_send = notify.ha_notify_send || "off";
const imessage_send = notify.imessage_send || "off";
const tts_send = notify.tts_send || "off";
// ===== 其他資料 =====
const telegram_entity_list = Array.isArray(notify.telegram_entity_list)
? notify.telegram_entity_list.filter(v => typeof v === "string" && v.trim() !== "")
: [];
const imessage_list = Array.isArray(notify.imessage_list)
? notify.imessage_list.filter(v => typeof v === "string" && v.trim() !== "")
: [];
const imessage_keyword = notify.imessage_keyword || "";
const image_send = notify.image_send || "off";
const image_path = notify.image_path || "";
const image_url = notify.image_url || "";
const tts_voice = notify.tts_voice || "";
const tts_speakers = Array.isArray(notify.tts_speakers) ? notify.tts_speakers : [];
const tts_night_mode = notify.tts_night_mode || "on";
const tts_message = notify.tts_message || message;
const night_start_hour = Number(notify.night_start_hour ?? 23);
const night_end_hour = Number(notify.night_end_hour ?? 7);
// ===== 2. 準備輸出 =====
let msg_telegram_photo = null;
let msg_telegram_text = null;
let msg_synology_chat = null;
let msg_set_volume = null;
let msg_tts = null;
let msg_imessage = null;
let msg_ha_notify = null;
// ===== 3. Telegram =====
if (telegram_send === "on" && telegram_entity_list.length > 0) {
if (image_send === "on" && image_path !== "") {
msg_telegram_photo = {
...msg,
payload_telegram_photo: {
entity_id: telegram_entity_list,
file: image_path,
caption: message
}
};
} else {
msg_telegram_text = {
...msg,
payload_telegram_text: {
entity_id: telegram_entity_list,
message: message
}
};
}
}
// ===== 4. Synology Chat =====
if (synology_chat_send === "on") {
if (image_send === "on" && image_url !== "") {
msg_synology_chat = {
...msg,
payload_synology_chat: {
data: {
file_url: image_url
},
message: message.replace(/%/g, "%25").replace(/&/g, "%26")
}
};
} else {
msg_synology_chat = {
...msg,
payload_synology_chat: {
message: message
}
};
}
}
// ===== 5. TTS =====
if (tts_send === "on" && tts_speakers.length > 0) {
const now = new Date();
const hour = now.getHours();
let isNight = false;
if (night_start_hour > night_end_hour) {
isNight = (hour >= night_start_hour || hour < night_end_hour);
} else if (night_start_hour < night_end_hour) {
isNight = (hour >= night_start_hour && hour < night_end_hour);
} else {
isNight = true;
}
if (!(tts_night_mode === "off" && isNight)) {
let volumeMsgs = [];
tts_speakers.forEach(sp => {
if (!sp || typeof sp.entity_id !== "string" || sp.entity_id.trim() === "") return;
const vol = (tts_night_mode === "nite" && isNight)
? sp.nite_vol
: sp.normal_vol;
if (vol !== undefined && vol !== null && vol !== "") {
volumeMsgs.push({
...msg,
payload_setvol: {
entity_id: sp.entity_id,
volume_level: Number(vol)
}
});
}
});
if (volumeMsgs.length > 0) {
msg_set_volume = volumeMsgs;
}
msg_tts = {
...msg,
payload_tts: {
media_player_entity_id: tts_speakers
.filter(s => s && typeof s.entity_id === "string" && s.entity_id.trim() !== "")
.map(s => s.entity_id),
message: tts_message,
cache: true,
options: {
volume: "+100%",
voice: tts_voice || "zh-TW-HsiaoChenNeural",
pitch: "+0Hz",
rate: "+0%"
}
}
};
}
}
// ===== 6. iMessage =====
if (imessage_send === "on" && imessage_list.length > 0) {
function formatPhone(num) {
num = String(num).replace(/\D/g, "");
if (num.startsWith("0")) {
return "+886" + num.slice(1);
}
if (num.startsWith("886")) {
return "+" + num;
}
if (num.startsWith("9")) {
return "+886" + num;
}
return "";
}
function toBase64(str) {
return Buffer.from(String(str), "utf-8").toString("base64");
}
let targets = imessage_list
.map(p => formatPhone(p))
.filter(p => p !== "");
let imessage_text = message;
if (imessage_keyword !== "") {
imessage_text = `${imessage_keyword}\n${message}`;
}
if (targets.length > 0) {
msg_imessage = targets.map(phone => ({
...msg,
payload_imessage: {
phone: phone,
message_b64: toBase64(imessage_text),
image_send: image_send,
image_url: image_url
}
}));
}
}
// ===== 7. HA Notify(out7)=====
if (ha_notify_send === "on") {
let payload = {
message: message
};
if (image_send === "on" && image_url !== "") {
payload.data = {
image: image_url
};
}
msg_ha_notify = {
...msg,
payload_ha_notify: payload
};
}
// ===== 8. 回傳 7 個輸出 =====
// 1 Telegram Photo
// 2 Telegram Text
// 3 Synology Chat
// 4 Volume
// 5 TTS
// 6 iMessage
// 7 HA Notify
return [
msg_telegram_photo,
msg_telegram_text,
msg_synology_chat,
msg_set_volume,
msg_tts,
msg_imessage,
msg_ha_notify
];以上為本次補充 有問題可以提出
設定畫面可能因版本與設備不同而有差異。想交流使用經驗,歡迎加入惟家 LINE 社群;社群以使用者互助為主,非官方一對一技術支援。
