Apps Script 部署 LINE 機器人
前言
每次到了晚餐時間都會遇到一個亙古難題,今天晚餐要吃什麼?
為了解決這個問題,我原先的想法是架設網站來抽籤,但抽完後還需截圖上傳群組,感覺太麻煩了。
那不如直接架設聊天機器人並拉進群組,用指定字詞呼叫它來幫忙抽籤。
接下來的問題就是,後端要部署在哪裡?
網路上很多人選擇的平臺是 Heroku,但免費方案有重新喚醒的等待時間,不適合。
突然想起之前使用過 Google 的服務 Apps Script,查了資料後似乎是可行的。
但機器人能自動回覆還不夠,還要實現動態新增餐廳,這就牽涉到資料存取的問題。
幸運的是 Apps Script 支援 Google 雲端硬碟的數據的讀寫,來看看如何實作。
實作過程
LINE Messaging API
前往 LINE Developers 建立 LINE Messaging API,過程不贅述。
- Channel access token:頻道存取金鑰,點選後方的 Issue 將產生一組。
- Use webhooks:使用 webhooks,勾選啟用 enabled。
- Webhook URL:webhook 的網址,貼上 Apps Script 執行網址。
- Allow bot to join group chats:是否允許機器人加入群聊。
詳細社群設定可以在 LINE Official Account Manager 中設定。
Apps Script 接收、回覆訊息
Apps Script 是一個基於 JavaScript 的平臺,主要用來開發或串接 Google 的各種服務。
利用其部署功能,就相當於產生了 webhook 網址,即可與 LINE 機器人溝通。
開始實作,先是常數設定。
const REPLY_URL = "https://api.line.me/v2/bot/message/reply"
const CHANNEL_ACCESS_TOKEN = "LINE Developers 中的頻道存取金鑰"
LINE 會用 POST 方法向 webhook 請求,故用doPost(e)
處理請求。
從e
中取出回覆權杖(token)與使用者訊息(user_msg)。
接著,呼叫自定函式send_msg()
回傳訊息。
function doPost(e)
{
if(!e) { return }
let post = JSON.parse(e.postData.contents)
let token = post.events[0].replyToken
let user_msg = post.events[0].message.text
if(!token) { return }
send_msg(user_msg + "(Bot)", token)
}
自定函式send_msg()
使用UrlFetchApp.fetch()
來回傳訊息,回傳格式可參考 官方手冊。
function send_msg(msg_string, token)
{
UrlFetchApp.fetch(REPLY_URL, {
"headers": {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + CHANNEL_ACCESS_TOKEN,
},
"method": "post",
"payload": JSON.stringify({
"replyToken": token,
"messages": [{
"type": "text",
"text": msg_string
}]
})
})
}
Apps Script 讀寫數據
檔案夾、檔案名稱的常數設定。
const folder_name = "檔案夾"
const file_name = "data.json"
使用類別DriveApp
的相關 API 來讀取雲端硬碟,詳情可參考 官方手冊。
function get_data()
{
let text = null
let file = DriveApp.getFoldersByName(folder_name).next().getFilesByName(file_name)
if(file.hasNext())
{
file = file.next()
text = file.getBlob().getDataAsString()
}
return text
}
寫入的部分,可利用setContent()
將指定字串寫入檔案。
function save_data(json_string)
{
if(!json_string) { return }
let folder = DriveApp.getFoldersByName(folder_name)
if(folder.hasNext()) { folder = folder.next() }
else { folder = DriveApp.createFolder(folder_name) }
let file = folder.getFilesByName(file_name)
if(file.hasNext()) { file.next().setContent(json_string) }
else { file = folder.createFile(file_name, json_string) }
}
貼文底端