顯示具有 Chatbot 標籤的文章。 顯示所有文章
顯示具有 Chatbot 標籤的文章。 顯示所有文章

2025年8月7日 星期四

Python 學習筆記 : LINE Bot 開發 (四) 在 Mapleboard 佈署 Echo Bot 聊天機器人

經過一周奮戰, 這幾天完成 Mapleboard 的 HTTP API 函式執行平台建置, 終於可以回到這波 Mapleboard 架站的目標 : 利用 Mapleboard 當作 LINE Bot 的 webhook 後台伺服器來開發 LINE Bot 應用. 本篇先來接續之前用 ngrok 模擬後台伺服器實作 Echo Bot 的測試, 改用 Mapleboard 的 HTTP API 函式執行平台來做. 

本系列之前的測試文章參考 :


先摘要整理一下 LINE Bot 的製作流程 :

一. 建立 LINE Bot 與平台設定
  1. 申請 LINE Developers 帳號 :
    前往 LINE Developers 以個人 LINE 帳號登入.
  2. 建立 Channel (頻道) :
    選擇「Messaging API」類型, 設定 Channel 名稱, 圖示, 說明等資訊.
  3. 取得存取 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 來推播或回覆訊息等. 

  4. 設定 Webhook URL :
    在 LINE Developers Console 中輸入 Webhook 網址, 啟用 Webhook 功能 (建議關閉「自動回應訊息」與「歡迎訊息」)

二. 撰寫 Webhook 程式 (例如 Flask)
  1. 安裝必要套件 :
    pip install flask line-bot-sdk
  2. 撰寫 Flask Webhook 程式 :
    # line_webhook.py
    from flask import Flask, request, abort
    from linebot import LineBotApi, WebhookHandler
    from linebot.exceptions import InvalidSignatureError
    from linebot.models import MessageEvent, TextMessage, TextSendMessage

    app = 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)
三. 部署與測試 : 
  1. 部署 Webhook 程式到網際網路 :
    (1). Replit (簡易測試用)
    (2). Google Cloud Function (第一年免費) 
    (3). Render 或 Railway 等免費/低價平台
    (3). 自架伺服器 (可使用 Let's Encrypt 憑證 + Nginx)
  2. 使用 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 模組 : 




將滑鼠移到 linebot_echo 後面的 "執行" 連結即可複製此函式模組的請求網址 (這是需要驗證的 POST 請求 webhook, 無法用 GET 直接執行) : 

# https://flask.tony1966.cc/function/linebot_echo 

接下來到 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, 點選 "好友名單" 或 "群組", 切換到 "官方帳號", 找到前面測試中建立 "小狐狸事務所聊天機器人", 點進去輸入聊天訊息, 果然收到訊息被原封不動傳回來 : 




收工啦! 

2025年8月4日 星期一

LINE Bot 學習筆記索引

我在 2024 年初開始測試 LINE Bot, 最初使用本機的 Flask web app 模擬 webhook 後端, 用 ngrok 來產生 https 網址, 但只要 ngrok 關掉重開, webhook 網址就要重設, 此非長久之計. 最近將 Mapleboard 的 HTTP API 函式執行平台 serverless 打造好, 可用來做為 LINE Bot 的 webhook 後端, 接下來就可以繼續 LINE Bot 的測試了, 先將 LINE Bot 測試文章做成如下索引 : 



~進行中~

2025-11-02 補充 :

我 10 月份又把 serverless 平台成功移植到 render.com, 利用樹莓派週期性 (15 分鐘內) 提出請求, 可以讓 render.com 虛擬機器不休眠, 也可以用來作為放置 webhook 程式之雲端伺服器. 

2025-11-03 補充 :

我手邊關於 LINE Bot 的書籍如下 :
其中深智 OXXO 寫的這本不錯, 篇幅不多簡潔扼要, 書中原始碼可在 GitHub 下載 :


2024年1月30日 星期二

Python 學習筆記 : LINE Bot 開發 (三) 在本機佈署 Echo Bot 聊天機器人

在前一篇測試中已經建立 Line 聊天機器人的官方帳號, 並將自己加入為好友. 接下來要測試 Line Bot 最重要的部分 : 設定 Webhook URL, 這樣才能將 Line Bot 伺服器串接到我們的後台網頁伺服器, 讓裡面的 Python 程式來處理所接收到的訊息並決定如何回應.

本系列之前的文章參考 :


教學文章參考 :



Line 聊天機器人其實就是安裝在開發者的後台網頁伺服器中的網頁程式 (可用 Python 的 Flask 或 Django 等實作), 使用者傳送的訊息會透過 Line 的訊息伺服器 (Messaging Server) 作為介接, 利用 Webhook (採用 RESTful 的一個 URL) 以 HTTPS 向後台網頁伺服器提出 POST 請求, 我們的 Line Bot 程式須根據收到的訊息決定如何回應, 處理架構與流程如下圖所示 :




Line 聊天機器人的開發程序如下 :
  1. 到 Line 開發者網站將自己的 Line 帳號註冊為開發者帳號 (Business ID).
  2. 建立供應商 (Provider) 名稱, 相當於開發者的公司招牌.
  3. 建立頻道 (Channel) 名稱, 一個頻道代表一個聊天機器人 (會有一個 @ 開頭的 Line 官方帳號), 一個供應商可以建立多個頻道. 
  4. 到 Line 官方帳號管理頁面進行此頻道之回應設定並將此官方帳號加為好友.
  5. 安裝 line-bot-sdk 套件以便撰寫聊天機器人網頁程式, 並佈署到自己的網頁伺服器 (取得HTTPS 網址). 
  6. 將聊天機器人的網址設為此頻道的 Webhook URL 並進行測試. 
在前兩篇測試中已完成步驟 1~4, 本篇將以簡單的 Echo Bot (鸚鵡機器人, 會將收到的訊息值接回傳) 為例完成步驟 5~6. 參考官方教學文件 :


首先我們必須來完成後台網頁伺服器的架設, 首先用 Flask 寫一個運行於本機的網頁伺服器, 然後再用 Ngrok 程式讓其擁有 HTTPS 網域功能 (Line 訊息伺服器的 Webhook 必須是 HTTPS 域名網址, 不可以使用 IP). 


一. 取得頻道密鑰 (Channel secret) 與頻道存取權杖 (Channel access token) : 

先開啟 Line 開發者首頁 : 





按左邊的 "Console" 進入控制台, 或直接拜訪下列網址 :


控制台會顯示所有已建立之頻道 (即聊天機器人, 目前只有一個) : 




點選要設定的頻道進入基本設定頁面 : 




拉到頁面最底下可以看到 Channel secret, 如果沒有就按 "Issue" 鈕產生一個 : 




按複製鈕先將其貼到記事本保存, 例如 :

secret="abcdefbd24f2f1eabd274024catony1966"   (此為範例)

然後切換到 Messaging API 頁籤, 上面是官方帳號與其 QR code (可以讓測試者加入好友) : 




往下拉到底可以看到 Channel access token 欄位, 因尚未發行過權杖目前是空的 :




按 "Issue" 鈕新增一個 : 




按複製鈕將其貼到記事本保存, 例如 : 

token="tony1966/pOjFWcV+AJ2qQTcGSQXvvDdoRa98W6W44fC/vwCG5B+Pg03dPP/gHVuUpRcJGZ6glj5aA1PEoN9VlCIBDgFW5/wRGMP+/hvfscg3P2M1jMCNLv8pjIV53DIyT9WpY4MPQYMWgAKbdV3GySwdB04t89/1O/w1cDabcdef="  (此為範例)

此 Messaging API 設定頁面還有一個 Webhook 欄位要設定, 等網頁伺服器架設好再來填. 

參考 :



二. 用 Flask 在本機架設一個網頁伺服器 : 

Flask 是一個 Python 輕量級的 Web 框架套件, Flask 應用程式執行時會在本機建立一個網頁伺服器 (localhost 或 127.0.0.1, 預設埠口為 5000), 因語法簡單常被用來快速建立 Web 網站雛形以驗證 RESTful 服務, 關於 Flask 用法參考 : 



1. 安裝 flask 套件 : 

使用 pip install 來安裝 flask 套件 :

pip install flask  


2. 匯入 Flask 類別建立 app : 

從 flask 匯入 Flask() 類別, 然後呼叫其建構子並傳入 __name__ 即可建立一個網頁應用程式 (即傳回之 Flask 物件) :

>>> from flask import Flask    
>>> app=Flask(__name__)    
>>> type(app)   
<class 'flask.app.Flask'>   


3. 用裝飾器定義路由與處理函式 : 

接下來是定義路由 (routes), 即 URL 路徑與所對應之處理函式, Flask 使用 app.route() 函式的裝飾器來定義 URL 路徑, 放在所對應之處理函式前面, 作法簡單直觀, 語法如下 :

@app.route("路徑")
def handler([參數序列]):
    ,,,
    return 回應訊息      # HTML 格式字串

URL 路徑其實就是所謂的 Web API, 這是一種採用 RESTful 規範的 GET 請求方式, 它以斜線做區隔的層級路徑來區別要執行網站伺服器裡的哪一個資源 (程式), "/" 表示網站首頁, 亦即根目錄, 斜線底下是資源名稱, 例如 "/hello" 表示存取 hello 資源, 也可以傳遞多個參數, 語法如下 :

/資源名稱/參數1/<值1>/參數2/<值2> ....

此 RESTful 的 GET 請求路徑相當下列的一般 GET 路徑語法 :

資源名稱?參數1=值1&參數2=值2 ....  

如果有傳入參數的話, 要依序傳入處理函式 :

def handler(參數1, 參數2, ...): 

參考 :


例如可為上面建立的 app 建立三個路由 :

"/" : 請求首頁, 回應 "<h1>歡迎光臨我的網站</h1>"
"/ok" : 請求 ok 資源, 回應 "<p>OK</p>" 
"/hello/<name>" : 請求 hello 資源傳入 name 參數, 回應 "<h3>Hello, {name}!"

例如 :  

>>> @app.route('/')  
def main():  
    return '<h1>歡迎光臨我的網站</h1>'    
>>> @app.route('/ok')   
def ok():   
    return '<h1>OK</h1>'   
>>> @app.route('/hello/<name>')   
def hello(name):   
    return f'<h1>Hello, {name}!</h1>'   

這裡的路徑有分大小寫 (因為 Python 字串是區分大小寫的), 寫錯會回應 not found, 但最後面的參數則無關大小寫 (參數字串會被忠實傳送). 例如請求 /hello/TONY 會回應 "Hello, TONY"; 請求 /hello/tony 會回應 "Hello, tony"; 但請求 /HELLO/TONY 則是回應網頁不存在. 

注意, 在 Python 命令列執行上面指令時, 裝飾器必須與其所修飾的函式一起輸入後再按 Enter, 若只輸入 @app.route() 就按 Enter 會出現 SyntaxError. 


4. 呼叫 app.run() 建立網站 :  

定義好路由後只要呼叫 app.run() 就會將此網站架起來了 :

>>> app.run(debug=True)   
 * Serving Flask app '__main__'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000   
Press CTRL+C to quit
 * Restarting with watchdog (windowsapi)
OK

可見此 Flask 應用程式預設監聽本機 (127.0.0.1 或 localhost) 的 5000 埠請求, 注意, 此處 debug=True 表示會顯示呼叫 app.run() 時也傳入 host 與 port 等參數, 例如 (先按 Ctrl + C 停止上面已啟動之開發伺服器) : 

>>> app.run(host='0.0.0.0', port=8080, debug=True)   
 * Serving Flask app 'flask_app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080   
 * Running on http://192.168.2.128:8080   
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 260-650-195

此處 host='0.0.0.0' 表示 Flask 開發伺服器監聽對本機任何 IPv4 位址的請求, 可見除了 127.0.0.1 外, 還多了一個 192.168.2.128, 這是本機從區網 DHCP 獲得之網址. port 參數則是開發伺服器要監聽的埠號. debug=True 表示要顯示偵錯訊息. 關於 0.0.0.0 與 127.0.0.0 參考 :


網站架起來後即可開啟瀏覽器拜訪 http://127.0.0.1:8080 (或此處本機獲得之區網網址 192.168.2.128:8080), 基於安全會出現防火牆警告視窗 :




按 "允許" 後會出現首頁頁面 : 



在網址列輸入 127.0.0.1/ok 會回應 OK 網頁 : 




在網址列輸入 127.0.0.1/hello/tony 則會回應 Hello, tony 網頁 : 



可見這個 Flasp 網站已能正常運作, 我們的聊天機器人運作模式也是如此, Line Messaging Server 會依照 Webhook URL 網址對我們的後台網頁伺服器提出請求, 然後網頁伺服器依據請求做出是當的回應. 

完整程式碼如下 :

# app.py
from flask import Flask

app=Flask(__name__)

@app.route('/')
def main():
    return '<h1>歡迎光臨我的網站</h1>'

@app.route('/ok')
def ok():
    return '<h1>OK</h1>'

@app.route('/hello/<name>')
def hello(name):
    return f'<h1>Hello, {name}!</h1>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)
    
注意, 在本機執行 Flask 應用程式建立網站時, 建議使用 flask run 指令於命令列執行, 且應用程式檔案名稱要取名為 app.py, 指令語法如下 :

flask run -h localhost -p 埠號   

或 

flask run -h 127.0.0.1 -p 埠號

如果用 python 指令執行或在 Thonny 中按 Play 鍵執行, 在有些電腦可能會因 "網站無回應" 而無法顯示網頁, 參考 : 


不過這個網站只能在本機存取, 外網無法連線此網站. 若要用本機網站充當聊天機器人的後台伺服器, 必須讓此網站具有 HTTPS 域名網址 (這是 Line 訊息伺服器對 Webhook URL 的要求), 這可以透過 Ngrok 代理伺服器來達成, 它會將我們在本機指定埠號上執行的網頁伺服器綁定一個 HTTPS 域名網址. 參考 : 



5. 下載代理伺服器 Ngrok :   

上面用 Flask 在本機所架設的 localhost 網站只支援 HTTP 協定, 且只有本機區域 IP 網段能存, 外網無法連線此網站, 解決辦法是使用 Ngrok 代理伺服器, 它可以為 localhost 本機伺服器上的網站提供一個 HTTP 與 HTTPS 網域網址 (不是 IP), 讓外網能存取此本機網站, 其 HTTPS 網址就能符合 Line Bot 的 webhook URL 要求. 參考 :


Ngrok 可在官網下載 zip 檔 (v3 約 9MB 左右), 毋須安裝, 解開即可使用 : 





ngrok-v3-stable-windows-amd64.zip 解開後得到一個單一的 ngrok.exe, 不過執行時需要輸入認證用的權杖 (token), 故須先按上圖右上角的 "Sign up" 鈕申請帳號 (可以使用 Email 或 GitHub 帳號), 然後點左方導覽列 "Getting Started" 項下的 "Your Authtoken" 會在右方顯示權杖 :




按 "Copy" 鈕將權杖複製到記事本儲存. 取得權杖後開啟一個命令提示字元視窗, 用下列指令進行認證 (注意, token 不可用括號括起來) :

ngrok authtoken 權杖 

例如 :

D:\python\test>ngrok authtoken tonybyjkVWZ1RXPyZVSQ1nKz8Mn_3kbM1966At62W7RGhsx1L   (此為範例權杖) 
Authtoken saved to configuration file: C:\Users\tony1\AppData\Local/ngrok/ngrok.yml   

如看到 "Authtoken saved to configuration file ... " 表示認證 OK, 然後就可以用下列指令讓 Ngrok 為目前監聽本機 8080 埠的網站伺服器產生一個 HTTPS 網址 :

ngrok http 8080   

此處 8080 為目前本機網頁伺服器的埠號. 注意, 新版 ngrok 程式已取消用來指定伺服器所在區域的 -r 參數 (會自動選擇較近之主機). 執行後就會出現所綁定的 HTTPS 網址 : 




複製此 HTTPS 網址 https://b55d-220-133-183-132.ngrok-free.app/ 貼到瀏覽器即可顯示本機網站內容了 (不須指定埠號) :





可見任何對 https://b55d-220-133-183-132.ngrok-free.app 的連線請求都會被轉發到本機的區網位址 127.0.0.1:8080 來, 從而使外網也可以看得到位於內網的網站. 只要沒按 CTRL + C 終止這個轉發程序, 這個 HTTPS 網址就會一直存在. 注意, 結束程序再次執行時會綁定不同的 HTTPS 網址. 


三. 安裝 line-bot-sdk 套件與撰寫聊天機器人程式 :     

LINE 公司為 Python 聊天機器人開發者提供了一個開源套件 line-bot-sdk, 其原始碼與範例檔寄存於 GitHub :



1. 安裝 line-bot-sdk 套件 : 

撰寫 Line 聊天機器人程式需要安裝 Line 提供的開發套件 line-bot-sdk : 

pip install line-bot-sdk 

如果使用 Thonny 編輯器可在 "工具/管理套件" 搜尋 "line-bot-sdk" 後安裝 :




Line Bot 開發套件安裝完成後, 即可開始聊天機器人程式, 最簡單的便是將收到的訊息回傳的 Echo Bot 鸚鵡聊天機器人.

參考 :



2. 撰寫 LINE 聊天機器人程式 : 

在 line-bot-sdk 套件的 GitHub 寄存庫中有提供 Echo Bot 的範例程式, 參考 : 


複製此範例程式碼到 Thonny 或其他編輯器中, 稍微修改一下, 加入存取根目錄 "/" 的路由為顯示歡迎訊息, 方便檢查網站是否正常運行 : 

from flask import Flask, request, abort
from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.messaging import (
    Configuration,
    ApiClient,
    MessagingApi,
    ReplyMessageRequest,
    TextMessage
    )
from linebot.v3.webhooks import (
    MessageEvent,
    TextMessageContent
    )

secret='abcdefbd24f2f1eabd274024catony1966'        # 此為範例密鑰
token='tony1966/pOjFWcV+AJ2qQTcGSQXvvDdoRa98W6W44fC/vwCG5B+Pg03dPP/gHVuUpRcJGZ6glj5aA1PEoN9VlCIBDgFW5/wRGMP+/hvfscg3P2M1jMCNLv8pjIV53DIyT9WpY4MPQYMWgAKbdV3GySwdB04t89/1O/w1cDabcdef='                # 此為範例權杖
configuration=Configuration(access_token=token)    # 設定權杖 
handler=WebhookHandler(secret)                               # 建立處理 Webhook 的物件

app=Flask(__name__)      # 建立 Web app

@app.route('/')                  # 測試網站是否正常運行的路由
def main():
    return '<h1>歡迎光臨我的網站</h1>'

@app.route("/callback", methods=['POST'])     # 聊天機器人頻道之 Webhook 網址
def callback():
    # 從 HTTP 標頭取得數位簽證 X-Line-Signature
    signature=request.headers['X-Line-Signature'] 
    # 取得 HTTP 請求之內容 (BODY)
    body=request.get_data(as_text=True)
    app.logger.info('HTTP 請求內容: ' + body) 
    # 比對數位簽證是否符合, 符合就觸發 MessageEvent 呼叫 handle_message() 
    try:
        handler.handle(body, signature)     # 比對數位簽證是否符合
    except InvalidSignatureError:            # 不符合就拋出例外
        app.logger.info("驗證失敗: 頻道密鑰存取權杖不符合")
        abort(400)  # 回應 400: 中斷連線
    return 'OK'

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):       # 驗證通過時用來處理回應的函式
    with ApiClient(configuration) as api_client:
        line_bot_api=MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
            ReplyMessageRequest(
                reply_token=event.reply_token,
                messages=[TextMessage(text=event.message.text)]
            )
        )

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

此程式的核心是 @app.route() 所修飾的 "/callback" 路由處理函式與 @handler.add() 所修飾的訊息事件處理函式, 這個 "/callback " 是自訂的, 也可以用 "/linebot" 或任意其他名稱 (甚至直接用根目錄 "/" 也行), 它將會是 Webhook URL 的一部分. 

將上面的程式儲存為 app.py (如果要用 flask run 執行 Flask 應用程式一定要用此檔名), 然後在相同目錄下執行 flask run 指令運行本機網頁伺服器 (此處指定伺服器監聽 3000 埠, 其實指定 80, 5000, 或 8080 等埠亦可) : 

D:\python\flask>flask run -h localhost -p 3000    
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://localhost:3000    
Press CTRL+C to quit

這樣 Flask 網頁伺服器就成功運行了, 用瀏覽器拜訪 http://localhost:3000 或 http://127.0.0.1:3000 就會顯示歡迎網頁, 證明網站運作正常 :

 


接下來執行 ngrok.exe 程式指定上面這個監聽 3000 埠的網站, 讓此區網內的網站擁有一個公網的 HTTPS 網址, 這樣才能讓 Line Messaging Server 可以透過 Webhook URL 將訊息 : 

>>> ngrok http 3000   




這樣此內網的網站便有了如下可讓 Internet 公網存取的 HTTPS 網址 :

https://c9d8-2001-b400-e7ac-be86-5522-c9e6-d934-88d.ngrok-free.app

而所謂的 Line 聊天機器人 Webhook URL 便是在後面加上 "/calback" :

https://c9d8-2001-b400-e7ac-be86-5522-c9e6-d934-88d.ngrok-free.app/callback    

如上所述, 如果直接把處理 Line Messaging Server 傳來的 HTTP 訊息的路由直接做在根目錄 "/" 而非 "/callback" 上, 則 Webhook URL 就直接用 Ngrok 產生的 HTTPS 網址即可. 


四. 設定 Webhook URL :   

透過 Ngrok 為區網網站取得 HTTPS 網址後便要回 Line 開發者控制台設定 Webhook URL :


切到第二個頁籤 "Messaging API", 往下拉到 "Webhook setting" 欄按 "Edit" 鈕, 在 Webhook URL 框中輸入上面的 HTTPS 網址 (後面串接 /callback) :




開啟底下的 "Use webhook" 後按 "Update" 鈕即可 : 



這樣便完成全部設定, 按 "Verify" 鈕驗證 Webhook URL 是否可順利連線後台 Flask 伺服器 : 




出現 Success 表示一切正常, 這時開啟手機 Line App, 點 "好友名單" 再切換到 "官方帳號", 找到上面已加入好友的 "小狐狸事務所聊天機器人", 點進去聊天 (傳送訊息) 就可看到訊息原封不動傳回來了 : 





Bingo! 奮戰了兩個禮拜終於 (才) 搞定這個最簡單的 Line 聊天機器人測試, 到底是難呢還是自己笨都有點搞不清楚了. 總之, 經過這段時間奮鬥算是搞懂整個流程了, 接下去還有更多實務的 Line Bot 要玩, 可參考官方範例 :


2024-02-01 補充 : 

如果用瀏覽器拜訪 Webhook URL 會得到 "Method Not Allowed" 回應 :



 
這是因為此網址必須用 POST 方法提出請求, 而網址列是使用 GET 方法之故. 


2024-03-08 補充 :

看到一篇 iT 邦幫忙好文章 :


2024年1月16日 星期二

Python 學習筆記 : LINE Bot 開發 (二) 設定 Line 官方帳號與加入好友

我在 2021 年初申請 Line Business ID 想要進行 Line 聊天機器人測試, 但申請好 LINE Messaging API (訊息應用程式介面) 的供應商 (Provider) 與頻道 (Channel) 後就跑去玩別的. 最近因為測試 Azure AI 服務需要串接 Line Bot, 於是把手上跟 Line Bot 有關的書找出來, 繼續來把 Line Bot 學完. 參考書籍如下 : 
  1. 超圖解 Python 程式設計入門 (旗標, 趙英傑, 2019) 
  2. Python 與 LINE Bot 機器人全面實戰特訓班 (碁峰, 文淵閣工作室, 2019)
  3. LINE Bot by Python 全攻略:從Heroku到AWS跨平台實踐 (博碩, 饒孟桓, 2020)
  4. 打造股市小秘書:聊天機器人x網路爬蟲x NoSQLxPython整合應用實務 (碁峰, 楊超霆, 2019)
  5. Line Bot + Python + Google Dialogflow 打造全方位 AI 機器人 (深智, 2022)
教學文章參考 :


本系列之前文章參考 :



1. 設定 Line 官方帳號 : 

建立 provider 與 channel 後, 接下來是要設定 Line 官方帳號, 首先前往下列網址 :





往下拉到左下角的 "Line 官方帳號管理頁面", 按 "登入管理頁面" 鈕 : 




這會進入帳號一覽表列出目前所有帳號, 在前一篇測試中首次建立的 Line Bot 帳號會自動列出來, 也可以按左上角的 "建立" 鈕新建一個帳號, 每一個帳號就對應一個聊天機器人 (綁定一個不同的後端應用程式) :




點已建立之帳號 (此處為 '小狐狸事務所聊天機器人') 會進入此聊天機器人管理頁面 : 





按左方導覽列的 "自動回應訊息" 超連結, 右方會顯示此設定之目前狀態, 預設會開啟自動回應訊息功能, 按 "變更" 超連結進入下一頁將其關閉, 以免每次跟機器人聊天都會跳出自動回覆訊息 :





最後點左方導覽列的 "帳號設定", 往下拉到 "帳號資訊" 可以看到此聊天機器人之 Line ID :




接下來要用此聊天機器人帳號的 ID 將它加為自己的好友以利後續測試. 


2. 將 Line Bot 加入好友 : 

然後開啟手機的 Line App, 按左下角的 "主頁" 再按右上角的 "加入好友" 鈕, 再按 "搜尋" 鈕 :




輸入上面帳號資訊中機器人的 Line ID :



搜尋到之後按 "加入" 鈕即可 :




這樣就把這個聊天機器人加入自己的好友了 :




Line Bot 預設會在有人加為好友時會傳送歡迎訊息, 可以按左方導覽列的 "加入好友的歡迎訊息" 超連結去修改歡迎訊息 : 




直接在中間的編輯器中修改即可. 如果要取消傳送歡迎訊息的功能, 可以在上面關閉自動回應訊息功能的設定中, 同時將上方的 "加入好友的回應訊息" 取消即可 (按左方導覽列的 "自動回應訊息" 超連結, 再按右邊自動回應訊息設定中的 "變更" 超連結進入下一頁將其關閉) : 



 
我們可以發訊息給它, 但訊息只會顯示已讀而不會回應, 這是因為還沒有設定 Webhook URL 串接後台伺服器的關係 (後台伺服器的程式要實作對各種訊息的回應方式, 這是下一階段要作的事) :






3. 建立加入好友行動條碼 :  

如果要在 Line Bot 開發過程中邀請親朋好友加入測試行列, 可以按左方導覽列的 "增加好友工具", 然後按右邊的 "建立加入好友行動條碼", 這樣就會產生 QR Code :





將 QR Code 圖檔下載或直接分享即可. 未來聊天機器人正式上線時也可以將此 QR Code 圖檔放在行銷海報或網站上.