Files
WechatHookBot/utils/hookbot.py
2025-12-03 15:48:44 +08:00

199 lines
6.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
HookBot - 机器人核心类
处理消息路由和事件分发
"""
import tomllib
from typing import Dict, Any
from loguru import logger
from WechatHook import WechatHookClient, MESSAGE_TYPE_MAP, normalize_message
from utils.event_manager import EventManager
class HookBot:
"""
HookBot 核心类
负责消息处理、路由和事件分发
"""
def __init__(self, client: WechatHookClient):
"""
初始化 HookBot
Args:
client: WechatHookClient 实例
"""
self.client = client
self.wxid = None
self.nickname = None
# 读取配置
with open("main_config.toml", "rb") as f:
main_config = tomllib.load(f)
bot_config = main_config.get("Bot", {})
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.ignore_mode = bot_config.get("ignore-mode", "None")
self.whitelist = bot_config.get("whitelist", [])
self.blacklist = bot_config.get("blacklist", [])
# 性能配置
perf_config = main_config.get("Performance", {})
self.log_sampling_rate = perf_config.get("log_sampling_rate", 1.0)
# 消息计数和统计
self.message_count = 0
self.filtered_count = 0
self.processed_count = 0
logger.info("HookBot 初始化完成")
def update_profile(self, wxid: str, nickname: str):
"""
更新机器人信息
Args:
wxid: 机器人 wxid
nickname: 机器人昵称
"""
self.wxid = wxid
self.nickname = nickname
logger.info(f"机器人信息: wxid={wxid}, nickname={nickname}")
async def process_message(self, msg_type: int, data: dict):
"""
处理接收到的消息
Args:
msg_type: 消息类型
data: 消息数据
"""
# 过滤 API 响应消息
if msg_type in [11174, 11230]:
return
# 消息计数
self.message_count += 1
# 日志采样 - 只记录部分消息以减少日志量
should_log = self._should_log_message(msg_type)
if should_log:
logger.debug(f"处理消息: type={msg_type}")
# 重要事件始终记录
if msg_type in [11098, 11099, 11058]: # 群成员变动、系统消息
logger.info(f"重要事件: type={msg_type}")
# 获取事件类型
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}, wx_type: {data.get('wx_type')}, 内容预览: {content_preview}")
return
# 格式转换
try:
message = normalize_message(msg_type, data)
except Exception as e:
logger.error(f"格式转换失败: {e}")
return
# 过滤消息
if not self._check_filter(message):
self.filtered_count += 1
if should_log:
logger.debug(f"消息被过滤: {message.get('FromWxid')}")
return
self.processed_count += 1
# 采样记录处理的消息
if should_log:
content = message.get('Content', '')
if len(content) > 50:
content = content[:50] + "..."
logger.info(f"处理消息: type={event_type}, from={message.get('FromWxid')}, content={content}")
# 触发事件
try:
await EventManager.emit(event_type, self.client, message)
except Exception as e:
logger.error(f"事件处理失败: {e}")
def _should_log_message(self, msg_type: int) -> bool:
"""判断是否应该记录此消息的日志"""
# 重要消息类型始终记录
important_types = {
11058, 11098, 11099, 11025, # 系统消息、群成员变动、登录信息
11051, 11047, 11052, 11055 # 视频、图片、表情、文件消息
}
if msg_type in important_types:
return True
# 其他消息按采样率记录
import random
return random.random() < self.log_sampling_rate
def _check_filter(self, message: Dict[str, Any]) -> bool:
"""
检查消息是否通过过滤
Args:
message: 消息字典
Returns:
是否通过过滤
"""
from_wxid = message.get("FromWxid", "")
sender_wxid = message.get("SenderWxid", "")
msg_type = message.get("MsgType", 0)
# 系统消息type=11058不过滤因为包含重要的群聊事件信息
if msg_type == 11058:
return True
# 过滤机器人自己发送的消息,避免无限循环
if self.wxid and (from_wxid == self.wxid or sender_wxid == self.wxid):
return False
# None 模式:处理所有消息
if self.ignore_mode == "None":
return True
# Whitelist 模式:仅处理白名单
if self.ignore_mode == "Whitelist":
return from_wxid in self.whitelist or sender_wxid in self.whitelist
# Blacklist 模式:屏蔽黑名单
if self.ignore_mode == "Blacklist":
return from_wxid not in self.blacklist and sender_wxid not in self.blacklist
return True
def get_stats(self) -> dict:
"""获取消息处理统计信息"""
return {
"total_messages": self.message_count,
"filtered_messages": self.filtered_count,
"processed_messages": self.processed_count,
"filter_rate": self.filtered_count / max(self.message_count, 1),
"process_rate": self.processed_count / max(self.message_count, 1)
}