修复OCR拉丁混淆(php↔4p)导致去重失效引发的重复回复:新增中文骨架匹配兜底
This commit is contained in:
+58
@@ -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
|
||||
# 快速长度剪枝
|
||||
|
||||
Reference in New Issue
Block a user