diff --git a/config.py b/config.py index bf63d02..b65581f 100644 --- a/config.py +++ b/config.py @@ -14,6 +14,7 @@ DEFAULT_CONFIG = { "qq_enabled": True, "auto_reply_enabled": False, "auto_reply_min_score": 0.35, + "qq_nicknames": [], # 需要从消息文本开头剥离的QQ昵称列表 } diff --git a/monitor.py b/monitor.py index a61f9b2..3b99c14 100644 --- a/monitor.py +++ b/monitor.py @@ -3155,6 +3155,8 @@ class MessageMonitor: # 永久丢失(bot 回复一次后再也不回复)。剥离后保留用户提问。 msg = self._strip_bot_reply_fragments(msg) if msg: + # ⭐ 剥离QQ用户昵称,防止昵称拼凑进消息文本导致查库关键词被压住 + msg = self._strip_qq_nickname(msg) if self._is_reply_fragment(msg): logger.info(f"[ocr] stage[{i}] [filter4/回复残片] DROP 回复残片跳过: {msg[:60]!r}") else: @@ -3199,6 +3201,8 @@ class MessageMonitor: # ⭐ 同上:尾部消息也要先剥离 bot 旧回复片段,避免整段误判为残片 msg = self._strip_bot_reply_fragments(msg) if msg: + # ⭐ 剥离QQ用户昵称,防止昵称拼凑进消息文本导致查库关键词被压住 + msg = self._strip_qq_nickname(msg) if self._is_reply_fragment(msg): logger.info(f"[ocr] stage[{i}] [filter4/回复残片] DROP 回复残片跳过(尾部): {msg[:60]!r}") else: @@ -4311,6 +4315,64 @@ class MessageMonitor: ts = re.sub(r'\s+', ' ', ts).strip().strip(',').strip() return ts + def _strip_qq_nickname(self, text: str) -> str: + """从消息文本开头剥离QQ用户昵称,防止昵称拼凑进消息文本导致查库关键词被压住。 + + QQ群聊截图中,OCR会把用户昵称和消息文本拼在一起,如: + "草原探险服务器备用号游戏专用在哪下载登陆插件" + 其中"草原探险服务器备用号"和"游戏专用"是用户昵称,"在哪下载登陆插件"是实际消息。 + + 策略: + 1. 检查消息文本是否以已知昵称列表中的某个昵称开头 + 2. 如果是,剥离该昵称(可能包括后续的空格/标点) + 3. 重复步骤1-2,直到没有匹配的昵称 + 4. 如果消息文本以"//"或"=>"开头,这些可能是OCR识别的箭头,一并剥离 + """ + if not text or not text.strip(): + return text + ts = text.strip() + # 剥离箭头前缀(OCR可能识别为"//"或"=>") + if ts.startswith('//') or ts.startswith('=>'): + ts = ts[2:].lstrip() + + # ⭐ 剥离QQ群头衔+用户名格式:【头衔】用户名 或 [头衔] 用户名 + # OCR可能把【识别为[,把】识别为],所以同时处理两种括号 + # 匹配模式:[【\[]任意内容】\]] + 可选空格 + 用户名(中文/英文/数字/下划线) + qq_title_pattern = re.compile(r'^[\[【][^】\]]+[】\]]\s*[\w\u4e00-\u9fff]+[,,]?\s*') + while True: + m = qq_title_pattern.match(ts) + if m: + ts = ts[m.end():].lstrip() + else: + break + + # 加载配置中的昵称列表 + try: + from config import load_config + cfg = load_config() + nicknames = cfg.get('qq_nicknames', []) + except Exception: + nicknames = [] + # 如果没有配置昵称,使用默认的已知昵称(从日志中提取) + if not nicknames: + nicknames = [ + "草原探险服务器备用号", + "游戏专用", + # 可以添加更多已知昵称 + ] + # 逐个尝试剥离昵称 + changed = True + while changed: + changed = False + for nick in nicknames: + if ts.startswith(nick): + ts = ts[len(nick):].lstrip() + changed = True + break + # 清理残留的标点/空格 + ts = re.sub(r'^[\s,,.。::;;!!??]+', '', ts) + return ts.strip() + def _is_reply_fragment(self, text: str) -> bool: """检查文本是否为 bot 回复内容的碎片(OCR 截取的 md 残片/跨行碎片)。