新增斜杠指令面板:/v2/panels 自动注册,帮助消息带指令按钮

- panel.py 调开放平台 2026-08 新增的 /v2/panels 接口,把 点歌/歌词/帮助
  三条指令同步到输入框 / 菜单:READY 时幂等同步(没有则创建、内容有变
  则更新、一致则跳过,重连自愈),target_type=all 对全部群生效;失败
  只记日志不影响运行;COMMAND_PANEL=off 可关闭
- 斜杠指令与 @机器人 消息归一解析:_normalize 剥开头 / 与 @ 前缀,
  与裸文本命令共用 handle_command(/点歌 稻香、@bot 点歌 稻香、
  点歌 稻香 三种写法等价)
- 只敲命令不带参数(斜杠指令参数可为空)时回复用法提示
- 帮助消息带 [🎤 点歌][📝 歌词] 指令按钮(action type=2,点击把
  /点歌 填进输入框),键盘无权限自动回退纯文本;帮助文案改以 / 指令呈现
This commit is contained in:
snowy-yang committed 2026-09-24 21:55:32 +08:00
1 parent d71809d144
commit 94ee3298b7
5 files changed
+303 -9

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
"""指令面板(群里输入框敲 / 唤出的指令菜单)自动注册。
调开放平台 2026-08 新增的 /v2/panels 接口,把点歌指令同步到 QQ:
启动与每次重连(READY)各同步一次——没有就创建(target_type=all
对全部群生效),内容有变就更新,一致则跳过,幂等可重入。
失败只记日志不影响运行(如功能未对机器人开放)。
"""
from typing import Any
from aiohttp import ClientError
from loguru import logger
from qqbotsdk.api import ApiError, BotApi
_API_FAILS = (ApiError, TimeoutError, ClientError)
# 面板挂在群聊场景,对所有群生效
_SCOPE = "group"
_TARGET_TYPE = "all"
# remark 兼作身份标识:重连同步时按它找到自己注册过的面板
_REMARK = "音乐bot"
_PANEL_ITEMS = [
{"name": "点歌", "type": "command", "desc": "出选歌菜单,点编号选歌"},
{"name": "歌词", "type": "command", "desc": "查看歌词"},
{"name": "帮助", "type": "command", "desc": "查看帮助"},
]
_PANEL_BODY = {"items": _PANEL_ITEMS, "remark": _REMARK}
def _signature(items: list[dict]) -> list[tuple]:
"""面板条目的比较指纹:只取本模块管理的字段,忽略其余与键序。"""
return [
(
i.get("name"),
i.get("type"),
i.get("desc"),
i.get("only_admin", False),
i.get("link"),
)
for i in items
]
def _unwrap(body: Any, key: str) -> Any:
"""响应可能整体包一层 {"data": ...},也可能直接是数据本体,两者都认。"""
if not isinstance(body, dict):
return body
inner = body.get("data")
return inner if key not in body and isinstance(inner, dict) else body
def _mine(records: list[Any]) -> dict | None:
"""在面板列表里找本 bot 注册过的那张(按 remark 识别)。"""
for record in records:
if not isinstance(record, dict):
continue
if (record.get("panel") or {}).get("remark") == _REMARK:
return record
return None
async def sync_command_panel(api: BotApi) -> None:
"""把点歌指令面板同步到 QQ(幂等)。任何失败只记日志,绝不抛出。"""
try:
resp = await api.get("/v2/panels", params={"scope": _SCOPE, "limit": "20"})
records = _unwrap(resp, "records").get("records") or []
mine = _mine(records)
unchanged = mine and _signature(mine["panel"].get("items") or []) == _signature(
_PANEL_ITEMS
)
if unchanged:
logger.info("指令面板已是最新(点歌/歌词/帮助)")
return
if mine:
await api.put(f"/v2/panels/{mine['panel_id']}", json={"panel": _PANEL_BODY})
logger.info("指令面板已更新(点歌/歌词/帮助)")
else:
body = {"scope": _SCOPE, "panel": _PANEL_BODY, "target_type": _TARGET_TYPE}
await api.post("/v2/panels", json=body)
logger.info("指令面板已创建(点歌/歌词/帮助,对全部群生效)")
except _API_FAILS as e:
# 失败不影响运行;指令可改为在开放平台后台手工配置
logger.warning(f"指令面板同步失败: {e!r}")