OCR截屏保存到根目录用于调试排错

- OCR截取后立即保存图片到程序根目录 (ocr_debug_时间戳.png)
- 自动清理旧文件,仅保留最近20张
- 防止截错区域无法肉眼核实
This commit is contained in:
youpaishidifu
2026-07-26 11:21:20 +08:00
parent acbadb95a7
commit f0b221f570
+70 -12
View File
@@ -14,6 +14,8 @@ import win32process
import re
import ctypes
import difflib
import os
from datetime import datetime
from PIL import ImageGrab
from winrt.windows.media.ocr import OcrEngine
from winrt.windows.globalization import Language
@@ -509,7 +511,7 @@ class MessageMonitor:
exe_path = win32process.GetModuleFileNameEx(hproc, 0)
win32api.CloseHandle(hproc)
exe_name = exe_path.split('\\')[-1].lower() if exe_path else ''
if exe_name in ('qq.exe', 'tim.exe', 'qq.exe', 'tim.exe'):
if exe_name in ('qq.exe', 'tim.exe'):
return True
except Exception:
pass
@@ -563,15 +565,29 @@ class MessageMonitor:
return []
left, top, right, bottom = rect
win_w = right - left
cap_left = int(left + win_w * 0.30)
cap_top = top + 35
cap_right = int(left + win_w * 0.55)
cap_bottom = bottom - 80
win_h = bottom - top
# QQ NT 窗口布局: ~0-5% 标签栏, ~5-30% 联系人/群列表, ~30-55% 会话列表, ~55-100% 消息面板
# 之前裁剪到中间会话列表区域(30-55%)→ 读到按钮/提示/删除碎片等 UI 文字
# 修正为只裁剪右侧消息面板区域
cap_left = int(left + win_w * 0.55)
cap_top = top + max(40, int(win_h * 0.04)) # 标题栏下方
cap_right = int(left + win_w * 0.95) # 跳过滚动条
cap_bottom = bottom - max(100, int(win_h * 0.10)) # 输入框上方
if cap_right <= cap_left or cap_bottom <= cap_top:
return []
im = ImageGrab.grab(bbox=(cap_left, cap_top, cap_right, cap_bottom))
if im.width < 50 or im.height < 50:
return []
# 保存截屏到程序根目录用于调试(保留最近 20 张)
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
path = os.path.join(script_dir, f"ocr_debug_{ts}.png")
im.save(path)
# 清理旧文件:只保留最近 20 张
_clean_old_ocr_debug(script_dir, keep=20)
except Exception as e:
logger.debug(f"[ocr] 保存截屏失败: {e}")
if im.mode != 'RGBA':
im = im.convert('RGBA')
sb = SoftwareBitmap(BitmapPixelFormat.RGBA8, im.width, im.height)
@@ -597,15 +613,26 @@ class MessageMonitor:
line = self._normalize_ocr_text(line)
if not line:
continue
# ⭐ QQ NT 界面固定噪声:整行匹配直接跳过
if line in ("查看更多", "收起", "选择会话开始聊天"):
continue
# 连续纯标点+符号(emoji、装饰字符等)
if re.match(r'^[✿♡♥❤★☆✦✧❀❁❂❃❄❅❆❇❈❉❊❋➠➡➢➣➤.·•●○◦◎◉◍◐◑◒◓◔◕◖◗◢◣◤◥☺☻☼♠♣♦♤♧♡♢♔♕♖♗♘♙🕐-🕧\s]{1,3}$', line):
continue
# 去噪:去掉行首未读红点数字(如 "0")和行尾删除按钮误识别(勖彐 等)
# 否则同一消息每次 OCR 输出微小差异 → 精确去重失效 → 重复触发
line = re.sub(r'^0+', '', line)
line = re.sub(r'[勖彐"\'\s]+$', '', line)
line = re.sub(r'[勖彐"\'\s\u2000-\u206f]+$', '', line)
if not line:
continue
# 半角全角数字混杂的界面碎片(如 "1.0s 0.35后监控应用设置")
line = re.sub(r'^\d+[\..]\d+s?\s*\d*[.\.]?\d*', '', line)
line = re.sub(r'^[\d.\.\s]{2,20}[一-鿿]{1,5}?[,。、]', '', line)
if not line:
continue
if self._is_system_message(line):
continue
if re.match(r'^[一-鿿·A-Za-z0-9_\-]{1,10}$', line):
if re.match(r'^[一-鿿·A-Za-z0-9_\-]{1,15}$', line):
continue
if re.match(r'^\d{1,2}[::\s]\d{2}', line):
continue
@@ -654,8 +681,9 @@ class MessageMonitor:
left, top, right, bottom = rect
w = right - left
h = bottom - top
click_x = int(w * 0.50)
click_y = int(h * 0.85) # 消息区域(比输入框稍上)
# QQ NT 消息面板在 ~55-95% 宽度区域
click_x = int(w * 0.72) # 消息面板中部
click_y = int(h * 0.60) # 消息面板中部偏下
lparam = win32api.MAKELONG(click_x, click_y)
win32gui.PostMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
win32gui.PostMessage(hwnd, win32con.WM_LBUTTONUP, 0, lparam)
@@ -896,12 +924,21 @@ class MessageMonitor:
return True
# 系统前缀/标记
system_markers = [
"✅", "❌", "📝", "🔍", "📄", "➡", "♡",
"✅", "❌", "📝", "🔍", "📄", "➡", "♡", "✿",
"[WECHAT]", "[QQ]", "[wechat]", "[qq]",
]
for m in system_markers:
if text.startswith(m):
return True
# ⭐ QQ NT 界面噪声文字(按钮/提示/碎片,非用户消息)
qq_ui_noise = [
"查看更多", "可查看在其他设备产生的聊天记录",
"收藏", "转发", "多选", "引用", "回复",
"选择会话开始聊天",
]
for n in qq_ui_noise:
if text == n:
return True
# 系统事件模板
system_patterns = [
"已自动回复", "知识库中未找到", "找到相关结果",
@@ -986,7 +1023,8 @@ class MessageMonitor:
left, top, right, bottom = rect
w = right - left
h = bottom - top
click_x = int(w * 0.50)
# QQ NT 输入框在右侧消息面板底部
click_x = int(w * 0.72)
click_y = int(h * 0.92)
lparam = win32api.MAKELONG(click_x, click_y)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
@@ -1014,7 +1052,7 @@ class MessageMonitor:
left, top, right, bottom = rect
w = right - left
h = bottom - top
click_x = int(w * 0.50)
click_x = int(w * 0.72)
click_y = int(h * 0.92)
lparam = win32api.MAKELONG(click_x, click_y)
win32gui.PostMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
@@ -1392,6 +1430,13 @@ class MessageMonitor:
continue
if self._is_system_message(ts):
continue
# 纯装饰符号(单字符非汉字非字母数字)跳过
if len(ts) == 1 and not re.match(r'[\u4e00-\u9fff\w]', ts):
continue
# 只有符号/空格/emoji 的非消息文本(<3 字符且长度等于标点去掉后长度)
clean = re.sub(r'[\s\u2000-\u206f\u2190-\u2bff\U0001F000-\U0001FFFF✿♡♥❤★☆]', '', ts)
if len(ts) <= 3 and len(clean) == 0:
continue
seen.add(ts)
new_items.append(ts)
# 不截断 _seen_messages — _replied_texts 已拦截回复回环,截断会误放历史消息
@@ -1443,6 +1488,19 @@ class MessageMonitor:
self._cached_pid.pop(platform, None)
def _clean_old_ocr_debug(dir_path: str, keep: int = 20):
"""清理 ocr_debug_*.png 文件,只保留最近 keep 张。"""
try:
files = [f for f in os.listdir(dir_path) if f.startswith("ocr_debug_") and f.endswith(".png")]
if len(files) <= keep:
return
files.sort() # 按字母序 = 时间序
for f in files[:-keep]:
os.remove(os.path.join(dir_path, f))
except Exception:
pass
def _walk_list_controls(ctrl, depth):
if depth > 6:
return []