利用 FB Bot 建立自動回覆機器人

流程

  • 概覽

    User 透過 Messenger 傳訊息到 Facebook Server, Facebook Server 會打一個Post Request,把event傳給
    Webhook endpoint ,在 Business Server 內邏輯判斷是什麼event並形成request body
    然後將打一個包括request bodyPost requestSend API通知 Facebook Server 回覆 User

  • Component

    • PSID(Page-scoped ID): 一個 User 連一個 Messenger Bot 時會被 Messenger Platform 指派一個 PSID,
      這可以確保每個 User 和 Bot 的互動是一對一的關係。
    • Send API: Business Server在收到Facebook Server透過Webhooks傳來的event後,
      可以透過Send API來回傳東西去Facebook Server,內容可以是textimagefile
    • Webhook: 一個end point,通常是xxxsite/webhook,用來接收Facebook Server傳來的event
      並根據其性質來看它是哪哪種webhook event。至少會用到messagesmessaging_postbacks這兩種 webhook events
  • Steps

    1. Create FanPage

    2. Create Facebook App

    3. Add Messenger Platform to App

    4. Coding

      • 建立Http server
      • 加入 webhook verify token
        • 自行設定verify token
        • 在接收到GET Request之後,驗證verify token是否一致,若是,則subscribe webhook 到
      • 加入 webhook endpoint 以接收POST Request傳來的event
    5. Test

      • 啟動: $node index.js
      • GET Request以 subscribe webhook: $ curl -X GET "localhost:1337/webhook?hub.verify_token=<YOUR_VERIFY_TOKEN>&hub.challenge=CHALLENGE_ACCEPTED&hub.mode=subscribe"
        • 若正確: WEBHOOK_VERIFIEDCHALLENGE_ACCEPTED
      • POST Request以測試 webhook: $ curl -H "Content-Type: application/json" -X POST "localhost:1337/webhook" -d '{"object": "page", "entry": [{"messaging": [{"message": "TEST_MESSAGE"}]}]}'
        • 若正確: TEST_MESSAGEEVENT RECEIVED
    6. Deploy

    7. Configure the webhook for app

      • Callback URL: 貼上ngrok的URL/webhook
      • Verify Token: 貼上先前自訂的 Token
      • 若以上兩個都正確,在點選驗證後,FB會發一個GET Request確認並儲存下來,
        此時應已將 app 給 subscribe 至粉專,在粉專 Messenger 已可 key 訊息
    8. 建立自動回覆 Bot 相關 coding

      • 將環境變數設為連結粉專時取得的Auth,這將在打 POST Reqest 給
        Send API時使用
      • 建立 handleMessagecallSendAPI兩個 function 來建立
        判別event type 及 call Send API
      • 取得 PSID
      • handleMessage內區別 event typemessages還是messaging_postback
        若是messages則建立response
      • 透過 callSendAPI 來建立request body並利用POST request打到
        https://graph.facebook.com/v2.6/me/messages來 trigger Send API
        這邊就會須要使用粉專的Auth Token
    9. 完成,此時FB 粉專 Bot 應可以正常 echo 訊息給 User。

Terminal Command

  • curl -X GET “localhost:1337/webhook?hub.verify_token=newMessenger&hub.challenge=CHALLENGE_ACCEPTED&hub.mode=subscribe”

    • -X: 定義方法
    • URI的內容包括
      1. localhost: domain
      2. :1337: port 號
      3. /webhook: path
      4. ?: 使用GET時後面要帶出Query String,這個問號就是用來帶Query String用的
      5. hub.verify_token=newMessenger: 前面hub.verify_token是 key, 後面newMessenger是 value
      6. &: 分隔 Query String 用
      7. hub.challenge=CHALLENGE_ACCEPTED: 同前述
      8. &: 同前述
      9. hub.mode=subscribe": 同前述
  • curl -H “Content-Type: application/json” -X POST “localhost:1337/webhook” -d ‘{“object”: “page”, “entry”: [{“messaging”: [{“message”: “TEST_MESSAGE”}]}]}’

    • -H: 定義 header
      • 這邊是把 content-type 定義為 json
    • -X: 定義方法
    • -d: 定義 data
      • 可以透過 postman,點 body,選項選 raw

        {
          "object": "page", 
          "entry": [
            {
              "messaging": [
                {
                  "message": "TEST_MESSAGE"
                  
                }]
              
            }
            ]
        }
        
      • 可以看到最外圍是一個object,裡面有兩個東西,一個叫"object",一個叫"entry",裡面就一直包array和object

code

 'use strict';
 // Imports dependencies and set up http server
 const
   express = require('express'),
   bodyParser = require('body-parser'),
   app = express().use(bodyParser.json()), // creates express http server
   port = 5000,
   // 把原本是process.env.port (應該是要require process檔案 去指向裡面的.env,再指向以port為key的值) 直接改掉寫死 (node.js裡面的點點大概可以想成php的->) 
   page_access_token = 'EAAh84HrF238BAEsGvODMhMVvUbk8jT4czB99LTwM7lp2B6rt8bfaA4leyF2dU7e99VZCvSNS48eRAPzTnve8jN9Xhkz8IPJNX7IIvhJ44sUWiAHYL6CMVBa9QJ8yyKBDOvTak0Q0P4QMv9ssKD8rT72DiI8oPV16UZCJ4HZAwZDZD';
   // 這邊原本是要設環境變數 後來是直接把它寫成常數放這
 // Sets server port and logs message on success
 app.listen(port || 1337, () => console.log('webhook is listening'));
 
 // Creates the endpoint for our webhook 
 app.post('/webhook', (req, res) => {  
                     // 這是個callback,可以想像成前面有function以及function name被省略了,而裡面有兩個參數,一個是req (request),一個是res (respond)
   let body = req.body;
                     // 把req參數裡的body存到body變數裡
   // Checks this is an event from a page subscription
   if (body.object === 'page') {
        // 可以想成body->object的值要等於'page'
     // Iterates over each entry - there may be multiple if batched
     body.entry.forEach(function(entry) {
        // body->entry,並foreach出來,可以想成foreach (entry as entry) {},後面的花括號才是要做的事
       // Gets the message. entry.messaging is an array, but 
       // will only ever contain one message, so we get index 0
       let webhook_event = entry.messaging[0];
       // entry.messaging裡面可能不只放一個東西(可能會放是誰傳的、何時傳的...目前的範例是只有放一個訊息),但我們只要一個,而這個是訊息,所以設參數0
       console.log(webhook_event);
     });
 
     // Returns a '200 OK' response to all requests
     res.status(200).send('EVENT_RECEIVED');
   } else {
     // Returns a '404 Not Found' if event is not from a page subscription
     res.sendStatus(404);
   }
 
 });
 
 // Adds support for GET requests to our webhook
 app.get('/webhook', (req, res) => {
   // 也是一樣有callback, 傳req和res進來,只是是用query string傳,傳進來後會變成array
   // Your verify token. Should be a random string.
   let verify_token = "newMessenger"
     
   // Parse the query params
   let mode = req.query['hub.mode'];
   // 由於透過 query string 傳進來會是array,所以這邊用array的方式取值 (key是'hub.mode', value在此例是'subscribe')
   let token = req.query['hub.verify_token'];
   let challenge = req.query['hub.challenge'];
     
   // Checks if a token and mode is in the query string of the request
   if (mode && token) {
     // Checks the mode and token sent is correct
     if (mode === 'subscribe' && token === verify_token) {
       
       // Responds with the challenge token from the request
       console.log('WEBHOOK_VERIFIED');
       res.status(200).send(challenge);
     
     } else {
       // Responds with '403 Forbidden' if verify tokens do not match
       res.sendStatus(403);      
     }
   }
 });
 
  • PS. 上面的foreach在粉專key “Test” 的話會把資料都印出來
```
{
  sender: { id: '2205210249526222' },
  recipient: { id: '1421661017849456' },
  timestamp: 1560922272916,
  message: {
    mid: '6To75VlOa8MhW6BLs3xGHMiEYWEsnLoC3bTCQVJLbL8yaC7w3TRkv3e6sj86MI7gHyzOJF4uAkrxmmSczKEl-A',
    seq: 0,
    text: 'Test'
  }
}
```