Task #1476: QQ群聊非消息文本过滤 - 图片引用剥离/表情包碎片检测/UI元素跳过

行切割阶段增加3层过滤:
1. 行内图片引用清理: !0(img/xxx.png) 剥离后保留用户消息
2. 表情包文字碎片: CJK<=6且分隔符>=2判定为贴纸OCR残留
3. QQ UI元素跳过: 【头衔】Username / ## @标签 / )username / 纯图片后缀

新增_is_qq_ui_element()静态方法检测5种QQ特有非消息模式。
This commit is contained in:
youpaishidifu
2026-08-09 10:58:54 +08:00
parent cf59ff4c4d
commit 1b899b9219
+51
View File
@@ -3028,8 +3028,21 @@ class MessageMonitor:
ocr_lines = []
for l in raw_text.split('\n'):
l = re.sub(r'\s*(?:[A-Za-z0-9]~)+\s*$', '', l).strip()
# ⭐ Task #1476: 行内图片引用清理。
# QQ 发送图片后 OCR 识别出 !0(img/xxx.png) 格式,混在用户消息文本中。
# 先剥离这些引用,保留用户真实消息部分。
l = re.sub(r'!0?\s*\(?\s*img/[\w./\\-]+\)?', '', l, flags=re.IGNORECASE).strip()
if not l:
continue
# ⭐ Task #1476: 表情包/贴纸文字碎片过滤。
# QQ 表情包图片上常有文字(如"点击"、"按钮"),OCR 识别后产出
# 短文本+大量分隔符混排(如 "点击, ~按钮; ,"),不是真实用户消息。
# 判定:中文字符≤6 且 分隔符(逗号/分号/波浪号/斜杠等)≥2 → 碎片。
_delim_count = len(re.findall(r'[,;~!@#%^&*,。;~、/\\|]', l))
_cjk_in_l = sum(1 for c in l if '\u4e00' <= c <= '\u9fff')
if _cjk_in_l <= 6 and _delim_count >= 2:
logger.debug(f"[{platform}] OCR: 跳过表情包碎片(分隔符密度高): {l[:60]!r}")
continue
# ⭐ Task #1475: 单行 Markdown 跳过。
# 正常用户从不手动打 markdown(# 标题、|表格|、**加粗** 等)。
# 撞上 md 段落标记 → 跳过该行;没有 md 标记 → 正常收集。
@@ -3037,6 +3050,12 @@ class MessageMonitor:
if self._is_markdown_line(l):
logger.debug(f"[{platform}] OCR: 跳过Markdown行: {l[:60]!r}")
continue
# ⭐ Task #1476: QQ 非消息文本跳过。
# QQ 群聊截图中,OCR 会识别出图片引用、用户头衔/徽章、用户标签等
# 非消息内容,混入消息流后触发残片判定。整行跳过这些 UI 元素。
if self._is_qq_ui_element(l):
logger.debug(f"[{platform}] OCR: 跳过QQ UI元素: {l[:60]!r}")
continue
ocr_lines.append(l)
# —— 连续行合并(修复 EasyOCR 逗号断行) ——
@@ -4840,6 +4859,38 @@ class MessageMonitor:
return True
return False
@staticmethod
def _is_qq_ui_element(line: str) -> bool:
"""⭐ Task #1476: 检测单行是否为 QQ 界面 UI 元素(非用户消息)。
QQ 群聊截图中,OCR 会把以下非消息文本识别出来混入消息流:
- 图片引用: !0(img/skin_3.png) 等
- 用户头衔/徽章: 【专业皮肤定制】C418minecraft
- 用户标签: ## @C418minecraft, )youpaishidifu关于我们
这些整行跳过,避免混入消息流触发残片判定。
"""
if not line:
return False
s = line.strip()
# 1) 图片文件引用: !0(img/xxx.png) 或 !0(img/xxx.jpg) 等
# QQ 发送图片后,OCR 识别出的文件引用格式
if re.match(r'^!0?\s*\(?\s*img/', s, re.IGNORECASE):
return True
# 2) 用户头衔/徽章: 【...】Username
# QQ 群成员的头衔标签,如【专业皮肤定制】C418minecraft
if re.match(r'^【[^】]{1,30}】\s*[\w\u4e00-\u9fff]+', s):
return True
# 3) 用户标签: ## @Username — QQ 的 @ 提及格式
if re.match(r'^##\s*@[\w\u4e00-\u9fff]+', s):
return True
# 4) 闭合括号开头的用户标签: )username关于我们 — OCR 识别出的 UI 残留
if re.match(r'^\)[\w\u4e00-\u9fff]{2,}', s):
return True
# 5) 纯图片引用后缀: .png .jpg .gif 等,无中文 → 图片文件名碎片
if re.match(r'^[\w/\\._-]+\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$', s, re.IGNORECASE):
return True
return False
@staticmethod
def _is_md_continuation(line: str) -> bool:
"""⭐ Task #1475: 判断某行是否为 markdown 块被 OCR 拆出来的"内容延续行"。