修复2问题: 斜线英文命令触发回复 + 微信Electron安全发送\n\n问题1: 斜线开头英文命令碎片触发自动回复\n 日志: /sdfl undo撤销导入 匹配度9.53触发了回复\n 旧过滤: 只拦>=2个/cmd, /sdfl只有1个→漏过\n 修复(OCR+Poll双级):\n - _process_ocr_output新增: re.match(r'^/[A-Za-z]', line)→跳过\n - _poll_on_new_messages同级新增\n 不拦/兑换码(斜线后中文), 只拦/register /sdfl等英文命令\n\n问题2: 微信SetForegroundWindow异常+Electron闪崩风险\n 日志: SetForegroundWindow/SwitchToThisWindow异常 code=0\n WeChat也是Electron, AttachThreadInput+SendMessage同TIM风险\n 修复:\n - _paste_and_send_tim→_paste_and_send_safe, 加platform参数\n 硬编码[qq]改为[platform]避免写错字典key\n 清理return True后不可达死代码\n - _paste_and_send路由: wechat也走安全路径\n Electron双平台(TIM+微信)统一走SwitchToThisWindow+前台确认→keybd_event\n 无AttachThreadInput、无SendMessage同步点击

This commit is contained in:
youpaishidifu
2026-07-29 20:14:47 +08:00
parent b1ca2f058c
commit d72d994db1
+24 -25
View File
@@ -1697,6 +1697,13 @@ class MessageMonitor:
logger.debug(f"[{platform}] OCR 过滤: 指令列表碎片(多/cmd={slash_cmds}) line={line!r}")
continue
# —— 斜线开头英文命令碎片:"/sdfl undo撤销导入" "/register密码" ——
# 首字符是 / 且紧跟 ASCII 字母 → 命令帮助/参考碎片,非用户普通对话
# 不拦截 "/兑换码怎么用"(斜线后是中文)
if re.match(r'^/[A-Za-z]', line):
logger.debug(f"[{platform}] OCR 过滤: 斜线英文命令 line={line!r}")
continue
# —— JSON/YAML 碎片兜底:OCR 截屏混入配置面板 ——
# 行首是 "key": value 或 key: value(YAML)→ 配置面板碎片
if re.match(r'^["\']?[a-zA-Z_][\w]*["\']?\s*:\s*["\d\[\{tfn]', line):
@@ -2626,8 +2633,8 @@ class MessageMonitor:
return results
def _paste_and_send_tim(self, text: str, hwnd: int, msg_hash: int) -> bool:
"""TIM (Electron) 安全发送。无 AttachThreadInput、无 SendMessage 同步点击。
def _paste_and_send_safe(self, text: str, hwnd: int, msg_hash: int, platform: str) -> bool:
"""Electron 应用安全发送(TIM/微信)。无 AttachThreadInput、无 SendMessage 同步点击。
Electron 多线程架构下 AttachThreadInput 会导致死锁,
SendMessage 同步阻塞在渲染进程 busy 时直接炸进程。
@@ -2654,7 +2661,7 @@ class MessageMonitor:
wc.SetClipboardText(text, wc.CF_UNICODETEXT)
wc.CloseClipboard()
except Exception as e:
logger.error(f"[tim] 剪贴板设文本失败: {e}")
logger.error(f"[{platform}] 剪贴板设文本失败: {e}")
return False
root_hwnd = win32gui.GetAncestor(hwnd, win32con.GA_ROOT)
@@ -2673,13 +2680,13 @@ class MessageMonitor:
win32gui.SetForegroundWindow(root_hwnd)
time.sleep(0.3)
except Exception as e:
logger.debug(f"[tim] 窗口唤醒异常: {e}")
logger.debug(f"[{platform}] 窗口唤醒异常: {e}")
# 验证前台 — 失败则放弃,不做 SendMessage 防崩
fg_now = win32gui.GetForegroundWindow()
fg_ok = (fg_now == root_hwnd) or (fg_now and win32gui.IsChild(root_hwnd, fg_now))
if not fg_ok:
logger.warning(f"[tim] 前台失败,放弃发送(不做 SendMessage 防崩)")
logger.warning(f"[{platform}] 前台失败,放弃发送(不做 SendMessage 防崩)")
return False
# keybd_event 发送(只有在前台确认后才做)
@@ -2699,10 +2706,10 @@ class MessageMonitor:
self._key_up(VK_RETURN)
time.sleep(0.5)
except Exception as e:
logger.error(f"[tim] keybd_event 发送失败: {e}")
logger.error(f"[{platform}] keybd_event 发送失败: {e}")
return False
logger.info(f"[tim] 已回复(安全路径): {text[:60]}")
logger.info(f"[{platform}] 已回复(安全路径): {text[:60]}")
# 记录回复到去重集(与 _paste_and_send 一致)
reply_text = text.strip()
@@ -2723,14 +2730,14 @@ class MessageMonitor:
if no_hash and len(no_hash) > 2:
self._replied_texts.add(no_hash)
self._seen_messages.add(no_hash)
self._last_reply_time["qq"] = time.time()
self._self_reply_cooldown_until["qq"] = time.time() + 15.0
self._last_reply_time[platform] = time.time()
self._self_reply_cooldown_until[platform] = time.time() + 15.0
flat = reply_text.replace('\n', ' ')
if len(flat) > 15:
self._replied_texts.add(flat[:30])
if len(flat) > 40:
self._replied_texts.add(flat[-30:].strip())
self._foreground_cooldown_until["wechat"] = time.time() + 30.0
self._foreground_cooldown_until[platform] = time.time() + 30.0
success = True
return True
finally:
@@ -2747,18 +2754,6 @@ class MessageMonitor:
if not success:
self._failed_send_cooldown[msg_hash] = time.time()
self._seen_messages.add(text.strip())
self._foreground_cooldown_until["wechat"] = time.time() + 30.0
# 恢复剪贴板
if old is not None:
try:
wc.OpenClipboard()
wc.EmptyClipboard()
wc.SetClipboardText(old, wc.CF_UNICODETEXT)
wc.CloseClipboard()
except Exception:
pass
return True
def _paste_and_send(self, platform: str, text: str) -> bool:
@@ -2811,9 +2806,9 @@ class MessageMonitor:
logger.error(f"[{platform}] 剪贴板校验异常,放弃发送")
raise
# ── TIM (Electron) 安全发送路径:无 AttachThreadInput、无 SendMessage 点击 ──
if platform == "qq" and self._app_variant.get("qq") == "tim":
return self._paste_and_send_tim(text, hwnd, msg_hash)
# ── Electron 安全发送路径:TIM/微信都无 AttachThreadInput、无 SendMessage ──
if (platform == "qq" and self._app_variant.get("qq") == "tim") or platform == "wechat":
return self._paste_and_send_safe(text, hwnd, msg_hash, platform)
# ── 更激进的窗口唤醒 ──
# 1) 允许任意进程 SetForegroundWindow(移除系统限制)
@@ -3393,6 +3388,10 @@ class MessageMonitor:
if cjk > 0 and cjk / max(len(ts), 1) < 0.15:
logger.debug(f"[{platform}] Poll过滤: CJK比例过低乱码 ts={ts!r}")
continue
# === 斜线英文命令碎片:"/sdfl undo撤销导入" 命令帮助碎片 ===
if re.match(r'^/[A-Za-z]', ts):
logger.debug(f"[{platform}] Poll过滤: 斜线英文命令 ts={ts!r}")
continue
# === 结构化数据面板:QQ/TIM 指令面板(指令:/rtp 权限类型:玩家 图标:标题:)===
colon_kv = re.findall(r'[\u4e00-\u9fff]{1,6}[::](?:[A-Za-z0-9/]|[\u4e00-\u9fff])', ts)
if len(colon_kv) >= 3: