ForcePilot/backend/package/yuxi/channels/adapters/yuanbao/dispatch.py
Kris eb25707668 feat(yuanbao): 新增元宝渠道适配器完整实现
新增元宝(Yuanbao)渠道的完整适配器实现,包含以下核心模块:
- 基础适配器与导出入口
- 协议编解码与WebSocket帧处理
- 会话管理与路由逻辑
- 事件队列与出站消息队列
- 消息格式转换与发送重试
- 安全审计与权限校验
- 配置映射与账户管理
- 视觉分析与工具函数
- 文档生成与设置向导
2026-05-12 00:52:20 +08:00

143 lines
4.5 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any
from yuxi.utils.logging_config import logger
class DispatchAction(StrEnum):
MESSAGE = "message"
COMMAND = "command"
CARD_ACTION = "card_action"
BOT_MENU = "bot_menu"
REACTION = "reaction"
MEMBER_EVENT = "member_event"
READ_RECEIPT = "read_receipt"
SYSTEM_EVENT = "system_event"
UNKNOWN = "unknown"
@dataclass
class CardAction:
action_id: str = ""
action_type: str = ""
button_id: str = ""
value: str = ""
user_id: str = ""
chat_id: str = ""
raw_data: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_event(cls, raw_event: dict) -> CardAction:
return cls(
action_id=raw_event.get("action_id", ""),
action_type=raw_event.get("type", ""),
button_id=raw_event.get("button_id", raw_event.get("button", {}).get("id", "")),
value=raw_event.get("value", ""),
user_id=raw_event.get("open_id", ""),
chat_id=raw_event.get("group_open_id", raw_event.get("open_id", "")),
raw_data=raw_event,
)
@dataclass
class BotMenuAction:
menu_id: str = ""
menu_name: str = ""
user_id: str = ""
chat_id: str = ""
raw_data: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_event(cls, raw_event: dict) -> BotMenuAction:
return cls(
menu_id=raw_event.get("menu_id", ""),
menu_name=raw_event.get("menu_name", raw_event.get("content", "")),
user_id=raw_event.get("open_id", ""),
chat_id=raw_event.get("group_open_id", raw_event.get("open_id", "")),
raw_data=raw_event,
)
@dataclass
class DispatchContext:
action: DispatchAction
raw_event: dict[str, Any]
channel_message: Any = None
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class DispatchResult:
handled: bool = False
action: DispatchAction = DispatchAction.UNKNOWN
response: Any = None
metadata: dict[str, Any] = field(default_factory=dict)
class InteractiveDispatcher:
def __init__(self):
self._handlers: dict[DispatchAction, list[callable]] = {}
self._fallback_handler: callable | None = None
def register(self, action: DispatchAction, handler: callable) -> None:
if action not in self._handlers:
self._handlers[action] = []
self._handlers[action].append(handler)
def set_fallback(self, handler: callable) -> None:
self._fallback_handler = handler
async def dispatch(self, ctx: DispatchContext) -> DispatchResult:
handlers = self._handlers.get(ctx.action, [])
for handler in handlers:
try:
result = await handler(ctx)
if result is not None:
return result
except Exception as e:
logger.error(
f"[Yuanbao] Dispatch handler error for {ctx.action}: {e}",
exc_info=True,
)
if self._fallback_handler:
try:
return await self._fallback_handler(ctx) or DispatchResult(
handled=False,
action=ctx.action,
)
except Exception as e:
logger.error(f"[Yuanbao] Fallback handler error: {e}", exc_info=True)
return DispatchResult(handled=False, action=ctx.action)
@staticmethod
def classify_event(raw_event: dict[str, Any]) -> DispatchAction:
event_type = raw_event.get("type", "")
if event_type == "message":
content = raw_event.get("content", "")
if content and content.startswith("/"):
return DispatchAction.COMMAND
return DispatchAction.MESSAGE
elif event_type == "edited_message":
return DispatchAction.MESSAGE
elif event_type in ("reaction_added", "reaction_removed"):
return DispatchAction.REACTION
elif event_type == "bot_menu":
return DispatchAction.BOT_MENU
elif event_type in ("member_joined", "member_left", "bot_added", "bot_removed"):
return DispatchAction.MEMBER_EVENT
elif event_type in ("card_action", "card_click"):
return DispatchAction.CARD_ACTION
elif event_type == "read_receipt":
return DispatchAction.READ_RECEIPT
elif event_type in ("system_event", "typing"):
return DispatchAction.SYSTEM_EVENT
return DispatchAction.UNKNOWN