前言

每次到了晚餐時間都會遇到一個亙古難題,今天晚餐要吃什麼?

為了解決這個問題,我原先的想法是架設網站來抽籤,但抽完後還需截圖上傳群組,感覺太麻煩了。

那不如直接架設聊天機器人並拉進群組,用指定字詞呼叫它來幫忙抽籤。

接下來的問題就是,後端要部署在哪裡?

網路上很多人選擇的平臺是 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) }
}