處理事件與回覆訊息
回聲機器人只會複誦。這頁讓它「聽得懂」:認識 LINE 傳來的事件種類、搞清楚 Reply 與 Push 的差別、寫一個指令路由,並學會送出貼圖、快速回覆按鈕與 Flex 訊息。程式接續 上一頁 的 callLine、reply、push、replyText。
事件(event)長什麼樣
doPost 收到的 JSON 大致是:
{
"destination": "機器人的 userId",
"events": [
{
"type": "message",
"replyToken": "一次性回覆憑證",
"source": { "type": "user", "userId": "使用者的 userId" },
"webhookEventId": "事件唯一編號",
"message": { "type": "text", "id": "訊息編號", "text": "你好" }
}
]
}
events 是陣列,因為 LINE 可能一次送多個事件,一定要逐一處理。常見的事件類型:
event.type |
何時發生 | 有 replyToken? |
常見處理 |
|---|---|---|---|
message |
使用者傳訊息(文字、圖片、貼圖、位置…,看 event.message.type) |
有 | 依內容回覆 |
follow |
加機器人好友或解除封鎖 | 有 | 送歡迎詞、說明指令 |
unfollow |
使用者封鎖機器人 | 沒有 | 標記使用者已離開(無法回覆) |
postback |
使用者按了帶資料的按鈕 | 有 | 依 event.postback.data 執行動作 |
join/leave |
機器人被加入或移出群組 | join 有 |
群組機器人初始化 |
event.source.userId 是使用者在你這個機器人底下的識別碼,不是 LINE ID,也不是暱稱。要主動推播給某人,就要先把它存起來。群組事件的 source 會多帶 groupId。
Reply 與 Push 的差別
| Reply(回覆) | Push(推播) | |
|---|---|---|
| 用途 | 回應使用者剛傳來的事件 | 主動發訊息(提醒、通知) |
| 需要什麼 | replyToken |
對方的 userId |
| 時效 | replyToken 只能用一次,收到後約 1 分鐘內要用 |
隨時 |
| 費用 | 免費,不計入每月訊息則數 | 計入每月則數 |
| 一次最多 | 5 則訊息 | 5 則訊息 |
原則:能 Reply 就 Reply。Push 有每月免費則數限制(見 上線注意),留給定時提醒這類「使用者沒開口」的情境。
指令路由
把使用者輸入切成「指令 + 參數」,用 switch 分派,是最簡單也最好維護的寫法。把 handleEvent 改成:
const HELP_TEXT = "可用指令:\n說明\n時間\n算 12 + 30";
function handleEvent(event) {
if (event.type === "follow") {
return replyText(event.replyToken, "歡迎!輸入「說明」看看我會做什麼。");
}
if (event.type !== "message" || event.message.type !== "text") return;
handleText(event);
}
function handleText(event) {
const text = event.message.text.trim();
const [cmd, ...args] = text.split(/\s+/);
switch (cmd) {
case "說明":
return replyText(event.replyToken, HELP_TEXT);
case "時間":
return replyText(
event.replyToken,
Utilities.formatDate(new Date(), "Asia/Taipei", "yyyy-MM-dd HH:mm")
);
case "算":
return replyText(event.replyToken, calc(args));
default:
return replyText(event.replyToken, "看不懂「" + text + "」,輸入「說明」查看指令。");
}
}
function calc(args) {
const [a, op, b] = [Number(args[0]), args[1], Number(args[2])];
if (Number.isNaN(a) || Number.isNaN(b)) return "格式:算 12 + 30";
const ops = { "+": a + b, "-": a - b, "*": a * b, "/": b === 0 ? "不能除以 0" : a / b };
return op in ops ? String(ops[op]) : "只支援 + - * /";
}
設計重點:一定要有 default 分支,回一句「看不懂」加上提示,不要讓使用者傳了沒反應。使用者的輸入不可預期,格式錯誤時要回「正確格式長怎樣」,比回錯誤訊息有用。
其他訊息型態
reply/push 的 messages 是陣列,一次最多 5 則,可以混搭型態。
貼圖
reply(event.replyToken, [
{ type: "text", text: "收到!" },
{ type: "sticker", packageId: "446", stickerId: "1988" },
]);
貼圖要用 LINE 官方開放給機器人使用的 packageId/stickerId 組合,可查 官方貼圖清單。
快速回覆(Quick Reply)
在輸入框上方出現幾顆按鈕,點下去等於代替使用者「傳出一則文字」。適合讓不想打字的人選擇。
reply(event.replyToken, [{
type: "text",
text: "要查哪一段?",
quickReply: {
items: [
{ type: "action", action: { type: "message", label: "今天", text: "今天" } },
{ type: "action", action: { type: "message", label: "本月", text: "本月" } },
],
},
}]);
按鈕送出的文字會回到你的 handleText,所以「按鈕」和「打字」走同一套指令路由,不用另外寫。
Flex Message(自訂版面)
需要卡片、表格式版面、圖文並排時使用。結構是 JSON,用官方的 Flex Message Simulator 拖拉設計,再把 JSON 貼進程式。
reply(event.replyToken, [{
type: "flex",
altText: "今日摘要", // 通知與不支援 Flex 的畫面會顯示這段
contents: {
type: "bubble",
body: {
type: "box",
layout: "vertical",
contents: [
{ type: "text", text: "今日摘要", weight: "bold", size: "xl" },
{ type: "text", text: "共 3 筆,合計 420 元", margin: "md" },
],
},
},
}]);
圖片、影片、檔案
使用者傳來的檔案,事件裡只有 event.message.id,要另外用 GET https://api-data.line.me/v2/bot/message/{messageId}/content 下載,再存進 Drive。這超過入門範圍,需要時看 官方參考。
主動推播與取得使用者資料
Push 用在使用者沒開口、你想主動通知的時候,push 的第一個參數是 userId:
function notifyMe() {
const myUserId = PropertiesService.getScriptProperties().getProperty("MY_USER_ID");
push(myUserId, [{ type: "text", text: "提醒:今天要交報告" }]);
}
自己的 userId 怎麼取得:在 handleEvent 裡暫時加一行 console.log(event.source.userId),用手機傳訊息給機器人,再到 GAS 的 執行 頁看紀錄,複製後存進 Script Properties。
想在回覆裡用對方的暱稱,可以呼叫 GET https://api.line.me/v2/bot/profile/{userId} 取得 displayName。這是 GET,要另外用 UrlFetchApp.fetch 帶 Authorization 標頭。
把 notifyMe 掛上 時間驅動觸發,就成了每天定時提醒。
下一步:把使用者輸入存下來,做一個真的有用的機器人,見 試算表記帳機器人。