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} 分。"
)