Files
tg-shuttle-score/README.md
T
JianMiauandClaude Fable 5 2e88a7d85a 建立建喵打羽球 Bot:移植 Node-RED 報名流程並新增群組記分板
根本原因:
原本的「今日羽球報名」bot 寫在 Node-RED 的 function 節點裡,不易測試與擴充;
羽球團又需要在 Telegram 群組內直接記分、看發球者,並把戰績寫進 web 版共用的 DB。

影響:
同一個 bot(@JianMiauBadmintonBot)改由本專案服務:報名指令、按鈕、暱稱、
週一 10:00 排程、白名單行為與訊息格式皆與 Node-RED 版相同,舊訊息按鈕仍可用;
另新增 /記分(/score、/new)記分板,並在結束/再一局時寫入共用的 history 表。

修法:
- signup.js:逐句移植 Node-RED bd_sm 狀態機(attendance / tg_poll / tg_members)
- scoreboard.js + match.js + render.js:羽球規則(發球區、換位、Deuce、30 分封頂)、
  上排 1 2/下排 4 3 版面、從今日出席選人開局、⏱ 結算、兩段式結束確認、
  開局者/管理員權限、scoreList(web 版格式)
- history.js:寫入 history 表,欄位與 web 版 ensureHistoryTable 相同
- scripts/nodered-switch.mjs:透過 admin API 暫停/恢復 Node-RED 舊流程
- 單元測試 23 項(規則、報名、選人、結算、權限、歷史寫入),Dockerfile / compose

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 11:10:27 +08:00

112 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tg-shuttle-score — 建喵打羽球 Bot
Telegram bot「建喵打羽球 @JianMiauBadmintonBot」的 Node.js 版本,包含:
1. **今日羽球報名**(從 Node-RED「羽球報名」分頁移植,行為、訊息格式、DB 表完全相同)
2. **群組記分板**(新功能):`/記分` 開局,訊息本身就是記分板,用 inline 按鈕記分
只在白名單群組運作:`建喵測試 (-1002488036686)``羽球團~ (-4679836160)`
## 指令
| 指令 | 英文別名 | 說明 |
| --- | --- | --- |
| `/羽球 [YYYYMMDD]` | `/badminton` | 發今日(或指定日期)報名訊息,附「✅ 參加 / ❌ 不參加」按鈕 |
| `/羽球名單` | `/bdlist` | 回今天的參加名單 |
| `/羽球暱稱 名字` | `/bdname` | 設定報名顯示的名字(存 `tg_members`,綁 user_id |
| `/記分` | `/score``/new` | 列出今天出席的人(`attendance`),依 1→4 點選上場人員:1、2 一隊,3、4 一隊;點滿 4 人自動開局,選 2 人可按「單打開始」 |
| `/記分 小明 小華 vs 阿強 阿美` | `/score``/new` | 直接開一場雙打記分板(單打各 1 人;最後加數字可改目標分) |
| `/help` | | 指令說明 |
英文指令已用 `setMyCommands` 註冊(啟動時自動),群組打 `/` 會有選單。
### 報名按鈕
- 參加 → `INSERT attendance`;不參加 → `DELETE`;重按只跳提示、不動 DB
- 名單直接改在原訊息上;`callback_data``bd|yes||YYYYMMDD` / `bd|no||YYYYMMDD`**Node-RED 時期發的舊訊息按鈕仍可用**
- 每週一 10:00`SIGNUP_CRON`)自動發到正式群
### 記分板按鈕
- **權限**:只有開局者(下 `/記分` 的人)或群組管理員(creator / administrator,查詢結果快取 5 分鐘)能按;其他人按了只跳提示「只有開局者或群組管理員可以操作記分板」。報名按鈕不受限。
- 選人(無參數開局):點名字依序標 1️⃣~4️⃣,`↩️ 取消上一位``▶️ 單打開始`(剛好 2 人時)、`✖️ 取消`
- 開局:`🅰️ / 🅱️ 先發球`;雙打可 `🔀 換位` 決定誰站右區(誰先發/先接)
- 進行中(三排):`🅰️ +1` `🅱️ +1` `↩️ 上一步` `🔄 重新開始` `⏱ 結算` `🏁 結束`
- `⏱ 結算`:時間到以目前比分定勝負(領先方 🏆、同分平手),可 `上一步` 復原
- 分出勝負顯示 🏆;`🏁 結束` 需兩段確認(第一次按 → 出現 `✅ 確定結束 / ↩️ 取消`,按其他按鈕也會取消),確定後移除按鈕、鎖定
- 規則:偶數分右區發、發球方得分同隊換位續發、失分換發不換位、21 分制、Deuce 領先 2 分、30 分封頂(與 badminton-scoreboard 一致)
## 安裝與啟動
```bash
npm install
cp .env.example .env # 填 BOT_TOKEN、DB_*、ALLOWED_CHAT_IDS
npm test # 15 個單元測試(規則、報名流程、選人、指令解析)
npm start # 或 npm run dev
```
### ⚠️ 同一個 bot 只能有一個 polling 端
Node-RED`http://192.168.5.45:1880`)的「羽球報名」分頁已於 2026-08-18 停用,由本專案接手。
若要暫時切回 Node-RED,或反過來,用下列指令(兩邊同時 poll 會互相 409):
```bash
npm run nodered:status # 看目前狀態
npm run nodered:pause # 停用 bd_tab + bot 設定改 send-only → 之後 npm start
npm run nodered:resume # 還原 Node-RED(本專案要先停)
```
(透過 Node-RED admin APIdeploy type = nodes,只重啟有變動的節點。)
## Docker / NAS
```bash
sudo docker compose up -d --build
```
`docker-compose.yml``.env`,狀態檔掛在 `./data`。上線步驟:NAS 起容器 → 確認 log 出現「已啟動」→ `npm run nodered:pause`
## 資料庫
與 badminton-scoreboard / Node-RED 共用 `jianmiau.tk:3307/badminton`
| 表 | 用途 |
| --- | --- |
| `attendance` | 今日出席(只存要參加的人;PK poll_date+user_id |
| `history` | 完成的比賽戰績(與 web 版共用,記分板寫入) |
| `tg_poll` | 每天報名訊息的 chat_id / message_id(改暱稱後回頭更新用) |
| `tg_members` | `/羽球暱稱` 設的名字,綁 user_id |
記分板狀態存 `DATA_FILE`(JSON),重啟後舊訊息按鈕仍可用。
**歷史戰績**:分出勝負(打到目標分或 `⏱ 結算`)且有得分的比賽,在按 `✅ 確定結束``🔄 再一局` 時寫入 `history` 表(`DB_HISTORY_TABLE`,預設 `history`,欄位與 web 版相同),web 版「歷史戰績」頁直接看得到。未結算就結束的比賽不會存(確認提示會寫明)。寫入失敗時不會結束,可再按一次重試。
- `players = [1號,2號,3號,4號]``team = [[1,2],[3,4]]``score = [🅰️,🅱️]``type` 0 雙打/1 單打、`winScore` 目標分、`dayOfWeek` 台北時區
- 單打 `players``[p1,p1,p3,p3]`web 歷史頁用 `players[starter]` 顯示發球者)
每一分都會累積 `scoreList`(不顯示在 TG,供之後寫 `history` 用),格式沿用 web 版:`[round, starter, winCount, winner]`
- `round` 第幾分(0 起算)
- `starter` 發球位置編號 0~3 = 畫面 1~4 號位減 1(🅰️ 上排:偶數分 0、奇數分 1;🅱️ 下排:偶數分 2、奇數分 3;只看位置不看人)
- `winCount` 同隊連續得分數,第一分為 0
- `winner` 得分隊 0 = 🅰️、1 = 🅱️
- 上一步會一併回退;重新開始清空;結算不影響
## 專案結構
```
src/
index.js 進入點:白名單、指令註冊、callback 分流、排程
config.js .env 讀取
db.js mysql2 連線池
util.js 共用小工具(HTML 跳脫、日期、指令 regex)
signup.js 報名功能(移植自 Node-RED bd_sm 狀態機)
scoreboard.js 記分板功能
match.js 羽球規則(純邏輯)
render.js 記分板文字 + 鍵盤
store.js 記分板狀態持久化
scripts/
nodered-switch.mjs 暫停 / 恢復 Node-RED 舊流程
test/
match.test.js signup.test.js
```