iT邦幫忙

4

LINE Bot 實作:做個群組 LINE 機器人記錄誰 +1( App Script )

  • 分享至 

  • xImage
  •  

最近我用 Google App Script 做了一款 LINE +1 紀錄機器人,自動紀錄群組有誰傳 +1,之後群組開團購、要報名,都可以用 LINE Bot 來達成!這篇分享我是怎麼實作的。

| 本文同步刊登於我的個人網頁

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

+1 LINE 機器人設計動機 ?

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

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

重點功能

  • 自動識別傳「+1」的使用者,並將 LINE 名稱記錄到名單
  • 搭配 Google 試算表做免費資料庫,快速好用
  • 支援「+2」( 預約兩位 ) / 「-1」( 取消預約 ) 功能
  • 支援候補名額與自動替補
  • 使用 Google App Script 語法開發

feature

用 App Script 製作超方便

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

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

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

Google 試算表當資料庫

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

+1 機器人實測( Demo )

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

demo

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

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

來寫 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,
 }),
 });

}

這個檔案的功能包含拆解使用者傳送的 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 變數名稱改掉

我是用連結的方式讓 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 等於哪些字就好。

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

  • 「+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_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 預約"
     }]
 }

 }
  • 使用者傳「+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

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

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
    }]
    }

第五步:部署 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 影片:
https://youtu.be/Bjg_vZnDHbc

附上 LINE 機器人程式碼

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

+1 LINE Bot 程式碼

結語

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

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

未來更多教學文會放在我的個人部落格


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言