diff --git a/monitor.py b/monitor.py index 4607cac..8c9ad23 100644 --- a/monitor.py +++ b/monitor.py @@ -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 # 拿不准保守处理