昨天我們實作出了一個 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_trainingalpaca_training表單提取資料。ORDER BY record_no DESCrecord_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 按鈕