refactor(chain): 处理链功能域 mixin 化,清理未使用导入并根治兼容层循环导入

- ChainBase 拆分为 RecognitionMixin/MessageProcessingMixin/NotificationMixin
- TransferChain 拆分为 7 个功能 mixin(_mixins.py),SubscribeChain 音乐订阅域拆出 _music.py
- 斜杠命令交互四件套收敛为 InteractionChainMixin 委托,会话管理器移至 application 层,chain 层不再 re-export
- 模块基础类收敛到 app/modules/_base(notification/mediaserver 语义重命名)
- 清理 app/chain/__init__.py 24 个未使用导入,修正 49 处测试 patch 目标到实际命名空间
- 兼容层 legacy 符号不再并入 __all__,根治 schemas 初始化反向拉起 application.transfer 的循环导入
- 修复 bangumi 集数为字符串时 set_bangumi_info 抛 TypeError
- 新增重复代码等架构门禁测试;capability 清单校验排除下划线内部目录
This commit is contained in:
jxxghp
2026-08-16 16:30:16 +08:00
parent 24671f8f18
commit 7e851dbfa7
102 changed files with 6041 additions and 5888 deletions
+6 -100
View File
@@ -1,28 +1,24 @@
import copy
import json
import re
from typing import Dict, Optional, Union, List, Tuple, Any
from app.domain.context import MediaInfo, Context
from app.runtime.events import eventmanager
from app.application.messaging.agent import (
matches_channel_admin,
register_channel_admin_resolver,
resolve_config_principal_ids,
)
from app.runtime.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.modules._base import _MessageChannelModuleBase
from app.modules.telegram.telegram import Telegram
from app.schemas import (
MessageChannel,
CommingMessage,
Notification,
CommandRegisterEventData,
NotificationConf,
MessageResponse,
)
from app.schemas.types import ModuleType, ChainEventType
from app.foundation.collections import DictUtils
from app.schemas.types import ModuleType
register_channel_admin_resolver(
@@ -33,11 +29,14 @@ register_channel_admin_resolver(
)
class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
class TelegramModule(_MessageChannelModuleBase[Telegram]):
"""
Telegram 通知模块,负责模块生命周期、消息解析和通知发送。
"""
# 管理员配置键,与渠道 resolver 保持一致
_admin_config_key = "TELEGRAM_ADMINS"
def init_module(self) -> None:
"""
初始化模块
@@ -83,53 +82,12 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
except Exception as err:
logger.error(f"停止Telegram模块实例失败:{err}")
def test(self) -> Optional[Tuple[bool, str]]:
"""
测试模块连接性
"""
if not self.get_instances():
return None
for name, client in self.get_instances().items():
state = client.get_state()
if not state:
return False, f"Telegram {name} 未就绪"
return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""
获取模块初始化配置项。
"""
pass
@staticmethod
def _get_admins(config: Optional[dict]) -> List[str]:
"""
解析 Telegram 管理员配置,兼容逗号分隔和首尾空白。
"""
return [
admin.strip()
for admin in str((config or {}).get("TELEGRAM_ADMINS") or "").split(",")
if admin.strip()
]
@classmethod
def _should_reject_admin_command(
cls,
config: Optional[dict],
*user_ids: Optional[Union[str, int]],
) -> bool:
"""
判断 Telegram 命令或命令型按钮回调是否应因非管理员身份被拒绝。
"""
admins = cls._get_admins(config)
if not admins:
return False
return not matches_channel_admin(
MessageChannel.Telegram,
config,
*user_ids,
)
def message_parser(
self, source: str, body: Any, form: Any, args: Any
) -> Optional[CommingMessage]:
@@ -795,58 +753,6 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
)
return None
def register_commands(self, commands: Dict[str, dict]):
"""
注册命令,实现这个函数接收系统可用的命令菜单
:param commands: 命令字典
"""
for client_config in self.get_configs().values():
client = self.get_instance(client_config.name)
if not client:
continue
# 触发事件,允许调整命令数据,这里需要进行深复制,避免实例共享
scoped_commands = copy.deepcopy(commands)
event = eventmanager.send_event(
ChainEventType.CommandRegister,
CommandRegisterEventData(
commands=scoped_commands,
origin="Telegram",
service=client_config.name,
),
)
# 如果事件返回有效的 event_data,使用事件中调整后的命令
if event and event.event_data:
event_data: CommandRegisterEventData = event.event_data
# 如果事件被取消,跳过命令注册,并清理菜单
if event_data.cancel:
client.delete_commands()
logger.debug(
f"Command registration for {client_config.name} canceled by event: {event_data.source}"
)
continue
scoped_commands = event_data.commands or {}
if not scoped_commands:
logger.debug("Filtered commands are empty, skipping registration.")
client.delete_commands()
# scoped_commands 必须是 commands 的子集
filtered_scoped_commands = DictUtils.filter_keys_to_subset(
scoped_commands, commands
)
# 如果 filtered_scoped_commands 为空,则跳过注册
if not filtered_scoped_commands:
logger.debug("Filtered commands are empty, skipping registration.")
client.delete_commands()
continue
# 对比调整后的命令与当前命令
if filtered_scoped_commands != commands:
logger.debug(
f"Command set has changed, Updating new commands: {filtered_scoped_commands}"
)
client.register_commands(filtered_scoped_commands)
def download_telegram_file_to_base64(self, file_id: str, source: str) -> Optional[str]:
"""
下载Telegram文件并转为base64