205 lines
6.4 KiB
Python
205 lines
6.4 KiB
Python
"""
|
|
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"]
|