From d1ed67358ef99eb26aa2077556c2587d03245182 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Fri, 11 Sep 2026 19:57:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DOCR=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=A4=9A=E4=B8=AA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Task #1696: 管道符碎片过滤器修复 - 添加疑问词例外逻辑,避免误杀包含表格内容的真实用户消息 - 例如:'是否支持|,长期活跃奖是如何计算的'不再被丢弃 2. Task #1700: 用户消息被误判为bot回复片段的问题 - 在'反向包含剥离bot回复片段'逻辑中添加问句豁免机制 - 用户提问即使含与旧回复共享的片段,也不应被剥离 - 例如:'领地系统有哪几种操作模式'不再被错误识别为bot回复 3. Task #1697: OCR消息入库时机修复 - 在评分管道之前立即记录OCR消息到暂存表 - 确保消息被首次OCR抓取时入库,避免历史消息被重复处理 - 修改第二关逻辑,同时检查ocr_staging表和user_messages表 --- monitor.py | 111 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 32 deletions(-) diff --git a/monitor.py b/monitor.py index e98f319..a221f69 100644 --- a/monitor.py +++ b/monitor.py @@ -4663,9 +4663,15 @@ class MessageMonitor: # —— 绿色气泡 OCR 乱码:| 管道符混中文 —— # bot 的绿色气泡/蓝色气泡文字经过 OCR 后常把 :; 识别为 |, # 正常中文聊天从不混用 | 与中文。如 "GU |进行配置" "SMTP | 设置" + # ⭐ Task #1696: 含疑问词的消息不丢弃(如"是否支持|,长期活跃奖是如何计算的") if '|' in line and re.search(r'[\u4e00-\u9fff]', line) and len(line) > 5: - logger.debug(f"[{platform}] OCR 过滤: 管道符碎片(绿色气泡) line={line!r}") - continue + # 检查是否包含疑问词或问号(用户真实查询) + _has_question = re.search(r'[??吗呢多少怎么如何哪什么几]', line) + if _has_question: + logger.debug(f"[{platform}] OCR 过滤: 管道符碎片但含疑问词,放行: line={line!r}") + else: + logger.debug(f"[{platform}] OCR 过滤: 管道符碎片(绿色气泡) line={line!r}") + continue # —— 游戏指令列表碎片:重复 /cmd 模式 —— # OCR 截到命令帮助文本如 "/sdfl主指令 /sdfl reload /sdfl update" @@ -10444,9 +10450,15 @@ class MessageMonitor: logger.debug(f"[{platform}] Poll过滤: 游戏UI乱码(CJK+大写英文) ts={ts!r}") continue # === 绿色气泡 OCR 乱码:| 管道符混中文(正常聊天从不混用)=== + # ⭐ Task #1696: 含疑问词的消息不丢弃(如"是否支持|,长期活跃奖是如何计算的") if '|' in ts and re.search(r'[\u4e00-\u9fff]', ts) and len(ts) > 5: - logger.debug(f"[{platform}] Poll过滤: 管道符碎片(绿色气泡) ts={ts!r}") - continue + # 检查是否包含疑问词或问号(用户真实查询) + _has_question = re.search(r'[??吗呢多少怎么如何哪什么几]', ts) + if _has_question: + logger.debug(f"[{platform}] Poll过滤: 管道符碎片但含疑问词,放行: ts={ts!r}") + else: + logger.debug(f"[{platform}] Poll过滤: 管道符碎片(绿色气泡) ts={ts!r}") + continue # === JSON/YAML 碎片兜底:OCR 截屏混入配置面板 === if re.match(r'^["\']?[a-zA-Z_][\w]*["\']?\s*:\s*["\d\[\{tfn]', ts): logger.debug(f"[{platform}] Poll过滤: JSON行 ts={ts!r}") @@ -10498,7 +10510,11 @@ class MessageMonitor: # r="从未注册过草原探险服务器的玩家、账号。\n使用全新的邮箱进行验证。" # → "从未注册过草原" in r, "使用全新的邮箱" in r → 剥离这两段 _has_comma = ',' in ts or (',' in ts and re.search(r'[\u4e00-\u9fff],[\u4e00-\u9fff]', ts)) - if not stripped_bot and _has_comma: + # ⭐ Task #1700: 问句豁免——用户提问即使含与旧回复共享的片段,也不应被剥离 + is_question_mark = bool(re.search(r'[??]', ts)) + is_question_word = any(qw in ts for qw in ('吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几')) + is_question = is_question_mark or is_question_word + if not stripped_bot and _has_comma and not is_question: parts = re.split(r'[,,]', ts) parts = [p.strip() for p in parts if p.strip()] if len(parts) >= 2: @@ -10905,6 +10921,15 @@ class MessageMonitor: logger.info(f"[{platform}] 候选病句/噪声拦截: {cleaned_item[:50]!r}") self._last_new_msg_time[platform] = time.time() continue + # ⭐ Task #1697: 立即记录OCR消息到暂存表(评分管道之前) + # 原因:record_ocr_message()在评分管道之后才调用,导致消息在第一关/第二关失败时不入库 + # 后果:历史消息每次OCR抓取都被当作"新消息"处理 + # 修复:在评分管道之前立即记录,确保消息被首次OCR抓取时入库 + if self._msg_history: + try: + self._msg_history.record_ocr_message(platform, cleaned_item) + except Exception as e: + logger.debug(f"[{platform}] OCR暂存记录失败(评分前): {e}") # ⭐ 检查是否与最近bot回复内容相同(OCR噪声干扰,把bot回复当作用户消息) # ⭐ Task #1574: 降低阈值 0.9→0.65,骨架长度 6→4,防止自言自语漏过 # ⭐ Task #1574 v2: 拆行比对——bot回复是多行(含表格),OCR只读首行 @@ -11021,28 +11046,58 @@ class MessageMonitor: # 旧消息通过 get_latest_record_timestamp() 搭上新消息的新鲜DB记录, # 导致 time_diff≤300s → 通关 → 分数从15→20/18→23,盖过真正的新消息。 # 修复:改为查询「本条消息自身的 DB 记录」时间戳,而非全局最新记录。 + # ⭐ Task #1697: 同时检查 ocr_staging 表 + # 原因:record_ocr_message()在评分管道之前调用,确保消息被首次OCR抓取时入库 + # 修复:在检查user_messages表之前,先检查ocr_staging表 if self._msg_history: - # 查询本条消息自身的入库时间(created_at) - my_record_ts = self._msg_history.get_message_record_timestamp(platform, cleaned_item) - - if my_record_ts is not None: - # 有本条消息的数据库记录:计算本地时间与记录时间戳的差值 - time_diff_seconds = (datetime.now() - my_record_ts).total_seconds() - logger.debug(f"[{platform}] 第二关:有本条DB记录,时间差={time_diff_seconds:.0f}秒: {item[:50]}") - - if time_diff_seconds <= 300: - # 本地时间 - 记录时间戳 ≤ 300秒:通关 + # 先检查ocr_staging表(评分管道之前已记录) + has_ocr_record = self._msg_history.has_ocr_message_record(platform, cleaned_item) + + if has_ocr_record: + # ocr_staging表中有记录:查询本条消息自身的入库时间(created_at) + my_record_ts = self._msg_history.get_message_record_timestamp(platform, cleaned_item) + + if my_record_ts is not None: + # 有本条消息的数据库记录:计算本地时间与记录时间戳的差值 + time_diff_seconds = (datetime.now() - my_record_ts).total_seconds() + logger.debug(f"[{platform}] 第二关:有本条DB记录(ocr_staging),时间差={time_diff_seconds:.0f}秒: {item[:50]}") + + if time_diff_seconds <= 300: + # 本地时间 - 记录时间戳 ≤ 300秒:通关 + old_score = score + score = max(score + 5.0, 5.0) + logger.info(f"[{platform}] 第二关通过:DB记录时间差{time_diff_seconds:.0f}秒≤300秒,放行: {old_score:.1f}→{score:.1f}: {item[:50]}") + else: + # 本地时间 - 记录时间戳 > 300秒:返团拦截 + score = -100.0 + logger.info(f"[{platform}] 第二关拦截:DB记录时间差{time_diff_seconds:.0f}秒>300秒,丢弃: {item[:50]}") + else: + # ocr_staging表有记录但user_messages表无记录:放行(首次抓取) old_score = score score = max(score + 5.0, 5.0) - logger.info(f"[{platform}] 第二关通过:DB记录时间差{time_diff_seconds:.0f}秒≤300秒,放行: {old_score:.1f}→{score:.1f}: {item[:50]}") - else: - # 本地时间 - 记录时间戳 > 300秒:返团拦截 - score = -100.0 - logger.info(f"[{platform}] 第二关拦截:DB记录时间差{time_diff_seconds:.0f}秒>300秒,丢弃: {item[:50]}") + logger.info(f"[{platform}] 第二关通过:ocr_staging有记录但user_messages无记录,放行: {old_score:.1f}→{score:.1f}: {item[:50]}") else: - # 无本条消息的数据库记录:返团拦截 - score = -100.0 - logger.info(f"[{platform}] 第二关拦截:无本条消息DB记录,丢弃: {item[:50]}") + # ocr_staging表无记录:检查user_messages表 + my_record_ts = self._msg_history.get_message_record_timestamp(platform, cleaned_item) + + if my_record_ts is not None: + # 有本条消息的数据库记录:计算本地时间与记录时间戳的差值 + time_diff_seconds = (datetime.now() - my_record_ts).total_seconds() + logger.debug(f"[{platform}] 第二关:有本条DB记录(user_messages),时间差={time_diff_seconds:.0f}秒: {item[:50]}") + + if time_diff_seconds <= 300: + # 本地时间 - 记录时间戳 ≤ 300秒:通关 + old_score = score + score = max(score + 5.0, 5.0) + logger.info(f"[{platform}] 第二关通过:DB记录时间差{time_diff_seconds:.0f}秒≤300秒,放行: {old_score:.1f}→{score:.1f}: {item[:50]}") + else: + # 本地时间 - 记录时间戳 > 300秒:返团拦截 + score = -100.0 + logger.info(f"[{platform}] 第二关拦截:DB记录时间差{time_diff_seconds:.0f}秒>300秒,丢弃: {item[:50]}") + else: + # 无本条消息的数据库记录:返团拦截 + score = -100.0 + logger.info(f"[{platform}] 第二关拦截:无本条消息DB记录,丢弃: {item[:50]}") else: # msg_history不可用:强制丢弃 score = -100.0 @@ -11146,15 +11201,7 @@ class MessageMonitor: self._last_msg[platform] = _cleaned engine_tag = self._last_chosen_engine.get(platform, "") logger.info(f"[{platform}] 新消息[{engine_tag}]: {_raw[:60]}") - # ⭐ Task #1572: 记录OCR消息到暂存表(用于后续查重) - if self._msg_history: - # ⭐ 数据库查重:重复率≥50%就跳过该条消息(continue继续处理下一条) - # ⚠️ 注意:这里不再查重,因为第10106行已经查过了(且用了exclude_self=True排除自身) - # 如果在这里再查一次,刚入库的消息会被自身匹配上,导致误跳过 - try: - self._msg_history.record_ocr_message(platform, _cleaned) - except Exception as e: - logger.debug(f"[{platform}] OCR暂存记录失败: {e}") + # ⭐ Task #1697: OCR消息已在评分管道之前记录到ocr_staging表,此处不再重复记录 # ⚠️ 急停检查:回复前最后检查,防止急停后仍然发送 if self._stop_event.is_set() or not self._running: return