iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
2
Modern Web

從LINE BOT到資料視覺化:賴田捕手系列 第 11

第 11 天:LINE BOT SDK:應用程式編程介面

第 11 天:LINE BOT SDK:應用程式編程介面

她是位很可愛的小女孩,親愛的 Phoebe。我不會說她貌美如花或什麼的,但她實在讓我魂牽夢縈。她說話的時候會不自覺的會哼起歌來,尤其是當她在說話而且越講越開心的時候。一個音符一個音符從她口中跳出,從她舌上跳出,從她唇上跳出。

~節錄自《賴田捕手》第十一章

  昨天我們將一位像鸚鵡一樣會學你說話的聊天機器人快速上架了(或至少上架了一位能接收 LINE 資料的聊天機器人)。如果覺得內容寫得不很清楚,或是按照步驟卻無法得到想要效果的,歡迎在下方留言,我會盡可能地回覆。另外,今天用到的程式碼我也先放在 Github 上了(這裡),歡迎大家過去參考。那麼,今天就先來細細的解釋一下,昨天那堆程式碼在寫什麼、我們需要了然於心的是哪些、而透過這些程式碼我們又可以做到些什麼?

配置檔(config.ini)

  但首先,我們先來把程式碼整理下。下面是昨天上架的機器人核心架構1_app_core.py (Github 程式碼範例在這裡):

# 載入需要的模組
from __future__ import unicode_literals
import os
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError

app = Flask(__name__)

# LINE 聊天機器人的基本資料
line_bot_api = LineBotApi('聊天機器人的 Chennel access token')
handler = WebhookHandler('聊天機器人的 Channel secret')

# 接收 LINE 的資訊
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

if __name__ == "__main__":
    app.run()
  • LINE 聊天機器人的基本資料:
      這段程式碼,是我想要重新整理的地方。
# LINE 聊天機器人的基本資料
line_bot_api = LineBotApi('聊天機器人的 Chennel access token')
handler = WebhookHandler('聊天機器人的 Channel secret')

  怎麼說呢?Channel access token 跟 Channel secret 是將 Python 檔案及 LINE 聊天機器人帳號緊密連接必要的步驟。如果我們手上有 10 個 Python 檔案需要連接到 LINE 聊天機器人,那麼這 10 個 Python 檔案的開頭必然會有這一段程式碼。結果有一天,你在更新 LINE 聊天機器人的時候,太專心以至於忽略了在你身後的草泥馬,結果不小心把 Channel access token 跟 Channel secret 的資料都透漏給這隻調皮的小動物了。萬一牠大嘴巴到處去宣傳,或是牠拿著你的 Channel access token 跟 Channel secret 做出了比我們還猛的聊天機器人,那可就糟了,面子掛不住啊。為了防止這類情況發生,你只好到登入 LINE Developers、找到屬於這隻 LINE 聊天機器人的設定,按下 "Issue" 重新申請一組 Channel access token 和 Channel secret。
  然後再回過頭來把 10 個 Python 檔案上的 Channel access token 和 Channel secret 全部更新一遍。想到就累對吧。有沒有更牽一髮而動全身的方法呢?我們能不能把 Channel access token 和 Channel secret 放在某個地方,讓所有(以我們剛才舉的例子:10 個!)需要使用到 Channel access token 和 Channel secret 的 Python 檔案都去讀取它,而不是把這些資料寫死在所有 Python 檔案裡。
  我們的願望configparser聽到了!
  首先,創造一個config.txt檔,並更改附檔名使其成為config.iniconfig.ini裡面需要填些什麼呢?

[section_a]
key_a = value_a
key_b = value_b

  在放有config.ini的資料夾裡,新增一份 Jupyter Notebook。如圖一
https://ithelp.ithome.com.tw/upload/images/20190919/20120178XHDAzpgPeW.png
圖一、將config.ini和 Jupyter Notebook 放在一起,方便讀取

  就可以用 Python 來讀取config.ini啦!試試看吧:

In [1]: import configparser

  首先我們需要載入一套內建在 Python 裡的模組 configparser。接著利用其提供的ConfigParser()物件來讀取我們的 config.ini檔案:

In [2]: config = configparser.ConfigParser()
        config.read('config.ini')
Out[3]: ['config.ini']

  來看看它能讀到些什麼:

In [4]: for i in config.sections():
            for j in config[i]:
                print(f'{config[i]}\t: {j}\t: {config.get(i, j)}')
Out[4]: <Section: sections-a>	: key_a	: value_a
        <Section: sections-a>	: key_b	: value_b

  現在我們把要用來記錄下 Channel_access_token 跟 Channel_secret 的config.ini改成

[line-bot]
channel_access_token = your_channel_access_token
channel_secret = your_channel_secret

  而所有需要用到 Channel_access_token 跟 Channel_secret 的 Python 檔案改成:

import configparser

# LINE 聊天機器人的基本資料
config = configparser.ConfigParser()
config.read('config.ini')

line_bot_api = LineBotApi(config.get('line-bot', 'channel_access_token'))
handler = WebhookHandler(config.get('line-bot', 'channel_secret'))

  漂亮吧!這樣萬一以後要修改 Channel access token 跟 Channel secret,只要更改在config.ini裡面的資料就行囉!

初探 LINE 應用程式編程介面(API)

  接下來,我們要從核心功能開始,藉由 LINE 提供的應用程式編程介面➀➁,一點一點的裝備起我們的 LINE 聊天機器人囉!這是我所謂的核心功能,你的伺服器必須要有一個 "/callback" 的位置,用來接收 LINE 發送過來的資訊,並且回傳'OK'

# 接收 LINE 的資訊
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

  怎麼知道 LINE 的資訊有沒有成功送到 "/callback" 去呢?在 LINE Developers 裡面,聊天機器人的 Channel settings 中,有一區 Messaging settings,如圖二。在 Use webhooks 設定為 Enabled,Webhook URL 設定為"https://你-APP-的名字.herokuapp.com/callback" ,按下 Verify,看到 Success 出現,就代表 LINE 跟我們的聊天機器人溝通無礙了!(由於採用免費方案的 Heroku 應用程式有睡覺的習慣,有時候按一次 Verify 不會出現 Success,不要緊,多按幾次試試。我也會在之後的文章裡花一點篇幅去改善 Heroku 應用程式睡覺習慣的問題。)
https://ithelp.ithome.com.tw/upload/images/20190919/20120178vxKqsOJa8t.png
圖二、Messaging settings

  那要怎麼讓 LINE 聊天機器人發送訊息到其他使用者那邊呢?來看看下面這段程式碼:

# 學你說話
@handler.add(MessageEvent, message=TextMessage)
def echo(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text)
    )
  • 第一行:@handler.add(MessageEvent, message=TextMessage)
      這一行程式碼,是提醒我們的 LINE 機器人,當收到 LINE 的 MessageEvent (信息事件),而且信息是屬於 TextMessage (文字信息)的時候,就執行下列程式碼。依照 LINE 的應用程式編程介面,LINE 的事件包括有:MessageEvent (信息事件)、FollowEvent (加好友事件)、UnfollowEvent (刪好友事件)、JoinEvent (加入聊天室事件)、LeaveEvent (離開聊天室事件)、MemberJoinedEvent (加入群組事件)、MemberLeftEvent (離開群組事件),還有許多許多(詳見這裡➀)。而MessageEvent又依照信息內容再分成TextMessageImageMessageVideoMessageStickerMessageFileMessage等等,當然,還有許多許多。(詳見這裡➀)
    聽到這裡,聰明的你是不是開始躍躍欲試,準備針對各種不同的事件撰寫不同的回應方式了呢?

  • 第二行: def echo(event):
      定義一個叫做echo的函數,該函數會接收一個 LINE 發送過來的資訊,並貼上event的標籤,方便後續的操作。

  • 第三行: line_bot_api.reply_message(
      還記得一開始我們一開始在 LINE 基本資料那邊寫下了一行程式碼:

line_bot_api = LineBotApi(config.get('line-bot', 'channel_access_token'))

  這行程式碼初始化了一個 LineBotApi的物件,該物件有一個方法reply_message。顧名思義,只能用在接收到其他 LINE 使用者的時候回覆信息,而不能用在主動推送信息。要使用這個方法需要提供兩個參數。

  • 第四行: event.reply_token,
      reply_message的第一個參數:reply_token,只能使用一次,用完即丟。當其他使用者傳送信息給你的 LINE 聊天機器人,會產生一個reply_token,你的聊天機器人拿著這個reply_token回覆傳信息的使用者,回覆完畢,reply_token消失。因此利用reply_message只能在收到其他使用者信息的時候,回傳一則信息。

  • 第五行: TextSendMessage(text=event.message.text)
      reply_message的第二個參數:要執行的動作。這邊,因為我們目前設計的是一個學你說話的機器人,所以要執行的動作就是TextSendMessage。當然當然,LINE 應用程式編程介面還提供了其他包括:ImageSendMessageVideoSendMessageStickerSendMessage等等的許多許多動作供您選用。我們這邊先選TextSendMessage,然後輸入需要的參數,也又是文字信息的內容text

  那event.message.text又是什麼意思呢?

  還記得函數一開始我們幫 LINE 送過來的資料貼上了event的標籤嗎?現在就來看看event裡面到底有什麼➁:

event = {"reply_token":"就是代表reply_token的一串亂碼", 
         "type":"message",
         "timestamp":"1462629479859", 
         "source":{"type":"user",
                   "user_id":"就是代表user的一串亂碼"}, 
         "message":{"id":"就是代表這次message的一串代碼", 
                    "type":"text", 
                    "text":"使用者傳來的文字信息內容"}} 
  • "timestamp"
      程式語言中標準的時間計算方式,採用毫秒(ms)為單位,從1970年1月1日(00:00:00 GMT)開始計算。
  • "message"
      與使用者發送的信息相關的資料,包括信息的類型("type")與內容("text")。

  其他有的介紹過了,有的蠻直覺的,就不多做說明了。看完了event的內容,我們知道event.message.text就是使用者送來信息的文字內容。

  好啦,一切都沒問題之後,試試看我們的第二版:

from __future__ import unicode_literals
import os
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

import configparser

app = Flask(__name__)

# LINE 聊天機器人的基本資料
config = configparser.ConfigParser()
config.read('config.ini')

line_bot_api = LineBotApi(config.get('line-bot', 'channel_access_token'))
handler = WebhookHandler(config.get('line-bot', 'channel_secret'))


# 接收 LINE 的資訊
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

# 學你說話
@handler.add(MessageEvent, message=TextMessage)
def echo(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text)
    )

if __name__ == "__main__":
    app.run()

  還記得怎麼把資料推向 Heroku 嗎?推向 Heroku 之前,記得要確認你的Procfile裡面選用的是更改過的 Python 檔案喔。

web: gunicorn Python 檔案名:app –preload

  再來一次,先從命令提示字元登入 Heroku:

D:\alpaca_fighting>heroku login -i
heroku: Enter your login credentials
Email: 你的 Heroku 帳號
Password: 你的 Heroku 密碼
Logged in as 你的 Heroku 帳號

  這邊雖然用heroku login一樣可以登入 Heroku,而且是從瀏覽器自動幫你登入,如下:

D:\alpaca_fighting>heroku login
heroku: Press any key to open up the browser to login or q to exit:

  省卻了輸入帳密的步驟,但某些清況,比如說你申請了兩個 Heroku 帳號,想要切換帳號的時候,就需要藉由手動輸入來指定登入的帳號,因此我還是推薦用heroku login -i來登入 Heroku。
  登入了 Heroku 後,可以輸入指令來指定你等等要將檔案推向哪一個 Heroku 應用程式(尤其是如果你有多個 Heroku 應用程式的話),指令為heroku git:remote –a 你-APP-的名字
  指定了 Heroku 的應用程式後,來到欲推向 Heroku 的資料夾,以我為例,就是D:\alpaca_fighting,接著依序輸入:git add .git commit –m "這次更新的註解"git push heroku master

D:\alpaca_fighting>git add .
warning: LF will be replaced by CRLF in config.ini.
The file will have its original line endings in your working directory
warning: LF will be replaced by CRLF in app_2nd.py.
The file will have its original line endings in your working directory

D:\alpaca_fighting>git commit -m "app_2nd"
[master 13d9715] app_2nd
 2 files changed, 46 insertions(+), 1 deletion(-)
 create mode 100644 app_2nd.py

D:\alpaca_fighting>git push heroku master
…以下略
remote: Verifying deploy... done.
To https://git.heroku.com/你-APP-的名字.git
   1738a62..13d9715  master -> master

  這樣就算完成更新了。好,來看看我們新的聊天機器人狀況如何。
https://ithelp.ithome.com.tw/upload/images/20190919/20120178qPr61npf1n.png
圖三、我們的 echo 聊天機器人

  棒,可以完美的讀出我的唇語並加以複製,在我說出口的同時馬上回覆我相同的訊息,真不愧是拷貝機器人 Phoebe。
  不過這個機器人有個小小的說不上缺陷的問題。讓我們去 LINE Developers 的 Channel settings 的 Messaging settings 看看,結果發現 Webhook URL 認證的時候出了一點小狀況,說是The webhook returned an invalid HTTP status code. (The expected status code is 200.),如圖四
https://ithelp.ithome.com.tw/upload/images/20190919/20120178Mr3WCLVYFF.png
圖四、聊天機器人可以正常回覆,但 Webhook URL 卻出了點小狀況

  怎麼會這樣呢?

  雖說這真的是小問題中的小問題,完全不會影響使用者體驗(不過影響開發者體驗?),但想趁這個機會跟大家分享一下,遇到 Heroku 應用程式出了問題可以怎麼尋找解決之道。
  那就是從 Heroku 提供的精美介面找起。首先從官網登入 Heroku,來到主畫面之後看到 Heroku 把你所有的應用程式列表出來,如圖五
https://ithelp.ithome.com.tw/upload/images/20190919/201201782ic9XNGe7s.png
圖五、Heroku 主畫面

  選取存放有我們 LINE 聊天機器人的應用程式,來到應用程式的控制面板。裡面提供了各式各樣的訊息,從有沒有擴充元件(add-ons),應用程式的類型,執行的方式,到最近做過的更新,應有盡有。右上角有一個 "More",點下去之後選擇 "View logs",如圖六,最後來到我們應用程式的工作日誌(logs),如圖七

https://ithelp.ithome.com.tw/upload/images/20190919/20120178TYUhforK5h.png
圖六、View logs

https://ithelp.ithome.com.tw/upload/images/20190919/20120178RKV3Mi9hU7.png
圖七、Heroku 應用程式工作日誌(logs)

  如果你才剛在 LINE Developers 的 Webhook URL 那邊按了 Verify,沒意外紀錄應該還留著。如果沒有也沒關係,到 Webhook URL 再按一次 Verify,隨著程式的運作,Heroku 工作日誌會記錄下運作的情形,或是出了哪些狀況。我們看到隨著按下 Verify,工作日誌跳出一大堆訊息,最後一行:

linebot.exceptions.LineBotApiError: LineBotApiError: status_code=400, error_response={"details": [], "message": "Invalid reply token"}

  說明了問題的原因:我沒辦法用這個 LINE 給的 reply token。怎麼會呢?還是不太懂,讓我們稍微改一下 LINE 聊天機器人的程式碼,請程式講清楚說明白。

# 接收 LINE 的資訊
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    
    # 我加了下面那一行
    print(body)
    # 我加了上面那一行

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

  因為這位 LINE 聊天機器人能夠相當完美的回覆使用者訊息,唯一的小小問題是沒辦法在 Webhook URL 看到 Success,所以我們只對負責接收 LINE 資訊的程式碼做小小更動。聞到了嗎,是print(body)的味道!
  重新推向 Heroku,重新按下 Verify,重新回到 Heroku 應用程式工作日誌,然後又重新看到了 linebot.exceptions.LineBotApiError。但這次我們多看到了點什麼:

{
   "events": [
     {
       "replyToken": "00000000000000000000000000000000",
       "type": "message",
       "timestamp": 1568894460464,
       "source": {
         "type": "user",
         "userId": "Udeadbeefdeadbeefdeadbeefdeadbeef"
       },
       "message": {
         "id": "100001",
         "type": "text",
         "text": "Hello, world"
       }
     },
     {
       "replyToken": "ffffffffffffffffffffffffffffffff",
       "type": "message",
       "timestamp": 1568894460464,
       "source": {
         "type": "user",
         "userId": "Udeadbeefdeadbeefdeadbeefdeadbeef"
       },
       "message": {
         "id": "100002",
         "type": "sticker",
         "packageId": "1",
         "stickerId": "1"
       }
     }
   ]
 }

  原來是隨著我們按下 Verify,LINE 送來了兩個罐頭訊息,而且是兩個難以下嚥的罐頭訊息。為什麼這麼說呢?還記的我們的 LINE 聊天機器人在 Heroku 提供的工作日誌上寫下的:
linebot.exceptions.LineBotApiError: LineBotApiError: status_code=400, error_response={"details": [], "message": "Invalid reply token"}
  最後一段,"Invalid reply token",說的就是你啊
"replyToken": "00000000000000000000000000000000"
"replyToken": "ffffffffffffffffffffffffffffffff"
  大概是因為這是測試用的罐頭訊息,所以 reply token 也搞得很罐頭,罐頭到我們的 LINE 聊天機器人都不想吃了。
  不想吃可以嗎?
  As you wish!

from __future__ import unicode_literals
import os
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage

import configparser

app = Flask(__name__)

# LINE 聊天機器人的基本資料
config = configparser.ConfigParser()
config.read('config.ini')

line_bot_api = LineBotApi(config.get('line-bot', 'channel_access_token'))
handler = WebhookHandler(config.get('line-bot', 'channel_secret'))


# 接收 LINE 的資訊
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']

    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

# 學你說話
@handler.add(MessageEvent, message=TextMessage)
def echo(event):
    
    # 這次我加了下面這一行
    if event.source.user_id != "Udeadbeefdeadbeefdeadbeefdeadbeef":
    # 這次我加了上面這一行
    
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=event.message.text)
        )

if __name__ == "__main__":
    app.run()

  只要是 LINE 關放送來的訊息,我就不回,我就是任性。試著把這個任性的 LINE 聊天機器人推向 Heroku 吧!

https://ithelp.ithome.com.tw/upload/images/20190919/20120178XegcBiUXM3.png
圖八、Webhook URL verify success

  成功了!

  既然我們對 LINE 基本的應用程式編程介面有一些了解了,何不利用前幾天學到的 Python 各種技巧,讓我們的聊天機器人唱起歌來呢:

# Phoebe愛唱歌
@handler.add(MessageEvent, message=TextMessage)
def pretty_echo(event):
    
    if event.source.user_id != "Udeadbeefdeadbeefdeadbeefdeadbeef":
        
        pretty_note = '♫♪♬'
        pretty_text = ''
        
        for i in event.message.text:
        
            pretty_text += i
            pretty_text += random.choice(pretty_note)
    
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=pretty_text)
        )

https://ithelp.ithome.com.tw/upload/images/20190919/201201782S6xodHkJJ.png
圖九、我家的 Phoebe 愛唱歌

LINE 聊天機器人的Channel settings

  最後提醒大家一下,可以到 LINE Developers 的聊天機器人的 Channel settings,在 Using LINE@ features 的欄位上,找到 Set message,如圖十紅框處。點下去之後會開啟新的分頁,如圖十一,是我目前聊天機器人的設定。

https://ithelp.ithome.com.tw/upload/images/20190919/20120178gpiG0M3VN6.png
圖十、Using LINE@ features

https://ithelp.ithome.com.tw/upload/images/20190919/20120178JoiLxnBcVb.png
圖十一、回應設定

  今天我們從建構配置檔開始、初始化聊天機器人、利用 LINE 提供的應用程式編程介面為聊天機器人加入回話功能、閱讀 Heroku 應用程式工作日誌偵錯、到最後為聊天機器人添加裝可愛回話功能,相信大家對寫程式慢慢的有點感覺,也越來越熟悉了!我把我到今天為止,推向 Heroku 的資料夾放在 Github 上了(這裡),有興趣的可以過去參考看看。如果我今天的文章有哪邊寫得不夠清楚的,也可以在下面留言,我會盡量改善。就用我可愛的 LINE 聊天機器人 QR code 做個結尾,如果想知道以上程式碼會做出如何的效果,歡迎來加!別擔心,就跟大家的一樣可愛!謝謝大家!

https://ithelp.ithome.com.tw/upload/images/20190919/201201785ZYk5ZAu1j.png
圖十二、我到今天為止建立的聊天機器人

參考資料

➀ 官方 LINE-Bot-SDK
➁ 官方 LINE Developers Messaging API

註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕


上一篇
第 10 天:LINE BOT SDK:初始化聊天機器人
下一篇
第 12 天:LINE BOT SDK:應用程式編程介面(續)
系列文
從LINE BOT到資料視覺化:賴田捕手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
改革家ForFun
iT邦新手 4 級 ‧ 2020-10-28 17:02:15

圖四、聊天機器人可以正常回覆,但 Webhook URL 卻出了點小狀況

這邊開始拯救了我
感謝您~

0
peter1125
iT邦新手 5 級 ‧ 2020-12-09 00:45:59

大大您好,line有收到我的request,但一直出現400 error,沒辦法讓機器人回覆我相同的訊息,請問這是為什麼? 本人程式初學者,如果您願意的話還請您幫我解惑~
更新:已解決,config的設定我加了引號,就錯了

0
brianhjli
iT邦新手 5 級 ‧ 2021-09-28 21:40:41

想請教兩個問題

  1. procfileweb: gunicorn Python 檔案名:app –preload檔案名的部分是否需要副檔名?
  2. 我從上一篇文章開始webhook就一直沒辦法verify成功,error message是503,想請教可能是什麼部分出錯了?

謝謝原po,這個系列文很詳盡且淺顯易懂

我要留言

立即登入留言