新增元宝(Yuanbao)渠道的完整适配器实现,包含以下核心模块: - 基础适配器与导出入口 - 协议编解码与WebSocket帧处理 - 会话管理与路由逻辑 - 事件队列与出站消息队列 - 消息格式转换与发送重试 - 安全审计与权限校验 - 配置映射与账户管理 - 视觉分析与工具函数 - 文档生成与设置向导
143 lines
4.5 KiB
Python
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
|