昨天我們實作出了一個 LINE 儲存資料的小幫手,藉由輸入特定格式的指令,LINE 聊天機器人就會將訊息轉換為符合 Heroku Postgres 表格的資料,並透過 psycopg2 下達 SQL 指令,將資料存入表格中。
既然存入資料都可以請 LINE 聊天機器人幫忙了,我們是否也可以請 LINE 聊天機器人幫我們從 Heroku Postgres 的表格中讀出特定的資料呢?今天我們就來試著讓 LINE 聊天機器人幫我們查詢表格中的資料吧。
我們先來寫一段讀取資料的程式碼看看。經過這幾天浸淫在滿滿的 psycopg2 跟 SQL 指令的氛圍當中,相信大家對於該怎麼做,心裡應該有點概念了。我就先野人獻曝一番,讓大家看看我寫的程式碼:
import os
import psycopg2
def line_select_overall(fetchnumber):
DATABASE_URL = os.environ['DATABASE_URL']
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
cursor = conn.cursor()
postgres_select_query = f"""SELECT * FROM alpaca_training ORDER BY record_no DESC;"""
cursor.execute(postgres_select_query)
raw = cursor.fetchmany(int(fetchnumber))
message = []
for i in raw:
message.append((i[0], i[1], i[2], str(i[3])[:-3], str(i[4])))
cursor.close()
conn.close()
return message
上面那段程式碼,我首先引進需要用到的套件,並定義了一個函數line_select_overall()
,以及操作這個函數時需要用到的參數fetchnumber
。接著,嘗試連接到 Heroku Postgres 提供的資料庫。一樣的,再提醒大家一次,在 Heroku 的環境中,取得資料庫的位置只需要用 DATABASE_URL = os.environ['DATABASE_URL']
就可以了,這也是 Heroku 建議的做法。
而這個函數當中,我下達的 SQL 指令是這樣的:
SELECT *
FROM alpaca_training
ORDER BY record_no DESC;
SELECT *
FROM alpaca_training
alpaca_training
表單提取資料。ORDER BY record_no DESC
record_no
的順序,由大到小(DESC
, descending)依序將資料提取出來。 為什麼這麼做呢?因為我想看最新的資料嘛。
接著我用cursor.fetchmany(int(fetchnumber))
,以參數fetchnumber
為依據,傳回特定數量的資料。剩下的程式碼就只是在做一些讓資料更利於閱讀的文字操作而已。
所以這整段程式碼在做的,就是從alpaca_training
表單中,傳回一共fetchnumber
筆最新的(最後進入表單的)訓練資料。這樣就結束了。沒錯,就跟大家想的一樣,並不困難,沒枉費我們前幾天學 psycopg2 跟 SQL 指令學的那麼認真。但這樣今天的內容也太沒挑戰性了?所以我們要來試著做點好玩的東西。
首先我們來模擬一下情境:
我:「我想查詢alpaca_training
裡面的訓練資料。」
LINE 聊天機器人:「您想查詢所有(Overall)資料、還是要根據草泥馬的名字(alpaca_name
)、還是要根據草泥馬的訓練內容(training
)來做特定的查詢呢?」
我:「所有資料,謝謝」
因為所有資料真的太多了,一次傳過來我可能會看得頭昏眼花,貼心的 LINE 聊天機器人於是又發問了。
LINE 聊天機器人:「您想要看最新的一筆(Last 1)資料呢,還是最新的十筆(Last 10)資料呢?」
我:「最新的十筆,謝謝。」
於是乎,按著我的回答,LINE 聊天機器人傳給了我最新的十筆資料。
這樣子的對話邏輯,有可能在 LINE 聊天機器人裡面被實現嗎?當然是可能的。我們今天就來試著用 LINE 提供的新玩具:FlexMessage去建立我們想要的對話模式吧!
不囉嗦,先來看看實作出來的結果:
圖一、FlexMessage 一問一答
FlexMessage 的其中一個功能,讓 LINE 聊天機器人可以提供類似問卷一樣的選擇情境,而根據使用者的回答或選擇,進一步觸發 LINE 聊天機器人去執行特定指令。
LINE 所提供的 FlexMessage 其架構有點像是一個網頁,可以放入的元素相當豐富,因此能夠做出各式各樣美輪美奐的 FlexMessage。
FlexMessage 的基本架構如官方網頁內容所示➂。
若用 Python 的語法來寫,則是一個超大的字典物件:
contents = {'type': 'bubble',
'herder':,
'hero':,
'body':,
'footer':}
而'herder'
、'hero'
、'body'
、'footer'
又可以繼續填入如'box'
、'image'
、'text'
、'seperator'
、'button'
等等的物件➂。
contents = {'type':'box',
'layout':,
'contents':}
contents = {'type':'image',
'url':,
'size':,
'aspect_ratio':,
'aspect_mode':,
'action':}
contents = {'type':'text',
'text':,
'size':,
'align':,
'color':}
contents = {'type':'seperator',
'type':}
contents = {'type':'button',
'type':,
'style':,
'color':,
'action':}
當然上面只是舉幾個常用於 FlexMessage 的元素,以及這些元素常用的參數。詳細的內容可以參考 LINE 提供的應用程式編程介面➃。不過如果大家是以 Python 來寫 LINE 聊天機器人的時候,在看官方提供的文件時,有件事得先謹記在心:官方文件提供的變數名稱都是以首字母小寫的駝峰式命名來呈現➄,也就是 JavaScript 習慣使用的命名方式。但是但是,在 LINE 提供給 Python 的官方套件 line-bot-sdk-python 中,各個變數卻是以小寫字母加底線的方式命名。大家還記得 LINE 的 MessageEvent 的結構嗎?
MessageEvent = {'reply_token':,
'type':,
'timestamp':,
'source': {'type':, 'user_id':},
'message':{'id':, 'type':, 'text':}}
不知道大家有沒有很好奇:我們在 LINE 官方提供的文件裡面,看到的格式明明是
MessageEvent = {'replyToken':,
'type':,
'timestamp':,
'source': {'type':, 'userId':},
'message':{'id':, 'type':, 'text':}}
官方文件給的明明是'replyToken'
跟'userId'
啊?但我們卻用'reply_token'
跟'user_id'
,而且不這樣用還不行呢。沒錯我在說的就是這件事。因此如果你是用 Python 來寫 LINE 的聊天機器人,記得,在官方文件看到關鍵字像是'aspectRatio'
,你要改成'aspect_ratio'
,line-bot-sdk-python 才看得懂你在寫什麼。
由於 FlexMessage 的架構實在是太大了,'body'
裡面可以有'box'
,'box'
裡面又可以繼續放入'box'
、'text'
或是'seperator'
。有些時候我們就會在無限的字典迴圈裡面迷路,而不知道我們會造出怎麼樣的 FlexMessage。於是乎,LINE 官方很貼心的提供了一個 FlexMessage 模擬器。先登入 LINE Developers,進到 Provider List 的頁面,就可以看到最左邊有一排 Providers、Tools、Support。點擊 Tools,就會看到如圖三 FlexMessage 模擬器(Flex Message Simulator)出現在我們面前啦。
選擇 Flex Message Simulator 之後,會跳出一個新的分頁,右上角有一個 + 的符號,如圖四。點下去之後會出現 LINE 提供的一些 FlexMessage 模板,選擇一個接近我們需要的模板,就可以快速地開始修改、編輯囉!
圖二、選擇 Flex Message Simulator
圖三、選擇適合模板
圖四、開始編輯囉!
我做出來的模板大概是長這樣:
圖六、透過 Flex Message Simulator 創造出的 FlexMessage
紅色框框處是一個一個的按鈕(button),按下去等於是告訴 LINE 聊天機器人我們的回答,而我希望 LINE 聊天機器人能根據我們的回答作出相應的反應。這時候就需要用到'button'
裡面的'action'
這個項目。
LINE 提供的'action'
有兩種。一種可以帶你上網,另一種則可以回傳資料給 LINE 聊天機器人,藉此告訴 LINE 聊天機器人我們的選擇。
{'type':'uri',
'label':,
'uri':}
上面那種就是帶你上網的,'label'用來設定'button'
上顯示的字串。
{'type':'postback',
'label':,
'data':}
而這邊第二種,是我今天想用的,可以製造出 PostbackEvent 的。讓我們來看一看 PostbackEvent:
{"type":"postback",
"reply_token":,
"source":{"user_id":, "type":},
"timestamp":,
"postback":{"data":,"params":{"datetime":}}}
裡面資料有很多,但真正重要的是"postback"
裡面的"data"
,這正是我們靠'action'
傳送過去的"data"
。
當我們從 FlexMessage 模擬器寫出想要的 FlexMessage 之後,要怎麼交給 LINE 聊天機器人呢?
溫故知新一下,大家還記得我們的TextSendMessage()
嗎?
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=reply)
)
好的,那麼FlexSendMessage()
就是這樣用的:
FlexSendMessage(
alt_text = '說明文字',
contents = 你在Flex Message Simulator寫出來的程式碼
)
alt_text
不重要,但是一定要有。contents
則是我們在 FlexMessage 模擬器中創造出來的一大串程式碼。好的,我們實際來試一試:
def index_flex(event):
if '查詢訓練紀錄' in event.message.text:
line_bot_api.reply_message(
event.reply_token,
FlexSendMessage(
alt_text = 'index',
contents = index
)
)
我先定義一個用來回傳 FlexMessage 的函數,啟動的條件是,對 LINE 機器人說出'查詢訓練紀錄'
。contents = index
中的index
,則是我在 FlexMessage 模擬器寫出來的程式碼。大家想看嗎?好喔:
index = {
"type": "bubble",
"hero": {
"type": "image",
"url": "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_2_restaurant.png",
"size": "full",
"aspect_ratio": "20:13",
"aspect_mode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"spacing": "md",
"contents": [
{
"type": "text",
"text": "Into the alpaca_training",
"size": "xl",
"weight": "bold"
},
{
"type": "separator",
"margin": "xxl"
},
{
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "Overall",
"weight": "bold",
"margin": "sm",
"flex": 0
}
]
},
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "# 查詢所有 alpaca_training 中的資料",
"size": "sm",
"color": "#aaaaaa"
}
]
},
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "alpaca_name",
"weight": "bold",
"margin": "sm",
"flex": 0
}
]
},
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "# 依照 alpaca_name 查詢",
"size": "sm",
"color": "#aaaaaa"
}
]
},
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "training",
"weight": "bold",
"margin": "sm",
"flex": 0
}
]
},
{
"type": "box",
"layout": "baseline",
"contents": [
{
"type": "text",
"text": "# 依照 training 查詢",
"size": "sm",
"color": "#aaaaaa"
}
]
}
]
},
{
"type": "separator",
"margin": "xxl"
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "button",
"style": "link",
"color": "#1DB446",
"action": {
"type": "postback",
"label": "Overall",
"data": "Overall"
}
},
{
"type": "button",
"style": "link",
"color": "#1DB446",
"action": {
"type": "postback",
"label": "alpaca_name",
"data": "alpaca_name"
}
},
{
"type": "button",
"style": "link",
"color": "#1DB446",
"action": {
"type": "postback",
"label": "training",
"data": "training"
}
}
]
}
}
暈頭轉向吧?
接著再用去對PostbackEvent作反應:
@handler.add(PostbackEvent)
def reply_postback(event):
report_record(event)
這樣子我們就可以用 FlexMessage 跟 LINE 聊天機器人對話了!
今天的內容有點亂。總而言之,我們的目標是透過 FlexMessage 一問一答的形式,告訴 LINE 聊天機器人我們想要依照什麼條件去抓取需要的資料。今天算是對 FlexMessage 以及 PostbackEvent 有的初步的了解,明天會繼續說明如何使用 FlexMessage 作出一個 LINE 查詢資料小幫手。相關的程式碼我也會在明天放到 Github 上,今天的內容若有不清楚的地方,歡迎直接在下面留言,我會盡可能地回覆大家的。謝謝大家!
➀ Psycopg2 使用教學
➁ PostgreSQL 使用教學
➂ FlexMessage 基本架構
➃ FlexMessage 應用程式編程介面
➄ 駝峰式命名 wiki
註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕