diff --git a/Flask/FlaskService.py b/Flask/FlaskService.py index 979adf8..f6b29c2 100644 --- a/Flask/FlaskService.py +++ b/Flask/FlaskService.py @@ -96,7 +96,7 @@ def deviceAppList(): non_system_apps = [app for app in apps if not app["bundleId"].startswith("com.apple")] return ResultData(data=non_system_apps).toJson() -# 打开置顶app +# 打开指定app @app.route('/launchApp', methods=['POST']) def launchApp(): body = request.get_json() @@ -125,7 +125,10 @@ def tapAction(): client = wda.USBClient(udid) session = client.session() session.appium_settings({"snapshotMaxDepth": 0}) - session.tap(x, y) + print(client.window_size()) + width = client.window_size()[0] + height = client.window_size()[1] + session.tap(x * width, y * height) return ResultData(data="").toJson() # 拖拽事件 diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index 381aaea..88e0ab7 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -73,8 +73,9 @@ class Deviceinfo(object): def connectDevice(self, identifier): d = wda.USBClient(identifier, 8100) d.app_start(WdaAppBundleId) - time.sleep(2) - d.app_start(tikTokApp) + d.home() + # time.sleep(2) + # d.app_start(tikTokApp) target = self.relayDeviceScreenPort() self.pidList.append({ "target": target, diff --git a/Utils/AiUtils.py b/Utils/AiUtils.py new file mode 100644 index 0000000..04c75f0 --- /dev/null +++ b/Utils/AiUtils.py @@ -0,0 +1,29 @@ + +import os +import re + + +# 工具类 +class AiUtils(object): + + @classmethod + def findNumber(cls, str): + # 使用正则表达式匹配数字 + match = re.search(r'\d+', str) + if match: + return int(match.group()) # 将匹配到的数字转换为整数 + return None # 如果没有找到数字,返回 None + + + # 根据名称获取图片地址 + @classmethod + def pathWithName(cls, name): + current_file_path = os.path.abspath(__file__) + # 获取当前文件所在的目录(即script目录) + current_dir = os.path.dirname(current_file_path) + # 由于script目录位于项目根目录下一级,因此需要向上一级目录移动两次 + project_root = os.path.abspath(os.path.join(current_dir, '..')) + # 构建资源文件的完整路径,向上两级目录,然后进入 resources 目录 + resource_path = os.path.abspath(os.path.join(project_root, 'resources', name + ".jpg")).replace('/', '\\') + return resource_path + diff --git a/Utils/LogManager.py b/Utils/LogManager.py new file mode 100644 index 0000000..cc74e99 --- /dev/null +++ b/Utils/LogManager.py @@ -0,0 +1,71 @@ +import logging +import os + +class LogManager: + # 获取项目根目录 + projectRoot = os.path.dirname(os.path.dirname(__file__)) + logDir = os.path.join(projectRoot, "log") + infoLogFile = os.path.join(logDir, "info.log") + warningLogFile = os.path.join(logDir, "warning.log") + errorLogFile = os.path.join(logDir, "error.log") + + # 类变量,存储日志记录器 + _infoLogger = None + _warningLogger = None + _errorLogger = None + + @classmethod + def _setupLogger(cls, name, logFile, level=logging.INFO): + """设置日志记录器""" + os.makedirs(cls.logDir, exist_ok=True) # 确保日志目录存在 + logger = logging.getLogger(name) + logger.setLevel(level) + fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8") + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + fileHandler.setFormatter(formatter) + logger.addHandler(fileHandler) + return logger + + @classmethod + def _initializeLoggers(cls): + """初始化所有日志记录器""" + if not cls._infoLogger: + cls._infoLogger = cls._setupLogger("infoLogger", cls.infoLogFile, level=logging.INFO) + if not cls._warningLogger: + cls._warningLogger = cls._setupLogger("warningLogger", cls.warningLogFile, level=logging.WARNING) + if not cls._errorLogger: + cls._errorLogger = cls._setupLogger("errorLogger", cls.errorLogFile, level=logging.ERROR) + + @classmethod + def info(cls, text): + """记录 INFO 级别的日志""" + cls._initializeLoggers() + cls._infoLogger.info(text) + + @classmethod + def warning(cls, text): + """记录 WARNING 级别的日志""" + cls._initializeLoggers() + cls._warningLogger.warning(text) + + @classmethod + def error(cls, text): + """记录 ERROR 级别的日志""" + cls._initializeLoggers() + cls._errorLogger.error(text) + + @classmethod + def clearLogs(cls): + """删除所有日志文件""" + # 关闭所有日志记录器的处理器 + for logger in [cls._infoLogger, cls._warningLogger, cls._errorLogger]: + if logger: + for handler in logger.handlers[:]: # 使用切片避免在迭代时修改列表 + handler.close() + logger.removeHandler(handler) + + # 删除日志文件 + for logFile in [cls.infoLogFile, cls.warningLogFile, cls.errorLogFile]: + if os.path.exists(logFile): + os.remove(logFile) # 删除文件 + print("所有日志文件已删除") \ No newline at end of file diff --git a/resources/bgv.jpg b/resources/bgv.jpg index bc0d292..9224af7 100644 Binary files a/resources/bgv.jpg and b/resources/bgv.jpg differ diff --git a/script/AiTools.py b/script/AiTools.py deleted file mode 100644 index f067f08..0000000 --- a/script/AiTools.py +++ /dev/null @@ -1,218 +0,0 @@ -import os -import cv2 -import numpy as np -import imutils - - -# 工具类 -class AiTools(object): - - @classmethod - def find_image_in_image(cls, big_image_path, small_image_path, threshold=0.6, use_multiscale=True, scale_range=(0.7, 1.3), scale_steps=10, visualize=True, enable_subpixel=True, preprocess='histogram'): - # 读取大图和小图 - large_image = cv2.imread(big_image_path) - small_image = cv2.imread(small_image_path) - - if large_image is None or small_image is None: - print(f"无法加载图像,请检查路径是否正确!\n大图路径: {big_image_path}\n小图路径: {small_image_path}") - return -1, -1 - - # 打印图像尺寸信息 - print(f"大图尺寸: {large_image.shape[1]}x{large_image.shape[0]}") - print(f"小图尺寸: {small_image.shape[1]}x{small_image.shape[0]}") - - # 图像预处理函数 - def preprocess_image(image, method='histogram'): - if method == 'histogram': - # 直方图均衡化 - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - equalized = cv2.equalizeHist(gray) - return equalized - elif method == 'blur': - # 高斯模糊 - blurred = cv2.GaussianBlur(image, (5, 5), 0) - gray = cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY) - return gray - elif method == 'edge': - # 边缘检测 - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - edges = cv2.Canny(gray, 100, 200) - return edges - elif method == 'sharp': - # 锐化 - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) - sharpened = cv2.filter2D(gray, -1, kernel=kernel) - return sharpened - else: - # 默认转为灰度图 - return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - - # 预处理图像 - large_image_gray = preprocess_image(large_image, preprocess) - small_image_gray = preprocess_image(small_image, preprocess) - - best_match_val = -1 - best_match_loc = (-1, -1) - best_scale = 1.0 - best_method = None - best_method_enum = None - - # 定义要尝试的匹配方法 - match_methods = [ - (cv2.TM_CCOEFF, 'TM_CCOEFF'), - (cv2.TM_CCOEFF_NORMED, 'TM_CCOEFF_NORMED'), - (cv2.TM_CCORR, 'TM_CCORR'), # 添加这个方法 - (cv2.TM_CCORR_NORMED, 'TM_CCORR_NORMED'), - (cv2.TM_SQDIFF, 'TM_SQDIFF'), - (cv2.TM_SQDIFF_NORMED, 'TM_SQDIFF_NORMED') - ] - - if use_multiscale: - # 多尺度匹配 - small_height, small_width = small_image_gray.shape - scale_step = (scale_range[1] - scale_range[0]) / scale_steps - scales = [scale_range[0] + i * scale_step for i in range(scale_steps + 1)] - print(f"多尺度匹配范围: {scales}") - - for scale in scales: - # 调整小图大小 - new_width = int(small_width * scale) - new_height = int(small_height * scale) - if new_width < 10 or new_height < 10: # 防止图像太小 - continue - # 使用INTER_AREA插值方法提高缩放精度 - resized_small = cv2.resize(small_image_gray, (new_width, new_height), interpolation=cv2.INTER_AREA) - print(f"尝试尺度: {scale}, 调整后小图尺寸: {new_width}x{new_height}") - - # 尝试多种匹配方法 - for method in [cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF_NORMED]: - method_name = 'TM_CCOEFF_NORMED' if method == cv2.TM_CCOEFF_NORMED else 'TM_CCORR_NORMED' if method == cv2.TM_CCORR_NORMED else 'TM_SQDIFF_NORMED' - result = cv2.matchTemplate(large_image_gray, resized_small, method) - if method == cv2.TM_SQDIFF_NORMED: - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - current_val = 1 - min_val # 转换为类似其他方法的值 - current_loc = min_loc - else: - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - current_val = max_val - current_loc = max_loc - - print(f" 方法: {method_name}, 匹配值: {current_val:.6f}") - if current_val > best_match_val: - best_match_val = current_val - best_match_loc = current_loc - best_scale = scale - best_method = method_name - best_method_enum = method - else: - # 单一尺度匹配,但尝试多种方法 - for method in [cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF_NORMED]: - method_name = 'TM_CCOEFF_NORMED' if method == cv2.TM_CCOEFF_NORMED else 'TM_CCORR_NORMED' if method == cv2.TM_CCORR_NORMED else 'TM_SQDIFF_NORMED' - result = cv2.matchTemplate(large_image_gray, small_image_gray, method) - if method == cv2.TM_SQDIFF_NORMED: - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - current_val = 1 - min_val # 转换为类似其他方法的值 - current_loc = min_loc - else: - min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) - current_val = max_val - current_loc = max_loc - - print(f"方法: {method_name}, 匹配值: {current_val:.6f}") - if current_val > best_match_val: - best_match_val = current_val - best_match_loc = current_loc - best_method = method_name - best_method_enum = method - - print(f"最佳匹配值: {best_match_val:.6f}") # 打印最佳匹配值,用于调试 - print(f"最佳匹配方法: {best_method}") - print(f"使用尺度: {best_scale}") # 打印使用的尺度 - print(f"匹配位置(左上角): {best_match_loc}") - - # 设置一个阈值,只有当匹配度高于这个阈值时才认为找到 - if best_match_val >= threshold: - # 计算小图在大图中的中心坐标 - top_left = best_match_loc # 左上角坐标 - # 根据最佳尺度调整小图尺寸 - small_height, small_width = small_image_gray.shape - adjusted_width = small_width * best_scale - adjusted_height = small_height * best_scale - - # 亚像素级精确匹配 - if enable_subpixel and best_method_enum is not None: - # 调整小图到最佳尺度 - resized_small = cv2.resize(small_image_gray, (int(adjusted_width), int(adjusted_height)), interpolation=cv2.INTER_AREA) - # 重新执行模板匹配以获取更精确的结果 - result = cv2.matchTemplate(large_image_gray, resized_small, best_method_enum) - - # 在最佳匹配点周围定义一个小区域进行亚像素精确化 - x, y = top_left - window_size = 5 - x1, x2 = max(0, x - window_size), min(result.shape[1], x + window_size + 1) - y1, y2 = max(0, y - window_size), min(result.shape[0], y + window_size + 1) - - # 提取局部区域 - local_result = result[y1:y2, x1:x2] - - # 拟合二次曲线找到亚像素级最大值 - if best_method_enum == cv2.TM_SQDIFF_NORMED: - min_val, _, min_loc, _ = cv2.minMaxLoc(local_result) - subpixel_offset = (min_loc[0] - window_size, min_loc[1] - window_size) - else: - _, max_val, _, max_loc = cv2.minMaxLoc(local_result) - subpixel_offset = (max_loc[0] - window_size, max_loc[1] - window_size) - - # 计算亚像素级精确位置 - subpixel_x = x + subpixel_offset[0] - subpixel_y = y + subpixel_offset[1] - - print(f"亚像素级精确位置: ({subpixel_x:.2f}, {subpixel_y:.2f})") - top_left = (subpixel_x, subpixel_y) - - # 使用浮点运算计算中心坐标 - center_x = top_left[0] + adjusted_width / 2 - center_y = top_left[1] + adjusted_height / 2 - print(f"计算得到的中心坐标: ({center_x:.2f}, {center_y:.2f})") - - # 可视化匹配结果 - if visualize: - # 在彩色大图上绘制矩形和中心点 - large_image_copy = large_image.copy() - # 绘制矩形 - if enable_subpixel: - top_left_int = (int(round(top_left[0])), int(round(top_left[1]))) - adjusted_width_int = int(round(adjusted_width)) - adjusted_height_int = int(round(adjusted_height)) - bottom_right = (top_left_int[0] + adjusted_width_int, top_left_int[1] + adjusted_height_int) - center_int = (int(round(center_x)), int(round(center_y))) - else: - bottom_right = (top_left[0] + int(adjusted_width), top_left[1] + int(adjusted_height)) - center_int = (int(center_x), int(center_y)) - top_left_int = top_left - - cv2.rectangle(large_image_copy, top_left_int, bottom_right, (0, 255, 0), 2) - cv2.circle(large_image_copy, center_int, 5, (0, 0, 255), -1) - # 显示图像 - cv2.imshow('匹配结果', large_image_copy) - cv2.waitKey(0) - cv2.destroyAllWindows() - - return center_x, center_y - else: - print(f"匹配值 {best_match_val:.6f} 低于阈值 {threshold},未找到匹配") - return -1, -1 - - # 根据名称获取图片地址 - @classmethod - def pathWithName(cls, name): - current_file_path = os.path.abspath(__file__) - # 获取当前文件所在的目录(即script目录) - current_dir = os.path.dirname(current_file_path) - # 由于script目录位于项目根目录下一级,因此需要向上一级目录移动两次 - project_root = os.path.abspath(os.path.join(current_dir, '..')) - # 构建资源文件的完整路径,向上两级目录,然后进入 resources 目录 - resource_path = os.path.abspath(os.path.join(project_root, 'resources', name + ".jpg")).replace('/', '\\\\') - return resource_path - diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 4c31091..d7d5b7c 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -1,46 +1,41 @@ -import cv2 -import lxml -import wda -from lxml import etree -from script.AiTools import AiTools +import wda + +from Utils.AiUtils import AiUtils # 脚本管理类 class ScriptManager(): + + # 单利对象 + _instance = None # 类变量,用于存储单例实例 + + def __new__(cls): + # 如果实例不存在,则创建一个新实例 + if cls._instance is None: + cls._instance = super(ScriptManager, cls).__new__(cls) + # 返回已存在的实例 + return cls._instance + def __init__(self): super().__init__() + # 脚本开关 + self.running = False + self.initialized = True # 标记已初始化 # 养号 - @classmethod def growAccount(self, udid): client = wda.USBClient(udid) session = client.session() - session.appium_settings({"snapshotMaxDepth": 0}) - - # deviceWidth = client.window_size().width - # deviceHeight = client.window_size().height - - img = client.screenshot() - tempPath = "resources/bgv.jpg" - img.save(tempPath) - - smallImage = AiTools.pathWithName("like") - bigImage = AiTools.pathWithName("bgv") - - x, y = AiTools.find_image_in_image(bigImage, smallImage) - print(x, y) - # client.tap(x, y) - - - # xml = session.source() - # print(xml) - # root = etree.fromstring(xml.encode('utf-8')) - # try: - # msg = client.xpath('label="收件箱"') - # msg.click() - # print(msg) - # except Exception as e: - # print(e) + session.appium_settings({"snapshotMaxDepth": 15}) + numberLabel = session.xpath("//*[@name='a11y_vo_inbox']") + if numberLabel: + content = numberLabel.label + number = AiUtils.findNumber(content) + print(number) + else: + print("没找到") +manager = ScriptManager() +manager.growAccount("eca000fcb6f55d7ed9b4c524055214c26a7de7aa")