Jaba LINE Bot 是呷爸 AI 午餐訂便當系統的 LINE 介面,讓使用者可以直接在 LINE 中與 AI 對話點餐。
這個專案是 jaba 核心系統 的延伸,透過 LINE Messaging API 將點餐功能帶入最常用的通訊軟體中。
為什麼做這個?
jaba 系統本身是一個網頁應用,但每次點餐都要打開瀏覽器略顯麻煩。既然大家每天都在用 LINE,何不讓點餐對話直接在 LINE 中進行?
使用情境:
- 個人聊天:設定個人偏好(「叫我小明」「我不吃辣」)
- 群組聊天:說「開單」開始群組點餐,成員直接喊餐點名稱
系統架構
┌─────────────────┐ HTTPS ┌─────────────────┐ HTTP ┌─────────────────┐
│ LINE 使用者 │ ────────────> │ LINE Platform │ ────────────> │ Render │
│ (手機/電腦) │ <──────────── │ (webhook) │ <──────────── │ (jaba-line-bot)│
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│
│ HTTPS
│ (API Key)
▼
┌─────────────────┐ ┌─────────────────┐
│ jaba 系統 │ <──────────── │ nginx │
│ (AI 點餐核心) │ │ (API Gateway) │
└─────────────────┘ └─────────────────┘
核心元件
| 元件 | 說明 | 部署位置 |
|---|---|---|
| LINE Bot | 接收/回覆 LINE 訊息 | Render (雲端) |
| jaba 系統 | AI 點餐核心邏輯 | 本地伺服器 |
| nginx | API Gateway、流量分流 | 本地伺服器 |
功能特色
- 1對1 聊天:設定個人偏好(名稱、飲食限制),在群組點餐時自動套用
- 群組點餐 Session:群組專屬點餐流程,訂單獨立管理
- 白名單機制:僅啟用的使用者/群組可使用
- 自動清理:Bot 被踢出群組或使用者封鎖時,自動清除相關資料
群組點餐流程
這是最主要的功能,讓群組成員可以快速完成團體訂餐:
1. 說「開單」→ 開始群組點餐,顯示今日菜單
2. 成員直接說餐點(如「雞腿便當」「珍奶 微微 L」)
3. 支援跟單:「+1」「我也要」
4. 說「菜單」→ 查看今日菜單
5. 說「目前訂單」→ 查看點餐狀況
6. 說「收單」或「結單」→ 結束點餐,顯示訂單摘要
群組指令
| 指令 | 說明 |
|---|---|
開單 |
開始群組點餐,自動顯示今日菜單 |
收單 / 結單 |
結束點餐,顯示訂單摘要 |
菜單 |
查看今日可點的菜單 |
目前訂單 |
查看目前點餐狀況 |
+1 / 我也要 |
跟單(點和上一個人相同的餐點) |
@jaba 呷爸 |
顯示狀態和可用指令說明 |
飲料縮寫
支援常見的飲料點法縮寫:
| 縮寫 | 意思 |
|---|---|
微微 |
微糖微冰 |
少少 |
少糖少冰 |
半半 |
半糖半冰 |
全全 |
全糖全冰 |
例如:「珍奶 微微 L」= 珍珠奶茶 微糖微冰 大杯
觸發方式
| 情境 | 觸發方式 |
|---|---|
| 1對1 聊天 | 偏好設定相關訊息(點餐請透過群組) |
| 群組(未點餐) | 開單、菜單、@jaba 呷爸、啟用密碼 |
| 群組(點餐中) | 所有訊息都會處理(AI 過濾非訂餐訊息) |
白名單 + 密碼啟用
為了控制使用權限,採用密碼啟用機制:
- 管理員設定啟用密碼(環境變數
REGISTER_SECRET) - 使用者在 LINE 中輸入密碼
- 系統自動將使用者/群組加入白名單
- 之後即可正常使用點餐功能
自動移除機制:
- Bot 被踢出群組 → 自動移除該群組白名單
- 使用者封鎖 Bot → 自動移除該使用者白名單
使用者:(輸入密碼)
Bot:🎉 啟用成功!現在你可以使用點餐功能了。
技術棧
| 技術 | 用途 |
|---|---|
| Flask | Web 框架,處理 Webhook |
| line-bot-sdk v3 | LINE Messaging API SDK |
| Render | 雲端部署平台(免費 HTTPS) |
| requests | HTTP 客戶端,呼叫 jaba API |
開發重點
1. Webhook 驗證
LINE Platform 會在每個請求加上 X-Line-Signature,需驗證以防偽造:
@app.route("/callback", methods=["POST"])
def callback():
signature = request.headers.get("X-Line-Signature", "")
body = request.get_data(as_text=True)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return "OK"
2. 群組 Session 機制
群組使用 Session 機制控制回應:
def should_respond(event, user_text):
# 1對1 聊天:永遠回應
if event.source.type == "user":
return True, user_text
# 群組:使用 Session 機制
group_id = event.source.group_id
text_stripped = user_text.strip()
# 檢查群組是否在點餐中
is_ordering = check_group_session(group_id)
if is_ordering:
# 點餐中:所有訊息都轉發給 jaba(AI 會過濾非訂餐訊息)
return True, user_text
else:
# 非點餐中:只回應特定指令
if text_stripped in ["開單", "菜單"]:
return True, user_text
# 還會檢查:啟用密碼、@呷爸/@jaba 等(簡化省略)
return False, user_text
這樣的好處:
- 點餐中不用每則訊息都加關鍵字
- AI 會自動過濾閒聊,只回應訂餐相關訊息
- 非點餐時不會被無關訊息打擾
3. API Key 保護
外部請求 jaba API 需攜帶 API Key,nginx 負責驗證:
def get_jaba_headers():
headers = {"Content-Type": "application/json"}
if jaba_api_key:
headers["X-API-Key"] = jaba_api_key
return headers
環境變數
| 變數 | 必要 | 說明 |
|---|---|---|
LINE_CHANNEL_SECRET |
✅ | LINE Channel Secret |
LINE_CHANNEL_ACCESS_TOKEN |
✅ | LINE Channel Access Token |
JABA_API_URL |
❌ | jaba API 網址(未設定則為 Echo 模式) |
JABA_API_KEY |
❌ | API 驗證金鑰 |
REGISTER_SECRET |
❌ | 啟用密碼 |
部署到 Render
專案包含 render.yaml,可一鍵部署:
- Fork 專案到自己的 GitHub
- 在 Render 建立新 Web Service,連結 GitHub repo
- 設定環境變數
- 部署完成後取得 URL(如
https://jaba-line-bot.onrender.com) - 到 LINE Developers Console 設定 Webhook URL
專案結構
jaba-line-bot/
├── app.py # 主程式
├── requirements.txt # Python 依賴
├── render.yaml # Render 部署設定
├── .env.example # 環境變數範本
├── nginx-config-for-server.conf # nginx 設定參考
├── docs/ # 詳細文件
│ ├── architecture.md # 系統架構
│ ├── line-setup.md # LINE 設定
│ ├── deployment.md # 部署指南
│ ├── jaba-integration.md # jaba 整合
│ └── configuration.md # 環境變數
└── openspec/ # OpenSpec 規格文件
├── specs/ # 功能規格
└── changes/ # 變更歷史
學到的事
LINE Bot SDK v3 的變化
v3 版本改用 linebot.v3 模組,與舊版差異不小:
# v3 寫法
from linebot.v3 import WebhookHandler
from linebot.v3.messaging import Configuration, ApiClient, MessagingApi
# 需要用 Context Manager
with ApiClient(configuration) as api_client:
messaging_api = MessagingApi(api_client)
messaging_api.reply_message(...)
群組中取得使用者名稱
1對1 聊天用 get_profile(user_id) 就好,但群組中要用 get_group_member_profile(group_id, user_id):
if event.source.type == "group":
profile = messaging_api.get_group_member_profile(
event.source.group_id, user_id
)
else:
profile = messaging_api.get_profile(user_id)
Render 免費方案的限制
- 閒置 15 分鐘後會休眠
- 喚醒需要 30-60 秒
- 第一次訊息可能會因 timeout 失敗,使用者需再發一次