From 81604bfe8a268bdd67cfd4cfc8391991d33d40bb Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 14 Mar 2026 22:35:44 +0800 Subject: [PATCH] feat: Implement basic and city command handlers for the bot, including user identity binding and unbinding. --- src/bot/handlers/basic.py | 92 +++++++++++++++++++++++++++++---------- src/bot/handlers/city.py | 7 ++- src/bot/handlers/deb.py | 7 ++- src/bot/io_layer.py | 6 +-- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/bot/handlers/basic.py b/src/bot/handlers/basic.py index 84bc5230..1495aa2f 100644 --- a/src/bot/handlers/basic.py +++ b/src/bot/handlers/basic.py @@ -8,6 +8,51 @@ from src.bot.observability import CommandTrace from src.bot.runtime_coordinator import RuntimeStatus, render_runtime_status_html +def _normalized_command_head(text: str | None) -> str: + raw = str(text or "") + for marker in ( + "\ufeff", + "\u200b", + "\u200c", + "\u200d", + "\u200e", + "\u200f", + "\u2060", + "\u2066", + "\u2067", + "\u2068", + "\u2069", + "\u202a", + "\u202b", + "\u202c", + "\u202d", + "\u202e", + ): + raw = raw.replace(marker, "") + raw = raw.strip() + if raw[:1] in {"/", "⁄", "∕", "╱", "⧸"}: + raw = "/" + raw[1:] + return raw.split(maxsplit=1)[0].lower() if raw else "" + + +def _is_command_head(head: str, name: str) -> bool: + base = f"/{name}" + return head == base or head.startswith(f"{base}@") + + +def _is_basic_command_text(text: str | None) -> bool: + head = _normalized_command_head(text) + return ( + _is_command_head(head, "start") + or _is_command_head(head, "help") + or _is_command_head(head, "id") + or _is_command_head(head, "top") + or _is_command_head(head, "diag") + or _is_command_head(head, "bind") + or _is_command_head(head, "unbind") + ) + + class BasicCommandHandler: def __init__( self, @@ -20,29 +65,30 @@ class BasicCommandHandler: self.runtime_status_provider = runtime_status_provider def register(self) -> None: - @self.bot.message_handler(commands=["start", "help"]) - def _start_help(message): - self.handle_start_help(message) - - @self.bot.message_handler(commands=["id"]) - def _id(message): - self.handle_id(message) - - @self.bot.message_handler(commands=["top"]) - def _top(message): - self.handle_top(message) - - @self.bot.message_handler(commands=["diag"]) - def _diag(message): - self.handle_diag(message) - - @self.bot.message_handler(commands=["bind"]) - def _bind(message): - self.handle_bind(message) - - @self.bot.message_handler(commands=["unbind"]) - def _unbind(message): - self.handle_unbind(message) + @self.bot.message_handler( + content_types=["text"], + func=lambda message: _is_basic_command_text(getattr(message, "text", None)), + ) + def _basic(message): + head = _normalized_command_head(getattr(message, "text", None)) + if _is_command_head(head, "start") or _is_command_head(head, "help"): + self.handle_start_help(message) + return + if _is_command_head(head, "id"): + self.handle_id(message) + return + if _is_command_head(head, "top"): + self.handle_top(message) + return + if _is_command_head(head, "diag"): + self.handle_diag(message) + return + if _is_command_head(head, "bind"): + self.handle_bind(message) + return + if _is_command_head(head, "unbind"): + self.handle_unbind(message) + return def handle_start_help(self, message: Any) -> None: trace = CommandTrace("/start", message) diff --git a/src/bot/handlers/city.py b/src/bot/handlers/city.py index eb1868cd..9625f7ec 100644 --- a/src/bot/handlers/city.py +++ b/src/bot/handlers/city.py @@ -40,7 +40,12 @@ def _normalized_command_head(text: str | None) -> str: def _is_city_command_text(text: str | None) -> bool: head = _normalized_command_head(text) - return head == "/city" or head.startswith("/city@") + return ( + head == "/city" + or head.startswith("/city@") + or head == "/pwcity" + or head.startswith("/pwcity@") + ) class CityCommandHandler: diff --git a/src/bot/handlers/deb.py b/src/bot/handlers/deb.py index 47c6cbaf..3c0d396c 100644 --- a/src/bot/handlers/deb.py +++ b/src/bot/handlers/deb.py @@ -40,7 +40,12 @@ def _normalized_command_head(text: str | None) -> str: def _is_deb_command_text(text: str | None) -> bool: head = _normalized_command_head(text) - return head == "/deb" or head.startswith("/deb@") + return ( + head == "/deb" + or head.startswith("/deb@") + or head == "/pwdeb" + or head.startswith("/pwdeb@") + ) class DebCommandHandler: diff --git a/src/bot/io_layer.py b/src/bot/io_layer.py index 60a2f154..3f150d5b 100644 --- a/src/bot/io_layer.py +++ b/src/bot/io_layer.py @@ -145,15 +145,15 @@ class BotIOLayer: return ( "🚀 PolyWeather 天气查询机器人\n\n" "可用指令:\n" - f"/city [城市名] - 查询城市天气预测与实测 (消耗 {CITY_QUERY_COST} 积分)\n" - f"/deb [城市名] - 查看 DEB 融合预测准确率 (消耗 {DEB_QUERY_COST} 积分)\n" + f"/city [城市名] 或 /pwcity [城市名] - 查询城市天气预测与实测 (消耗 {CITY_QUERY_COST} 积分)\n" + f"/deb [城市名] 或 /pwdeb [城市名] - 查看 DEB 融合预测准确率 (消耗 {DEB_QUERY_COST} 积分)\n" "/top - 查看积分排行榜\n" "/id - 获取当前聊天的 Chat ID\n\n" "/diag - 查看 Bot 启动诊断\n\n" "/bind - 绑定 Supabase 账号(可选)\n" "/unbind - 解除当前 Telegram 与网页账号绑定\n\n" "🔐 /city 与 /deb 仅限官方群成员使用。\n\n" - "示例: /city 伦敦\n" + "示例: /city 伦敦/pwcity 伦敦\n" f"💡 提示: 每日签到(有效发言满 {MESSAGE_MIN_LENGTH} 字)获得 {MESSAGE_POINTS} 积分," f"每日上限 {MESSAGE_DAILY_CAP} 分。" )