新增 Zalo OA 官方账号完整集成能力,包含: 1. 基础通信能力:消息编解码、目标归一化、文本分块 2. 安全与校验:Webhook 签名验证、DM 策略管理、配对流程 3. 辅助工具:重复事件去重、请求限流、异常告警 4. 管理功能:账号多实例管理、配置验证、健康诊断 5. 扩展能力:媒体托管、视觉识别、TTS 语音合成 6. 运维支持:审计日志、状态监控、目录同步
121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from collections.abc import Callable, Coroutine
|
|
|
|
COMMAND_PREFIXES = ("/", "!")
|
|
|
|
CommandHandler = Callable[[str, dict[str, Any]], Coroutine[Any, Any, str | None]]
|
|
|
|
|
|
class CommandRegistry:
|
|
def __init__(self):
|
|
self._commands: dict[str, dict[str, Any]] = {
|
|
"help": {"description": "Show available commands", "handler": self._handle_help},
|
|
"status": {"description": "Show OA status", "handler": self._handle_status},
|
|
"ping": {"description": "Check if OA is responding", "handler": self._handle_ping},
|
|
}
|
|
|
|
def register(
|
|
self,
|
|
name: str,
|
|
description: str = "",
|
|
handler: CommandHandler | None = None,
|
|
):
|
|
self._commands[name] = {
|
|
"description": description,
|
|
"handler": handler or self._make_default_handler(name),
|
|
}
|
|
|
|
def unregister(self, name: str):
|
|
if name.lower() not in ("help", "status", "ping"):
|
|
self._commands.pop(name.lower(), None)
|
|
|
|
def get_list(self) -> list[dict[str, str]]:
|
|
return [{"command": f"/{cmd}", "description": info["description"]} for cmd, info in self._commands.items()]
|
|
|
|
def is_registered(self, name: str) -> bool:
|
|
return name.lower() in self._commands
|
|
|
|
async def handle(
|
|
self,
|
|
cmd: str,
|
|
args: str,
|
|
context: dict[str, Any],
|
|
extra_handlers: dict[str, CommandHandler] | None = None,
|
|
) -> str | None:
|
|
cmd = cmd.lower()
|
|
|
|
if extra_handlers and cmd in extra_handlers:
|
|
return await extra_handlers[cmd](args, context)
|
|
|
|
entry = self._commands.get(cmd)
|
|
if entry is None:
|
|
return None
|
|
|
|
handler = entry["handler"]
|
|
if handler:
|
|
return await handler(args, context)
|
|
|
|
return None
|
|
|
|
async def _handle_help(self, args: str, context: dict[str, Any]) -> str | None:
|
|
lines = ["Available commands:"]
|
|
for name, info in self._commands.items():
|
|
lines.append(f"/{name} - {info['description']}")
|
|
return "\n".join(lines)
|
|
|
|
@staticmethod
|
|
async def _handle_status(args: str, context: dict[str, Any]) -> str | None:
|
|
oa_name = context.get("oa_name", "Unknown")
|
|
status = context.get("status", "unknown")
|
|
policy = context.get("dm_policy", "unknown")
|
|
return f"OA: {oa_name}\nStatus: {status}\nDM Policy: {policy}"
|
|
|
|
@staticmethod
|
|
async def _handle_ping(args: str, context: dict[str, Any]) -> str | None:
|
|
return "pong"
|
|
|
|
@staticmethod
|
|
def _make_default_handler(cmd_name: str) -> CommandHandler:
|
|
async def _handler(args: str, context: dict[str, Any]) -> str | None:
|
|
return f"Command /{cmd_name} executed"
|
|
|
|
return _handler
|
|
|
|
|
|
_registry = CommandRegistry()
|
|
|
|
|
|
def extract_command(text: str) -> tuple[str | None, str]:
|
|
text = text.strip()
|
|
for prefix in COMMAND_PREFIXES:
|
|
if text.startswith(prefix):
|
|
cmd_text = text[len(prefix) :]
|
|
parts = cmd_text.split(None, 1)
|
|
cmd = parts[0].lower()
|
|
args = parts[1] if len(parts) > 1 else ""
|
|
return cmd, args
|
|
return None, text
|
|
|
|
|
|
def get_command_list() -> list[dict[str, str]]:
|
|
return _registry.get_list()
|
|
|
|
|
|
def register_command(name: str, description: str = "", handler: CommandHandler | None = None):
|
|
_registry.register(name, description, handler)
|
|
|
|
|
|
def get_command_registry() -> CommandRegistry:
|
|
return _registry
|
|
|
|
|
|
async def handle_command(
|
|
cmd: str,
|
|
args: str,
|
|
context: dict[str, Any],
|
|
handlers: dict[str, CommandHandler] | None = None,
|
|
) -> str | None:
|
|
return await _registry.handle(cmd, args, context, handlers)
|