經過一周奮戰, 這幾天完成 Mapleboard 的 HTTP API 函式執行平台建置, 終於可以回到這波 Mapleboard 架站的目標 : 利用 Mapleboard 當作 LINE Bot 的 webhook 後台伺服器來開發 LINE Bot 應用. 本篇先來接續之前用 ngrok 模擬後台伺服器實作 Echo Bot 的測試, 改用 Mapleboard 的 HTTP API 函式執行平台來做.
本系列之前的測試文章參考 :
先摘要整理一下 LINE Bot 的製作流程 :
一. 建立 LINE Bot 與平台設定
- 申請 LINE Developers 帳號 :
前往 LINE Developers 以個人 LINE 帳號登入. - 建立 Channel (頻道) :
選擇「Messaging API」類型, 設定 Channel 名稱, 圖示, 說明等資訊. - 取得存取 LINE messaging API 之權限 :
- Channel secret (通道密鑰) :
此密鑰是當後端應用程式收到 Webhook 請求時用來驗證 Webhook 傳入之請求真實性, 當使用者傳訊息給 LINE Bot, LINE 的伺服器會將該事件透過 Webhook 傳送給我們的後端伺服器, 同時在 HTTP Header 中夾帶一個 X-Line-Signature 簽章參數. 後端程式需要使用 Channel Secret 對請求內容進行 HMAC-SHA256 簽章驗證以來確保這是 LINE 官方伺服器傳來的請求而不是偽造的. - Channel access token (通道存取權杖) :
此權杖用來呼叫 LINE Messaging API 來推播或回覆訊息等. - 設定 Webhook URL :
在 LINE Developers Console 中輸入 Webhook 網址, 啟用 Webhook 功能 (建議關閉「自動回應訊息」與「歡迎訊息」)
二. 撰寫 Webhook 程式 (例如 Flask)
- 安裝必要套件 :
pip install flask line-bot-sdk - 撰寫 Flask Webhook 程式 :
# line_webhook.pyfrom flask import Flask, request, abortfrom linebot import LineBotApi, WebhookHandlerfrom linebot.exceptions import InvalidSignatureErrorfrom linebot.models import MessageEvent, TextMessage, TextSendMessageapp = Flask(__name__)line_bot_api = LineBotApi('你的 Channel access token')handler = WebhookHandler('你的 Channel secret')@app.route("/line/webhook", methods=['POST'])def callback():signature = request.headers['X-Line-Signature']body = request.get_data(as_text=True)try:handler.handle(body, signature)except InvalidSignatureError:abort(400)return 'OK'@handler.add(MessageEvent, message=TextMessage)def handle_message(event):reply = f"你說的是:{event.message.text}"line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply))if __name__ == "__main__":app.run(port=8080)
三. 部署與測試 :
- 部署 Webhook 程式到網際網路 :
(1). Replit (簡易測試用)
(2). Google Cloud Function (第一年免費)
(3). Render 或 Railway 等免費/低價平台
(3). 自架伺服器 (可使用 Let's Encrypt 憑證 + Nginx) - 使用 HTTPS 串接 LINE 平台 :
LINE 要求 Webhook URL 必須是 公開的 HTTPS
我在第一篇測試中已經用個人的 LINE 帳號申請了 LINE Developers 帳號, 建立提供者 (Provider) 與 messaging API 頻道 (Channels), 在此頻道上創建了一個名為 "小狐狸事務所" 的聊天機器人, 取得 Channel secret (通道密碼) 與 Assertion signing key, 完成了 LINE messaging API 的註冊.
在第二篇測試中, 我建立了一個 LINE 官方帳號, 用來綁定第一篇中所創建的聊天機器人與後端應用程式, 並取得此聊天機器人之 Line ID 利用它將自己加入為好友以利後續測試.
在第三篇測試中, 我登入 Line 開發者頁面後, 在 Basic settings 頁籤中建立一個頻道密鑰 (Channel secret); 在 Messaging API 頁籤中建立頻道存取權杖 (Channel access token), 這樣就可以利用 Mapleboard 的 HTTP API 函式執行平台撰寫 Flask 應用程式作為 LINE Bot 的 webhook. 關於此函式執行平台參考 :
以下測試是根據 HTTP API 函式執行平台 (4) 這篇優化過的版本, LINE Bot 後端應用程式需依照下列格式撰寫 :
# func_module.py
def main(request, **kwargs):
config=kwargs.get('config', {}) # 預設為空 dict
result='do something'
return result
所有金鑰或權杖等資訊都儲存在主程式目錄下的 .env 環境變數檔案裡, 主程式 serverless.py 會讀取此檔案並將內容放在 config 參數 (字典) 中以關鍵字參數 config 傳遞給被載入之函式模組的 main() 函式, 呼叫字典的 get() 方法即可取出此
1. 添加 LINE messagin API 環境變數 :
因為在 HTTP API 函式執行平台 (4) 這篇裡已重新規劃將所有權杖與金鑰集中於平台的環境變數檔 .env 中統一管理, 主程式 serverless.py 會讀取 .env 檔後將全部內容放在 config 字典中傳遞給被載入模組的 main() 函式, 因此要執行的函式都放在 Mapleboard 的 ~/flask_apps/serverless/functions 下, 所以我先在此資料夾下用 nano 編輯一個 .env 環境變數檔來儲存 LINE Bot 的密鑰與權杖 :
tony1966@LX2438:~/flask_apps/serverless$ sudo nano .env
在最後面添加 LINE_CHANNEL_SECRET 與 LINE_CHANNEL_ACCESS_TOKEN 這兩個變數 :
按 Ctrl+O 存檔後按 Ctrl+X 跳出 nano, 然後必需重啟服務, 這樣 serverless.py 才會讀取新的 .env 內容 :
tony1966@LX2438:~/flask_apps/serverless$ sudo systemctl restart serverless
2. 安裝 line-bot-sdk 套件 :
在 Mapleboard 用 pip3 安裝 line-bot-sdk 套件 :
pip3 install line-bot-sdk
tony1966@LX2438:/$ pip3 install line-bot-sdk
Defaulting to user installation because normal site-packages is not writeable
Collecting line-bot-sdk
Downloading line_bot_sdk-3.18.0-py2.py3-none-any.whl (824 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 824.9/824.9 KB 2.1 MB/s eta 0:00:00
Collecting future>=1.0.0
Downloading future-1.0.0-py3-none-any.whl (491 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 491.3/491.3 KB 3.0 MB/s eta 0:00:00
Collecting urllib3<3,>=2.0.5
Downloading urllib3-2.5.0-py3-none-any.whl (129 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 129.8/129.8 KB 5.1 MB/s eta 0:00:00
Collecting Deprecated>=1.2.18
Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB)
Requirement already satisfied: pydantic<3,>=2.0.3 in /home/tony1966/.local/lib/python3.10/site-packages (from line-bot-sdk) (2.11.7)
Collecting aenum<4,>=3.1.11
Downloading aenum-3.1.16-py3-none-any.whl (165 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 165.6/165.6 KB 6.5 MB/s eta 0:00:00
Collecting aiohttp<4,>=3.10.9
Downloading aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.7 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 7.0 MB/s eta 0:00:00
Requirement already satisfied: python_dateutil<3,>=2.5.3 in /home/tony1966/.local/lib/python3.10/site-packages (from line-bot-sdk) (2.9.0.post0)
Requirement already satisfied: requests<3,>=2.32.3 in /home/tony1966/.local/lib/python3.10/site-packages (from line-bot-sdk) (2.32.4)
Collecting frozenlist>=1.1.1
Downloading frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (224 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 224.6/224.6 KB 4.7 MB/s eta 0:00:00
Collecting propcache>=0.2.0
Downloading propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (201 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 201.9/201.9 KB 6.9 MB/s eta 0:00:00
Collecting aiosignal>=1.4.0
Downloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
Collecting yarl<2.0,>=1.17.0
Downloading yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (323 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 323.8/323.8 KB 6.5 MB/s eta 0:00:00
Requirement already satisfied: attrs>=17.3.0 in /home/tony1966/.local/lib/python3.10/site-packages (from aiohttp<4,>=3.10.9->line-bot-sdk) (23.2.0)
Collecting aiohappyeyeballs>=2.5.0
Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)
Collecting async-timeout<6.0,>=4.0
Downloading async_timeout-5.0.1-py3-none-any.whl (6.2 kB)
Collecting multidict<7.0,>=4.5
Downloading multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl (242 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 242.5/242.5 KB 5.3 MB/s eta 0:00:00
Requirement already satisfied: wrapt<2,>=1.10 in /usr/lib/python3/dist-packages (from Deprecated>=1.2.18->line-bot-sdk) (1.13.3)
Requirement already satisfied: typing-extensions>=4.12.2 in /home/tony1966/.local/lib/python3.10/site-packages (from pydantic<3,>=2.0.3->line-bot-sdk) (4.14.0)
Requirement already satisfied: typing-inspection>=0.4.0 in /home/tony1966/.local/lib/python3.10/site-packages (from pydantic<3,>=2.0.3->line-bot-sdk) (0.4.1)
Requirement already satisfied: pydantic-core==2.33.2 in /home/tony1966/.local/lib/python3.10/site-packages (from pydantic<3,>=2.0.3->line-bot-sdk) (2.33.2)
Requirement already satisfied: annotated-types>=0.6.0 in /home/tony1966/.local/lib/python3.10/site-packages (from pydantic<3,>=2.0.3->line-bot-sdk) (0.7.0)
Requirement already satisfied: six>=1.5 in /usr/lib/python3/dist-packages (from python_dateutil<3,>=2.5.3->line-bot-sdk) (1.16.0)
Requirement already satisfied: certifi>=2017.4.17 in /home/tony1966/.local/lib/python3.10/site-packages (from requests<3,>=2.32.3->line-bot-sdk) (2024.2.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests<3,>=2.32.3->line-bot-sdk) (3.3)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/tony1966/.local/lib/python3.10/site-packages (from requests<3,>=2.32.3->line-bot-sdk) (3.4.2)
Installing collected packages: aenum, urllib3, propcache, multidict, future, frozenlist, Deprecated, async-timeout, aiohappyeyeballs, yarl, aiosignal, aiohttp, line-bot-sdk
Successfully installed Deprecated-1.2.18 aenum-3.1.16 aiohappyeyeballs-2.6.1 aiohttp-3.12.15 aiosignal-1.4.0 async-timeout-5.0.1 frozenlist-1.7.0 future-1.0.0 line-bot-sdk-3.18.0 multidict-6.6.3 propcache-0.3.2 urllib3-2.5.0 yarl-1.20.1
3. 撰寫後端應用程式 :
為了搭配 serverless 函式執行平台架構, 撰寫處理 LINE webhook 請求與產生 echo 回覆的後端程式 linebot_echo.py 如下 :
# linebot_echo.py
from flask import abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
def main(request, **kwargs):
# 取得主程式讀取 .env 後以 config 字典傳遞之 LINE 權杖與金鑰
config=kwargs.get('config', {})
secret=config.get('LINE_CHANNEL_SECRET')
token=config.get('LINE_CHANNEL_ACCESS_TOKEN')
# 檢查是否有傳遞密鑰與權杖
if not secret or not token:
return {"error": "LINE_CHANNEL_SECRET or LINE_CHANNEL_ACCESS_TOKEN is missing"}
# 建立呼叫 LINE API 與處理 webhook 的物件
line_bot_api=LineBotApi(token) # 發送回覆訊息的 API 客戶端
handler=WebhookHandler(secret) # 驗證簽章與分派事件的 webhook 處理器
# 註冊 MessageEvent + TextMessage 事件的處理器
# 注意:這段必須寫在 handler.handle() 之前, 否則事件不會被處理
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text)
)
# 取得 X-Line-Signature (用於簽章驗證) 與 request body (即使用者訊息)
signature=request.headers.get("X-Line-Signature", "")
body=request.get_data(as_text=True)
# 驗證簽章是否正確, 防止偽造的請求 (確保是 LINE 平台發出的請求)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400, "Invalid signature")
# 處理成功回傳狀態訊息 (LINE 要求回應 HTTP 200 才視為成功)
return {"status": "ok"}
此程式需注意的地方是註冊 LINE 事件處理器必須放在呼叫 handler.handle() 之前; 其次是末了處理成功時要傳回一個 ok 訊息, Flask 預設會將 {"status": "ok"} 轉換成 JSON 格式的回應, 這會回傳 HTTP 狀態碼 200 OK, 以符合 LINE 訊息伺服器要求.
我用線上管理功能新增此 linebot_echo.py 模組 :
接下來到 Line 開發者控制台設定 Webhook URL :
切換到第二個頁籤 "Messaging API" 後往下拉到 "Webhook setting" 欄按 "Edit" 鈕,
在 Webhook URL 框中輸入後端程式網址 https://flask.tony1966.cc/function/linebot_echo, 按 Update 鈕更新網址 :
按 "Verify" 鈕驗證 Webhook 網址 :
出現 Success OK 表示 Webhook 可順利連線後台 Flask 伺服器 :
這樣就完成後台程式的設定了, 開啟手機 LINE App, 點選 "好友名單" 或 "群組", 切換到 "官方帳號", 找到前面測試中建立 "小狐狸事務所聊天機器人", 點進去輸入聊天訊息, 果然收到訊息被原封不動傳回來 :
收工啦!








沒有留言 :
張貼留言