This commit is contained in:
HuangHai
2026-01-26 09:42:25 +08:00
parent 16b4d30f91
commit 437df303c5
11 changed files with 221 additions and 187 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -12,7 +12,7 @@ if project_root not in sys.path:
sys.path.append(project_root) sys.path.append(project_root)
# 配置日志 # 配置日志
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
@@ -27,6 +27,9 @@ logging.basicConfig(
logger = logging.getLogger("T1_OpenChat") logger = logging.getLogger("T1_OpenChat")
def open_chat(target_name="糖豆爸爸"): def open_chat(target_name="糖豆爸爸"):
# 运行前清理 Logs 和 Output
WxUtil.setup_script_environment()
logger.info(f"开始执行 T1: 打开微信并进入 '{target_name}' 的对话框...") logger.info(f"开始执行 T1: 打开微信并进入 '{target_name}' 的对话框...")
# 连接设备 # 连接设备

View File

@@ -13,7 +13,7 @@ from WeiXin import WxUtil
from WeiXin.WxUtil import analyze_chat_image from WeiXin.WxUtil import analyze_chat_image
# 配置日志 # 配置日志
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
@@ -28,6 +28,9 @@ logging.basicConfig(
logger = logging.getLogger("T2_GetHistory") logger = logging.getLogger("T2_GetHistory")
async def get_history(target_name="对方"): async def get_history(target_name="对方"):
# 运行前清理 Logs 和 Output
WxUtil.setup_script_environment()
logger.info("开始执行 T2: 获取当前屏幕对话历史...") logger.info("开始执行 T2: 获取当前屏幕对话历史...")
d = WxUtil.connect_device() d = WxUtil.connect_device()
@@ -35,7 +38,7 @@ async def get_history(target_name="对方"):
return return
# 截图 # 截图
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") screenshot_dir = WxUtil.OUTPUT_DIR
if not os.path.exists(screenshot_dir): if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir) os.makedirs(screenshot_dir)

View File

@@ -15,7 +15,7 @@ from WeiXin.WxUtil import find_input_box_center, perform_input_action, analyze_c
from Util.LlmUtil import get_llm_response from Util.LlmUtil import get_llm_response
# 配置日志 # 配置日志
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
@@ -30,10 +30,10 @@ logging.basicConfig(
logger = logging.getLogger("T3_InputLlmText") logger = logging.getLogger("T3_InputLlmText")
async def generate_and_input(): async def generate_and_input():
logger.info("开始执行 T3: 生成 LLM 回复并输入...") # 运行前清理 Logs 和 Output
WxUtil.setup_script_environment()
# 0. 清除旧截图 logger.info("开始执行 T3: 生成 LLM 回复并输入...")
clean_screenshots_dir()
try: try:
# 1. 连接设备 # 1. 连接设备
@@ -42,9 +42,7 @@ async def generate_and_input():
return return
# 2. 截图并识别对话历史 # 2. 截图并识别对话历史
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") screenshot_dir = WxUtil.OUTPUT_DIR
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
tmp_shot = os.path.join(screenshot_dir, "t4_temp_history_check.jpg") tmp_shot = os.path.join(screenshot_dir, "t4_temp_history_check.jpg")
analyzed_shot = os.path.join(screenshot_dir, "t4_temp_history_analyzed.jpg") analyzed_shot = os.path.join(screenshot_dir, "t4_temp_history_analyzed.jpg")

View File

@@ -3,98 +3,116 @@ import os
import sys import sys
import time import time
import cv2 import cv2
import logging
# 添加项目根目录到 sys.path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 添加项目根目录到 sys.path
if project_root not in sys.path: project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(project_root) if project_root not in sys.path:
sys.path.append(project_root)
from WeiXin import WxUtil
from WeiXin.WxUtil import find_all_template_matches from WeiXin import WxUtil
from WeiXin.WxUtil import find_all_template_matches
def run_cv_debug(): # 配置日志
# 1. 拍照 (获取当前设备屏幕) log_dir = WxUtil.LOG_DIR
print("📸 正在连接设备并截取屏幕...") if not os.path.exists(log_dir):
d = WxUtil.connect_device() os.makedirs(log_dir)
if not d:
return logging.basicConfig(
level=logging.INFO,
try: format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") handlers=[
if not os.path.exists(screenshot_dir): logging.FileHandler(os.path.join(log_dir, "T4_CV_Voice_Debug.log"), encoding='utf-8'),
os.makedirs(screenshot_dir) logging.StreamHandler()
]
image_path = os.path.join(screenshot_dir, "t4_live_shot.jpg") )
output_path = os.path.join(screenshot_dir, "T4_debug_view.jpg") logger = logging.getLogger("T4_CV_Voice_Debug")
d.screenshot(image_path)
print(f"✅ 截图已保存: {image_path}") def run_cv_debug():
except Exception as e: # 运行前清理 Logs 和 Output
print(f"❌ 拍照失败: {e}") WxUtil.setup_script_environment()
return
# 1. 拍照 (获取当前设备屏幕)
print(f"🔍 正在分析实时图片...") logger.info("📸 正在连接设备并截取屏幕...")
d = WxUtil.connect_device()
# 模板路径 if not d:
audio_template = r"d:\dsWork\aiData\WeiXin\Templates\audio.jpg" return
red_point_template = r"d:\dsWork\aiData\WeiXin\Templates\red_point.jpg"
try:
if not os.path.exists(audio_template) or not os.path.exists(red_point_template): screenshot_dir = WxUtil.OUTPUT_DIR
print("错误: 模板文件不存在")
return image_path = os.path.join(screenshot_dir, "t4_live_shot.jpg")
output_path = os.path.join(screenshot_dir, "T4_debug_view.jpg")
# 2. 识别逻辑
audio_matches = find_all_template_matches(image_path, audio_template, threshold=0.8) d.screenshot(image_path)
red_points = find_all_template_matches(image_path, red_point_template, threshold=0.8) logger.info(f"✅ 截图已保存: {image_path}")
except Exception as e:
print(f"发现语音图标数量: {len(audio_matches)}") logger.error(f"❌ 拍照失败: {e}")
print(f"发现红点数量: {len(red_points)}") return
# 3. 读取图片并绘制 logger.info(f"🔍 正在分析实时图片...")
img = cv2.imread(image_path)
if img is None: # 模板路径
print("错误: 无法读取图片") template_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Templates")
return audio_template = os.path.join(template_dir, "audio.jpg")
red_point_template = os.path.join(template_dir, "red_point.jpg")
for ax, ay in audio_matches:
# 排除顶部标题栏和底部输入区 (假设 300-1800 为有效区) if not os.path.exists(audio_template) or not os.path.exists(red_point_template):
if ay < 300 or ay > 1800: logger.error("错误: 模板文件不存在")
continue return
sender = "对方" if ax < 500 else "我" # 2. 识别逻辑
audio_matches = find_all_template_matches(image_path, audio_template, threshold=0.8)
# --- 1. 先判断是否未读 (寻找附近的红点) --- red_points = find_all_template_matches(image_path, red_point_template, threshold=0.8)
is_unread = False
for rx, ry in red_points: logger.info(f"发现语音图标数量: {len(audio_matches)}")
if abs(ry - ay) < 50 and rx > ax: logger.info(f"发现红点数量: {len(red_points)}")
is_unread = True
break # 3. 读取图片并绘制
img = cv2.imread(image_path)
# --- 2. 根据状态选择颜色 --- if img is None:
# BGR 格式: 红色 (0, 0, 255), 绿色 (0, 255, 0) logger.error("错误: 无法读取图片")
color = (0, 0, 255) if is_unread else (0, 255, 0) return
status_text = "未读" if is_unread else "已读"
for ax, ay in audio_matches:
# --- 3. 绘制标注 --- # 排除顶部标题栏和底部输入区 (假设 300-1800 为有效区)
# 语音图标框 (加粗) if ay < 300 or ay > 1800:
cv2.rectangle(img, (int(ax-35), int(ay-35)), (int(ax+35), int(ay+35)), color, 3) continue
# 中心点击位置 (实心圆) sender = "对方" if ax < 500 else "我"
cv2.circle(img, (int(ax), int(ay)), 15, color, -1)
# --- 1. 先判断是否未读 (寻找附近的红点) ---
# 如果是未读,把原本识别到的红点也再次标出 is_unread = False
if is_unread: for rx, ry in red_points:
for rx, ry in red_points: if abs(ry - ay) < 50 and rx > ax:
if abs(ry - ay) < 50 and rx > ax: is_unread = True
cv2.circle(img, (int(rx), int(ry)), 12, (0, 0, 255), -1) break
print(f"标注语音消息: ({ax}, {ay}), 发送者: {sender}, 状态: {status_text}") # --- 2. 根据状态选择颜色 ---
# BGR 格式: 红色 (0, 0, 255), 绿色 (0, 255, 0)
# 保存结果 color = (0, 0, 255) if is_unread else (0, 255, 0)
cv2.imwrite(output_path, img) status_text = "未读" if is_unread else "已读"
print(f"✅ 调试图片已保存至: {output_path}")
# --- 3. 绘制标注 ---
# 语音图标框 (加粗)
cv2.rectangle(img, (int(ax-35), int(ay-35)), (int(ax+35), int(ay+35)), color, 3)
# 中心点击位置 (实心圆)
cv2.circle(img, (int(ax), int(ay)), 15, color, -1)
# 如果是未读,把原本识别到的红点也再次标出
if is_unread:
for rx, ry in red_points:
if abs(ry - ay) < 50 and rx > ax:
cv2.circle(img, (int(rx), int(ry)), 12, (0, 0, 255), -1)
logger.info(f"标注语音消息: ({ax}, {ay}), 发送者: {sender}, 状态: {status_text}")
# 保存结果
cv2.imwrite(output_path, img)
logger.info(f"✅ 调试图片已保存至: {output_path}")
if __name__ == "__main__": if __name__ == "__main__":
run_cv_debug() run_cv_debug()

View File

@@ -19,12 +19,12 @@ from Util.LlmUtil import get_llm_response
from Util.EasyOcrKit import EasyOcrKit from Util.EasyOcrKit import EasyOcrKit
# 配置日志 # 配置日志
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Logs") log_dir = WxUtil.LOG_DIR
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
# 设置 logger # 设置 logger
logger = logging.getLogger("T6_AutoChatMonitor") logger = logging.getLogger("T5_AutoChatMonitor")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
# 清除现有的 handlers,防止重复打印或配置冲突 # 清除现有的 handlers,防止重复打印或配置冲突
@@ -32,7 +32,7 @@ if logger.hasHandlers():
logger.handlers.clear() logger.handlers.clear()
# 创建 FileHandler # 创建 FileHandler
log_file_path = os.path.join(log_dir, "T6_AutoChatMonitor.log") log_file_path = os.path.join(log_dir, "T5_AutoChatMonitor.log")
file_handler = logging.FileHandler(log_file_path, encoding='utf-8', mode='w') file_handler = logging.FileHandler(log_file_path, encoding='utf-8', mode='w')
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler) logger.addHandler(file_handler)
@@ -50,17 +50,19 @@ logger.info(f"日志文件路径: {log_file_path}")
# 配置参数 # 配置参数
CHECK_INTERVAL = 5 # 检查频率 (秒) CHECK_INTERVAL = 5 # 检查频率 (秒)
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Templates")
class ChatBot: class ChatBot:
def __init__(self): def __init__(self):
# 运行前清理 Logs 和 Output
WxUtil.setup_script_environment()
self.d = WxUtil.connect_device() self.d = WxUtil.connect_device()
if not self.d: if not self.d:
raise Exception("无法连接到设备,任务终止") raise Exception("无法连接到设备,任务终止")
self.last_message_text = "" self.last_message_text = ""
self.last_processed_msg = None # 记录上一条已处理/回复过的对方消息内容 self.last_processed_msg_id = None # 记录上一条已处理的消息标识 (文本+坐标)
self.screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") self.screenshot_dir = WxUtil.OUTPUT_DIR
if not os.path.exists(self.screenshot_dir):
os.makedirs(self.screenshot_dir)
self.ocr_kit = EasyOcrKit(gpu=True) self.ocr_kit = EasyOcrKit(gpu=True)
@@ -121,7 +123,7 @@ class ChatBot:
logger.info(f"📸 截取菜单图: {menu_shot_path}") logger.info(f"📸 截取菜单图: {menu_shot_path}")
self.d.screenshot(menu_shot_path) self.d.screenshot(menu_shot_path)
convert_template = r"d:\dsWork\aiData\WeiXin\Templates\zhun_wen_zi.jpg" convert_template = os.path.join(TEMPLATE_DIR, "zhun_wen_zi.jpg")
logger.info(f"🔍 寻找模板: {convert_template}") logger.info(f"🔍 寻找模板: {convert_template}")
convert_btn = find_template_match(menu_shot_path, convert_template, threshold=0.6) convert_btn = find_template_match(menu_shot_path, convert_template, threshold=0.6)
@@ -234,7 +236,7 @@ class ChatBot:
menu_shot_path_cancel = os.path.join(self.screenshot_dir, "t6_menu_shot_cancel.jpg") menu_shot_path_cancel = os.path.join(self.screenshot_dir, "t6_menu_shot_cancel.jpg")
self.d.screenshot(menu_shot_path_cancel) self.d.screenshot(menu_shot_path_cancel)
cancel_template = r"d:\dsWork\aiData\WeiXin\Templates\cancel_zhuan_wen_zi.jpg" cancel_template = os.path.join(TEMPLATE_DIR, "cancel_zhuan_wen_zi.jpg")
cancel_btn = find_template_match(menu_shot_path_cancel, cancel_template, threshold=0.6) cancel_btn = find_template_match(menu_shot_path_cancel, cancel_template, threshold=0.6)
if cancel_btn: if cancel_btn:
@@ -249,9 +251,6 @@ class ChatBot:
async def run(self): async def run(self):
logger.info("🚀 大张老师自动巡课系统启动...") logger.info("🚀 大张老师自动巡课系统启动...")
# 0. 清除旧截图
clean_screenshots_dir()
last_screen_md5 = None last_screen_md5 = None
while True: while True:
@@ -279,8 +278,8 @@ class ChatBot:
logger.info("<EFBFBD>️ 正在进行本地视觉扫描...") logger.info("<EFBFBD>️ 正在进行本地视觉扫描...")
# A. 寻找语音图标 (audio.jpg) 和 红点 (red_point.jpg) # A. 寻找语音图标 (audio.jpg) 和 红点 (red_point.jpg)
audio_template = r"d:\dsWork\aiData\WeiXin\Templates\audio.jpg" audio_template = os.path.join(TEMPLATE_DIR, "audio.jpg")
red_point_template = r"d:\dsWork\aiData\WeiXin\Templates\red_point.jpg" red_point_template = os.path.join(TEMPLATE_DIR, "red_point.jpg")
audio_matches = find_all_template_matches(tmp_shot, audio_template, threshold=0.8) audio_matches = find_all_template_matches(tmp_shot, audio_template, threshold=0.8)
red_points = find_all_template_matches(tmp_shot, red_point_template, threshold=0.8) red_points = find_all_template_matches(tmp_shot, red_point_template, threshold=0.8)
@@ -353,7 +352,8 @@ class ChatBot:
"y": ay, "y": ay,
"text": f"{sender}: [语音] {duration_text}", "text": f"{sender}: [语音] {duration_text}",
"is_voice": True, "is_voice": True,
"id": f"voice_{ax}_{ay}" "id": f"voice_{ax}_{ay}",
"v_msg": v_msg
}) })
# 处理剩余的 OCR 文字块 (普通文本) # 处理剩余的 OCR 文字块 (普通文本)
@@ -391,7 +391,7 @@ class ChatBot:
logger.info(f"🎨 已保存视觉调试图: {debug_shot_path}") logger.info(f"🎨 已保存视觉调试图: {debug_shot_path}")
# C. 寻找输入框 (CV 模板匹配) # C. 寻找输入框 (CV 模板匹配)
input_template = r"d:\dsWork\aiData\WeiXin\Templates\input_box.jpg" # 假设有这个模板 input_template = os.path.join(TEMPLATE_DIR, "input_box.jpg") # 假设有这个模板
input_center = find_template_match(tmp_shot, input_template, threshold=0.6) input_center = find_template_match(tmp_shot, input_template, threshold=0.6)
if not input_center: if not input_center:
# 几何兜底:屏幕底部 88% 处 # 几何兜底:屏幕底部 88% 处
@@ -401,81 +401,65 @@ class ChatBot:
input_center = [w // 2, int(h * 0.88)] input_center = [w // 2, int(h * 0.88)]
logger.info(f"<EFBFBD> 使用几何兜底输入框坐标: {input_center}") logger.info(f"<EFBFBD> 使用几何兜底输入框坐标: {input_center}")
# 4. 语音处理逻辑 # 4. & 5. 统一处理最后一条消息逻辑 (只看最后一条)
processed_voice_content = None should_reply = False
input_y = input_center[1] if input_center else None input_y = input_center[1] if input_center else None
# 只有未读的才处理
for v_msg in voice_messages:
if v_msg.get("is_unread") or self.is_first_run:
logger.info(f"🔴 发现未读/待处理语音: {v_msg['content']}")
# 找到 OCR 结果中的下一条作为边界
idx = -1
# 这里简化逻辑,直接处理
text = await self.process_single_voice(v_msg, None, input_y)
if text:
# 更新 log 中的内容
for item in dialogue_log:
if item.get("is_voice") and f"[语音] {v_msg['content']}" in item["text"]:
item["text"] = item["text"].replace("[语音]", f"[语音转文字: {text}]")
break
self.is_first_run = False
# 5. LLM 回复逻辑
final_dialogue_texts = [item['text'] for item in dialogue_log]
history_text = "\n".join(final_dialogue_texts)
# 判断是否需要回复:
# 核心规则:只有当最后一条消息是“对方”说的,且内容未处理过,才回复。
should_reply = False
current_last_content = ""
if dialogue_log: if dialogue_log:
last_item = dialogue_log[-1] last_item = dialogue_log[-1]
last_log = last_item["text"] last_text = last_item["text"]
# 构造唯一标识符:文本内容 + 坐标 (Y坐标取整到10像素以容纳轻微位移)
current_msg_id = f"{last_text}_{int(last_item['y']/10)*10}"
# 检查最后一条消息的发送者 # 核心规则:只有当最后一条消息是“对方”说的,且内容未处理过,才回复。
if last_log.startswith("对方"): if last_text.startswith("对方"):
parts = last_log.split(":", 1) if current_msg_id != self.last_processed_msg_id:
if len(parts) > 1: logger.info(f"💡 发现新消息: {last_text}")
current_last_content = parts[1].strip()
else: # 如果是语音,且需要回复,则先转换
current_last_content = last_log if last_item.get("is_voice"):
v_msg = last_item.get("v_msg")
if current_last_content != self.last_processed_msg: if v_msg:
logger.info(f"💡 发现新消息,准备回复。内容: {current_last_content}") logger.info(f"🎤 最后一条是语音,开始转换: {v_msg['content']}")
should_reply = True converted_text = await self.process_single_voice(v_msg, None, input_y)
else: if converted_text:
# logger.info(f"⚪ 消息已回复过,跳过: {current_last_content}") # 更新文本内容以便 LLM 理解
should_reply = False last_item["text"] = f"对方: [语音转文字: {converted_text}]"
logger.info(f"✅ 语音转换成功: {converted_text}")
else:
logger.warning("⚠️ 语音转换未提取到文字,将尝试直接回复或跳过。")
should_reply = True
else:
# 消息已处理过
should_reply = False
else: else:
# logger.info(f"⚪ 最后一条消息是我发送的,无需回复。") # 最后一条是我发送的,或者是系统消息
should_reply = False should_reply = False
# 如果最后一条是我发的,重置 last_processed_msg # 记录一下,避免在没有新消息时重复进入逻辑
self.last_processed_msg = None if current_msg_id != self.last_processed_msg_id:
self.last_processed_msg_id = current_msg_id
logger.info(f"⚪ 最后一条消息非对方发送,无需回复: {last_text}")
if should_reply: if should_reply:
logger.info("🤖 准备调用 LLM 生成回复...") logger.info("🤖 准备调用 LLM 生成回复...")
# 立即更新状态,防止在回复生成期间(如果耗时)重复触发 # 立即更新状态,防止在回复生成期间重复触发
self.last_processed_msg = current_last_content self.last_processed_msg_id = current_msg_id
# 构建完整历史用于上下文
final_dialogue_texts = [item['text'] for item in dialogue_log]
history_text = "\n".join(final_dialogue_texts)
reply = await self.get_reply(history_text) reply = await self.get_reply(history_text)
if reply: if reply:
logger.info(f"💡 LLM 回复: {reply}") logger.info(f"💡 LLM 回复: {reply}")
if input_center: if input_center:
# 输入并发送
perform_input_action(self.d, input_center, reply) perform_input_action(self.d, input_center, reply)
# 发送后,为了防止下一轮 OCR 识别到自己的回复片段并误判为对方消息 time.sleep(1) # 等待发送完成
# 我们把 last_processed_msg 设置为一个特殊的占位符,直到下一次真正识别到对方的新消息
# 或者更简单:在下一轮循环开始前稍微多等一下,让消息气泡完全显示
time.sleep(1)
# 将最后处理的消息内容标记为已处理,防止 LLM 回复逻辑在下一轮立即触发
# 注意:这里的 current_last_content 是对方的最后一条
else: else:
logger.warning("⚠️ LLM 未生成有效回复。") logger.warning("⚠️ LLM 未生成有效回复。")
self.is_first_run = False
# 休眠 # 休眠
await asyncio.sleep(CHECK_INTERVAL) await asyncio.sleep(CHECK_INTERVAL)

View File

@@ -25,6 +25,35 @@ ocr_kit = EasyOcrKit()
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("WxUtil") logger = logging.getLogger("WxUtil")
# 目录配置
BASE_DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOG_DIR = os.path.join(BASE_DATA_DIR, "Logs")
OUTPUT_DIR = os.path.join(BASE_DATA_DIR, "Output")
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Templates")
def clear_directory(dir_path):
"""清理指定目录下的所有文件"""
if not os.path.exists(dir_path):
os.makedirs(dir_path)
return
import shutil
for filename in os.listdir(dir_path):
file_path = os.path.join(dir_path, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
logger.warning(f"Failed to delete {file_path}. Reason: {e}")
def setup_script_environment():
"""运行前清理日志和输出目录"""
logger.info("清理运行环境: Logs 和 Output 目录...")
clear_directory(LOG_DIR)
clear_directory(OUTPUT_DIR)
def connect_device(): def connect_device():
""" """
连接设备并返回设备对象,同时打印详细的设备信息 连接设备并返回设备对象,同时打印详细的设备信息
@@ -328,15 +357,14 @@ async def analyze_chat_image(image_path, output_path, device=None, target_name="
def clean_screenshots_dir(): def clean_screenshots_dir():
"""清理截图目录""" """清理截图目录"""
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots") if not os.path.exists(OUTPUT_DIR):
if not os.path.exists(screenshot_dir): os.makedirs(OUTPUT_DIR)
os.makedirs(screenshot_dir)
return return
for f in os.listdir(screenshot_dir): for f in os.listdir(OUTPUT_DIR):
if f.lower().endswith(('.jpg', '.png', '.jpeg')): if f.lower().endswith(('.jpg', '.png', '.jpeg')):
try: try:
os.remove(os.path.join(screenshot_dir, f)) os.remove(os.path.join(OUTPUT_DIR, f))
except Exception as e: except Exception as e:
logger.warning(f"Failed to delete {f}: {e}") logger.warning(f"Failed to delete {f}: {e}")
@@ -475,11 +503,11 @@ def perform_input_action(d, center_point, text, auto_send=True):
try: try:
# --- 新增逻辑:确保处于文字输入模式 --- # --- 新增逻辑:确保处于文字输入模式 ---
logger.info("正在检查输入模式...") logger.info("正在检查输入模式...")
tmp_check_shot = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp_input_check.jpg") tmp_check_shot = os.path.join(OUTPUT_DIR, "temp_input_check.jpg")
d.screenshot(tmp_check_shot) d.screenshot(tmp_check_shot)
wen_zi_template = r"d:\dsWork\aiData\WeiXin\Templates\wen_zi_input.jpg" wen_zi_template = os.path.join(TEMPLATE_DIR, "wen_zi_input.jpg")
input_text_template = r"d:\dsWork\aiData\WeiXin\Templates\input_text.jpg" input_text_template = os.path.join(TEMPLATE_DIR, "input_text.jpg")
# 1. 检查是否存在 '切换到文字' 图标 (表示当前是语音模式) # 1. 检查是否存在 '切换到文字' 图标 (表示当前是语音模式)
# 注意:这里假设 wen_zi_input.jpg 是那个“键盘”图标 # 注意:这里假设 wen_zi_input.jpg 是那个“键盘”图标
@@ -548,8 +576,8 @@ def perform_input_action(d, center_point, text, auto_send=True):
tmp_screen = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp_send_check.jpg") tmp_screen = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp_send_check.jpg")
d.screenshot(tmp_screen) d.screenshot(tmp_screen)
# 使用绝对路径 # 使用相对路径
template_path = r"d:\dsWork\aiData\WeiXin\Templates\send.jpg" template_path = os.path.join(TEMPLATE_DIR, "send.jpg")
send_btn_pos = find_template_match(tmp_screen, template_path, threshold=0.7) # 稍微降低阈值以提高召回 send_btn_pos = find_template_match(tmp_screen, template_path, threshold=0.7) # 稍微降低阈值以提高召回
if send_btn_pos: if send_btn_pos: