本次提交对渠道模块进行了全面升级,包含以下核心改进: 1. 新增二维码登录相关协议方法,完善登录流程 2. 优化配置监听逻辑,增加渠道运行状态前置校验 3. 重构动作注册机制,支持动态注册渠道动作并新增批量操作能力 4. 扩展渠道能力模型,新增广播、文件传输等支持 5. 优化适配器加载路径,新增元宝适配器支持 6. 新增凭证过期检查与告警能力,完善运维监控 7. 重构统计收集器,支持多维度渠道统计数据 8. 优化消息路由策略,新增策略缓存与安全处理逻辑 9. 重构基础适配器,新增凭证管理工具方法 10. 完善状态存储功能,支持凭证数据管理与批量清理 11. 重构渠道管理器,新增配置校验、动态渠道管理、限流能力 12. 优化健康检查与状态上报逻辑,完善审计日志与异常处理
228 lines
7.0 KiB
Python
228 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
from yuxi.channels.base import BaseChannelAdapter
|
|
|
|
_BUILTIN_ADAPTERS: dict[str, type[BaseChannelAdapter]] = {}
|
|
_BUILTIN_ADAPTER_ALIASES: dict[str, str] = {}
|
|
|
|
|
|
def _register_builtin(channel_id: str, cls: type[BaseChannelAdapter], aliases: list[str] | None = None) -> None:
|
|
"""统一的内置适配器注册入口 — 所有注册路径最终都调用此函数"""
|
|
_BUILTIN_ADAPTERS[channel_id] = cls
|
|
if aliases:
|
|
for alias in aliases:
|
|
_BUILTIN_ADAPTER_ALIASES[alias] = channel_id
|
|
|
|
|
|
def register_builtin_adapter(cls: type[BaseChannelAdapter] | None = None, *, aliases: list[str] | None = None):
|
|
def _decorator(cls_inner: type[BaseChannelAdapter]) -> type[BaseChannelAdapter]:
|
|
_register_builtin(cls_inner.channel_id, cls_inner, aliases)
|
|
return cls_inner
|
|
|
|
if cls is None:
|
|
return _decorator
|
|
return _decorator(cls)
|
|
|
|
|
|
def _load_builtin_adapters():
|
|
if _BUILTIN_ADAPTERS:
|
|
return
|
|
try:
|
|
from yuxi.channels.adapters.telegram import TelegramAdapter
|
|
|
|
_BUILTIN_ADAPTERS["telegram"] = TelegramAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.slack import SlackAdapter
|
|
|
|
_BUILTIN_ADAPTERS["slack"] = SlackAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.matrix import MatrixAdapter
|
|
|
|
_BUILTIN_ADAPTERS["matrix"] = MatrixAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.mattermost import MattermostAdapter
|
|
|
|
_BUILTIN_ADAPTERS["mattermost"] = MattermostAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.nostr import NostrAdapter
|
|
|
|
_BUILTIN_ADAPTERS["nostr"] = NostrAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.twitch.adapter import TwitchAdapter
|
|
|
|
_BUILTIN_ADAPTERS["twitch"] = TwitchAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.imessage import IMessageAdapter
|
|
|
|
_BUILTIN_ADAPTERS["imessage"] = IMessageAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.whatsapp import WhatsAppAdapter
|
|
|
|
_BUILTIN_ADAPTERS["whatsapp"] = WhatsAppAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.discord import DiscordAdapter
|
|
|
|
_BUILTIN_ADAPTERS["discord"] = DiscordAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.irc import IRCAdapter
|
|
|
|
_BUILTIN_ADAPTERS["irc"] = IRCAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.dingding import DingDingChannelAdapter
|
|
|
|
_BUILTIN_ADAPTERS["dingding"] = DingDingChannelAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.feishu import FeishuAdapter
|
|
|
|
_BUILTIN_ADAPTERS["feishu"] = FeishuAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.msteams import MSTeamsAdapter
|
|
|
|
_BUILTIN_ADAPTERS["msteams"] = MSTeamsAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.bluebubbles import BlueBubblesAdapter
|
|
|
|
_BUILTIN_ADAPTERS["bluebubbles"] = BlueBubblesAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.synologychat import SynologyChatAdapter
|
|
|
|
_BUILTIN_ADAPTERS["synologychat"] = SynologyChatAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.nextcloudtalk import NextcloudTalkAdapter
|
|
|
|
_BUILTIN_ADAPTERS["nextcloud-talk"] = NextcloudTalkAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.googlechat import GoogleChatAdapter
|
|
|
|
_BUILTIN_ADAPTERS["googlechat"] = GoogleChatAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.line import LINEAdapter
|
|
|
|
_BUILTIN_ADAPTERS["line"] = LINEAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.qqbot import QQBotAdapter
|
|
|
|
_BUILTIN_ADAPTERS["qqbot"] = QQBotAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.signal.channel import SignalChannel
|
|
|
|
_BUILTIN_ADAPTERS["signal"] = SignalChannel
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.zalo_user import ZaloUserAdapter
|
|
|
|
_BUILTIN_ADAPTERS["zalo_user"] = ZaloUserAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.urbit import UrbitAdapter
|
|
|
|
_BUILTIN_ADAPTERS["urbit"] = UrbitAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.zalo_oa import ZaloOAAdapter
|
|
|
|
_BUILTIN_ADAPTERS["zalo_oa"] = ZaloOAAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.wechat import WeChatAdapter
|
|
|
|
_BUILTIN_ADAPTERS["wechat"] = WeChatAdapter
|
|
except ImportError:
|
|
pass
|
|
try:
|
|
from yuxi.channels.adapters.yuanbao.adapter import YuanbaoAdapter
|
|
|
|
_BUILTIN_ADAPTERS["yuanbao"] = YuanbaoAdapter
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
class ChannelRegistry:
|
|
def __init__(self):
|
|
self._adapters: dict[str, type[BaseChannelAdapter]] = {}
|
|
self._alias_map: dict[str, str] = {}
|
|
|
|
def _build_alias_map(self) -> None:
|
|
for alias, channel_id in _BUILTIN_ADAPTER_ALIASES.items():
|
|
self._alias_map[alias] = channel_id
|
|
for channel_id, adapter_cls in {**_BUILTIN_ADAPTERS, **self._adapters}.items():
|
|
meta = getattr(adapter_cls, "meta", None)
|
|
if meta and meta.aliases:
|
|
for alias in meta.aliases:
|
|
self._alias_map[alias] = channel_id
|
|
|
|
def get(self, channel_id: str) -> type[BaseChannelAdapter] | None:
|
|
if channel_id in self._adapters:
|
|
return self._adapters[channel_id]
|
|
if channel_id in _BUILTIN_ADAPTERS:
|
|
return _BUILTIN_ADAPTERS[channel_id]
|
|
if not self._alias_map:
|
|
self._build_alias_map()
|
|
resolved = self._alias_map.get(channel_id)
|
|
if resolved:
|
|
return self._adapters.get(resolved) or _BUILTIN_ADAPTERS.get(resolved)
|
|
return None
|
|
|
|
def register(self, channel_id: str, adapter_cls: type[BaseChannelAdapter]) -> None:
|
|
self._adapters[channel_id] = adapter_cls
|
|
|
|
def list_channels(self) -> list[str]:
|
|
return list({*self._adapters.keys(), *_BUILTIN_ADAPTERS.keys()})
|
|
|
|
def unregister(self, channel_id: str) -> None:
|
|
self._adapters.pop(channel_id, None)
|
|
|
|
def is_available(self, channel_id: str) -> bool:
|
|
return channel_id in self._adapters or channel_id in _BUILTIN_ADAPTERS
|
|
|
|
def load_builtins(self) -> None:
|
|
"""将 _BUILTIN_ADAPTERS 中的所有内置适配器加载到 self._adapters 中"""
|
|
_load_builtin_adapters()
|
|
for channel_id, adapter_cls in _BUILTIN_ADAPTERS.items():
|
|
self._adapters.setdefault(channel_id, adapter_cls)
|
|
|
|
|
|
BUILTIN_ADAPTERS = _BUILTIN_ADAPTERS
|