ForcePilot/backend/package/yuxi/channels/adapters/zalo_oa/commands.py
Kris 30dd9e16f4 feat(zalo-oa): 实现完整的 Zalo OA 渠道适配器模块
新增 Zalo OA 官方账号完整集成能力,包含:
1. 基础通信能力:消息编解码、目标归一化、文本分块
2. 安全与校验:Webhook 签名验证、DM 策略管理、配对流程
3. 辅助工具:重复事件去重、请求限流、异常告警
4. 管理功能:账号多实例管理、配置验证、健康诊断
5. 扩展能力:媒体托管、视觉识别、TTS 语音合成
6. 运维支持:审计日志、状态监控、目录同步
2026-05-12 00:52:47 +08:00

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)