diff --git a/monitor.py b/monitor.py index 5233fcc..e8bde35 100644 --- a/monitor.py +++ b/monitor.py @@ -703,8 +703,9 @@ class MessageMonitor: except Exception: pass + # 仅清 HWND,保留 PID 供后续 EnumWindows 快速复用 + old_pid = self._cached_pid.get(platform) self._cached_hwnd.pop(platform, None) - self._cached_pid.pop(platform, None) # 非阻塞:窗口未找到冷却期内直接跳过搜索,不浪费 12s 超时 cooldown_until = self._window_not_found_until.get(platform, 0) @@ -712,6 +713,35 @@ class MessageMonitor: return None if platform == "wechat": + # ── 快速路径:用缓存 PID 直接 EnumWindows(毫秒级,跳过 psutil 全进程扫描)── + if old_pid: + try: + pid_windows = [] + def _fast_enum_cb(h, results, _pid=old_pid): + try: + _, found_pid = win32process.GetWindowThreadProcessId(h) + if found_pid == _pid: + top = win32gui.GetAncestor(h, win32con.GA_ROOT) + if top not in results: + results.append(top) + except Exception: + pass + return True + win32gui.EnumWindows(_fast_enum_cb, pid_windows) + for top in pid_windows: + if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)): + continue + rect = win32gui.GetWindowRect(top) + if (rect[2] - rect[0]) > 300 and (rect[3] - rect[1]) > 200: + self._cached_hwnd[platform] = top + self._window_not_found_until.pop(platform, None) + title = win32gui.GetWindowText(top) + logger.info(f"[{platform}] 窗口已定位(PID 快速): pid={old_pid} title={title!r} top={top}") + return top + except Exception: + pass + + # ── UIA 搜索 ── for name in ["微信", "WeChat"]: try: w = auto.WindowControl(Name=name) @@ -844,8 +874,8 @@ class MessageMonitor: continue except Exception: pass - # 搜索完毕没找到 → 设冷却避免重复 12s 超时 - self._window_not_found_until[platform] = time.time() + 30 + # 搜索完毕没找到 → 设冷却避免重复 12s 超时(10s 比 30s 更快恢复轮询) + self._window_not_found_until[platform] = time.time() + 10 return None def _ensure_hwnd_with_timeout(self, platform: str, timeout: float = 12.0) -> Optional[int]: @@ -875,7 +905,7 @@ class MessageMonitor: t.start() if not done.wait(timeout=timeout): logger.warning(f"[{platform}] _ensure_hwnd 超时({timeout:.0f}s),跳过本轮") - self._window_not_found_until[platform] = time.time() + 30 + self._window_not_found_until[platform] = time.time() + 10 return None return result[0] @@ -4129,6 +4159,35 @@ class MessageMonitor: logger.debug(f"[ocr] 过滤器2结果: 未检测到噪声特征,返回False") return False + # ⭐ OCR噪声前缀清理:去除UI按钮标签等OCR误识别的前缀 + # 如"必选,运营方有哪些权利和义务?" → "运营方有哪些权利和义务?" + _OCR_NOISE_PREFIXES = re.compile( + r'^(?:必选|必填|可选|请输入|搜索|查找|查询|发送|取消|确认|确定|提交|保存|删除|编辑| ' + r'|添加|新建|创建|设置|修改|返回|关闭|展开|收起|更多|选项|操作|' + r'选择|查看|下载|上传|导入|导出|刷新|重置|清空|复制|粘贴|剪切|' + r'同意|拒绝|同意并|下一步|上一步|完成|跳过|继续|登录|注册|退出|' + r'开启|关闭|启用|禁用|暂停|恢复|开始|停止|运行|' + r'是|否|有|无|对|错|真|假|好|坏|新|旧|已|未)' + r'[,,::\s]*' + ) + + @staticmethod + def _clean_ocr_prefix(text: str) -> str: + """清理OCR噪声前缀(UI按钮标签等)。 + + 如"必选,运营方有哪些权利和义务?" → "运营方有哪些权利和义务?" + 只清理开头的短前缀(≤6字),避免误删正常消息内容。 + """ + if not text or len(text) < 4: + return text + m = MessageMonitor._OCR_NOISE_PREFIXES.match(text) + if m and len(m.group(0)) <= 6: + cleaned = text[len(m.group(0)):].strip() + if cleaned and len(cleaned) >= 2: + logger.debug(f"[ocr] 清理噪声前缀: {text[:30]!r} → {cleaned[:30]!r}") + return cleaned + return text + def _paste_and_send_safe(self, text: str, hwnd: int, msg_hash: int, platform: str) -> bool: """Electron 应用安全发送(TIM/微信)。无 AttachThreadInput、无 SendMessage 同步点击。 @@ -5211,14 +5270,16 @@ class MessageMonitor: self._last_new_msg_time[platform] = time.time() else: self._last_new_msg_time[platform] = time.time() - self._last_msg[platform] = item + self._last_msg[platform] = cleaned_item engine_tag = self._last_chosen_engine.get(platform, "") logger.info(f"[{platform}] 新消息[{engine_tag}]: {item[:60]}") + # ⭐ 清理OCR噪声前缀(如"必选,")后再搜索知识库 + cleaned_item = self._clean_ocr_prefix(item) # ⚠️ 急停检查:回复前最后检查,防止急停后仍然发送 if not self._running: return if self._on_message: - self._on_message(platform, item) + self._on_message(platform, cleaned_item) if platform == "qq": if stale == 3: logger.debug(f"[QQ] 冻结 #{stale},强制刷新") diff --git a/msg_history.db b/msg_history.db index 88d8f4a..24fcd93 100644 Binary files a/msg_history.db and b/msg_history.db differ