LINE Bot 機器人實作

做個 LINE 機器人記錄誰 +1!群組 LINE Bot 實作教學與分享

不少人會在 LINE 群組開團購、點餐、或是大家要預約課程,就會一直傳「+1」,你也覺得一直滑聊天室紀錄誰傳加一很麻煩嗎?這裡我用 Google App Script 做了一款 LINE +1 紀錄機器人,自動紀錄群組有誰傳 +1!

這是我第一次寫技術文章,盡量梳理我寫這個 LINE 機器人的過程、思緒邏輯和程式碼寫法,但無論是程式碼或是這篇技術文都有很多要改進的地方,若我的寫法很亂還請多包涵,也請大神多多指教!


+1 LINE 機器人設計動機 💡

我媽有在上瑜珈課程,報名課程的方式是要參加的人在 LINE 群打「加一」,老師再手動記錄有哪些人要上課

加上課程名額有限,大家都是瘋傳訊息,也不知到額滿了沒。而且瑜珈老師這樣紀錄很沒有效率,也容易出錯,所以我構思做一支 LINE 機器人,自動記錄 +1 名單。

+1 機器人實測( Demo )

這是瑜珈課程的群組截圖,群組只要有人傳 +1,機器人會自動記錄,並回傳告知報名成功與剩下多少名額:

LINE 加一機器人實測截圖

傳指定關鍵字「名單」就能讓機器人傳送完整的報名名單:

更酷的是,資料都是暫存在 Google 試算表裡,不用另建伺服器或資料庫:

+1 機器人功能 👍

以下是加一 LINE Bot 具備的功能:

  1. 當群組有人傳「加一」訊息,擷取傳訊息者的帳號名稱並紀錄到資料庫
  2. 有人 +1 時,機器人回傳群組剩下多少名額、或額滿了沒?
  3. 設定「減一」( 取消課程 )以及候補名額,當有正式學員 -1,自動讓候補者替補。
  4. 老師傳送指定關鍵字,機器人會回傳報名名單

這樣就能不僅節省老師紀錄的時間,要報名的同學也能知道明天的課程是否額滿了

我是以課程預約為前提來設計這隻機器人的,你也可以改成用來記錄團購,或是單純的關鍵字回覆機器人。

用 App Script 製作超方便

App Script 是一個由 Google 設計的程式語言,語法很接近 Javascript,可以用來達成一些自動化操作,像是編輯 Google Sheet( 試算表 )或自動寄 Gmail。

會用 App Script 寫 LINE Bot,主要有以下兩個優點:

  • 免費用 Google 雲端主機,穩定快速,且不會跟 HeroKu 一樣喚醒要很久
  • 不用設定 Port,對我來說很方便 ( 筆者用 ngork 跟 Heroku 開 port 常常失敗 )

Google 試算表當資料庫

前面我有提到要讓機器人把名單紀錄到資料庫,因為內建的 LINE Bot 本身沒有紀錄資料的功能,所以我們可以用 Google 的試算表( 類似 Excel )充當簡易的資料庫,紀錄哪些人報名了。而且用 App Script 寫 LINE Bot 的話,連接試算表只要幾句 code 就好,不用設定 API Key 等工作。


來寫 Code!實作加一 LINE 機器人 🤖

接著來分享一些技術性內容,分享我如何用 Google App Script 來做紀錄加一的 LINE Bot。

第一步: 設定 LINE API with App Script

先來寫 Google App Script 版的 LINE 機器人,下方是最簡單的 LINE Bot code:

// Maded By Chun Shawn in jcshawn.com
// Contact : contact@jcshawn.com
// 當 LINE BOT 接收到訊息,會自動執行 doPost
function doPost(e) {
// LINE Messenging API Token
var CHANNEL_ACCESS_TOKEN = ''; // 引號內放你的 LINE BOT Access Token
// 以 JSON 格式解析 User 端傳來的 e 資料
var msg = JSON.parse(e.postData.contents);
// 從接收到的訊息中取出 replyToken 和發送的訊息文字,詳情請看 LINE 官方 API 說明文件
const replyToken = msg.events[0].replyToken; // 回覆的 token
const userMessage = msg.events[0].message.text; // 抓取使用者傳的訊息內容
const user_id = msg.events[0].source.userId; // 抓取使用者的 ID,等等用來查詢使用者的名稱
const event_type = msg.events[0].source.type; // 分辨是個人聊天室還是群組,等等會用到
// reply_messgae 為要回傳給 LINE 伺服器的內容,JSON 格式,詳情可看 LINE 官方 API 說明
var reply_message = [{
"type":"text",
"text":"引號內放要回傳給 User 的訊息"
}]
//回傳 JSON 給 LINE 並傳送給使用者
var url = 'https://api.line.me/v2/bot/message/reply';
UrlFetchApp.fetch(url, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': replyToken,
'messages': reply_message,
}),
});
}
view raw simple_app.gs hosted with ❤ by GitHub

這個檔案的功能包含拆解使用者傳送的 JSON 格式訊息,並將同為 JSON 格式的回覆訊息傳給 LINE 伺服器,讓 LINE 處理並轉寄回給使用者,各個程式碼的說明都用註解附上了。

如果你單純想複製、使用我寫好的原始碼,我在文末有附上Github 完整程式碼的連結,可以直接滑到後面。

第二步:連接 Google 試算表

前面有提到我們要讓機器人將報名資料儲存到 Google 試算表裡,以下是讓 App Script 存取、寫入 Google 試算表的語法:

/* Created by Chun Shawn in jcshawn.com
* Google App Script 讀取、編輯 Google 試算表的語法,此為利用網址讀取的方法。
*/
const sheet_url = 'https://docs.google.com/spreadsheets/d/******'; // 將引號處的內容改成你的 Google 試算表連結
// 工作表名稱
const sheet_name = 'reserve'; // 將 reserve 改成你的工作表名稱
const SpreadSheet = SpreadsheetApp.openByUrl(sheet_url);
const reserve_list = SpreadSheet.getSheetByName(sheet_name); // 可以將 reserve_list 變數名稱改掉
view raw google_sheet.gs hosted with ❤ by GitHub

我是用連結的方式讓 App Script 存取 Google 試算表,也可以透過 Sheet ID。有了這段 code 就能讓 App Script 讀寫試算表,將使用者傳的資料存進去,需要時再爬取資料回傳給使用者。

第三步:設定報名時間與條件

瑜珈老師有提到他的報名時間和條件:

  1. 只在當天晚上九點到隔天晚上七點前接受報名
  2. 每次課程最多只收 40 人

所以需要讓 LINE 機器人收到訊息時,先取得訊息傳送的時間是否在開放時段裡,並判斷報名表單是否達到四十人。

報名時間部分,我參考了 Google App Script 說明文件 和這篇 Stack Overflow,App Script 取得時間的語法如下:

var current_hour = Utilities.formatDate(new Date(), "Asia/Taipei", "HH");
// 最後的引號是取得時間的格式,HH 是指小時
// 還有 yyyy (year), mm (分), ss (秒 ),更多可以看 App Script 官方說明
// https://developers.google.com/google-ads/scripts/docs/features/dates

程式最後的 “HH” 可以指定要取得的時間格式,若改成 “HH:mm:ss” 則會顯示「小時:分:秒」,因為目前我只需要判斷小時,所以用 “HH” ( Hour )

接著是判斷報名人數,我的資料記錄方式就是單純把人名記錄在 A row ( 第一列 ),所以只要判斷 A 列的行數是否達到 40 筆資料( 40 人 )即可。

取得 Google 試算表最大行數的 App Script 語法如下:

var current_list_row = reserve_list.getLastRow();
// 語法:{ 試算表變數 }.getLastRow();
// reserve_list 要改成你設定的名稱
view raw getlastrow.gs hosted with ❤ by GitHub
記得將 reserve_list 改成第二步驟設定的試算表變數名稱

第四步:設定關鍵字與報名動作

在第一步驟的程式碼中,我將使用者傳送的訊息文字變數設定為 “userMessage”,所以只要判斷 userMessage 等於哪些字就好。

我需要判斷的關鍵字有四個:

  • 「+1」:一人報名
  • 「+2」:一個使用者幫兩人報名
  • 「-1」:取消報名
  • 「名單」:列出報名名單

( 一個瑜珈課程報名也能這麼複雜 XDDDDDDD )

< +1 報名關鍵字程式碼 >

以下是我寫的「+1」關鍵字部分,各段重點說明放在註解裡:

// 「加一」報名關鍵字
if (userMessage == "+1" | userMessage == "加一" | userMessage == "+1") {
// 判斷時間是否為報名時段內,參考文章第三步驟說明
if (current_hour >= 0 & current_hour <= 19 | current_hour >= 21) {
// 若目前報名人數小於人數上限( 40 人 ),將使用者的名字記錄到試算表裡
if (current_list_row < maxium_member) {
reserve_list.getRange(current_list_row + 1, 1).setValue(reserve_name);
current_list_row = reserve_list.getLastRow();
// 報名成功回傳成功訊息
reply_message = [{
"type": "text",
"text": reserve_name + "成功預約 🙆,是第 " + current_list_row + " 位。" + "還有 " + (maxium_member current_list_row) + " 位名額"
}]
}
// 設有 3 位候補名額,若報名人數大於 40 人,將第 41 – 43 行的資料設為候補名額
else if (current_list_row >= maxium_member & current_list_row < (waiting_member + maxium_member)) {
reserve_name = "候補:" + reserve_name; // 加上「候補」兩字作為標籤
reserve_list.getRange(current_list_row + 1, 1).setValue(reserve_name);
// 回傳訊息,告知為候補名額
reply_message = [{
"type": "text",
"text": "超過 40 人。" + reserve_name + " 為候補預約"
}]
}
// 若報名名單與候補名額已滿( 大於 43 人 ),不再紀錄到試算表,並回傳已額滿訊息
else {
reply_message = [{
"type": "text",
"text": "⚠️ 報名額滿!已達 " + maxium_member + "人"
}]
}
}
// 非預約時間的提示訊息回覆
else {
reply_message = [{
"type": "text",
"text": "現在不是報名時間喔 ~ ,請在 00:00 – 19:00 預約"
}]
}
}
  • 使用者傳「+1」「加一」「+1」( 全形 ) 時觸發
  • 抓取 Google 資料表的行數與執行時間,判斷要不要紀錄
  • 設有 3 位候補名額,第 41 – 43 位會設為候補學員

「+2」部分我直接用填入兩格資料替代:

else if (userMessage == "+2" | userMessage == "加二") {
if (current_hour >= 0 & current_hour <= 19) {
if (current_list_row < maxium_member) {
reserve_list.getRange(current_list_row + 1, 1).setValue(reserve_name);
reserve_list.getRange(current_list_row + 2, 1).setValue(reserve_name);
current_list_row = reserve_list.getLastRow();
reply_message = [{
"type": "text",
"text": reserve_name + "成功預約兩位 🙆" + "還有" + (maxium_member current_list_row) + "位名額"
}]
}
else if (current_list_row >= maxium_member & current_list_row < maxium_member+2) { // 候補名額只剩一位時不給候補
reserve_name = "候補:" + reserve_name;
reserve_list.getRange(current_list_row + 1, 1).setValue(reserve_name);
reserve_list.getRange(current_list_row + 2, 1).setValue(reserve_name);
reply_message = [{
"type": "text",
"text": reserve_name + "預約兩位候補"
}]
}
else {
reply_message = [{
"type": "text",
"text": "⚠️ 報名額滿!已達 40 人"
}]
}
}
else {
reply_message = [{
"type": "text",
"text": "現在不是報名時間喔 ~ ,請在 00:00 – 19:00 預約"
}]
}
}
view raw add_two.gs hosted with ❤ by GitHub
  • 使用者傳「+2」時,自動填入兩格資料
  • 當候補名額剩一位,不開放 +2 的使用者

「減一」( 取消課程 )的部分,我先用迴圈檢查一次要取消的學員是否真的有報名過,並讓候補者替補取消的人的位置( 若有候補者 ):

else if (userMessage == "-1" | userMessage == "減一") {
// 檢查 -1 的人有沒有在報名名單裡
for (var checking_range = 1; checking_range <= current_list_row; checking_range++) {
// 如果有在裡面,刪除資料表裡的資料( 變成空格 )
if (reserve_name == reserve_list.getRange(checking_range, 1).getValue()) {
reserve_list.getRange(checking_range, 1).clearContent();
var state = reserve_name + "已退出預約";
current_list_row = reserve_list.getLastRow();
break;
}
// 如果沒在裡面,告知無需取消
else {
var state = "您尚未報名,不用減一"
}
}
// 檢查候補名額有沒有人,如果有就依照順序填補到正式名單內( 移到第 1 ~ 40 行資料裡 )
for (spaced_range = 1; spaced_range <= current_list_row; spaced_range++) {
if (reserve_list.getRange(spaced_range, 1).getValue() == "") {
for (var waiting_range = waiting_start; waiting_range <= (maxium_member + waiting_member); waiting_range++) {
if (reserve_list.getRange(waiting_range, 1).getValue() != "") {
var waiting_add = reserve_list.getRange(waiting_range, 1).getValue();
reserve_list.getRange(spaced_range, 1).setValue(waiting_add);
reserve_list.getRange(waiting_range, 1).clearContent();
break;
}
}
break;
}
}
reply_message = [{
"type": "text",
"text": state
},{
"type": "text",
"text": waiting_add+"候補進入上課名單"
}]
}
view raw cancelpart.gs hosted with ❤ by GitHub

最後是讓老師查報名名單的部分,我設計讓老師傳「名單」兩字,機器人會回覆有多少人報名,以及會上課的名單。先用迴圈紀錄一次試算表的資料,再加到回覆訊息中:

else if (userMessage == "報名人數" | userMessage == "名單") {
var ready_namelist = "【 報名名單 】\n";
for (var x = 1; x <= current_list_row; x++) {
ready_namelist = ready_namelist + "\n" + reserve_list.getRange(x, 1).getValue();
}
reply_message = [
{
"type": "text",
"text": "共有 " + current_list_row + " 位同學報名 ✋"
},
{
"type": "text",
"text": ready_namelist
}]
}
view raw report_list.gs hosted with ❤ by GitHub

第五步:部署 LINE 機器人

網路上有很多 Google App Script 部署 LINE Bot 的教學了,這裡我簡單講一下流程:

  1. 先到 LINE Developers 開一個 Provider 帳號,新增一個 LINE Messaging API
  2. 取得 Token 填入 App Script 的程式碼中
  3. 點選 App Script 網頁的部署按鈕,新增一個網路應用程式
  4. 將應用程式網址填入 LINE Webhook 中

LINE Developers 詳細操作可以參考這支 Youtube 影片:

部署 App Script 的步驟可以參考這篇文:

兩小時打造簡單 Line Chatbot — 使用 Google Apps Script & Google Sheet API


附上 LINE 機器人程式碼

如果你看前面的說明還是霧煞煞,或是單純想改成自己的機器人,我已經將完整的程式碼公開在個人 Github,也有部署教學。如果我的這個 side project 有幫到你,或是覺得我寫的還不錯,請幫我按個星星大力地鞭策我 XD 當然有任何指教也歡迎留言告知我。


注意事項 & 常見問題

我在開發 LINE 機器人犯過不少錯,這裡列幾個新手會常犯的錯誤及常見問題:

傳了訊息後,LINE 機器人沒有回傳訊息

如果傳測試訊息後,你的機器人沒有傳任何訊息( 只有已讀 ),代表是程式碼出錯,可能是 Token 錯誤,或是你的回覆訊息( reply_message )的 JSON 格式有問題,建議重新檢查一下變數跟 reply_message 格式是否正確。

機器人回傳的訊息是舊的,怎麼辦?

Google App Script 的每一次部署都會生成新的網址,所以每更改一次程式碼,就要重新按一次部署按鈕,並將新的應用程式網址貼到 LINE Console 的 Webhook 欄位,這很重要。

機器人沒有回傳 Google 試算表的資料?

先確認 Sheet_url 的網址、以及工作表等名稱是否正確,所有設定要跟你自己的試算表相同。再來是確定你用的 getValue 後面有沒有加括號()也可能該資料格沒有資料( 請善用 try & catch )

機器人加入群組卻沒回覆?

先到 LINE 商務網站,到你的 LINE Bot 頁面點右上角的設定:帳號設定 > 功能切換 > 改成「接受邀請加入群組或多人聊天室」,如果群組已有其他機器人要先移除,一個群組只能同時用一個機器人。


結語

在寫這個 +1 LINE 機器人時,我也是邊爬文邊學到很多,像是怎麼讓機器人取得 LINE 使用者的名稱,更遇到幾十次的錯誤 bug,建議在你的 code 加上一個測試關鍵字,確認是整個檔案出錯還是只有特定關鍵字的區塊無法執行。

當然如果你複製、照著設定走還是失敗,或是有其他疑問、建議的話,都歡迎留言告訴我~未來也會分享幾個 App Script + LINE API 的教學文。

留個言吧!