Fix OCR self-feed loop: bold md detect, repeat-char garbage, SequenceMatcher reply fragment catch

- _is_markdown_document: add bold **text** detection before len<10 gate
- Garbage filter: 5+ consecutive same char detect (LLLLL/11111)
- OCR filter + Poll filter: SequenceMatcher longest common CJK substring >=12 chars
  vs replied_texts. Catches TTTT八X1r以...工单]、**使用**权 fragments
  that exact/substring/fuzzy match miss due to OCR noise prefix.
This commit is contained in:
youpaishidifu
2026-07-28 17:42:07 +08:00
parent ba139113b1
commit 30d2b497a8
+59 -3
View File
@@ -1315,6 +1315,33 @@ class MessageMonitor:
logger.debug(f"[{platform}] OCR 过滤: md纯标题碎片 line={line!r}")
continue
# —— OCR 幻觉乱码检测:连续重复字符(LLL / 111 / ===) ——
# EasyOCR 对头像/特殊 UI 输出如 ":山LeLyeeuCL...ggsegU凶>债券管理"
# 普通消息不含连续 5+ 相同字符
if re.search(r'([A-Za-z0-9])\1{4,}', line):
logger.debug(f"[{platform}] OCR 过滤: 幻觉乱码(重复) line={line!r}")
continue
# —— 回复内容片段检测:OCR 捕获 bot 自己刚发的回复 ——
# 用 SequenceMatcher 找 OCR 行 CJK 与 replied 文本 CJK 的最长公共子串
if self._replied_texts and len(line) > 15:
import difflib
cjk_line = ''.join(c for c in line if '\u4e00' <= c <= '\u9fff')
if len(cjk_line) >= 12:
is_match = False
for r in self._replied_texts:
r_cjk = ''.join(c for c in r if '\u4e00' <= c <= '\u9fff')
if not r_cjk:
continue
sm = difflib.SequenceMatcher(None, cjk_line, r_cjk)
longest = sm.find_longest_match(0, len(cjk_line), 0, len(r_cjk))
if longest.size >= 12:
is_match = True
break
if is_match:
logger.debug(f"[{platform}] OCR 过滤: 回复内容片段 line={line[:60]!r}")
continue
texts.append(line)
logger.debug(f"[{platform}] OCR -> {len(ocr_lines)} raw, {len(texts)} filtered, "
f"last=\"{texts[-1][:50] if texts else ''}\"")
@@ -1684,6 +1711,9 @@ class MessageMonitor:
# OCR 退化 md 链接缺括号:短文本也检测
if re.search(r'\[.+?\]\(', text):
return True
# **加粗**语法:短文本也检测(bot md 回复标配,用户日常不用)
if re.search(r'\*\*.+?\*\*', text):
return True
if len(text) < 10:
return False
# ##直接跟汉字(OCR 丢失 ##后空格时常见:##文档导航)
@@ -2384,9 +2414,35 @@ class MessageMonitor:
continue
# _replied_texts 子串包含:OCR 提取的 md 表格细胞值(如"依赖插件可选/必选")
# 是 bot 回复表的一部分,不是用户提问。长度>5才检查避免误杀短消息。
if len(ts) > 5 and any(ts in r for r in replied):
logger.debug(f"[{platform}] 回复子串包含跳过({len(ts)}c): {ts[:50]}")
continue
# OCR 常把 ,转 , 等标点差异,做双向子串检查(原始+归一化标点)
if len(ts) > 5:
if any(ts in r for r in replied):
logger.debug(f"[{platform}] 回复子串包含跳过({len(ts)}c): {ts[:50]}")
continue
# 归一化标点后重试(OCR 噪声:,→, 。→. !→! ?→?)
norm_ts = ts.replace(',', ',').replace('.', '。').replace('!', '!').replace('?', '?')
if norm_ts != ts and any(norm_ts in r for r in replied):
logger.debug(f"[{platform}] 回复子串包含跳过(归一化,{len(ts)}c): {ts[:50]}")
continue
# _replied_texts 长公共子串检测:OCR 截取的碎片开头/结尾有噪声,精确/子串匹配失效
# 用 SequenceMatcher 找 CJK 字符的最长公共子串
if len(ts) > 15:
cjk_ts = ''.join(c for c in ts if '\u4e00' <= c <= '\u9fff')
if len(cjk_ts) >= 12:
import difflib
matched_r = False
for r in replied:
r_cjk = ''.join(c for c in r if '\u4e00' <= c <= '\u9fff')
if not r_cjk:
continue
sm = difflib.SequenceMatcher(None, cjk_ts, r_cjk)
longest = sm.find_longest_match(0, len(cjk_ts), 0, len(r_cjk))
if longest.size >= 12:
matched_r = True
break
if matched_r:
logger.debug(f"[{platform}] 回复公共子串跳过: {ts[:50]}")
continue
if ts in seen:
continue
# 模糊匹配:OCR 输出每次微小差异(勖彐碎片、行首数字、尾部截断)