OCR截屏保存到根目录用于调试排错
- OCR截取后立即保存图片到程序根目录 (ocr_debug_时间戳.png) - 自动清理旧文件,仅保留最近20张 - 防止截错区域无法肉眼核实
This commit is contained in:
+70
-12
@@ -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 []
|
||||
|
||||
Reference in New Issue
Block a user