修复OCR拉丁混淆(php↔4p)导致去重失效引发的重复回复:新增中文骨架匹配兜底

This commit is contained in:
youpaishidifu
2026-08-12 14:42:01 +08:00
parent c0025a6f12
commit b74fd5deb0
+58
View File
@@ -194,6 +194,24 @@ _UI_SUBSTR_BLOCKLIST = frozenset({
})
# ⭐ 2026-08-12: OCR 鲁棒去重——中文骨架
# OCR 常把拉丁字符混淆(php↔4p、o↔0、l↔1、I↔1),导致:
# 1) 字符级 SequenceMatcher 相似度跌破 90% 门槛(如 php→4p 整句 ~83%),
# 5 分钟重复识别去重失效;
# 2) is_user_message_replied 的精确 MD5 哈希因单字符差异而不同,DB 查重放行。
# 中文是语义主体,去掉所有非中文字符后比较「骨架」,可吸收这类拉丁混淆,
# 把同一句话的不同 OCR 版本正确识别为重复,杜绝「已回复消息被重复回复」。
_CN_RE = re.compile(r'[\u4e00-\u9fff]')
def _cn_skeleton(text: str) -> str:
"""提取中文骨架:只保留中文字符,忽略拉丁/数字/标点/OCR 易混淆差异。
返回空串表示无中文(调用方应跳过骨架匹配,避免误拦纯外文消息)。
"""
if not text:
return ""
return ''.join(_CN_RE.findall(text))
class MessageHistory:
"""管理消息历史数据库,用于判断无时间戳消息是否应放行"""
@@ -281,6 +299,29 @@ class MessageHistory:
)
count = cursor.fetchone()[0]
if count == 0:
# ⭐ 2026-08-12: 精确匹配未命中 → 中文骨架匹配(吸收 OCR 拉丁混淆 php↔4p)
# 同一句话的不同 OCR 版本(php / 4p)中文骨架相同,应判定为已回复,
# 否则会被当作「新消息」重复查库回复(自言自语)。
sk = _cn_skeleton(content)
if len(sk) >= 6:
for bot_content in self.get_recent_bot_replies(platform, limit=50):
if _cn_skeleton(bot_content) == sk:
# 骨架命中:若用户消息时间晚于该 bot 回复时间,视为新消息放行
if user_msg_time > 0:
try:
c2 = conn.execute(
"SELECT created_at FROM message_history WHERE platform=? AND sender_type='bot' AND content=? ORDER BY id DESC LIMIT 1",
(platform, bot_content)
)
row = c2.fetchone()
if row and row[0]:
from datetime import datetime as _dt
bot_time = _dt.strptime(row[0], "%Y-%m-%d %H:%M:%S").timestamp()
if user_msg_time > bot_time:
continue # 用户消息更新,视为新消息
except Exception:
pass
return True
return False # 没有相同内容的bot回复,未被回复过
# 如果有相同内容的bot回复,但用户消息时间戳晚于最后bot回复时间,则视为新消息
@@ -7096,6 +7137,15 @@ class MessageMonitor:
seen.add(cleaned_item)
self._last_new_msg_time[platform] = time.time()
continue # 跳过本条消息,处理下一条
# ⭐ 2026-08-12: 中文骨架匹配(吸收 OCR 拉丁混淆 php↔4p)
# 字符级相似度跌破 90% 时(php→4p 整句 ~83%),用中文骨架兜底拦截
sk_cur = _cn_skeleton(cleaned_item)
sk_rep = _cn_skeleton(last_reply)
if len(sk_cur) >= 6 and sk_rep and sk_cur == sk_rep:
logger.info(f"[{platform}] OCR噪声检测(骨架匹配): 与最近bot回复中文骨架相同,跳过: {cleaned_item[:60]}")
seen.add(cleaned_item)
self._last_new_msg_time[platform] = time.time()
continue
# ⭐ 5分钟内OCR重复识别去重:同一消息被OCR识别两次(乱码+正确文本),相似度>=90%则跳过
now = time.time()
# 清理过期条目
@@ -7108,6 +7158,14 @@ class MessageMonitor:
for recent_text, recent_ts in self._recent_processed_messages:
if now - recent_ts > 300:
continue
# ⭐ 2026-08-12: 中文骨架匹配(吸收 OCR 拉丁混淆 php↔4p)
# 同一消息被 OCR 识别两次(php / 4p),字符级相似度 <90% 时兜底拦截
sk_cur = _cn_skeleton(cleaned_item)
sk_old = _cn_skeleton(recent_text)
if len(sk_cur) >= 6 and sk_cur == sk_old:
logger.info(f"[{platform}] OCR重复识别去重(骨架匹配): 中文骨架相同,跳过: {cleaned_item[:60]}")
is_duplicate = True
break
if len(recent_text) < 4 or len(cleaned_item) < 4:
continue
# 快速长度剪枝