最近我用 Google App Script 做了一款 LINE +1 紀錄機器人,自動紀錄群組有誰傳 +1,之後群組開團購、要報名,都可以用 LINE Bot 來達成!這篇分享我是怎麼實作的。
| 本文同步刊登於我的個人網頁
這是我第一次寫技術文章,盡量梳理我寫這個 LINE 機器人的過程、思緒邏輯和程式碼寫法,但無論是程式碼或是這篇技術文都有很多要改進的地方,若我的寫法很亂還請多包涵,也請大神多多指教!
我媽有在上瑜珈課程,報名課程的方式是要參加的人在 LINE 群打「加一」,老師再手動記錄有哪些人要上課
加上課程名額有限,大家都是瘋傳訊息,也不知到額滿了沒。而且瑜珈老師這樣紀錄很沒有效率,也容易出錯,所以我構思做一支 LINE 機器人,自動記錄 +1 名單。
App Script 是一個由 Google 設計的程式語言,語法很接近 Javascript,可以用來達成一些自動化操作,像是編輯 Google Sheet( 試算表 )或自動寄 Gmail。
會用 App Script 寫 LINE Bot,主要有以下兩個優點:
前面我有提到要讓機器人把名單紀錄到資料庫,因為內建的 LINE Bot 本身沒有紀錄資料的功能,所以我們可以用 Google 的試算表( 類似 Excel )充當簡易的資料庫,紀錄哪些人報名了。而且用 App Script 寫 LINE Bot 的話,連接試算表只要幾句 code 就好,不用設定 API Key 等工作。
這是瑜珈課程的群組截圖,群組只要有人傳 +1,機器人會自動記錄,並回傳告知報名成功與剩下多少名額:
更酷的是,資料都是暫存在 Google 試算表裡,不用另建伺服器或資料庫:
我是以課程預約為前提來設計這隻機器人的,你也可以改成用來記錄團購,或是單純的關鍵字回覆機器人。
接著來分享一些技術性內容,分享我如何用 Google App Script 來做紀錄加一的 LINE Bot。
先來寫 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,
}),
});
}
這個檔案的功能包含拆解使用者傳送的 JSON 格式訊息,並將同為 JSON 格式的回覆訊息傳給 LINE 伺服器,讓 LINE 處理並轉寄回給使用者,各個程式碼的說明都用註解附上了。
如果你單純想複製、使用我寫好的原始碼,我在文末有附上Github 完整程式碼的連結,可以直接滑到後面。
前面有提到我們要讓機器人將報名資料儲存到 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 變數名稱改掉
我是用連結的方式讓 App Script 存取 Google 試算表,也可以透過 Sheet ID。有了這段 code 就能讓 App Script 讀寫試算表,將使用者傳的資料存進去,需要時再爬取資料回傳給使用者。
瑜珈老師有提到他的報名時間和條件:
只在當天晚上九點到隔天晚上七點前接受報名
每次課程最多只收 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 要改成你設定的名稱
記得將 reserve_list 改成第二步驟設定的試算表變數名稱
在第一步驟的程式碼中,我將使用者傳送的訊息文字變數設定為 “userMessage”,所以只要判斷 userMessage 等於哪些字就好。
我需要判斷的關鍵字有四個:
( 一個瑜珈課程報名也能這麼複雜 XDDDDDDD )
以下是我寫的「+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 預約"
}]
}
}
「+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_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 預約"
}]
}
}
「減一」( 取消課程 )的部分,我先用迴圈檢查一次要取消的學員是否真的有報名過,並讓候補者替補取消的人的位置( 若有候補者 ):
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
最後是讓老師查報名名單的部分,我設計讓老師傳「名單」兩字,機器人會回覆有多少人報名,以及會上課的名單。先用迴圈紀錄一次試算表的資料,再加到回覆訊息中:
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
}]
}
網路上有很多 Google App Script 部署 LINE Bot 的教學了,這裡我簡單講一下流程:
LINE Developers 詳細操作可以參考這支 Youtube 影片:
https://youtu.be/Bjg_vZnDHbc
如果你看前面的說明還是霧煞煞,或是單純想改成自己的機器人,我已經將完整的程式碼公開在個人 Github,也有部署教學。如果我的這個 side project 有幫到你,或是覺得我寫的還不錯,請幫我按個星星大力地鞭策我 XD 當然有任何指教也歡迎留言告知我。
在寫這個 +1 LINE 機器人時,我也是邊爬文邊學到很多,像是怎麼讓機器人取得 LINE 使用者的名稱,更遇到幾十次的錯誤 bug,建議在你的 code 加上一個測試關鍵字,確認是整個檔案出錯還是只有特定關鍵字的區塊無法執行。
當然如果你複製、照著設定走還是失敗,或是有其他疑問、建議的話,都歡迎留言告訴我~未來也會分享幾個 App Script + LINE API 的教學文。
未來更多教學文會放在我的個人部落格~