修复微信自身消息过滤+病句加强+QQ共同好友黑名单

1. 微信颜色检测重写(_has_user_colors):
   - 绿色=自身消息(右对齐),白色=对方消息(左对齐)
   - 自身消息是最顶层门禁:右绿+左无白→跳过,左有白→继续OCR
   - 修复:之前绿色被当作bot色,导致自身消息未过滤触发查库

2. 病句过滤加强:
   - 短句阈值从≤5字提升到≤8字,必须含疑问词才放行
   - 新增疑问词:是不是/会不会/能不能/怎么办/好不好/行不行
   - 新增病句4:OCR拼接系统提示碎片(含#本/请自行/源码修改)
   - 新增病句5:修字碎片(自行修?帮我修)

3. QQ群UI黑名单:
   - _UI_SUBSTR_BLOCKLIST增加'共同好友'和'点击添加好友'
   - 防止QQ群'你和xxx有x个共同好友'触发查库
This commit is contained in:
youpaishidifu
2026-07-29 23:29:38 +08:00
parent a181300df5
commit dfaa62e2c0
+69 -31
View File
@@ -124,6 +124,8 @@ _UI_EXACT_BLOCKLIST = frozenset({
_UI_SUBSTR_BLOCKLIST = frozenset({
'启用监控', '停止监控', '数据看板', '应用设置',
'债券总和', '注册用户总数', 'php管理员', '消息漫游',
# QQ 群"共同好友"推荐(非聊天内容,OCR误读触发查库)
'共同好友', '点击添加好友',
})
@@ -1256,7 +1258,7 @@ class MessageMonitor:
# ⭐ 颜色像素检测:如果截图中只有 bot 蓝色气泡,无白色用户消息,跳过
if not self._has_user_colors(im, "qq"):
self._ocr_unchanged["qq"] = True
logger.debug(f"[qq] 像素检测: 无用户气泡, 跳过OCR (截图: {os.path.basename(_dbg_path)})")
logger.debug(f"[qq] 像素检测: 无对方消息, 跳过OCR (截图: {os.path.basename(_dbg_path)})")
return []
# ⭐ 截图 hash:像素级指纹——内容没变就直接跳过整轮(easyocr 同一截图每次输出不同文本)
# dHash + 5秒强制刷新:即使hash相同,每5秒也强制做一次OCR,防止永久卡死
@@ -1356,10 +1358,10 @@ class MessageMonitor:
except Exception as e:
logger.warning(f"[wechat] 调试截图保存失败: {e} (路径: {os.path.join(script_dir, 'ocr_debug_wx_*.png')})")
# ⭐ 颜色像素检测:如果截图中只有 bot 绿色气泡,无白色用户消息,跳过
# ⭐ 颜色像素检测:如果截图中只有自身绿色气泡,无对方白色消息,跳过
if not self._has_user_colors(im, "wechat"):
self._ocr_unchanged["wechat"] = True
logger.debug(f"[wechat] 像素检测: 无用户气泡, 跳过OCR")
logger.debug(f"[wechat] 像素检测: 无对方消息(自身消息已过滤), 跳过OCR")
return []
# ⭐ 截图 hash:像素级指纹——内容没变就直接跳过整轮
@@ -1817,13 +1819,15 @@ class MessageMonitor:
# —— OCR 短句/病句检测:OCR 把消息"嚼碎"生成碎片 ——
# 通用疑问词(有这些词说明有查询意向,放行)
_q_words = {"什么", "怎么", "如何", "为什么", "吗", "呢", "吧", "能", "可以", "有没有", "哪些", "哪", "哪几个"}
_q_words = {"什么", "怎么", "如何", "为什么", "吗", "呢", "吧", "能", "可以", "有没有", "哪些", "哪", "哪几个",
"是不是", "会不会", "能不能", "怎么办", "好不好", "行不行"}
# "自行修?" → 3字无查询意向碎片;"将其修正后重三" → 病句
if len(line) <= 5:
# ≤5字短句:必须包含疑问词才放行,否则是OCR碎片
if len(line) <= 8:
# ≤8字短句:必须包含疑问词才放行,否则是OCR碎片
# 有问号但无疑问词 → 病句(如"自行修?"无查询意向)
has_q = any(w in line for w in _q_words)
if not has_q:
logger.debug(f"[{platform}] OCR 过滤: 短句碎片(≤5字) line={line!r}")
logger.debug(f"[{platform}] OCR 过滤: 短句碎片(≤8字无问词) line={line!r}")
continue
if 5 < len(line) < 50:
# 病句1:句子以动词/助词结尾但缺少宾语且无标点
@@ -1838,6 +1842,18 @@ class MessageMonitor:
if re.match(r'^[其将把被]', line) and not re.search(r'[。!?,,;;::?]$', line):
logger.debug(f"[{platform}] OCR 过滤: 病句(缺主语) line={line!r}")
continue
# 病句4:OCR 把用户消息和系统提示拼接的复合碎片
# 如"自行修?# 本插件" "请自行下载源码修改...使用类json"
if '#' in line or '源码' in line and '修改' in line:
# 含文档标记或系统提示特征
if re.search(r'#\s*本|请自行|源码.*修改|修改.*源码', line):
logger.debug(f"[{platform}] OCR 过滤: 病句(系统提示拼接) line={line!r}")
continue
# 病句5:句子含"修"但不是完整查询(如"自行修?" "帮我修" "能不能修")
# 除非有明确疑问词,否则"修"碎片不应触发查库
if re.search(r'修[??。]?\s*$', line) and not any(w in line for w in _q_words):
logger.debug(f"[{platform}] OCR 过滤: 病句(修字碎片) line={line!r}")
continue
texts.append(line)
logger.debug(f"[{platform}] OCR -> {len(ocr_lines)} raw, {len(texts)} filtered, "
@@ -2160,18 +2176,13 @@ class MessageMonitor:
@staticmethod
def _has_user_colors(im, platform: str) -> bool:
"""区域颜色检测:左/右半区独立分析,判断截图中有无用户消息。
QQ: bot 蓝色气泡右对齐,用户白色气泡左对齐
WeChat: bot 绿色气泡右对齐,用户白色气泡左对齐
策略:
- 跳过顶部 20%(聊天头部名称/标题干扰)
- 左右半区分别采样(~15×15 网格/半区)
- 右半区有 bot 色 + 左半区几乎无"其它"像素 → 仅 bot 回复 → 跳过
- 左半区有大量"其它"像素 → 用户内容(头像/文字/时间戳)→ 继续
返回 True=有用户内容/拿不准,False=仅 bot 回复
"""区域颜色检测:判断截图中是否有**对方发来的消息**,决定是否需要OCR。
⭐ 核心原则:自身消息 = 最顶层门禁,不过则全部取消。
- QQ: 用户白色气泡左对齐,bot/对方蓝色气泡右对齐
- WeChat: 对方白色气泡左对齐,自己绿色气泡右对齐
返回 True=有对方消息需要OCR,False=仅自身消息/无消息 → 跳过
"""
w, h = im.size
if w < 10 or h < 10:
@@ -2191,11 +2202,14 @@ class MessageMonitor:
step_y = max(2, ch // 15)
if platform == "qq":
# QQ: 蓝色 = bot/对方气泡(右对齐)
def is_bot(r, g, b):
return 110 <= r <= 190 and 170 <= g <= 235 and 220 <= b <= 255
else:
def is_bot(r, g, b):
# WeChat: 绿色 = 自己发的消息(右对齐)— 不是 bot 色
def is_self(r, g, b):
return 135 <= r <= 180 and 205 <= g <= 250 and 75 <= b <= 125
is_bot = is_self
def analyze(img):
p = img.load()
@@ -2219,27 +2233,51 @@ class MessageMonitor:
right_bot, right_other, right_white, right_total = analyze(right_half)
rlogger = logging.getLogger("msgbot.monitor")
rlogger.debug(f"[{platform}] 像素: 左其它={left_other}/{left_total} bot={left_bot} 白={left_white} | "
f"右其它={right_other}/{right_total} bot={right_bot} 白={right_white}")
rlogger.debug(f"[{platform}] 像素: 左其它={left_other}/{left_total} "
f"{'自身' if platform=='wechat' else 'bot'}={left_bot} 白={left_white} | "
f"右其它={right_other}/{right_total} "
f"{'自身' if platform=='wechat' else 'bot'}={right_bot} 白={right_white}")
# 右半区有 bot 气泡 + 左半区几乎无"其它" → 仅 bot 回复
# "其它"阈值:QQ 5 / WeChat 5(bot 气泡内白字不会产生其它像素)
# ====== 微信专用逻辑 ======
# 颜色语义:
# 白色(>225,>225,>225) = 对方消息(左对齐)
# 绿色(135-180,205-250,75-125) = 自己消息(右对齐)
# 其它 = 背景/UI元素
if platform == "wechat":
# ⭐ 自身消息门禁:右大量绿色(自身) + 左无白色(无对方) → 仅自身,跳过
if right_bot >= 3 and left_white < 3:
rlogger.debug(f"[wechat] 像素检测: 仅自身消息(右绿={right_bot},左白={left_white}), 跳过")
return False
# 左半区有白色气泡 → 有对方消息,必须OCR
if left_white >= 3:
rlogger.debug(f"[wechat] 像素检测: 有对方消息(左白={left_white}), 继续")
return True
# 左半区无白色气泡 → 无对方消息,跳过
# (包括:仅自身消息、空聊天、非聊天界面)
rlogger.debug(f"[wechat] 像素检测: 无对方白色气泡(左白={left_white}), 跳过")
return False
# ====== QQ/TIM 专用逻辑 ======
# 颜色语义:
# 白色(>225,>225,>225) = 用户消息(左对齐)
# 蓝色(110-190,170-235,220-255) = bot/对方消息(右对齐)
# 其它 = 背景/UI元素
# 右半区有 bot 蓝色 + 左半区几乎无"其它" → 仅 bot 回复
if right_bot >= 3 and left_other < 5 and left_bot == 0:
rlogger.debug(f"[{platform}] 像素检测: 仅bot回复(右bot={right_bot}), 跳过")
rlogger.debug(f"[qq] 像素检测: 仅bot回复(右蓝={right_bot}), 跳过")
return False
# 左右半区合计白色像素极少 → 无可见消息气泡
# QQ 深色主题聊天区背景暗,用户白色气泡是主要白色来源
# 微信浅色主题虽然背景较亮,但无气泡时"其它"像素占主导
total_white = left_white + right_white
total_bot = left_bot + right_bot
if total_white < 20 and total_bot < 3:
rlogger.debug(f"[{platform}] 像素检测: 无可见消息气泡(白={total_white}), 跳过")
rlogger.debug(f"[qq] 像素检测: 无可见消息气泡(白={total_white}), 跳过")
return False
# 白色气泡柱检测:左 35% 是否有白色垂直柱
# CMD 窗口/非聊天截图即使有零星白像素也不会形成柱状
# 用户气泡在左侧形成多行多列白色密集区,CMD 窗口白色文字分散在各行
left_35 = check.crop((0, 0, int(cw * 0.35), ch))
if left_35.width >= 15 and left_35.height >= 15:
l35_p = left_35.load()
@@ -2258,7 +2296,7 @@ class MessageMonitor:
row_whites.append(rw)
max_row_white = max(row_whites) if row_whites else 0
if max_row_white < 3:
rlogger.debug(f"[{platform}] 像素检测: 无白色气泡柱(r={row_whites}), 跳过(非聊天截图)")
rlogger.debug(f"[qq] 像素检测: 无白色气泡柱(r={row_whites}), 跳过(非聊天截图)")
return False
# 拿不准保守处理