diff --git a/monitor.py b/monitor.py index bacd4c4..9ade273 100644 --- a/monitor.py +++ b/monitor.py @@ -204,6 +204,33 @@ class MessageMonitor: pass return None + def _find_qq_window_fresh(self): + """通过类名重新查找 QQ/TIM 窗口,绕过 UIA 缓存(比 ControlFromHandle 更彻底)。""" + try: + # 先按窗口名称查找 + for name in ["QQ", "TIM"]: + try: + w = auto.WindowControl(Name=name) + if w.Exists(maxSearchSeconds=0.2): + rect = w.BoundingRectangle + if rect.width() > 200 and rect.height() > 100: + return w + except Exception: + pass + # 按 TXGuiFoundation 类名深度查找 + root = auto.GetRootControl() + for child in root.GetChildren(): + try: + if child.ClassName == "TXGuiFoundation": + rect = child.BoundingRectangle + if rect.width() > 200 and rect.height() > 100: + return child + except Exception: + pass + except Exception: + pass + return None + def _force_uia_refresh(self, platform: str): """通过交互强制窗口更新 UIA 控件树。""" hwnd = self._cached_hwnd.get(platform) @@ -401,6 +428,7 @@ class MessageMonitor: pass texts = [] + raw_lines = [] # 预初始化,防止 content 为空时 UnboundLocalError try: # 确保窗口在前台(失败则返回空,避免发送到错误窗口) # QQ 剪贴板优先路径需要更可靠的前台保证(与发送路径 retries 一致) @@ -793,47 +821,49 @@ class MessageMonitor: def _read_messages(self, window, platform: str = "") -> list: """每轮重新定位 ListControl。""" if platform == "qq": - # QQ NT UIA ListControl 缓存永不更新新消息(返回几天前的旧数据) - # 必须剪贴板优先(键盘导航 Enter+Ctrl+End+Ctrl+A/C 读实时消息) + # QQ NT: UIA 优先(但重建窗口绕过缓存)+ 剪贴板兜底 + # 第一步:用新窗口流式重建 UIA 树(不用 ControlFromHandle 缓存) + fresh_win = self._find_qq_window_fresh() + if fresh_win: + texts = self._read_message_list(fresh_win) + if texts: + return texts + texts = self._read_conversation_list(fresh_win) + if texts: + return texts + try: + rect = fresh_win.BoundingRectangle + boundary = rect.left + rect.width() * 0.40 + for lst in self._list_controls(fresh_win): + try: + cr = lst.BoundingRectangle + if cr.left >= boundary: + texts = self._extract_last_line(lst) + if texts: + logger.debug(f"[S-C] ListControl(x={cr.left}) → {len(texts)} items") + return texts + except Exception: + pass + except Exception: + pass + try: + texts = self._collect_filtered_texts(fresh_win, "qq", max_depth=5) + if texts: + logger.debug(f"[S-D] 全窗文本 → {len(texts)} items") + return texts + except Exception: + pass + + # 剪贴板兜底:仅当 UIA 完全没读到内容时启用 texts = self._read_via_clipboard_once(platform) - if texts: + # <=5 行的剪贴板很可能是输入框内容(非消息列表)→ 丢弃 + if texts and len(texts) > 5: return texts texts = self._read_via_clipboard(platform) - if texts: + if texts and len(texts) > 5: return texts - # UIA 兜底:先强制刷新生效,再读取 - self._force_uia_refresh(platform) - self._force_scroll_bottom(platform) - time.sleep(0.5) - texts = self._read_message_list(window) - if texts: - return texts - texts = self._read_conversation_list(window) - if texts: - return texts - try: - rect = window.BoundingRectangle - boundary = rect.left + rect.width() * 0.40 - for lst in self._list_controls(window): - try: - cr = lst.BoundingRectangle - if cr.left >= boundary: - texts = self._extract_last_line(lst) - if texts: - logger.debug(f"[S-C] ListControl(x={cr.left}) → {len(texts)} items") - return texts - except Exception: - pass - except Exception: - pass - try: - texts = self._collect_filtered_texts(window, "qq", max_depth=5) - if texts: - logger.debug(f"[S-D] 全窗文本 → {len(texts)} items") - return texts - except Exception: - pass - # 最终兜底:win32 文本 + + # 最终兜底 texts = self._read_win32_texts(platform) if texts: return texts