修复颜色检测+自身冷却+QQ裁切+2324异常 4项:QQ cap_left 0.25→0.50(避会话列表) / _has_user_colors改左右半区策略 / 回复后同平台15s冷却 / 2324行try-except兜住SetForegroundWindow崩溃

This commit is contained in:
youpaishidifu
2026-07-29 00:53:36 +08:00
parent 718f67804c
commit 94cd3ca448
+73 -54
View File
@@ -180,6 +180,8 @@ class MessageMonitor:
# 跨平台前台冷却:某平台刚切过前台(回复后),对端平台等待此时间再截图
self._foreground_cooldown_until: dict = {} # platform → float(time.time)
# 同平台回复冷却:回复后此秒数内同平台跳过 OCR(防读到自身回复回声)
self._self_reply_cooldown_until: dict = {} # platform → float(time.time)
# 本轮选中的 OCR 引擎(用于日志标记)
self._last_chosen_engine: dict = {} # platform → "windows"|"easyocr"
@@ -895,6 +897,9 @@ class MessageMonitor:
"""OCR 截屏 QQ 消息区域 -> 双引擎(Windows OCR + EasyOCR)提取文本。
包含窗口验证 + 垃圾过滤 + 连续垃圾清缓存。
"""
# 同平台冷却:回复后 15s 内跳过,防读到自身回复回声
if time.time() < self._self_reply_cooldown_until.get("qq", 0):
return []
hwnd = self._cached_hwnd.get("qq")
if not hwnd:
return []
@@ -954,9 +959,9 @@ class MessageMonitor:
win32gui.PostMessage(hwnd, win32con.WM_KEYUP, win32con.VK_END, 0)
time.sleep(0.3)
# TIM 窗口布局: ~5-30% 左栏, ~30-50% 会话列表(含文档预览), ~50-100% 消息面板
# 退回 c9acd7e 裁剪: 25% 覆盖消息面板两侧(对方+自己消息都可见)
# cap_left=50% 从消息面板左边界开始,避开会话列表干扰(文本→颜色检测 false)
# 右边界 78% 排除群成员侧栏(~80%+), md 过滤负责拦截 bot 的 md 格式回复
cap_left = int(left + win_w * 0.25)
cap_left = int(left + win_w * 0.50)
cap_top = top + max(80, int(win_h * 0.06)) # 跳过标题栏+群名头栏
cap_right = int(left + win_w * 0.78) # 跳过右栏群成员列表(~80%+) 和滚动条
cap_bottom = bottom - max(100, int(win_h * 0.10)) # 输入框上方
@@ -1053,6 +1058,9 @@ class MessageMonitor:
微信 UIA 树不暴露聊天消息内容(新版微信 UIA 架构变化),必须用 OCR。
微信自绘控件,keybd_event 无法滚动,只截屏当前可见区域。
"""
# 同平台冷却:回复后 15s 内跳过,防读到自身回复回声
if time.time() < self._self_reply_cooldown_until.get("wechat", 0):
return []
hwnd = self._cached_hwnd.get("wechat")
if not hwnd:
return []
@@ -1833,69 +1841,75 @@ class MessageMonitor:
@staticmethod
def _has_user_colors(im, platform: str) -> bool:
"""颜色像素检测:判断截图中有没有用户消息(白色底板)。
"""区域颜色检测:左/右半区独立分析,判断截图中有无用户消息。
QQ: bot 蓝色气泡 #7FBAFF 附近,用户白色气泡
WeChat: bot 绿色气泡 #95EC69 附近,用户白色气泡
QQ: bot 蓝色气泡右对齐,用户白色气泡左对齐
WeChat: bot 绿色气泡右对齐,用户白色气泡左对齐
规则:
- 只有 bot 色(无白色)→ 整张截图只有 bot 回复 → 跳过本轮
- 有白色像素 → 有用户消息 → 继续处理
策略:
- 跳过顶部 20%(聊天头部名称/标题干扰)
- 左右半区分别采样(~15×15 网格/半区)
- 右半区有 bot 色 + 左半区几乎无"其它"像素 → 仅 bot 回复 → 跳过
- 左半区有大量"其它"像素 → 用户内容(头像/文字/时间戳)→ 继续
返回 True=有用户消息,False=仅 bot 回复(应跳过)
返回 True=有用户内容/拿不准,False=仅 bot 回复
"""
w, h = im.size
if w < 10 or h < 10:
return True # 太小无法判断,保守处理
# 网格采样(约 20×20 网格)
step_x = max(2, w // 20)
step_y = max(2, h // 20)
bot_pixels = 0
white_pixels = 0
total = 0
pixels = im.load()
for y in range(0, h, step_y):
for x in range(0, w, step_x):
total += 1
try:
r, g, b = pixels[x, y][:3]
except Exception:
continue
if platform == "qq":
# QQ 蓝色气泡:偏蓝的亮色
is_bot = (110 <= r <= 190 and 170 <= g <= 235 and 220 <= b <= 255)
elif platform == "wechat":
# 微信绿色气泡:偏绿的亮色
is_bot = (135 <= r <= 180 and 205 <= g <= 250 and 75 <= b <= 125)
else:
is_bot = False
# 白色/接近白色(用户消息底板 + 聊天背景)
is_white = (r > 225 and g > 225 and b > 225)
if is_bot:
bot_pixels += 1
elif is_white:
white_pixels += 1
if total == 0:
return True
# 有白色像素 → 有用户消息
if white_pixels >= 3:
# 跳过顶部 20%(群名/大标题栏)
check = im.crop((0, int(h * 0.20), w, h))
cw, ch = check.size
if cw < 20 or ch < 20:
return True
# 只有 bot 色,无白色 → 仅 bot 回复
if bot_pixels >= 3 and white_pixels == 0:
logger = logging.getLogger("msgbot.monitor")
logger.debug(f"[{platform}] 像素检测: 仅bot色({bot_pixels}/{total}), 无用户白色消息, 跳过本轮")
# 左右半区
left_half = check.crop((0, 0, cw // 2, ch))
right_half = check.crop((cw // 2, 0, cw, ch))
step_x = max(2, cw // 15)
step_y = max(2, ch // 15)
if platform == "qq":
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):
return 135 <= r <= 180 and 205 <= g <= 250 and 75 <= b <= 125
def analyze(img):
p = img.load()
bot = other = white = total = 0
for y in range(0, img.height, step_y):
for x in range(0, img.width, step_x):
total += 1
try:
r, g, b = p[x, y][:3]
except:
continue
if r > 225 and g > 225 and b > 225:
white += 1
elif is_bot(r, g, b):
bot += 1
else:
other += 1
return bot, other, white, total
left_bot, left_other, left_white, left_total = analyze(left_half)
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}")
# 右半区有 bot 气泡 + 左半区几乎无"其它" → 仅 bot 回复
# "其它"阈值:QQ 5 / WeChat 5(bot 气泡内白字不会产生其它像素)
if right_bot >= 3 and left_other < 5 and left_bot == 0:
rlogger.debug(f"[{platform}] 像素检测: 仅bot回复(右bot={right_bot}), 跳过")
return False
# 拿不准就保守处理
# 拿不准保守处理
return True
def _is_reply_fragment(self, text: str) -> bool:
@@ -2321,7 +2335,10 @@ class MessageMonitor:
time.sleep(0.3)
# 再试一次 SetForegroundWindow(SendMessage 点击后可能已生效)
self._attach_set_foreground(root_hwnd)
win32gui.SetForegroundWindow(root_hwnd)
try:
win32gui.SetForegroundWindow(root_hwnd)
except Exception:
pass
time.sleep(0.2)
# keybd_event 发送(不依赖 PostMessage——Electron 不吃 PostMessage 键盘)
@@ -2386,6 +2403,8 @@ class MessageMonitor:
self._replied_texts.add(no_hash)
self._seen_messages.add(no_hash)
self._last_reply_time[platform] = time.time()
# 同平台 15s 冷却:回复回声截到也不触发
self._self_reply_cooldown_until[platform] = time.time() + 15.0
# 追加前30字符/后30字符变体(OCR 碎片常截取回复的开头或结尾)
flat = reply_text.replace('\n', ' ')
if len(flat) > 15: