根本原因: 原本的「今日羽球報名」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>
42 lines
1.2 KiB
JavaScript
42 lines
1.2 KiB
JavaScript
// 比賽狀態儲存:記憶體 Map + JSON 檔案持久化(重啟後按鈕仍可用)
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import { dirname } from 'node:path'
|
|
|
|
export function createStore(filePath) {
|
|
const matches = new Map()
|
|
|
|
if (filePath && existsSync(filePath)) {
|
|
try {
|
|
const data = JSON.parse(readFileSync(filePath, 'utf8'))
|
|
for (const [key, value] of Object.entries(data)) matches.set(key, value)
|
|
} catch (error) {
|
|
console.warn('讀取狀態檔失敗,將以空白狀態啟動:', error.message)
|
|
}
|
|
}
|
|
|
|
function persist() {
|
|
if (!filePath) return
|
|
try {
|
|
mkdirSync(dirname(filePath), { recursive: true })
|
|
writeFileSync(filePath, JSON.stringify(Object.fromEntries(matches)), 'utf8')
|
|
} catch (error) {
|
|
console.warn('寫入狀態檔失敗:', error.message)
|
|
}
|
|
}
|
|
|
|
const keyOf = (chatId, messageId) => `${chatId}:${messageId}`
|
|
|
|
return {
|
|
get: (chatId, messageId) => matches.get(keyOf(chatId, messageId)) ?? null,
|
|
set(chatId, messageId, match) {
|
|
matches.set(keyOf(chatId, messageId), match)
|
|
persist()
|
|
},
|
|
delete(chatId, messageId) {
|
|
matches.delete(keyOf(chatId, messageId))
|
|
persist()
|
|
},
|
|
size: () => matches.size,
|
|
}
|
|
}
|