""" HookBot - 机器人核心类 处理消息路由和事件分发 职责单一化:仅负责消息流程编排,具体功能委托给专门模块 """ import random from typing import Any, Dict, Optional from loguru import logger from WechatHook import WechatHookClient, MESSAGE_TYPE_MAP, normalize_message from utils.event_manager import EventManager from utils.config_manager import get_bot_config, get_performance_config from utils.message_filter import MessageFilter from utils.message_dedup import MessageDeduplicator from utils.message_stats import MessageStats class HookBot: """ HookBot 核心类 负责消息处理流程编排: 1. 接收消息 2. 去重检查 3. 格式转换 4. 过滤检查 5. 事件分发 具体功能委托给: - MessageDeduplicator: 消息去重 - MessageFilter: 消息过滤 - MessageStats: 消息统计 """ # API 响应消息类型(需要忽略) API_RESPONSE_TYPES = {11032, 11174, 11230} # 重要消息类型(始终记录日志) IMPORTANT_MESSAGE_TYPES = { 11058, 11098, 11099, 11025, # 系统消息、群成员变动、登录信息 11051, 11047, 11052, 11055 # 视频、图片、表情、文件消息 } def __init__(self, client: WechatHookClient): """ 初始化 HookBot Args: client: WechatHookClient 实例 """ self.client = client self.wxid: Optional[str] = None self.nickname: Optional[str] = None # 加载配置 bot_config = get_bot_config() perf_config = get_performance_config() # 预设机器人信息 preset_wxid = bot_config.get("wxid") or bot_config.get("bot_wxid") preset_nickname = bot_config.get("nickname") or bot_config.get("bot_nickname") if preset_wxid: self.wxid = preset_wxid logger.info(f"使用配置中的机器人 wxid: {self.wxid}") if preset_nickname: self.nickname = preset_nickname logger.info(f"使用配置中的机器人昵称: {self.nickname}") # 日志采样率 self.log_sampling_rate = perf_config.get("log_sampling_rate", 1.0) # 初始化组件(职责委托) self._filter = MessageFilter.from_config(bot_config) self._dedup = MessageDeduplicator.from_config(perf_config) self._stats = MessageStats() logger.info("HookBot 初始化完成") def update_profile(self, wxid: str, nickname: str): """ 更新机器人信息 Args: wxid: 机器人 wxid nickname: 机器人昵称 """ self.wxid = wxid self.nickname = nickname self._filter.set_bot_wxid(wxid) logger.info(f"机器人信息: wxid={wxid}, nickname={nickname}") async def process_message(self, msg_type: int, data: Dict[str, Any]): """ 处理接收到的消息 Args: msg_type: 消息类型 data: 消息数据 """ # 1. 过滤 API 响应消息 if msg_type in self.API_RESPONSE_TYPES: return # 2. 去重检查 try: if await self._dedup.is_duplicate(data): self._stats.record_duplicate() logger.debug(f"[HookBot] 重复消息已丢弃: type={msg_type}") return except Exception as e: logger.debug(f"[HookBot] 消息去重检查失败: {e}") # 3. 记录收到消息 self._stats.record_received() should_log = self._should_log_message(msg_type) if should_log: logger.debug(f"处理消息: type={msg_type}") # 重要事件始终记录 if msg_type in self.IMPORTANT_MESSAGE_TYPES: logger.info(f"重要事件: type={msg_type}") # 4. 获取事件类型 event_type = MESSAGE_TYPE_MAP.get(msg_type) if should_log and event_type: logger.info(f"[HookBot] 消息类型映射: {msg_type} -> {event_type}") if not event_type: content_preview = str(data.get('raw_msg', data.get('msg', '')))[:200] logger.warning( f"未映射的消息类型: {msg_type}, " f"wx_type: {data.get('wx_type')}, " f"内容预览: {content_preview}" ) return # 5. 格式转换 try: message = normalize_message(msg_type, data) except Exception as e: logger.error(f"格式转换失败: {e}") self._stats.record_error() return # 6. 过滤检查 if not self._filter.should_process(message): self._stats.record_filtered() if should_log: logger.debug(f"消息被过滤: {message.get('FromWxid')}") return # 7. 记录处理 self._stats.record_processed(event_type) if should_log: content = message.get('Content', '') if len(content) > 50: content = content[:50] + "..." logger.info( f"处理消息: type={event_type}, " f"from={message.get('FromWxid')}, " f"content={content}" ) # 8. 触发事件 try: await EventManager.emit(event_type, self.client, message) except Exception as e: logger.error(f"事件处理失败: {e}") self._stats.record_error() def _should_log_message(self, msg_type: int) -> bool: """判断是否应该记录此消息的日志""" if msg_type in self.IMPORTANT_MESSAGE_TYPES: return True return random.random() < self.log_sampling_rate def get_stats(self) -> Dict[str, Any]: """获取消息处理统计信息""" stats = self._stats.get_stats() stats["dedup"] = self._dedup.get_stats() return stats # ==================== 兼容旧接口 ==================== @property def message_count(self) -> int: """兼容旧接口:总消息数""" return self._stats.get_stats()["total_messages"] @property def filtered_count(self) -> int: """兼容旧接口:被过滤消息数""" return self._stats.get_stats()["filtered_messages"] @property def processed_count(self) -> int: """兼容旧接口:已处理消息数""" return self._stats.get_stats()["processed_messages"]