她是位很可愛的小女孩,親愛的 Phoebe。我不會說她貌美如花或什麼的,但她實在讓我魂牽夢縈。她說話的時候會不自覺的會哼起歌來,尤其是當她在說話而且越講越開心的時候。一個音符一個音符從她口中跳出,從她舌上跳出,從她唇上跳出。
~節錄自《賴田捕手》第十一章
昨天我們將一位像鸚鵡一樣會學你說話的聊天機器人快速上架了(或至少上架了一位能接收 LINE 資料的聊天機器人)。如果覺得內容寫得不很清楚,或是按照步驟卻無法得到想要效果的,歡迎在下方留言,我會盡可能地回覆。另外,今天用到的程式碼我也先放在 Github 上了(這裡),歡迎大家過去參考。那麼,今天就先來細細的解釋一下,昨天那堆程式碼在寫什麼、我們需要了然於心的是哪些、而透過這些程式碼我們又可以做到些什麼?
但首先,我們先來把程式碼整理下。下面是昨天上架的機器人核心架構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_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.ini
。config.ini
裡面需要填些什麼呢?
[section_a]
key_a = value_a
key_b = value_b
在放有config.ini
的資料夾裡,新增一份 Jupyter Notebook。如圖一。
圖一、將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 提供的應用程式編程介面➀➁,一點一點的裝備起我們的 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 應用程式睡覺習慣的問題。)
圖二、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
又依照信息內容再分成TextMessage
、ImageMessage
、VideoMessage
、StickerMessage
、FileMessage
等等,當然,還有許多許多。(詳見這裡➀)
聽到這裡,聰明的你是不是開始躍躍欲試,準備針對各種不同的事件撰寫不同的回應方式了呢?
第二行: 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 應用程式編程介面還提供了其他包括:ImageSendMessage
、VideoSendMessage
、StickerSendMessage
等等的許多許多動作供您選用。我們這邊先選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"
:"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
這樣就算完成更新了。好,來看看我們新的聊天機器人狀況如何。
圖三、我們的 echo 聊天機器人
棒,可以完美的讀出我的唇語並加以複製,在我說出口的同時馬上回覆我相同的訊息,真不愧是拷貝機器人 Phoebe。
不過這個機器人有個小小的說不上缺陷的問題。讓我們去 LINE Developers 的 Channel settings 的 Messaging settings 看看,結果發現 Webhook URL 認證的時候出了一點小狀況,說是The webhook returned an invalid HTTP status code. (The expected status code is 200.)
,如圖四。
圖四、聊天機器人可以正常回覆,但 Webhook URL 卻出了點小狀況
怎麼會這樣呢?
雖說這真的是小問題中的小問題,完全不會影響使用者體驗(不過影響開發者體驗?),但想趁這個機會跟大家分享一下,遇到 Heroku 應用程式出了問題可以怎麼尋找解決之道。
那就是從 Heroku 提供的精美介面找起。首先從官網登入 Heroku,來到主畫面之後看到 Heroku 把你所有的應用程式列表出來,如圖五。
圖五、Heroku 主畫面
選取存放有我們 LINE 聊天機器人的應用程式,來到應用程式的控制面板。裡面提供了各式各樣的訊息,從有沒有擴充元件(add-ons),應用程式的類型,執行的方式,到最近做過的更新,應有盡有。右上角有一個 "More",點下去之後選擇 "View logs",如圖六,最後來到我們應用程式的工作日誌(logs),如圖七。
圖六、View logs
圖七、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 吧!
圖八、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)
)
圖九、我家的 Phoebe 愛唱歌
最後提醒大家一下,可以到 LINE Developers 的聊天機器人的 Channel settings,在 Using LINE@ features 的欄位上,找到 Set message,如圖十紅框處。點下去之後會開啟新的分頁,如圖十一,是我目前聊天機器人的設定。
圖十、Using LINE@ features
圖十一、回應設定
今天我們從建構配置檔開始、初始化聊天機器人、利用 LINE 提供的應用程式編程介面為聊天機器人加入回話功能、閱讀 Heroku 應用程式工作日誌偵錯、到最後為聊天機器人添加裝可愛回話功能,相信大家對寫程式慢慢的有點感覺,也越來越熟悉了!我把我到今天為止,推向 Heroku 的資料夾放在 Github 上了(這裡),有興趣的可以過去參考看看。如果我今天的文章有哪邊寫得不夠清楚的,也可以在下面留言,我會盡量改善。就用我可愛的 LINE 聊天機器人 QR code 做個結尾,如果想知道以上程式碼會做出如何的效果,歡迎來加!別擔心,就跟大家的一樣可愛!謝謝大家!
圖十二、我到今天為止建立的聊天機器人
➀ 官方 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 按鈕
大大您好,line有收到我的request,但一直出現400 error,沒辦法讓機器人回覆我相同的訊息,請問這是為什麼? 本人程式初學者,如果您願意的話還請您幫我解惑~
更新:已解決,config的設定我加了引號,就錯了
想請教兩個問題
procfile
裡web: gunicorn Python 檔案名:app –preload
檔案名的部分是否需要副檔名?謝謝原po,這個系列文很詳盡且淺顯易懂