1. stage splitting 时间戳规整:第二段分隔符由 \2(同分隔符) 放宽为 [.;:], 使 15;27.05 这类 EasyOCR 混合分隔读法被正确识别为 15:27:05, 修复 _inherited_ts 不更新导致下方消息继承陈旧时间戳被'尾部较老'误杀。 2. 过滤器层调整顺序:先剥 bot 旧回复片段,再剥 md 尾巴。OCR 把 '用户新消息+bot旧回复'拼行时,md 尾巴剥离会在 bot 回复的 '::|::' 残片处切断、误删用户提问,导致 filter4 整体丢弃。 3. _is_qq_ui_element 规则6:日志措辞修正(跳过->豁免/放行),并新增 含@+疑问词直接放行的纵深防御,避免用户提问被 UI 规则误杀。
This commit is contained in:
+60
-34
@@ -3233,8 +3233,12 @@ class MessageMonitor:
|
||||
# 相邻的真实新消息(过滤1与stage继承双重误判)。
|
||||
# 后顾 ?<![;.0-9A-Za-z%]:匹配起点前不能在URL字符后,防止 0.50;57 中
|
||||
# 的子串 50;57 被单独转换(50:57 会被当合法时间戳)。
|
||||
# ⭐ 2026-08-12: 第二段分隔符放宽为任意 [.;:](原 \2 要求前后分隔符相同,
|
||||
# 但 EasyOCR 常把 15:27:05 读成 15;27.05,';' 与 '.' 不同 → 整串不被识别为
|
||||
# 时间戳 → last_ts/_inherited_ts 不更新 → 其下方消息继承陈旧时间戳被"尾部较老"误杀。
|
||||
# 与 _line_has_fresh_ts (5179) 的放宽逻辑保持一致。
|
||||
rl = re.sub(
|
||||
r'(?<![;.0-9A-Za-z%])(\d{1,2})([.;])(\d{2})(?:\2(\d{2}))?(?![;.0-9A-Za-z%])',
|
||||
r'(?<![;.0-9A-Za-z%])(\d{1,2})([.;])(\d{2})(?:[.;:](\d{2}))?(?![;.0-9A-Za-z%])',
|
||||
lambda m: f'{m.group(1)}:{m.group(3)}:{m.group(4)}' if m.group(4) else f'{m.group(1)}:{m.group(3)}',
|
||||
rl)
|
||||
# ⭐ 规整「逐字空格」时间戳:OCR 把 16:28:40 拆成 1 6 : 2 8,否则 re.split 找不到时间戳 → 漏消息
|
||||
@@ -3277,26 +3281,31 @@ class MessageMonitor:
|
||||
last_ts = part
|
||||
continue
|
||||
# —— 过滤器层(始终运行,绝不静默吞噬;每层放行/打回都记录)——
|
||||
# ⭐ 2026-08-11 先剥 md 尾巴:md 在末尾→切尾巴留用户提问;
|
||||
# md 大头/混中间→None 整段丢弃,防 md 尾巴入库撞关键词
|
||||
md_stripped = self._strip_markdown_tail(buf.strip())
|
||||
if md_stripped is None:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md大头整段丢弃: {buf.strip()[:80]!r}")
|
||||
elif md_stripped != buf.strip():
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] 剥离md尾巴: {buf.strip()[:60]!r} -> {md_stripped!r}")
|
||||
if md_stripped and self._is_markdown_document(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md文档跳过: {md_stripped[:80]!r}")
|
||||
elif md_stripped and self._is_noise_text(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter2/噪声] DROP 噪声文本跳过: {md_stripped[:80]!r}")
|
||||
# ⭐ 2026-08-12 调整顺序:必须先剥离 bot 旧回复片段,再剥 md 尾巴。
|
||||
# 原因:OCR 常把「用户新消息 + bot 旧回复」拼成一行,bot 回复若含
|
||||
# '::|::' 等 UI 残片,_strip_markdown_tail 会在第一个 '|' 处切断,
|
||||
# 误删其后真正的用户提问(如 "@游戏专用 PHP管理员端的那登录密码是多少?"),
|
||||
# 导致用户消息被后续 filter4 整体丢弃。先剥离 bot 回复保留用户提问,
|
||||
# 再走 md/噪声/残片过滤,用户提问才能存活。
|
||||
_msg_bot = self._strip_bot_reply_fragments(buf.strip())
|
||||
if not _msg_bot:
|
||||
logger.debug(f"[ocr] stage[{i}] [filter/bot] bot旧回复整体剥离后为空,跳过: {buf.strip()[:60]!r}")
|
||||
else:
|
||||
msg = re.sub(r'\s*(?:[A-Za-z0-9]~)+\s*$', '', md_stripped or '')
|
||||
if msg:
|
||||
msg = re.sub(r'\s+#[\u4e00-\u9fff][^\s#\n]{0,30}$', '', msg).strip()
|
||||
# ⭐ 剥 md 尾巴:md 在末尾→切尾巴留用户提问;
|
||||
# md 大头/混中间→None 整段丢弃,防 md 尾巴入库撞关键词
|
||||
md_stripped = self._strip_markdown_tail(_msg_bot)
|
||||
if md_stripped is None:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md大头整段丢弃: {_msg_bot[:80]!r}")
|
||||
elif md_stripped != _msg_bot:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] 剥离md尾巴: {_msg_bot[:60]!r} -> {md_stripped!r}")
|
||||
if md_stripped and self._is_markdown_document(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md文档跳过: {md_stripped[:80]!r}")
|
||||
elif md_stripped and self._is_noise_text(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter2/噪声] DROP 噪声文本跳过: {md_stripped[:80]!r}")
|
||||
else:
|
||||
msg = re.sub(r'\s*(?:[A-Za-z0-9]~)+\s*$', '', md_stripped or '')
|
||||
if msg:
|
||||
# ⭐ 先剥离 bot 旧回复片段:OCR 把「用户新消息 + bot 旧回复」
|
||||
# 拼成一行时,filter4 会误判整段为残片丢弃,导致用户新消息
|
||||
# 永久丢失(bot 回复一次后再也不回复)。剥离后保留用户提问。
|
||||
msg = self._strip_bot_reply_fragments(msg)
|
||||
msg = re.sub(r'\s+#[\u4e00-\u9fff][^\s#\n]{0,30}$', '', msg).strip()
|
||||
if msg:
|
||||
# ⭐ 剥离QQ用户昵称,防止昵称拼凑进消息文本导致查库关键词被压住
|
||||
msg = self._strip_qq_nickname(msg)
|
||||
@@ -3339,22 +3348,27 @@ class MessageMonitor:
|
||||
# else: 时间戳新鲜(<5min),直接保留
|
||||
# —— 过滤器层(始终运行,绝不静默吞噬;每层放行/打回都记录)——
|
||||
if buf.strip():
|
||||
md_stripped = self._strip_markdown_tail(buf.strip())
|
||||
if md_stripped is None:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md大头整段丢弃(尾部): {buf.strip()[:80]!r}")
|
||||
elif md_stripped != buf.strip():
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] 剥离md尾巴(尾部): {buf.strip()[:60]!r} -> {md_stripped!r}")
|
||||
if md_stripped and self._is_markdown_document(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md文档跳过(尾部): {md_stripped[:80]!r}")
|
||||
elif md_stripped and self._is_noise_text(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter2/噪声] DROP 噪声文本跳过(尾部): {md_stripped[:80]!r}")
|
||||
# ⭐ 2026-08-12 必须先剥离 bot 旧回复片段(同 for 循环块理由):
|
||||
# OCR 把「用户新消息 + bot 旧回复」拼成一行时,先剥 bot 回复才能
|
||||
# 保住用户提问,否则 _strip_markdown_tail 会在 bot 回复的 '::|::'
|
||||
# 残片处切断、误删用户提问,导致 filter4 整体丢弃。
|
||||
_msg_bot = self._strip_bot_reply_fragments(buf.strip())
|
||||
if not _msg_bot:
|
||||
logger.debug(f"[ocr] stage[{i}] [filter/bot] 尾部bot旧回复整体剥离后为空,跳过: {buf.strip()[:60]!r}")
|
||||
else:
|
||||
msg = re.sub(r'\s*(?:[A-Za-z0-9]~)+\s*$', '', md_stripped or '')
|
||||
if msg:
|
||||
msg = re.sub(r'\s+#[\u4e00-\u9fff][^\s#\n]{0,30}$', '', msg).strip()
|
||||
md_stripped = self._strip_markdown_tail(_msg_bot)
|
||||
if md_stripped is None:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md大头整段丢弃(尾部): {_msg_bot[:80]!r}")
|
||||
elif md_stripped != _msg_bot:
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] 剥离md尾巴(尾部): {_msg_bot[:60]!r} -> {md_stripped!r}")
|
||||
if md_stripped and self._is_markdown_document(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter3/md] DROP md文档跳过(尾部): {md_stripped[:80]!r}")
|
||||
elif md_stripped and self._is_noise_text(md_stripped):
|
||||
logger.info(f"[ocr] stage[{i}] [filter2/噪声] DROP 噪声文本跳过(尾部): {md_stripped[:80]!r}")
|
||||
else:
|
||||
msg = re.sub(r'\s*(?:[A-Za-z0-9]~)+\s*$', '', md_stripped or '')
|
||||
if msg:
|
||||
# ⭐ 同上:尾部消息也要先剥离 bot 旧回复片段,避免整段误判为残片
|
||||
msg = self._strip_bot_reply_fragments(msg)
|
||||
msg = re.sub(r'\s+#[\u4e00-\u9fff][^\s#\n]{0,30}$', '', msg).strip()
|
||||
if msg:
|
||||
# ⭐ 剥离QQ用户昵称,防止昵称拼凑进消息文本导致查库关键词被压住
|
||||
msg = self._strip_qq_nickname(msg)
|
||||
@@ -5549,6 +5563,16 @@ class MessageMonitor:
|
||||
if not line:
|
||||
return False
|
||||
s = line.strip()
|
||||
# ⭐ 2026-08-12 纵深防御:含 @ 且含疑问词的行一律视为用户提问,
|
||||
# 绝不被任何 UI 规则(规则1-6)判为界面元素而丢弃。即使后续规则误匹配,
|
||||
# 此处直接放行,确保用户提问永远不会因 UI 过滤而丢失。
|
||||
if '@' in s:
|
||||
_qq_qwords = ('?', '?', '请问', '是不是', '有没有', '怎么', '如何', '什么',
|
||||
'哪', '谁', '多少', '几', '为什么', '是否', '能否', '可以吗',
|
||||
'好吗', '对吗', '是吗', '对不对', '好不好', '行不行', '可不可以')
|
||||
if any(q in s for q in _qq_qwords):
|
||||
logger.debug(f"[qq-ui] 含@+疑问词,直接放行(非UI元素): {s}")
|
||||
return False
|
||||
# 1) 图片文件引用: !0(img/xxx.png) 或 !0(img/xxx.jpg) 等
|
||||
# QQ 发送图片后,OCR 识别出的文件引用格式
|
||||
if re.match(r'^!0?\s*\(?\s*img/', s, re.IGNORECASE):
|
||||
@@ -5596,7 +5620,9 @@ class MessageMonitor:
|
||||
'哪', '谁', '多少', '几', '为什么', '是否', '能否', '可以吗',
|
||||
'好吗', '对吗', '是吗', '对不对', '好不好', '行不行', '可不可以')
|
||||
if any(q in s for q in _question_words):
|
||||
logger.debug(f"[qq-ui] 规则6跳过(含疑问词): {s}")
|
||||
# ⭐ 2026-08-12 日志措辞修正:此处 return False 表示「不是UI元素、
|
||||
# 放行(当作用户消息处理)」,并非丢弃消息。"跳过"易误解为消息被跳过。
|
||||
logger.debug(f"[qq-ui] 规则6豁免(含疑问词,放行): {s}")
|
||||
return False
|
||||
# 匹配模式:可选中文前缀(群名片) + @ + 任意字符(可能乱码) + 空格 + 用户名(可多词)
|
||||
# 示例:"有人@拜 youpaishidifu"、"用户@张三 测试"、"@李四 123"
|
||||
|
||||
Reference in New Issue
Block a user