from __future__ import annotations import logging from typing import Any from yuxi.channel.extensions.qqbot.types import SlashCommand, SlashCommandResult logger = logging.getLogger(__name__) class SlashCommandRegistry: def __init__(self): self._commands: dict[str, SlashCommand] = {} self._framework_commands: dict[str, SlashCommand] = {} self._handlers: dict[str, Any] = {} def register(self, cmd: SlashCommand, handler: Any = None) -> None: self._commands[cmd.name] = cmd for alias in cmd.aliases: self._commands[alias] = cmd if cmd.require_auth: self._framework_commands[cmd.name] = cmd if handler: self._handlers[cmd.name] = handler def get_commands(self) -> list[SlashCommand]: seen: set[str] = set() result = [] for cmd in self._commands.values(): if cmd.name not in seen: seen.add(cmd.name) result.append(cmd) return result async def try_handle(self, content: str, msg: Any) -> SlashCommandResult: if not content.startswith("/"): return SlashCommandResult.ENQUEUE parts = content.strip().split() command_name = parts[0].lower() args = parts[1:] if len(parts) > 1 else [] cmd = self._commands.get(command_name) if cmd is None: return SlashCommandResult.ENQUEUE if cmd.c2c_only and msg.chat_type.value not in ("c2c", "dm"): return SlashCommandResult.HANDLED if cmd.require_auth: authorized = True if not authorized: return SlashCommandResult.HANDLED handler = self._handlers.get(cmd.name) if handler: try: result = await handler(msg, args) if result is not None: return SlashCommandResult.HANDLED except Exception: logger.exception("Command handler error: %s", cmd.name) return SlashCommandResult.ENQUEUE def find_command(self, name: str) -> SlashCommand | None: return self._commands.get(name.lower()) def register_builtin_commands(registry: SlashCommandRegistry, outbound: Any = None) -> None: def _resolve_target(msg: Any, ob: Any) -> str: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor return QQBotMonitor.to_unified_message(msg, "default").metadata.get("target_id", "") async def _ping_handler(msg, args): if outbound: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor target_id = QQBotMonitor.to_unified_message( msg, "default" ).metadata.get("target_id", "") if target_id: await outbound.send_text(target_id, "pong! 🏓") return "handled" async def _version_handler(msg, args): if outbound: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor target_id = QQBotMonitor.to_unified_message( msg, "default" ).metadata.get("target_id", "") if target_id: await outbound.send_text(target_id, "ForcePilot QQBot v1.0.0") return "handled" async def _help_handler(msg, args): cmds = registry.get_commands() help_text = "**可用命令:**\n" + "\n".join( f"- `{c.name}` - {c.description}" for c in cmds ) if outbound: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor target_id = QQBotMonitor.to_unified_message( msg, "default" ).metadata.get("target_id", "") if target_id: await outbound.send_text(target_id, help_text) return "handled" async def _streaming_handler(msg, args): if not args: if outbound: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor target_id = QQBotMonitor.to_unified_message( msg, "default" ).metadata.get("target_id", "") if target_id: await outbound.send_text(target_id, "流式消息状态: 已开启") return "handled" action = args[0].lower() if outbound: from yuxi.channel.extensions.qqbot.monitor import QQBotMonitor target_id = QQBotMonitor.to_unified_message( msg, "default" ).metadata.get("target_id", "") if target_id: if action == "on": await outbound.send_text(target_id, "流式消息已开启") elif action == "off": await outbound.send_text(target_id, "流式消息已关闭") else: await outbound.send_text(target_id, f"用法: `/bot-streaming [on|off]`") return "handled" registry.register( SlashCommand(name="/bot-ping", aliases=["/ping"], description="延迟测试"), _ping_handler, ) registry.register( SlashCommand(name="/bot-version", aliases=["/version"], description="显示版本信息"), _version_handler, ) registry.register( SlashCommand(name="/bot-help", aliases=["/help"], description="列出所有命令"), _help_handler, ) registry.register( SlashCommand( name="/bot-streaming", description="流式模式控制", usage="/bot-streaming [on|off]", require_auth=True, c2c_only=True, ), _streaming_handler, ) async def _approve_handler(msg, args): if not args or len(args) < 2: if outbound: target_id = _resolve_target(msg, outbound) if target_id: await outbound.send_text( target_id, "用法: `/bot-approve `\n" "支持的操作: `allow-once` / `allow-always` / `deny`" ) return "handled" approval_id = args[0] action = args[1] if outbound: target_id = _resolve_target(msg, outbound) if target_id: await outbound.send_text(target_id, f"审批操作已提交: id={approval_id}, action={action}") return "handled" async def _logs_handler(msg, args): if outbound: target_id = _resolve_target(msg, outbound) if target_id: await outbound.send_text(target_id, "日志导出功能: 请通过 Web 管理面板查看网关日志") return "handled" async def _clear_storage_handler(msg, args): if outbound: target_id = _resolve_target(msg, outbound) if target_id: await outbound.send_text(target_id, "存储已清除") return "handled" async def _upgrade_handler(msg, args): if outbound: target_id = _resolve_target(msg, outbound) if target_id: await outbound.send_text( target_id, "**升级指南:**\n" "1. 拉取最新代码: `git pull`\n" "2. 重建容器: `docker compose build`\n" "3. 重启服务: `docker compose up -d`", ) return "handled" registry.register( SlashCommand( name="/bot-approve", description="审批待处理操作", usage="/bot-approve ", require_auth=True, c2c_only=True, ), _approve_handler, ) registry.register( SlashCommand( name="/bot-logs", description="导出最近网关日志", require_auth=True, c2c_only=True, ), _logs_handler, ) registry.register( SlashCommand( name="/bot-clear-storage", description="清除存储", require_auth=True, c2c_only=True, ), _clear_storage_handler, ) registry.register( SlashCommand( name="/bot-upgrade", description="显示升级指南", require_auth=True, c2c_only=True, ), _upgrade_handler, )