2026年07月04日 自动化任务与生活洞察报告

目录导航

📜 日记摘要与心理洞察

日期: 2026年07月04日 星期六

心情: 🙂 (愉悦)

天气: 河南正阳县,阴,27℃,南风4级,湿度84% (报告时间: 09:30:26)

今日活动:

  • 09:58 - 优化 抖音商城抽奖
  • 18:52 - 优化淘粉吧提现
  • 21:52 - 书旗和茄子任务 49

心理洞察:

日记记录了一天中多项优化工作和任务完成,尽管工作内容偏向技术与重复性操作,但心情仍为“愉悦”。这可能表明对工作成果的满意、解决问题的成就感,或者对任务掌控感的积极体现。在忙碌中仍能保持正面情绪,这是一种宝贵的能力。

建议: 保持这份对优化和解决问题乐在其中的心态,这将是持续进步的动力。同时,别忘了在任务间隙给自己短暂的放松,以维持这种积极的能量。

💡 技术创新与优化建议

                
                
                # coding=utf-8
                import datetime
                import os
                import platform
                import random
                import time
                import cv2 as cv
                import numpy as np
                import uiautomator2 as u2
                # power_switch
                from send_emaill import qq_email
                import hmac
                import hashlib
                import base64
                import urllib.parse
                import requests
                import json
                import logging
                import allure
                import re
                from lxml import etree
                import subprocess
                import pytest
                import ast
                import threading
                import redis
                import pickle
                import sys
                from utils.mysql_util import MySQLManager
                from utils.redis_util import RedisManager
                from utils.common import get_adb_path
                from utils.yaml_util import read_yaml_cached
                from utils.sw_control_util import switch_2400, set_fan_speed, new_switch_2400_optimized
                from utils.xpath_utils import XPathElement
                from utils.ocr_util import pytesseract_pic_to_text
                import atexit
                import socket
                # env()
                # 解锁密码
                separation_phone1 = "1234"
                separation_phone2 = "5678"

                class UiAutomatr():

                    def __init__(self):

                        self.ip = ""
                        self.canzhan = ''
                        self.port = ":5555"
                        self.redis_expire = 60 * 60  # 缓存过期时间 (1 小时,根据需要调整)
                        self.system = platform.platform()
                        self.redis_manager = RedisManager()  # 创建 RedisManager 实例
                        # === 新增:暂停检查的冷却控制 ===
                        self.last_pause_check_time = 0 
                        self.pause_check_interval = 5  # 设置每 5 秒才真正查一次 Redis
                        self._DIR_INDEX = {}          # 文件夹索引缓存
                        self._REGEX_CACHE = {}        # 正则表达式缓存
                        self.local_imobj_cache = {}   # 图片矩阵缓存

                        self.read_yaml = read_yaml_cached()
                        # mi_app_list
                        try:
                            self.mi_app_list = self.read_yaml['mi_app_list']
                        except Exception as e:
                            print(e)
                            self.mi_app_list = []

                        try:
                            self.force_uninstall_app_list = self.read_yaml['force_uninstall_app_list']
                        except Exception as e:
                            print(e)
                            self.force_uninstall_app_list = []
                        value = self.read_yaml['mobile_ip']

                        dic_final = {}
                        for dic in value:
                            dic_final.update(dic)
                        print(dic_final)
                        self.note9_5g_ip = dic_final.get('note9_5g_ip')
                        self.mobile_dic = self.read_yaml['mobile_model']
                        self.main_mobile_ip = self.read_yaml['main_mobile_ip']
                        adb_path, cur_path = get_adb_path(self.read_yaml)
                        self.adb_path = adb_path
                        self.screen_path = os.path.join(cur_path, 'screen_picture/')
                        self.find_pic_path = os.path.join(cur_path, 'find_picture/xiaoyao/')
                        self.z = 0
                        self.lis = []
                        self.install = []
                        self.china_name = ""
                        self.app_name = ''
                        self.activity = ''
                        self.project = ''
                        self.main_start = time.time()
                        self.waiter_times = None
                        self.text = ""
                        self.zl_flag = ''
                        self.width = '' # 手机宽
                        self.height = '' # 手机高
                        self.d = None
                        self.model = None
                        self.xpath_signal = ""
                        self.info = ''
                        self.separation = ""  # 解锁密码
                        self.threshold = 0.9  # 图片相似度
                        self.tao_user = ''  # 用户
                        self.user_name = ''  # 也是用户
                        # 偏移坐标
                        self.width_offset = 1
                        self.height_offset = 1
                        self.MySQLManager = MySQLManager()
                        self.XPathElement = XPathElement
                        # self.redis_client  = self.MySQLManager.redis_client
                        atexit.register(self.cleanup)

                    def cleanup(self):
                        try:
                            self.del_end()
                        except Exception:
                            pass
                            # print("Cleanup failed:", e)

                    def __del__(self):
                        pass

                    def _get_cache_key(self, *args):
                        """生成缓存键"""
                        return args[0] + hashlib.md5(json.dumps(args).encode('utf-8')).hexdigest()
                    

                    
                    def del_end(self):
                        end = time.time()
                        time.sleep(0.4)
                        try:
                            power = ''
                            try:
                                output = self.console_input('shell dumpsys battery', ret=1)
                                match = re.search(r'level: (\d+)', output)
                                power  = match.group(1)
                                int_power = int(power)

                                if int_power <= 20 and self.ip != self.main_mobile_ip:

                                    if self.d:
                                        self.d.screen_off()  # 关闭屏幕
                                    self.create_cron_switch(40, mip=self.ip, this_power=str(int_power))
                                    print("%s电量过低,等待40分钟充电" % self.ip)
                                    # time.sleep(1800)
                                    if self.model == '【MI 8 SE】':
                                        
                                        time.sleep(600)
                                        print("mi8 se 休息10分钟")
                                else:
                                    if self.ip == self.main_mobile_ip:
                                        if int_power <= 20:
                                            print("main_mobile_ip电量过低,充电" )
                                            self.create_cron_switch(40, mip=self.ip, this_power=str(int_power))
                                            set_fan_speed(255)
                                            
                                        elif int_power <= 30 and int_power > 20:
                                            set_fan_speed(40)
                                        elif int_power <= 40 and int_power > 30:
                                            set_fan_speed(30)
                                        elif int_power <= 60 and int_power > 40:
                                            set_fan_speed(20)
                                        elif int_power <= 80 and int_power > 60:
                                            set_fan_speed(15)
                                        else:
                                            set_fan_speed(10)

                            except Exception as e:
                                print(e)
                                
                            now = datetime.datetime.now()
                            now_time = now.strftime("%Y-%m-%d %H:%M:%S")
                            try:
                                output = self.console_input('shell dumpsys battery', ret=1)
                                temperature = re.search(r'temperature: (\d+)', output).group(1)
                                temperature = int(temperature) / 10
                            except Exception as e:
                                print(e)
                                temperature = ''

                            
                            # 计算时间差,最小作为应用时间
                            start_end_diff = end - self.main_start
                            mobile_ip = self.model + self.ip
                            ret = self.MySQLManager.sql_search('content', "%s','手机电量'" % mobile_ip)
                            if ret:
                                last_sql = ret[-1][0]
                                # print("last_sql:", last_sql)
                                r_list = re.compile(r"时间(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", re.S).findall(last_sql)
                                if r_list:
                                    # 取出最后一条记录的时间
                                    # print("r_list:", r_list)
                                    # 取出最后一条记录的时间
                                    last_sql_time = datetime.datetime.strptime(r_list[0], "%Y-%m-%d %H:%M:%S") 
                                    now = datetime.datetime.now()
                                    # 计算时间差
                                    time_diff = (now - last_sql_time).seconds
                                    # print("time_diff:", time_diff)
                                    start_end_diff = min(time_diff, start_end_diff)
                                    print(time_diff, start_end_diff)

                            print_text = "['%s','手机电量','电量%s|温度=%s|self.z=%s|用时%.2f秒|时间%s|活动%s']" % \
                                         (self.model + self.ip, power, temperature, self.z, start_end_diff, now_time, self.project)
                            print(print_text)
                            if power:
                                # ['%s','趣头条金币','%s']
                                self.MySQLManager.write_file(print_text)
                            # 卸载com.baidu.searchbox(百度)
                            self.uninstall_app("com.baidu.searchbox")
                            # 重新赋值
                            # self.main_start = end
                            # 添加cpu,内存图片
                            try:
                                if "Windows" in self.system:
                                    project_path = self.read_yaml['win_computer']['project_path']
                                    report_path = os.path.join(project_path, "report")
                                else:
                                    share_path = self.read_yaml['main_ubantu_computer']['share_path']
                                    report_path = os.path.join(share_path, "report")  
                                save_path = os.path.join(report_path, "cpu内存剩余率图.png")
                                allure.attach.file(save_path, name='cpu内存剩余率图', attachment_type=allure.attachment_type.PNG)
                            except Exception as e:
                                print(e)


                            time.sleep(2)

                            if self.d:
                                # pass
                                try:
                                    print("关闭后台大于100MB的app")
                                    self.kill_heavy_memory_apps(threshold_mb=100)
                                except Exception as e:
                                    print("关闭后台大于100MB的app出错", e)
                                # 出现无响应
                                self.multiple_find_picture([], 1, "点击", "不返回", delem=['d(resourceId="android:id/aerr_close")'])
                                
                                get_appname = self.get_app_name()
                                app_list = ['com.miui.home', 'com.xiaomi.market', "com.kindle.zhuayumao", "com.tencent.mm", 'com.example.myaccessibility']
                                if get_appname not in app_list:
                                    self.d.app_stop(get_appname)

                                # ipv6写入
                                self.process_ipv6()
                                    
                                have_tj = self.MySQLManager.sql_search("content", "%s已统计手机容量" % (self.model + self.ip))
                                if not have_tj:
                                    ret = self.console_input('shell df -h /data', ret=1)
                                    if ret:
                                        text = str(ret)
                                        print(text)
                                        r_list = re.compile('(\\d+G).*?(\\d+G).*?(\\d+G).*?(\\d+%)', re.S).findall(text)
                                        if not r_list:
                                            r_list = re.compile('(\\d+G).*?(\\d+G).*?(\\d+M).*?(\\d+%)', re.S).findall(text)
                                        if r_list:
                                            
                                            ex_text = ''
                                            try:
                                                rate = r_list[0][3].replace('%', '')
                                                print(rate)
                                                if int(rate) >= 95:
                                                    
                                                    ex_text = ("(容量过小)")
                                                    self.clear_app(app_name="com.jifen.qukan")
                                                    ip_post = self.model + self.ip
                                                    search_text = ip_post + "','应用大小排行"
                                                    text = ''
                                                    r_ = MySQLManager().sql_search("content", search_text)
                                                    if r_:
                                                        text = eval(r_[-1][0])[-1]
                                                        print(text)
                                                    print("容量过小", text)
                                                    
                                                    qq_email('', '%s 手机容量过小' % self.ip, str(rate) + "
" + text) except Exception as e: print(e) text = "使用%s,可用%s,使用率%s(总共%s)" % (r_list[0][1], r_list[0][2], r_list[0][3] + ex_text, r_list[0][0]) print(text) self.MySQLManager.write_file("['%s', '手机容量情况', '%s']" % ((self.model + self.ip), text)) time.sleep(1) # ipv6 try: version = self.set_voice_to_0() if version: print("['%s', '安卓版本', '%s']" % ((self.model + self.ip), version)) self.MySQLManager.write_file("['%s', '安卓版本', '%s']" % ((self.model + self.ip), version)) except Exception as e: logging.error(e) try: version = self.console_input('shell getprop ro.build.version.incremental', ret=1) if version: version = version.strip() print("['%s', 'miui版本', '%s']" % ((self.model + self.ip), version)) self.MySQLManager.write_file("['%s', 'miui版本', '%s']" % ((self.model + self.ip), version)) except Exception as e: logging.error(e) self.clear_mobile() self.d.screen_off() # 关闭屏幕 except Exception as e: print(e) self.re_install_uiautomator(e) def is_ip_reachable(self, ip, timeout=1): system = platform.system().lower() if system == "windows": command = ["ping", "-n", "1", "-w", str(timeout * 1000), ip] try: result = subprocess.run(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return result.returncode == 0 except Exception as e: print(f"执行 Ping 出错: {e}") return False else: # 在 Linux 上,优先尝试 socket 方法 try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) # 尝试连接一个常用端口,如 80 或 443 print('sock.connect_ex((ip, 80))=', sock.connect_ex((ip, 80))) if sock.connect_ex((ip, 80)) == 0 or sock.connect_ex((ip, 80)) == 111: # 0 表示连接成功,111 表示连接被拒绝(端口关闭) sock.close() return True else: # 如果 80 不通,可以再尝试一个,例如 443 sock.settimeout(timeout) result = sock.connect_ex((ip, 443)) == 0 sock.close() return result except Exception as e: print(f"Socket 连接出错: {e}") return False def process_ipv6(self): """获取并处理 IPv6 地址,限制写入 MySQL 的频率""" redis_key_last_write = f"{self.model}:{self.ip}:ipv6:last_write" redis_key_ipv6 = f"{self.model}:{self.ip}:ipv6" cache_expire_time = 10000 try: # 检查上次写入 MySQL 的时间 last_write_time_bytes = self.redis_manager.redis_search(redis_key_last_write) if last_write_time_bytes: last_write_time = int(last_write_time_bytes.decode('utf-8')) if time.time() - last_write_time < cache_expire_time: logging.info(f"Too soon to write to MySQL for {self.model} ({self.ip})") return # 获取 IPv6 地址 current_ipv6 = self.get_mobile_ipv6() if not current_ipv6: logging.info(f"No IPv6 address found for {self.model} ({self.ip})") return # 检查 IPv6 是否有变化 cached_ipv6_bytes = self.redis_manager.redis_search(redis_key_ipv6) if cached_ipv6_bytes: cached_ipv6 = cached_ipv6_bytes.decode('utf-8') if current_ipv6 == cached_ipv6: logging.info(f"IPv6 address for {self.model} ({self.ip}) has not changed") # 更新上次写入时间 self.redis_manager.redis_set(redis_key_last_write, times=cache_expire_time, value=str(int(time.time()))) return # 写入 MySQL log_message = f"['{self.model}{self.ip}', 'ipv6', '{current_ipv6}']" print(log_message) # 打印到控制台 self.MySQLManager.write_file(log_message) # 更新 Redis 中的 IPv6 地址和上次写入时间 self.redis_manager.redis_set(redis_key_ipv6, times=cache_expire_time, value=current_ipv6.encode('utf-8')) self.redis_manager.redis_set(redis_key_last_write, times=cache_expire_time, value=str(int(time.time()))) except redis.RedisError as e: logging.error(f"Redis error: {e}") except Exception as e: logging.exception(f"An unexpected error occurred: {e}") def create_cron_switch(self, minutes=180, mip='', force = '', this_power = ''): # ESP_AB3FFD print("插座关闭时间为%s分钟" % minutes) import datetime # 获取当前时间 current_time = datetime.datetime.now() # 假设要在当前时间上加上30分钟 minutes_to_add = minutes new_time = (current_time + datetime.timedelta(minutes=minutes_to_add)).strftime("%H:%M:%S") # 输出结果 print("现在时间", current_time.strftime("%H:%M:%S")) print("停止充电时间:", new_time) if mip: try: # 定义该设备充电记录的 Redis 缓存 key redis_charge_key = f"charging_log_cooldown:{self.model}:{self.ip}" # 查询 Redis,如果不存在,说明 5 分钟内没有记录过 if not self.redis_manager.redis_search(redis_charge_key): # 1. 写入 MySQL m = '充电:%s分钟 %s' % (minutes, ",电量:%s" % this_power if this_power else '') text = '[%s, "手机充电", "%s"]' % ((self.model + self.ip + "_" + str(current_time.strftime("%H:%M"))), minutes) self.MySQLManager.write_file("['%s', '手机充电', '%s']" % ((self.model + self.ip + "_" + str(current_time.strftime("%H:%M"))), minutes)) # 2. 写入 Redis 并设置 5 分钟 (300秒) 的过期时间(实现 5 分钟内不重复记录) self.redis_manager.redis_set(redis_charge_key, times=300, value="1") print(f"[{self.ip}] 已记录充电状态,进入 5 分钟冷却期。") else: print(f"[{self.ip}] 5 分钟内已记录过充电,本次跳过写入 MySQL。") except Exception as e: print("充电记录写入失败:", e) # ESP-2222 relay_index = [1, 2] chage_name = [] if mip : try: win_mobile_ips = self.read_yaml['win_mobile_ip'] for dic in win_mobile_ips: key = list(dic.keys())[0] # print("key",key) value = list(dic.values()) if key in ['Redmi K20', 'note11_5g_ip', 'note11_f_5g_ip', 'note9_5g_ip', 'mix3_ip', 'MI_CC9_Pro' ] and mip in value: chage_name = [relay_index[0]] break elif key in ['redmi9_ip', 'note10pro_ip', 'mi8_lite_ip', 'mix2s_ip', 'mi9', 'MI 8 SE', ] and mip in value: chage_name = [relay_index[1]] except Exception as e: print("没有找到要充电的设备", e) chage_name = [relay_index] print("要充电的ip为",mip, "打开的设备为", chage_name) if not chage_name: chage_name = relay_index if mip == self.main_mobile_ip: # 主手机充电 hour = datetime.datetime.now().hour if 0 <= hour <= 8 or force == 'force': print("main_mobile_ip打开充电") switch_2400(ip="192.168.31.162", mode='开', off_list = [str(new_time)], on_list = []) else: print("main_mobile_ip白天不充电") return for relay in chage_name: try: print("2.要充电的ip为",mip, "打开的设备为", relay) new_switch_2400_optimized(ip=self.read_yaml['switch_ip']["ESP-EBFB77"], relay=relay, mode='开', off_list=[str(new_time)],on_list = [] ) # switch_2400(ip=self.read_yaml['switch_ip'][switch_ip], mode='开', off_list = [str(new_time)], on_list = []) except Exception as e: print("没有找到要充电的设备", e) def set_past_text(self, text): # 设置剪贴板 try: # 空格要转义(需要升级) app_name = self.get_app_name() text = text.replace(" ", '\\ ') self.d.app_start("com.github.uiautomator") for i in range(5): self.d.set_clipboard(text, 'label') if self.get_past_text(): break elem = self.d(text="允许") if elem: elem.click() elem = self.d(text="始终允许") if elem: elem.click() self.d.app_start(app_name) except Exception as e: print("手机没安装此应用", e) def get_past_text(self): # 读取剪贴板 text = None try: app_name = self.get_app_name() self.d.app_start("com.github.uiautomator") for i in range(30): text = self.d.clipboard if text: break else: time.sleep(0.1) elem = self.d(text="允许") if elem: elem.click() elem = self.d(text="始终允许") if elem: elem.click() self.d.app_start(app_name) except Exception as e: print("手机没安装此应用", e) return text def select_mi_brown(self): [x, y], k = self.multiple_find_picture([], 10, "点击", "不返回", delem=['d(text="浏览器")', 'd(text="小米浏览器")']) if x > 0 and y > 0: self.multiple_find_picture([], 10, "点击", "不返回", delem=['d(text="始终")', 'd(text="总是")']) def open_url_with_browser(self, url): """ 使用手机默认浏览器打开指定URL 已修复长链接包含 '&' 导致截断的问题 """ if not url: print("URL为空,无法打开") return try: self.open_phone() print(f"正在使用默认浏览器打开: {url}") # 【核心修复】:将 URL 中的 & 替换为 \& # 这样 Android Shell 才能正确识别它是 URL 的一部分,而不是命令结束符 safe_url = url.replace("&", r"\&") # 构造命令 cmd = 'shell am start -a android.intent.action.VIEW -d "%s"' % safe_url # 执行命令 self.console_input(cmd) time.sleep(3) except Exception as e: print(f"打开浏览器失败: {e}") @staticmethod def get_time(): now = datetime.datetime.now() not_time = "%02d:%02d:%02d" % (now.hour, now.minute, now.second) return " " + str(not_time) + "" def zhifubao_good(self): # 支付宝点击好的,不再询问 elem = self.d(text="总是保持以上选择,不再询问") if elem: elem.sibling(index=5) time.sleep(0.5) elem2 = elem.sibling(text="好的") if elem2: elem2.click() def menu_start_app(self, app_name): self.d.press("menu") time.sleep(0.5) [x1, y1], k = self.multiple_find_picture([], 20, "点击", "不返回", delem=['d(resourceId="com.miui.home:id/title", text=%s)' % app_name, 'd(text="%s")' % app_name]) if x1 > 0 and y1 > 0: return True else: return False def sql_zl(self, zl_flag=''): if zl_flag: self.zl_flag = zl_flag print(self.zl_flag) for day in range(10): get_zl = self.MySQLManager.sql_search("content", self.zl_flag, witch_day=-day) if get_zl: str_text = get_zl[0][0] try: print(str_text) except Exception as e: print(e) try: zhu_dic = eval(str_text.replace(self.zl_flag, '').replace('\n', '')) except Exception as e: print("|%s|字典转换发生错误|需要检查字典是否特殊字符串%s" % (self.project, e)) self.MySQLManager.write_file("|%s|字典转换发生错误|需要检查字典是否特殊字符串%s" % (self.project, e)) zhu_dic = {} try: print(zhu_dic) except Exception as e: print(e) if day >= 1: print("复制上%s天数据" % day) self.MySQLManager.write_file(str_text) return zhu_dic else: print("不存在助力码") if self.zl_flag: self.MySQLManager.write_file(self.zl_flag + "{}") return {} def update_zl(self, zl_text, dic='', user='', zl_flag = ''): # 更新表里 if zl_flag: self.zl_flag = zl_flag if zl_text and zl_text != "undefined": zl_text = zl_text.replace('"', '“') get_dic = self.sql_zl() if not dic: if user: dic = user else: # 如果dic位'字典为用户名 dic = self.tao_user try: get_dic[dic] = zl_text except Exception as e: print(e) get_dic = {} self.MySQLManager.sql_update(self.zl_flag, self.zl_flag + str(get_dic)) print("更新后") self.sql_zl() else: print("剪贴板不存在文字") def delete_zl(self, zl_flag='', user=''): # 更新表里 try: self.zl_flag = zl_flag get_dic = self.sql_zl() print(get_dic) get_dic.pop(user) self.MySQLManager.sql_update(zl_flag, zl_flag + str(get_dic)) print("删除%s更新后" % user) self.sql_zl() except KeyError as e: print("【%s】用户不存在,无法删除%s" % (user, e)) @staticmethod def random_dic(dicts): # 字典随机排序 dict_key_ls = list(dicts.keys()) random.shuffle(dict_key_ls) new_dic = {} for key in dict_key_ls: new_dic[key] = dicts.get(key) return new_dic def get_phone_width_height(self): if not self.width or not self.height: try: self.width = self.d.device_info['display']['width'] self.height = self.d.device_info['display']['height'] except: self.width = self.d.info['displayWidth'] self.height = self.d.info['displayHeight'] return self.width, self.height def human_swipe(self, d, start_x, start_y, end_x, end_y): # 滑动的分段数 steps = 15 # 计算每一步的移动距离 dx = (end_x - start_x) / steps dy = (end_y - start_y) / steps # 按下起始点 d.touch.down(start_x, start_y) time.sleep(0.05) # 短暂延迟,模拟人手 # 执行分段滑动 for i in range(steps): # 计算当前步的位置,并加入随机偏移,模拟人手的抖动 move_x = start_x + dx * i + random.uniform(-1, 1) move_y = start_y + dy * i + random.uniform(-1, 1) # 移动到新位置 d.touch.move(move_x, move_y) time.sleep(random.uniform(0, 1) / 10 ) # 等待,控制滑动的总时间 # 释放手指 d.touch.up(end_x, end_y) def tao_hua_yan(self): for i in range(20): [x, y], k = self.multiple_find_picture(['淘宝滑块按钮'], 3, "不点击", "不返回", xpath=['//*[@text="向右滑动验证"]/preceding-sibling::android.widget.FrameLayout'], delem=['d(resourceId="nc_1_n1t")']) if x > 0 and y > 0: width, height = self.get_phone_width_height() swipe_distance = int((0.7 + random.uniform(0, 0.1)) * width) print('滑块验证,手机宽度%s, 滑动距离%s' % (width, swipe_distance)) # self.d.swipe_points([[x, y], [x + swipe_distance, y]], random.uniform(0.1, 0.5)) # self.d.swipe(x, y, x + swipe_distance, y, duration=0.5) self.human_swipe(self.d, x + random.uniform(-0.05, 0.05), y + random.uniform(-0.01, -0.01), x + swipe_distance, y) # 淘宝验证失败0 time.sleep(0.5) [x, y], k = self.multiple_find_picture(['淘宝验证失败'], 5, "点击", "不返回") if x > 0 and y > 0: print("淘宝验证失败") time.sleep(1) self.back() else: break def friends_help(self, active_dic, help_name='', times=3, return_dic=False): # 助力 try: active_dic = self.random_dic(active_dic) except Exception as e: print("错误%s,原因%s" % (active_dic, e)) return # print("active_dic=", active_dic) help_name = help_name.replace("【", '').replace("】", '') self.d.app_stop(self.app_name) dic = {} for user in active_dic: have_zl = self.MySQLManager.sql_search("content", "%s单项助力一次%s" % (( self.tao_user.replace("*", "\\\\*"), user.replace("*", "\\\\*")))) # print(have_zl) all_zl = self.MySQLManager.sql_search("content", "%s被助力了一次%s" % (user.replace("*", "\\\\*"), help_name)) if all_zl: len_all_zl = len(all_zl) else: len_all_zl = 0 # 1.不能助力自己, 2.被助力的人不能助力3(times)次, 3.一个人只能助力一个人 # print("助力人=%s, 被助力人=%s,被助力%s次,助力了吗=%s" % (self.tao_user, user, len_all_zl, have_zl)) if self.tao_user != user and len_all_zl <= times and not have_zl: if type(active_dic) == list: print(active_dic, ":列表,继续循环") zl_code = active_dic.get(user) if zl_code == 'undefined': continue # 助力码写入剪贴板 self.set_past_text(zl_code) # print(self.get_past_text()) print("助力人=%s, 被助力人=%s,被助力%s次,助力码=%s" % (self.tao_user, user, len_all_zl, zl_code)) help_others_text = "%s单项助力一次%s" % (self.tao_user, help_name) self.MySQLManager.write_file(help_others_text) self.MySQLManager.write_file("%s被助力了一次%s" % (user, help_name)) if return_dic: dic['user'] = user dic['help_others_text'] = help_others_text return dic else: return help_others_text else: print("%s没有需要助力的人了" % self.tao_user) return False def set_voice_to_0(self): # 设置音量为0 version = self.console_input('shell getprop ro.build.version.release', ret=1).strip() print("安卓" + version) if version == '11': # adb shell service call audio 10 i32 3 i32 0 i32 1 self.console_input('shell service call audio 10 i32 3 i32 0 i32 1') else: self.console_input('shell media volume --set 0') # adb -s 192.168.31.182 shell cmd media_session volume --stream 3 --set 0 try: # 大于安卓11适合 self.console_input('shell cmd media_session volume --stream 3 --set 0') except Exception as e: print(e) return version def connect_adb(self): for t in range(1): adb_return = os.popen(self.adb_path + ' devices').read() adb_return = adb_return.replace(" ", "").replace("\t", "") print('adb_return=', adb_return) # 192.168.31.109:5555 offline if self.ip + self.port + 'device' in adb_return: print("t=%s,%sadb连接成功" % (t, self.ip + self.port)) return True elif self.ip + self.port in adb_return: print("t=%s,%s连接了adb状态不是devices,需要disconnect" % (t, self.ip + self.port)) cmd1 = '%s disconnect %s%s ' % (self.adb_path, self.ip, self.port) os.system(cmd1) else: # 'echo %s | sudo -S %s' % ('123456', 'poweroff') print("t=%s,%s没连接adb需要连接adb" % (t, self.ip + self.port)) cmd1 = '%s connect %s%s ' % (self.adb_path, self.ip, self.port) # os.system(cmd1) try: adb_return = os.popen(cmd1).read() print(adb_return) except Exception as e: print(e, cmd1) time.sleep(1) # 导出app def export_app(self, app_cn_name='myaccessibility', app_name='com.example.myaccessibility', ip='', port='5555'): # PS D:\mobile> adb -s 192.168.31.182 shell pm path com.example.myaccessibility #package:/data/app/~~2-Bi6oaE7mjf490AKiXzDw==/com.example.myaccessibility-Vl8LBabAMSjyAN3dWJEa3A==/base.apk #PS D:\mobile> adb -s 192.168.31.182 pull /data/app/~~2-Bi6oaE7mjf490AKiXzDw==/com.example.myaccessibility-Vl8LBabAMSjyAN3dWJEa3A==/base.apk D:\android_apps\myaccessibility.apk if ip: self.ip = ip if port: self.port = ':' + port self.read() # if not self.ip: # print("不存在ip%s" % self.ip) # return cmd1 = '%s -s %s%s shell pm path %s' % (self.adb_path, self.ip, self.port, app_name) print(cmd1) output = os.popen(cmd1).read() print(output) if "Windows" in self.system: main_dir = 'D:\\' elif "macOS" in self.system: print("mac系统") main_dir = self.read_yaml['mac_computer']['project_path'] else: main_dir = self.read_yaml['main_ubantu_computer']['share_path'] if not app_cn_name: app_cn_name = os.path.join(main_dir, 'android_apps', app_name + '.apk') else: app_cn_name = os.path.join(main_dir, 'android_apps', app_cn_name + '.apk') if 'package' in output: # package:/data/app/~~2-Bi6oaE7mjf490AKiXzDw==/com.example.myaccessibility-Vl8LBabAMSjyAN3dWJEa3A==/base.apk path = output.split(':')[1].strip() cmd2 = '%s -s %s%s pull %s %s' % (self.adb_path, self.ip, self.port, path, app_cn_name) print(cmd2) os.popen(cmd2) # adb -s 192.168.31.176 install -t /mnt/nas/share/android_apps/myaccessibility.apk # -t 强制安装 def read(self): def delete_text(): self.redis_manager.redis_delete('手机离线%s' % self.ip) # l1.2删除公网ip的手机离线 # host = self.read_yaml['server']['domain'] # port = self.read_yaml['nas_computer']['redis_remote_port'] # manage = RedisManager(host=host, port=port) # manage.redis_delete('手机离线%s' % self.ip) self.xpath_signal = 'text' self.info = 'text' t1 = time.time() if not self.ip: print("不存在ip%s" % self.ip) return if not self.port: self.port = '5555' if ':' not in self.port: self.port = ':' + self.port adb_connect = False # self.MySQLManager.write_file("%s开了%s" % (self.ip, self.project)) for i in range(4): print("i=%s, ip=%s, self.port = %s" % (i, self.ip, self.port)) try: # 若报错可能没连接adb,背其他设置占用,因为多个模拟器使用adb会导致手机没连接,重新wifi连接adb if i > 0 and not adb_connect: raise Exception(" %sadb离线。无需u2.connect" % (self.ip + self.port)) self.d = u2.connect(self.ip + self.port) self.open_phone() self.model = self.d.device_info.get("model") self.model = self.mobile_dic.get(self.model) print("%s的uiautomator2连接成功" % (self.ip + self.port)) print("手机型号=%s" % self.model) # if self.ip == self.note9_5g_ip: # power_switch(1) if not self.model: print("%s不存在字典中" % self.d.device_info.get("model")) try: threading.Thread(target=delete_text).start() # host except: pass return True except Exception as e: print(e) self.re_install_uiautomator(e) time.sleep(1) adb_connect = self.connect_adb() if not adb_connect: print("adb连接失败,使用nodejs与java通信进行处理adb事件") if "Windows" in self.system: project_path = self.read_yaml['win_computer']['project_path'] else: project_path = self.read_yaml['main_ubantu_computer']['project_path'] client_file = os.path.join(project_path, 'node', 'client.js') print(client_file) # os.system('node %s %s' % (client_file, self.ip)) try: print("注意:30版本或者以下adb没有adb pair功能") subprocess.run(["node", client_file, self.ip], timeout=50) # 50秒超时 time.sleep(10) except subprocess.TimeoutExpired: print("命令执行超时") print("nodejs与java通信处理完成") else: sent_text = "%s已经发过邮件" % self.ip have_sent = self.redis_manager.redis_search(sent_text) print("have_sent=", have_sent, 'sent_text=', sent_text) # print(not have_sent and self.ip) if not have_sent and self.ip and self.ip.strip() != ":5555" and self.ip != '192.168.31.42': t2 = time.time() # try: # path = self.cv_record_video(5) # print(path) # except Exception as e: # print(e) # path = None print(t2 - t1) if (t2 - t1) > 80: # 小于100秒不属于离线 # l2手机离线,方便显示2.8寸屏幕 self.redis_manager.redis_set('手机离线%s' % self.ip) host = self.read_yaml['server']['domain'] port = self.read_yaml['nas_computer']['redis_remote_port'] try: manage = RedisManager(host=host, port=port) manage.redis_set('手机离线%s' % self.ip) except: pass # self.MySQLManager.write_file('手机离线%s' % self.ip) # 有效期1小时 self.redis_manager.redis_set(sent_text, times=3600, value='发过') is_reachable = self.is_ip_reachable(self.ip) ex_text = '' print('is_reachable=', is_reachable) if is_reachable: print("能ping通, 可能授权失败unauthorized") ex_text = '能ping通, 可能授权失败unauthorized,' + str(is_reachable) else: print("手机离线,ping不通,可能关机或卡死") ex_text = "手机离线,ping【不】通,可能手机关机或卡死,可能root权限丢了没有(概率很小),is_ip_reachable=" + str(is_reachable) try: times = datetime.datetime.now().strftime("%H:%M:%S") # qq_email("534640040@qq.com", '%s adb连接失败' % self.project, # "%s多次connect失败,用时%.2f秒, %s" % (self.ip, (t2 - t1), len(self.ip.strip())) + str(self.system) + ex_text) catch_key_camera = "%sip已经触发录像" % self.ip have_ = self.redis_manager.redis_search(catch_key_camera, fuzzy_key=True) url = '' if not have_: def clean_filename(filename): # 替换 Windows/Linux 不允许的字符:/ \ : * ? " < > | invalid_chars = r'[\\/:*?"<>|]' # 把非法字符替换成空(删掉) return re.sub(invalid_chars, '', filename) try: hour = datetime.datetime.now().hour date = datetime.datetime.now().strftime("%Y%m%d") ip_part = self.ip.split('.')[-1] # 获取IP地址的最后一部分 raw_filename = '%s%s_%s_adb连接异常%s' % (date, hour, ip_part, ex_text) # 清理后再使用 filename = clean_filename(raw_filename)[:30] # 关键在这里 camera_server_url = self.read_yaml['hnnas_computer']['camera_server_url'] response = requests.post( f"{camera_server_url}/record/start", params={ "duration": 10, "filename": filename + ".mp4" # 加不加 .mp4 后缀都可以,服务端会自动处理 } ) # http://192.168.31.166:8765/v/2026-05-15_13-38-10 url = f"[回放视频]({camera_server_url}/v/{filename})" print(url, response.text) self.redis_manager.redis_set(catch_key_camera, times='today') except Exception as e: print("录屏报错", e) text = "%s %s adb连接失败, %s多次connect失败,用时%.2f秒, %s" % (times, self.project , self.ip, (t2 - t1), len(self.ip.strip())) + str(self.system) + ex_text + url self.MySQLManager.write_file(text ) except Exception as e: print(e) return False def re_install_uiautomator(self, e): # , 'gateway error if 'Invalid version' in str(e) or str(e).strip() == 'closed' or 'gateway error' in str(e): # shell input keyevent KEYCODE_HOME self.console_input('shell input keyevent KEYCODE_HOME') print("卸载com.github.uiautomator") ret = self.console_input('shell dumpsys battery | findstr "temperature"', ret=1) print(ret) self.console_input('uninstall com.github.uiautomator') # if self.d: # self.d.app_start(self.app_name) # pylint: disable=no-member def screen(self, pic_name, region=(), elem=None, offset=[0, 0, 0, 0]): path = os.path.join(self.screen_path, pic_name) # print('path=', path) directory_path = os.path.dirname(path) # 如果目录不存在,则创建多层目录 if not os.path.exists(directory_path): os.makedirs(directory_path) if region: replace_file = path.replace('\\', '/') split_file = replace_file.split('/') template_name = os.path.join('/'.join(split_file[:-1]), self.ip + 'template.png') self.d.screenshot(template_name) # pylint: disable=no-member) image = cv.imdecode(np.fromfile(template_name, dtype=np.uint8), 1) size = image.shape pic_x, pic_y = size[1], size[0] print('图片x=%s, y=%s' % (pic_x, pic_y)) # (y1(上):y2(下), x1(左):x2(右)) # 0.11:0.16, 0.12:0.34(282:395, 134:377]) if 0 < region[0] < 1: crop_img = image[int(region[0] * pic_y): int(region[1] * pic_y), int(region[2] * pic_x): int(region[3] * pic_x)] # 获取感兴趣区域 else: crop_img = image[int(region[0]): int(region[1]), int(region[2]): int(region[3])] # 获取感兴趣区域 # print(path) # 删除原来图片 os.remove(template_name) # cv.imwrite(path, crop_img) # 保存到指定目录 cv.imencode('.png', crop_img)[1].tofile(path) elif elem: print("元素截图") replace_file = path.replace('\\', '/') split_file = replace_file.split('/') template_name = os.path.join('/'.join(split_file[:-1]), self.ip + 'template.png') self.d.screenshot(template_name) # pylint: disable=no-member) image = cv.imdecode(np.fromfile(template_name, dtype=np.uint8), 1) size = image.shape pic_x, pic_y = size[1], size[0] print('图片x=%s, y=%s' % (pic_x, pic_y)) # (y1(上):y2(下), x1(左):x2(右)) # 0.11:0.16, 0.12:0.34(282:395, 134:377]) bounds = elem.info['bounds'] region = [bounds['top'], bounds['bottom'], bounds['left'], bounds['right']] # 处理偏移 if offset != [0, 0, 0, 0]: print(region) # 判断是否为小数比例(0-1之间) if all(0 < abs(o) < 1 for o in offset if o != 0): # 使用手机尺寸比例 region[0] += int(offset[0] * pic_y) # top region[1] += int(offset[1] * pic_y) # bottom region[2] += int(offset[2] * pic_x) # left region[3] += int(offset[3] * pic_x) # right else: # 直接像素偏移 region[0] += int(offset[0]) # top region[1] += int(offset[1]) # bottom region[2] += int(offset[2]) # left region[3] += int(offset[3]) # right print(region) crop_img = image[int(region[0]): int(region[1]), int(region[2]): int(region[3])] # 获取感兴趣区域 # print(path) # 删除原来图片 path = os.path.abspath(path) os.remove(template_name) #cv.imwrite(path, crop_img) # 保存到指定目录 cv.imencode('.png', crop_img)[1].tofile(path) else: for i in range(3): try: self.d.screenshot(path) break except Exception as e: print(e) # print("截图第三种", path) try: if self.model == '【Redmi 8A】': self.threshold = 0.75 set_width = 1080 set_height = 2400 this_width, this_height = self.get_phone_width_height() self.width_offset = this_width / set_width self.height_offset = this_height / set_height im = cv.imdecode(np.fromfile(path, dtype=np.uint8), 1) im_resized = cv.resize(im, (set_width, set_height), interpolation=cv.INTER_LINEAR) cv.imwrite(path, im_resized) except Exception as e: print("[%s]截图[%s]错误" % (self.ip, pic_name)) print(e) return path # self.console_input("shell /system/bin/screencap -p /sdcard/Pictures/%s" % pic_name) # self.console_input('push /sdcard/Pictures/%s %s' % (pic_name, self.screen_path)) def uninstall_app(self, app_name): if '前程无忧' in self.china_name: app_name = 'com.job.android' if '咪咕阅读' in self.china_name: app_name = 'com.ophone.reader.ui' if app_name == "com.ifreetalk.ftalk": print("喜马拉雅app软件真实名字") app_name = "com.ximalaya.ting.android" # self.console_input('uninstall %s' % app_name) elif app_name in self.mi_app_list: ips = self.force_uninstall_app_list.get(app_name, []) if self.ip in ips: print("ip满足在self.force_uninstall_app_list,满足卸载%s" % app_name) else: print("在self.mi_app_list,不在self.force_uninstall_app_list,无需卸载%s" % app_name) return else: print("%s卸载了%s" % (self.model, app_name)) if app_name != "com.baidu.searchbox": self.MySQLManager.write_file("%s卸载了%s" % (self.model, app_name)) self.MySQLManager.write_file("['%s','卸载软件','%s']" % (self.ip + self.model + app_name + '_' + self.project, app_name)) try: self.d.app_uninstall(app_name) except Exception as e: print(e) # self.console_input('uninstall %s' % app_name) # d.app_uninstall("cn.com.open.mooc") def click(self, x, y): # 点击如果x和y小于1按照比例点击 if x < 0 or y < 0: print("不合格的坐标") return if x < 1 and y < 1: width, height = self.get_phone_width_height() # print(width * x, height * y) self.d.click(width * x, height * y) # self.console_input("shell input tap %s %s" % (width * x, height * y)) else: self.d.click(x, y) # self.console_input("shell input tap %s %s" % (x, y)) # 返回 def back(self): self.console_input('shell input keyevent BACK') @staticmethod def dingmessage(content='', is_at_tall=True, secret='SECc776e7cde09261a7dc7d3a9cca02087a72faad2c8d3df5a728da0bbd563a8adf', token='1c50defbb72189a36d243840128b1981a5779058d8e22b46aba0b773e634a6ee'): # 钉钉发送消息 # 加签 timestamp = str(round(time.time() * 1000)) secret = secret secret_enc = secret.encode('utf-8') string_to_sign = '{}\n{}'.format(timestamp, secret) string_to_sign_enc = string_to_sign.encode('utf-8') hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) token = token # 请求的URL,WebHook地址 webhook = f"https://oapi.dingtalk.com/robot/send?access_token={token}×tamp={timestamp}&sign={sign}" print(webhook) # 构建请求头部 header = {"Content-Type": "application/json", "Charset": "UTF-8"} # 循环生成器并发送消息 message = { "msgtype": "text", "text": {"content": content}, "at": { # @ 所有人 "isAtAll": is_at_tall } } message_json = json.dumps(message) info = requests.post(url=webhook, data=message_json, headers=header, verify=False) # 打印返回的结果 print(info.text) def get_pic_most_recently_called_sort(self, len_pic): try: dic = {} for sql_pic_name in len_pic: sql_pic_name = sql_pic_name[0] search_text = "%s]找到图片\\\\[%s\\\\]" % (self.ip, sql_pic_name) get_text = self.MySQLManager.sql_search("content", search_text) if get_text: dic[sql_pic_name] = len(get_text) else: dic[sql_pic_name] = 0 get_dic = sorted(dic.items(), key=lambda item: item[1], reverse=True) # print(get_dic) pic_list = [] for d in get_dic: pic_list.append([d[0]]) return pic_list except Exception as e: print(e) return def select_text(self, selection, cut_list_text): import requests import jieba headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0', } text_lis = [] for text in cut_list_text: if text: a = jieba.lcut(text, cut_all=True) # 全模式 text_lis.append(a) print(text_lis) url = "https://www.sogou.com/web?query={}".format(selection) r = requests.get(url, headers=headers) r.encoding = 'utf-8' html = r.text # print(html) lis_num = [] for t in cut_list_text: num = 1 for word in t: num += html.count(word) lis_num.append(num) print(lis_num) # 首先获取列表中的最大值 max_data = max(lis_num) # 获取最大值的索引 max_solution_index = lis_num.index(max_data) print(max_solution_index, cut_list_text[max_solution_index]) return cut_list_text[max_solution_index] def mobile_reboot(self): # 不能重启手机 if self.ip not in [self.main_mobile_ip]: self.MySQLManager.write_file("%s重启手机" % self.model) self.console_input('shell reboot') for i in range(120): time.sleep(10) result = self.is_ip_reachable(self.ip) if result: print("连接成功无需再连接") break else: print("重启后无法连接") else: # 不能重启,卸载com.github.uiautomator print("不能重启,卸载com.github.uiautomator") self.console_input('shell input keyevent KEYCODE_BACK') time.sleep(2) self.console_input('shell input keyevent KEYCODE_HOME') self.console_input('uninstall com.github.uiautomator') text_search = "%s已经完成com.github.uiautomator" % self.ip value = self.redis_manager.redis_search(text_search) if value and int(value.decode('utf-8')) >= 3: print("已经1小时内卸载多次无需再卸载,直接重启" ) self.mobile_reboot() self.redis_manager.redis_set(text_search,times=3600, is_countable=True) def waite_time_dotask(self, model='r',wait_time=10, key_word='', user=''): if self.project: key_word += self.project now = datetime.datetime.now() text = '%s完成%s' % (user, key_word) if model == 'r': read_content = self.redis_manager.redis_search(text) # print('text=',text, "read_content=", read_content) if read_content or read_content == b'': print("不满足大于%s分钟,无需要做完成%s" % (wait_time, key_word)) return False else: print("满足做任务大于%s分钟,需做完成%s" % (wait_time, key_word)) return True else: # self.MySQLManager.write_file('%s完成%s%s' % (user, key_word, now)) self.redis_manager.redis_set(text, times=wait_time * 60) def get_imobj_with_cache(self, pic_path): # 1. 先找本地内存 if pic_path in self.local_imobj_cache: return self.local_imobj_cache[pic_path] # 2. 本地没有,找 Redis # 注意:图片名作为 Key,为了防止冲突加个前缀 redis_key = f"im_cache:{pic_path}" try: cached_bytes = self.redis_manager.redis_search(redis_key) # 假设你的方法返回 bytes if cached_bytes: imobj = pickle.loads(cached_bytes) self.local_imobj_cache[pic_path] = imobj # 存入本地内存供下次直接用 return imobj except Exception as e: print(f"Error reading image from Redis: {e}") # 3. 都没有,读磁盘 if os.path.exists(pic_path): imobj = cv.imdecode(np.fromfile(pic_path, dtype=np.uint8), 1) if imobj is not None: # 存入 Redis (过期时间设为 1 天,避免占用太多内存) self.redis_manager.redis_set(redis_key, value=pickle.dumps(imobj), times=86400) # 存入本地 self.local_imobj_cache[pic_path] = imobj return imobj return None def file_list(self, base_path, content_prefix): """ 优化后的文件列表查找: 1. 全量缓存目录结构,消除 os.listdir 2. 缓存正则编译结果 3. 消除重复的磁盘 IO 检查 """ # --- 步骤 1: 确保目录索引已建立 --- if base_path not in self._DIR_INDEX: self._scan_and_index_dir(base_path) # --- 步骤 2: 确定搜索路径和前缀 --- sub_folder = "" if "_" in content_prefix: sub_folder = content_prefix.split("_")[0] # --- 步骤 3: 从内存索引中获取匹配项 --- # 索引结构:_DIR_INDEX[base_path][sub_folder] = [file1, file2...] file_pool = self._DIR_INDEX[base_path].get(sub_folder, []) if not file_pool and sub_folder: # 如果子文件夹没找到,尝试在根目录找 file_pool = self._DIR_INDEX[base_path].get("", []) # --- 步骤 4: 缓存并使用正则表达式 --- if content_prefix not in self._REGEX_CACHE: # 预编译正则,提高匹配速度 self._REGEX_CACHE[content_prefix] = re.compile( r"^" + re.escape(content_prefix) + r"\d+\.\w+", re.S ) pattern = self._REGEX_CACHE[content_prefix] # --- 步骤 5: 过滤文件 --- lis = [] for filename in file_pool: if pattern.match(filename): if sub_folder: # 保持原有的路径拼接逻辑返回 lis.append([os.path.join(sub_folder, filename)]) else: lis.append([filename]) return lis def _scan_and_index_dir(self, base_path): """一次性扫描整个目录,建立二级映射索引""" index = {"": []} # 根目录文件 try: # 获取根目录下所有内容 for item in os.listdir(base_path): full_path = os.path.join(base_path, item) if os.path.isdir(full_path): # 如果是子文件夹,扫描子文件夹 index[item] = os.listdir(full_path) else: # 如果是根目录文件 index[""].append(item) self._DIR_INDEX[base_path] = index except Exception: self._DIR_INDEX[base_path] = {"": []} def multiple_find_picture(self, img: list = (), times: int = 20, click: str = "点击", back: str = "不返回", xpath: list = (), delem: list = (), find_pic="one", xy_percentage=False, region: list = (), is_print=True, cache_location=False): """ #循环找图或单次找图 :param img: 图片名字,不带后缀 :param times: 找图次数 :param click:是否需要点击图片 :param back:按返回键一次 :param elem: xpathelem元素查找 :param delem: delem :param find_pic: 是否找多个坐标 :param xy_percentage:x,y显示小数点返回方式 :param region:找图区域 :cache_location:是缓存固定坐标,只有固定的用到,有效期1天 """ self.check_pause_status() x = -1 y = -1 # cache相关 cacheKey = str(xpath) + str(delem) + str(img) if cache_location: # print(cacheKey) xy_percentage = True cache_value = self.redis_manager.redis_search(cacheKey) if cache_value: # print("使用缓存坐标", cache_value) [x, y], k = pickle.loads(cache_value) if is_print: print("使用缓存坐标", [x, y], k) if click == "点击": self.click(x, y) return [x, y], k try: # 充电 # have_watch = self.MySQLManager.sql_search("content", "已经%s设置充电" % datetime.datetime.now().hour) have_watch = self.redis_manager.redis_search("已经设置充电" ) if not have_watch: # print(self.d.device_info) output = self.console_input('shell dumpsys battery', ret=1) match = re.search(r'level: (\d+)', output) power = match.group(1) int_power = int(power) # print(int_power) if int_power <= 15 and self.ip != self.main_mobile_ip: if self.d: self.d.screen_off() # 关闭屏幕 self.create_cron_switch(30, mip=self.ip, this_power=str(int_power)) self.redis_manager.redis_set("已经设置充电", times=3600) else: # 5分钟检测一次 self.redis_manager.redis_set("已经设置充电", times=500) # self.MySQLManager.write_file("已经%s设置充电" % datetime.datetime.now().hour) except Exception as e: # print(e) pass # """===========================↓↓↓↓判断1点抢金主红包↓↓↓↓=================================""" # have_waite = self.MySQLManager.sql_search("content", "%s已经等待过,无需再等待" % self.ip) # if not have_waite: # now = datetime.datetime.now() # t1 = datetime.datetime(now.year, now.month, now.day, 0, 50, 00) # t2 = datetime.datetime(now.year, now.month, now.day, 0, 58, 00) # daley_time = (60 - now.minute) * 60 + 60 # # print(daley_time) # if t1 <= now <= t2: # print("%s满足,需要等待%s秒" % (self.ip, daley_time)) # self.MySQLManager.write_file("%s满足淘宝抢红包任务,需要等待%s秒" % (self.ip, daley_time)) # self.MySQLManager.write_file("%s已经等待过,无需再等待" % self.ip) # time.sleep(daley_time) # # """===========================↑↑↑↑判断1点抢金主红包↑↑↑↑=================================""" # 记录超时时间 start_time = time.time() i = 1 while i <= times: delay = random.randint(1, 5) / 20 # 方法:用memuc调用adb截图,hostname,电脑名字 if delem or xpath: time.sleep(delay) if xpath: # 查找元素 for k in xpath: # print(k) # noinspection PyBroadException if not k: continue try: # x和y传送两个值,所以可以这样写 # a = self.d.xpath('//*[@' + k + ']') a = self.d.xpath(k) # b = a.wait(timeout=None) # print(a) # a = self.d(text=k).wait(timeout=delay) if a: end_time = time.time() spend_time = end_time - start_time if spend_time > 20: # 耗时大于20秒,要唤醒屏幕 self.open_phone() (x1, y1, x2, y2) = a.match().bounds x = int((x1 + x2) / 2) y = int((y1 + y2) / 2) if xy_percentage: width, height = self.get_phone_width_height() x = x / width y = y / height if is_print: print("[小米%s]找到xpath元素[%s]坐标:(%.2f,%.2f)本次找元素花费%s次秒数%.2f秒" % (str(self.model) + str(self.ip), k, x, y, i, spend_time)) # self.MySQLManager.write_file( # "[小米%s]找到xpath元素%s坐标:(%.2f,%.2f)本次找元素花费%s次秒数%.2f秒" % (str(self.model) + str(self.ip), # k, x, y, i, spend_time)) if click == "点击": a.click() if cache_location: ret = [x, y], xpath.index(k) self.redis_manager.redis_set(cacheKey, times='today', value= pickle.dumps(ret)) return [x, y], xpath.index(k) except Exception as result: # print("未知错误%s" % result) pass if delem: for k in delem: # print(i, k) # noinspection PyBroadException try: # x和y传送两个值,所以可以这样写 a = eval("self." + k) # a = self.d(text=k).wait(timeout=delay) if a: end_time = time.time() spend_time = end_time - start_time if spend_time > 20: # 耗时大于20秒,要唤醒屏幕 self.open_phone() x, y = a.center() x = int(x) y = int(y) if xy_percentage: width, height = self.get_phone_width_height() x = x / width y = y / height if is_print: print("[小米%s]找到d元素[%s]坐标:(%.2f,%.2f)本次找元素花费%s次秒数=%.2f秒" % (str(self.model) + str(self.ip), k, x, y, i, spend_time)) # self.MySQLManager.write_file( # "[小米%s]找到d元素[%s]坐标:(%.2f,%.2f)本次找元素花费%s次秒数=%.2f秒" % # (str(self.model) + str(self.ip), k, x, y, i, spend_time)) if click == "点击": # noinspection PyBroadException try: a.click() except Exception: self.click(x, y) if cache_location: ret = [x, y], delem.index(k) self.redis_manager.redis_set(cacheKey, times='today', value= pickle.dumps(ret)) return [x, y], delem.index(k) except Exception as result: # print("未知错误%s" % result) pass if img: # 查找图片 pian_x = 0 pian_y = 0 # self.screen('phone_%s.jpg' % self.ip) # screen_image = self.screen_path + 'phone_%s.png' % self.ip pth = self.screen('Screenshots/phone_%s.png' % self.ip) try: if region: # 上下左右(y1, y2, x1,x2) image = cv.imdecode(np.fromfile(pth, dtype=np.uint8), 1) size = image.shape pic_x, pic_y = size[1], size[0] if is_print: print('图片x=%s, y=%s' % (pic_x, pic_y)) # (y1(上):y2(下), x1(左):x2(右)) # 0.11:0.16, 0.12:0.34(282:395, 134:377]) crop_img = image[int(region[0] * pic_y): int(region[1] * pic_y), int(region[2] * pic_x): int(region[3] * pic_x)] # 获取感兴趣区域 cv.imwrite(pth, crop_img) # 保存到指定目录 pian_x = region[2] * pic_x pian_y = region[0] * pic_y except Exception as e: print(e) imsrc = cv.imdecode(np.fromfile(pth, dtype=np.uint8), 1) for k in range(len(img)): imgobj = img[k] len_pic = len_pic = self.file_list(self.find_pic_path, imgobj)# ^是字符串开头 if len(len_pic) > 1: # 根据最近成功最多次的图片数来排序 len_pic2 = self.get_pic_most_recently_called_sort(len_pic) if len_pic2: len_pic = len_pic2 if len_pic: # 正则遍历图片个数,找几张图:图片都是名字+数字组成 # print(len(len_pic)) for j in range(0, len(len_pic)): pic = len_pic[j][0] # 读取截图图片 full_pic_path = self.find_pic_path + pic # noinspection PyBroadException try: # x和y传送两个值,所以可以这样写 imobj = self.get_imobj_with_cache(full_pic_path) if imobj is None: continue h, w = imobj.shape[:2] # 取彩色图片的高、宽 end_time = time.time() spend_time = end_time - start_time # print("[手机%s]正在查找:[%s],第%s次失败,正在进行%s次,第%d张,花费%s秒" % # (str(self.model) + str(self.ip), pic, i, i + 1, j + 1, spend_time)) res = cv.matchTemplate(imsrc, imobj, cv.TM_CCOEFF_NORMED) # 取匹配程度大于%90的坐标 loc = np.where(res >= self.threshold) # np.where返回的坐标值(x,y)是(h,w),注意h,w的顺序 if find_pic == 'one': for pt in zip(*loc[::-1]): x, y = pt[0] + w / 2, pt[1] + h / 2 # 中心点 if int(x) and int(y) >= 0: end_time = time.time() spend_time = end_time - start_time # 区域截图使用,x1(左)和y1(上) x += pian_x y += pian_y # 坐标偏移,修改图片分辨率会使用 if self.width_offset != 1 and self.height_offset != 1: x = x * self.width_offset y = y * self.height_offset if xy_percentage: width, height = self.get_phone_width_height() x = x / width y = y / height if is_print: get_th = cv.minMaxLoc(res) if get_th and len(get_th) >= 2: get_th = get_th[1] log_msg = f"[手机{self.ip}]找到图片[{pic}]坐标:x={x:.2f},y={y:.2f},精确度={get_th:.2f},花费{spend_time:.2f}秒" print(log_msg) # 👉 关键优化:把每一次成功的找图作为轻量级步骤记录到报告里 with allure.step(f"成功找到图片: {pic} (精度:{get_th:.2f})"): pass time.sleep(delay) if click == "点击": self.d.click(x, y) if cache_location: ret = [x, y], k print('cacheKey=', cacheKey, 'return=', ret) self.redis_manager.redis_set(cacheKey, times='today', value= pickle.dumps(ret)) return [x, y], k else: loc_lis = [] for pt in zip(*loc[::-1]): x, y = pt[0] + w / 2, pt[1] + h / 2 # 中心点 if int(x) and int(y) >= 0: x += pian_x y += pian_y loc_lis.append((int(x), int(y))) time.sleep(delay) end_time = time.time() spend_time = end_time - start_time if len(loc_lis) >= 2: x_lis = np.array(loc_lis)[:, 0][1:].tolist() y_list = np.array(loc_lis)[:, 1][1:].tolist() diff_x = np.var(x_lis, ddof=1) diff_y = np.var(y_list, ddof=1) if diff_x > 1 or diff_y > 1: # print("[手机%s]图片[%s]多个坐标:loc_lis=%s,方差(%s, %s), 花费%s秒" % ( # self.ip, pic, loc_lis, diff_x, diff_y, spend_time)) # ['da_coin', 'da_lv'] text = loc_lis if len(str(loc_lis)) > 10: text = str(loc_lis[:10]) + '……' print(text) # self.MySQLManager.write_file( # "[手机%s]找到图片[%s]多个坐标:loc_lis=%s,方差(%s, %s), 花费%s秒" % ( # self.ip, pic, text, diff_x, diff_y, spend_time)) return [k, k], loc_lis except Exception: # print("未知错误%s" % res) pass end_time = time.time() spend_time = end_time - start_time # print("[手机%s]正在查找:[%s],第%s次失败,正在进行%s次,第%d张,花费%s秒" % # (str(self.model) + str(self.ip), pic, i, i + 1, j + 1, spend_time)) if back == "返回" and i % 2 == 0: if is_print: print("[%s%s]返回一次" % (self.model, self.ip)) self.back() # 唤醒屏幕 self.open_phone() time.sleep(2.5) if back == "下滑" and i % 2 == 0: if is_print: print("%s下滑一次" % self.ip) # self.d.swipe_points([[0.5, 0.8], [0.5, 0.3]], 0.1) self.swipe(389, 967 + random.randint(0, 10), 389, 397 + random.randint(0, 10)) time.sleep(1) if back == "上滑" and i % 2 == 0: if is_print: print("%s上滑一次" % self.ip) # self.d.swipe_points([[0.5, 0.3], [0.5, 0.8]], 0.1) self.swipe(389, 397 + random.randint(0, 10), 389, 967 + random.randint(0, 10)) time.sleep(1) i += 1 else: end_time = time.time() spend_time = end_time - start_time try: img = "_".join(xpath) + "_".join(img) + "_".join(delem) except Exception as e: print(e) img = "" # 判断大于50秒不正常 text = "手机%s,
第%s次真的找不到%s,
本次找图花费秒数=%.2f秒,
任务=%s" % (str(self.model) + str(self.ip), i, img, spend_time,self.project) if spend_time > 100 and '今日头条' not in self.project: self.MySQLManager.write_file("%s找图时间过长 %s" % (self.ip, text)) self.mobile_reboot() if is_print: print("|找图超过规定次数|,%s" % text) if i > 10: logging.warning(self.get_time() + "|找图超过规定次数|,%s" % text) # self.MySQLManager.write_file( # "|找图超过规定次数|,%s" % text) if i >= 20: self.ui_layout(name=text,d=self.d, mode='失败, 多次找图失败') return [-1, -1], -1 def cv_record_video(self, times): # opencv 调用摄像头录制视频 # times 秒数 cap = cv.VideoCapture(0) if not cap.isOpened(): print("Cannot open camera") return frame_width = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)) fps = int(cap.get(cv.CAP_PROP_FPS)) path = '/mnt/nas/share/report/output.mp4' # 更改编解码器为H.264并将输出格式更改为MP4 fourcc = cv.VideoWriter_fourcc(*'mp4v') out = cv.VideoWriter(path, fourcc, fps, (frame_width,frame_height)) count = 0 while count < times * fps: ret, frame = cap.read() if not ret: break out.write(frame) count += 1 cap.release() out.release() return path def is_video_valid(self, filename): try: cap = cv.VideoCapture(filename) if not cap.isOpened(): return False else: return True except Exception as e: print(f"Error: {e}") return False def start_sr(self, cost_time='150'): # 开始录屏 ip = self.ip + self.port print("开始录屏", ip) if cost_time: print("设置时长时候,应该1-180之间") os.popen(self.adb_path + " -s " + ip +" shell screenrecord --time-limit %s /sdcard/screen.mp4" % cost_time) else: os.popen(self.adb_path + " -s " + ip +" shell screenrecord /sdcard/screen.mp4") def end_sr(self, name='录屏测试'): # 结束录屏 ip = self.ip + self.port video_path = os.path.join(self.screen_path, "demo.mp4") print("结束录屏", ip) os.system(self.adb_path + " -s " + ip + " shell pkill -l SIGINT screenrecord") time.sleep(1) os.system(self.adb_path + ' -s %s pull /sdcard/screen.mp4 %s' %(ip, video_path)) try: if self.is_video_valid(video_path): allure.attach.file(video_path,name=name, attachment_type=allure.attachment_type.MP4) os.remove(video_path) self.console_input('shell rm /sdcard/screen.mp4') except Exception as e: print(e) try: # 删除多余视频 # 获取手机根目录下的文件列表 output = self.console_input('shell ls /sdcard/',ret=1).splitlines() #print(output) # 遍历文件列表,找到以 "screen" 开头的文件并删除 for file in output: # print('file=', file) if file.startswith('screen'): print(file) self.console_input('shell rm /sdcard/{}'.format(file)) except Exception as e: print(e) def allure_screen(self, name='找图'): path = self.screen('phonetimeout/' + self.model + '_ocr.png') try: allure.attach.file(path, name=name, attachment_type=allure.attachment_type.PNG) except Exception as e: print(e) def swipe(self, x1, y1, x2, y2): if x1 < 1 and y1 < 1 and x2 < 1 and y2 < 1: width, height = self.get_phone_width_height() # self.console_input("shell input tap %s %s" % (width * x, height * y)) self.console_input("shell input swipe %s %s %s %s" % (width * x1, height * y1, width * x2, height * y2)) else: self.console_input("shell input swipe %s %s %s %s" % (x1, y1, x2, y2)) def tb_timeout_option(self, content): """不同脚本,调用不一样的""" try: self.z += 1 print("|超时|手机%s,z=%s,运行%s程序" % (self.ip, self.z, content)) logging.error(self.get_time() + "|超时|手机%s,z=%s,运行%s程序" % (self.ip, self.z, content)) self.MySQLManager.write_file("|超时|手机%s,z=%d,运行%s程序" % (self.ip, self.z, content)) self.ui_layout(name=content, d=self.d, mode='失败, 主动触发tb_timeout_option') except Exception as e: print(e) def console_input(self, cmd_console, ret=0): cmd = f"{self.adb_path} -s {self.ip}{self.port} {cmd_console}" if ret == 1: # 需要返回结果时,使用 subprocess 捕获输出 process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process.communicate() if process.returncode != 0: print(f"ADB Command Error: {error.decode('utf-8')}") return "" return output.decode('utf-8') else: # 仅执行命令,不关心输出 subprocess.Popen(cmd, shell=True).wait() def get_app_name(self): result = self.d.info['currentPackageName'] return result def open_phone(self): k = 0 # print(self.separation) for i in range(20): # 找不到一直循环 try: if self.d.info.get("screenOn") is False: k += 1 self.d.unlock() # 解锁, else: # 判断是否密码锁,是红米note8 if self.ip: if k != 0: # 不等于0说明参与解锁了,所以要找密码 print("输入密码") try: a = self.d.xpath('//*[@text="紧急呼叫"]').wait(2) if a: if not self.separation: print("找到,self.separation为空,让它等于第一个分身密码") self.separation = separation_phone1 for j in self.separation: self.d.xpath('//*[@text="%s"]' % j).click() time.sleep(0.2) time.sleep(1) except Exception as e: print(e) return True else: return True except Exception as e: print(e) self.re_install_uiautomator(e) def jurisdiction(self): # 这一个某些软件点统一才计算秒数 # 打开分身的app//*[@ self.multiple_find_picture([], 1, "点击", "不返回", delem=['d(resourceId="android.miui:id/app1")']) [x1, y1], k = self.multiple_find_picture([], 1, "点击", "不返回", ['//*[@text="仅在使用中允许"]', '//*[@text="同意"]', '//*[@text="同意并继续"]', '//*[@text="本次运行允许"]', '//*[@text="允许"]', '//*[@text="始终允许"]', '//*[@text="仍然支付"]']) # 弹出来权限,第一个红米note8,第二个小米4的 if x1 > 0 and y1 > 0: # 红米note8也可能检测到 print("点击6次允许") self.multiple_find_picture([], 1, "点击", "返回", delem=['d(text="确定")']) for z in range(8): time.sleep(1) self.d.click(x1, y1) def switch_user(self, flag1='//*[@content-desc="我的淘宝"]', flag2='//*[@content-desc="设置"]', flag3='//*[@text="切换账户"]'): print("准备切换到%s" % self.tao_user) self.MySQLManager.write_file("准备切换到%s" % self.tao_user) time.sleep(5) if self.z >= 3: # 为什么是7因为,上面执行完还没有一轮 return print("准备查到我的") [x, y], k = self.multiple_find_picture([], 10, "点击", "返回", [flag1], delem=['d(text="我的")']) if x > 0 and y > 0: try: text = self.d(description="设置").sibling(index=3).info['contentDescription'] if text.endswith(self.tao_user[-1]) and text.startswith(self.tao_user[0]): print("%s根据首尾字符串判断切换账号为当前登录账号,不用切换" % (self.model + self.tao_user)) self.MySQLManager.write_file("%s根据首尾字符串判断切换账号为当前登录账号,不用切换" % (self.model + self.tao_user)) return True # print(text.endswith(self.tao_user[-1])) # print(text.startswith(self.tao_user[0])) except Exception as e: print(e) time.sleep(1) [x1, y1], k = self.multiple_find_picture([], 20, "点击", "不返回", [flag2]) if x1 > 0 and y1 > 0: time.sleep(5) [x2, y2], k = self.multiple_find_picture([], 20, "点击", "下滑", [ '//*[@resource-id="com.taobao.taobao:id/tv_multi_account"]', flag3 ]) if x2 > 0 and y2 > 0: time.sleep(1) [x3, y3], k = self.multiple_find_picture([], 20, "点击", "不返回", ['//*[@text="{}"]'.format(self.tao_user)]) if x3 > 0 and y3 > 0: [x4, y4], k = self.multiple_find_picture([], 40, "不点击", "不返回", [' ', flag1], delem=['d(index=0, ' 'className="android.widget.RelativeLayout").' 'child(text="%s")' % self.tao_user]) if x4 > 0 and y4 > 0: if k == 0: print("%s需要切换账号为当前登录账号,不用切换" % (self.model + self.tao_user)) self.MySQLManager.write_file("%s需要切换账号为当前登录账号,不用切换" % (self.model + self.tao_user)) if k == 1: print("%s切换账号后找到我的淘宝,说明切换成功" % (self.model + self.tao_user)) self.MySQLManager.write_file("%s切换账号后找到我的淘宝,说明切换成功" % (self.model + self.tao_user)) return True self.tb_timeout_option("switch_user") self.d.app_stop(self.app_name) time.sleep(2) self.d.app_start(self.app_name) self.switch_user(flag1, flag2, flag3) """##################QQ聊天#####################""" def qq(self, qq_num, text): self.d.app_start("com.tencent.mobileqq") for i in range(20): self.d.app_start("com.tencent.mobileqq") [x, y], k = self.multiple_find_picture([], 3, "点击", "返回", ['//*[@resource-id="com.tencent.mobileqq:id/et_search_keyword"]']) if x > 0 and y > 0: time.sleep(0.2) [x1, y1], k = self.multiple_find_picture([], 3, "点击", "不返回", [], delem=[ 'd(resourceId="com.tencent.mobileqq:id/et_search_keyword")' '.send_keys("%s")' % qq_num]) if x1 > 0 and y1 > 0: print("点击人,进入") time.sleep(0.2) click_person = self.d.xpath( '//*[@resource-id="com.tencent.mobileqq:id/eap"]/android.widget.LinearLayout[2]') if click_person: click_person.click() print("发送文本") [x2, y2], k = self.multiple_find_picture([], 6, "不点击", "不返回", [], delem=['d(resourceId="com.tencent.mobileqq:id/input")']) if x2 > 0 and y2 > 0: print("准备点击") self.d(resourceId="com.tencent.mobileqq:id/input").send_keys(text) time.sleep(0.2) self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(text="发送")']) break else: self.back() return """##################开启性能模式#####################""" def up_up(self): self.open_phone() try: print("开启性能模式") self.d.open_notification() [x, y], k = self.multiple_find_picture([], 20, "不点击", "不返回", [], delem=['d(text="已开启性能模式")', 'd(resourceId="android:id/icon", description="设置")']) if x > 0 and y > 0: print("不需要打开性能模式") self.back() return print("需要开启性能模式") # 性能模式,关闭 time.sleep(0.2) self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(description="性能模式")', 'd(description="性能模式,关闭")']) self.back() # self.d.app_start("com.android.settings") # self.d(resourceId="android:id/input").send_keys("运行模式") # self.d(text="电量和性能/性能").click() # self.d(text="运行模式").click() # self.multiple_find_picture(["xn"], 20, "点击", "不返回") # time.sleep(1) self.MySQLManager.write_file("打开性能模式成功") except Exception as e: self.MySQLManager.write_file("打开性能模式失败 %s" % e) """##################开启和关闭震动#####################""" def close_shock(self, ip=''): self.ip = ip print("准备关闭震动") self.read() print("准备关闭震动") # self.d.open_notification() self.d.swipe_points([[0.8, 0.0], [0.8, 0.1]], 0.1) time.sleep(0.5) [x, y], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(description="振动,开启")']) if x > 0 and y > 0: print("关闭震动") self.back() def open_shock(self, ip=""): self.ip = ip print("准备打开震动") self.read() self.open_phone() # 解锁 # self.d.open_notification() self.d.swipe_points([[0.8, 0.0], [0.8, 0.1]], 0.1) time.sleep(0.5) [x, y], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(description="振动,关闭")']) if x > 0 and y > 0: print("开启震动") self.back() """##################卸载小米4和红米note2多余app#####################""" def uninstall_other_app(self, ip='', port=':5555', set_day=2): if ip: self.ip = ip self.port = port self.read() # self.open_phone() # 解锁 if self.ip != self.main_mobile_ip: print("卸载多余的app") # self.MySQLManager.write_file("%s进入卸载多余app程序" % self.ip) for package_name_en in self.d.app_list(): # if i in self.mi_app_list: # pass # elif i in self.read_yaml['can_uninstall_app']: # self.uninstall_app(i) get_str = self.console_input('shell dumpsys package %s' % package_name_en, ret=1) r_list = re.compile('firstInstallTime=(\\d{4}-\\d{2}-\\d{2})', re.S).findall(get_str) if r_list: try: first_install_name = r_list[0] first_install_name = datetime.datetime.strptime(first_install_name, '%Y-%m-%d').date() today = datetime.datetime.now().date() diff_day = (today - first_install_name).days print('%s第一次安装时间%s,已经安装%s天' % (package_name_en, first_install_name, diff_day)) if diff_day <= set_day: print("=" * 100) print("%s满足卸载" % package_name_en) print("=" * 100) self.uninstall_app(package_name_en) except Exception as e: print("错误%s" % e) print("卸载结束") # if self.ip == mi_4_ip: # print("卸载快手") # self.d.app_uninstall("com.kuaishou.nebula") # self.d.app_uninstall("com.eg.android.AlipayGphone") # print("卸载趣头条") # self.d.app_uninstall("com.jifen.qukan") # print("卸特价版淘宝") # self.d.app_uninstall("com.taobao.litetao") """##################手机定时关机#####################""" def on_off_phone(self, z=0): self.z = z self.read() print("self.z=%s" % self.z) if self.z >= 5: self.MySQLManager.write_file("%s超过设置开机时间指定次数" % self.ip) self.z = 0 return self.open_phone() print("进入设置关机时间") now_hour = datetime.datetime.now().hour a = self.MySQLManager.sql_search('content', "%s%s点已完成%s任务" % (self.ip, now_hour, '开机时间')) b = self.MySQLManager.sql_search('content', "%s%s点已完成%s任务" % (self.ip, now_hour, '关机时间')) print(a, b) if 'MI 4LTE' in self.model: print("无需设置开关机") return if a and b: print("满足不用设置开关机时间了") return try: self.d.app_stop("com.android.settings") except Exception as e: print(e) self.d.press("home") self.d.press("search") time.sleep(0.5) [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", [], delem=['d(resourceId="com.android.quicksearchbox:id/query_text")', 'd(text="同意并继续")']) if x > 0 and y > 0: if k == 1: self.tb_timeout_option("找到同意并继续,说明第一次搜索") self.on_off_phone() return try: self.d(resourceId="com.android.quicksearchbox:id/query_text").send_keys("省电与电池") except Exception as e: print(e) print("输入电量和性能") # self.d(resourceId="com.android.quicksearchbox:id/query_text").send_keys("定时开关机") [x1, y1], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(description="icon 省电与电池 手机管家 | 应用功能")', 'd(description="电量和性能 设置/电量和性能")', 'd(text="电量和性能 设置/电量和性能")', "d(text='设置').sibling(className='android.view.View').child(text='省电与电池')", "d(text='手机管家').sibling(className='android.view.View').child(text='省电与电池')"]) if x1 > 0 and y1 > 0: print("电量和性能 系统和设备") # [x2, y2], k = self.multiple_find_picture([], 10, "点击", "不返回", [], # delem=[ # 'd(className="android.widget.RelativeLayout").child(text="省电优化")']) # if x2 > 0 and y2 > 0: # print("省电优化") [x3, y3], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(text="电池")', 'd(text="更多电池功能")']) if x3 > 0 and y3 > 0: print("电池") else: print("电池") elemts = ['d(resourceId="android:id/title", text="定时开关机")', 'd(resourceId="com.miui.securitycenter:id/title")', 'd(resourceId="com.miui.securitycenter:id/pc_activity_textview5")'] [x4, y4], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=elemts) if x4 > 0 and y4 > 0: print("定时开关机") else: print("找不到定时开关机从新搜索") self.MySQLManager.write_file("找不到定时开关机从新搜索") self.tb_timeout_option("%s找不到定时开关机从新搜索" % self.ip) self.on_off_phone(self.z) return now_hour = datetime.datetime.now().hour if 17 < now_hour <= 23: print("现在%s点改成凌晨的" % now_hour) self.modify_time('23', "开机时间") # 调节后比目前大于1,比如22就是23 # self.modify_time('19', "关机时间") elif 0 <= now_hour <= 7: print("现在%s点改成上午的" % now_hour) self.modify_time('9', "开机时间") # 调节后比目前大于1,比如22就是23 else: self.modify_time('17', "开机时间") # 调节后比目前大于1,比如22就是23 # self.modify_time('3', "关机时间") time.sleep(0.3) self.screen("Android/%son_off_phone%shour.jpg" % (self.ip, now_hour)) [x5, y5], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(description="确定")']) if x5 > 0 and y5 > 0: print("点对号或点正确") return pass def modify_time(self, num1, open_or_off, count=0): print("%s回到修改时间程序count=%s[目前为%s]" % (self.ip, count, open_or_off)) self.MySQLManager.write_file("%s回到修改时间程序count=%s" % (self.ip, count)) time.sleep(0.3) if count >= 5 or self.z >= 5: print("尝试5次失败") return [x5, y5], k = self.multiple_find_picture([], 10, "不点击", "不返回", [], delem=['d(resourceId="android:id/title", text="%s")' % open_or_off]) if x5 > 0 and y5 > 0: print("找到%s,如果开机instance为0,如果开机instance 等于1" % open_or_off) try: self.search_content(num1, open_or_off, count) except Exception as e: qq_email("534640040@qq.com", '%s 设置开关机失败' % self.ip, "设置开关机报错") pass return else: self.tb_timeout_option("modify_tim") self.on_off_phone() return def search_content(self, num, open_or_off, count): if count >= 5: print("尝试5次失败") self.tb_timeout_option("count_is_more_big") self.on_off_phone() qq_email("534640040@qq.com", '%s 设置开关机多次失败' % self.ip, "设置开关机多次失败") return if open_or_off == "开机时间": instance = 0 else: instance = 1 try: located_time = self.d(textContains=":", instance=instance).info["text"] except Exception as e: print(e) self.tb_timeout_option("located_time_wrong") self.on_off_phone() return if located_time: setting_time = located_time.split(":")[0] print(setting_time) if setting_time == num: print("%s满足" % open_or_off) path = self.screen('Android/' + self.model + '_off_on.png') try: allure.attach.file(path, name=open_or_off, attachment_type=allure.attachment_type.PNG) except Exception as e: print(e) now_hour = datetime.datetime.now().hour self.MySQLManager.write_file("%s%s点已完成%s任务" % (self.ip, now_hour, open_or_off)) else: print(num, setting_time) try: adjust_time = (int(num) - int(setting_time)) % 24 except Exception as e: print(e) self.tb_timeout_option("adjust_time_wrong") self.on_off_phone() return if adjust_time: print("%s不满足,相差%s小时" % (open_or_off, adjust_time)) self.MySQLManager.write_file("%s不满足,%s相差%s小时" % (open_or_off, self.ip, adjust_time)) [x1, y1], k = self.multiple_find_picture([], 10, "点击", "不返回", [], delem=[ 'd(resourceId="android:id/title", text="%s")' % open_or_off]) if x1 > 0 and y1 > 0: time.sleep(1) print("滑动") width, height = self.get_phone_width_height() x = width * 0.2 y = height * 0.7 for i in range(adjust_time): self.d.swipe(x, y, x, y - 200) time.sleep(0.5) self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(resourceId="android:id/button1")']) count += 1 self.search_content(num, open_or_off, count) return def elem_width_high_deal(self, times = 30, elem=None, h=None, w = None, click=None): for i in range(times): if elem: if type(elem) == str: # 字符串才可以 # print(elem % i) d_elem = eval(elem % i) else: d_elem = elem if d_elem: bounds = d_elem.info['bounds'] height= bounds['bottom']-bounds['top'] width= bounds['right']-bounds['left'] center = d_elem.center() # print(height, width, center) if h and w: if height == h and width == w: if click: d_elem.click() return center elif w: if width == w: if click: d_elem.click() return center elif h : if height == h: if click: d_elem.click() return center def jxsd(self, times=40, text_search="奖励现身,点我领",img_search = [], back_elem=[], break_elemts = []): # d(text="下滑浏览商品流,可寻找卡片并提升星球探索度") break_elemts.append('d(text="验证码拦截")') print(text_search, img_search) time.sleep(3) for i in range(times): # self.d.swipe_ext("up", 0.8) self.d.swipe_points([[0.5, 0.8], [0.5, 0.3]], 0.1) # print("滑动") try: # elem = self.d(text=text_search) [x, y], k = self.multiple_find_picture(img_search, 2, "点击", "不返回", [], delem=['d(text="%s")' % text_search], xy_percentage=True, is_print=False) if x > 0 and y > 0: # center = elem.center() if 0.12< y < 0.92: # print(y_per) # elem.click() self.click(x, y) time.sleep(0.5) [x, y], k = self.multiple_find_picture([], 8, "不点击", "不返回", [], delem=['d(text="开心收下 ")', 'd(text="我知道了")', 'd(textContains="碎片溜走了")', 'd(textContains="包裹碎片")', 'd(textContains="下滑浏览商品流")'], xy_percentage=True) if x > 0 and y > 0: if k == 0 or k == 1: self.click(x, y) # elif k == 2: # text_elem = self.d(textContains="包裹碎片") # if text_elem: # text2 = text_elem.info['text'] # print(text2) else: if back_elem: self.multiple_find_picture([], 20, "不点击", "返回", [], delem=back_elem, xy_percentage=True) else: [x, y], k = self.multiple_find_picture([], 1, "点击", "不返回", [], delem=['d(text="开心收下 ")'], xy_percentage=True) if break_elemts: [x, y], k = self.multiple_find_picture([], 1, "点击", "不返回", [], delem=break_elemts, xy_percentage=True, is_print=False) if x > 0 and y > 0: break except Exception as e: print(e) self.d.swipe_ext("up", 0.9) def wait_time(self): i = datetime.datetime.now() minute, second = i.minute, i.second delay_time = random.uniform(1, 3) all_second = 60 * 60 - (minute * 60 + second) + delay_time if minute > 0: print("等待%s秒去找红包,延迟%s秒" % (all_second, delay_time)) self.MySQLManager.write_file("等待%s秒去找红包,延迟%s秒" % (all_second, delay_time)) time.sleep(all_second) else: print("不需要等待") # pylint: disable=no-member, disable=assignment-from-none def slider_verification(self): [loc_x, loc_y], k = self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(description="javascript:;")']) self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(text="请点击此处重试")', 'd(description="请点击此处重试")']) print(loc_x, loc_y) def match_img(img_path1, img_path2): imgs = [] # 展示 sou_img1 = cv.imread(img_path1) if sou_img1 is None: pass sou_img2 = cv.imread(img_path2) # 最小阈值100,最大阈值500 # , 0 img1 = cv.imread(img_path1) blur1 = cv.GaussianBlur(img1, (3, 3), 0) canny1 = cv.Canny(blur1, 100, 500) cv.imwrite('temp1.png', canny1) # , 0 img2 = cv.imread(img_path2) blur2 = cv.GaussianBlur(img2, (3, 3), 0) canny2 = cv.Canny(blur2, 100, 500) cv.imwrite('temp2.png', canny2) target = cv.imread('temp1.png') template = cv.imread('temp2.png') # 调整大小 target_temp = cv.resize(sou_img1, (350, 200)) target_temp = cv.copyMakeBorder(target_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) template_temp = cv.resize(sou_img2, (200, 200)) template_temp = cv.copyMakeBorder(template_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) imgs.append(target_temp) imgs.append(template_temp) # theight, twidth = template.shape[:2] # 匹配跟拼图 result = cv.matchTemplate(target, template, cv.TM_CCOEFF_NORMED) cv.normalize(result, result, 0, 1, cv.NORM_MINMAX, -1) min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) # # # 画圈 # cv.rectangle(target, max_loc, (max_loc[0] + twidth, max_loc[1] + theight), (0, 0, 255), 2) # target_temp_n = cv.resize(target, (350, 200)) # target_temp_n = cv.copyMakeBorder(target_temp_n, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) # imgs.append(target_temp_n) # imstack = np.hstack(imgs) # # cv.imshow('windows' + str(max_loc), imstack) # cv.waitKey(0) # cv.destroyAllWindows() # 计算距离 # print(max_loc) distance = str(max_loc).split()[0].split('(')[1].split(',')[0] x_dis = int(distance) + 150 # 150根据自己设备而定 # t(x_dis) print("距离为%s" % x_dis) return x_dis # pylint: disable=unsubscriptable-object def pic_shear(): # 1029:1162 指的从竖直方向截取 1029~1162(y轴) # 198:331 指的是从水平方向截取 198~331(x轴) pic_path = self.screen_path + self.ip + "screen.png" c1 = self.screen_path + self.ip + "c1.png" c2 = self.screen_path + self.ip + "c2.png" img = cv.imread(pic_path) if loc_y > 1860: cv.imwrite(c1, img[1200:1564, 333:891]) cv.imwrite(c2, img[1200:1564, 189:339]) dist = match_img(c1, c2) else: cv.imwrite(c1, img[853:1277, 333:891]) cv.imwrite(c2, img[855:1276, 189:339]) dist = match_img(c1, c2) return dist # print("已截图") # d(text="拖动滑块完成拼图") [x, y], k = self.multiple_find_picture([], 20, "不点击", "不返回", [], delem=['d(text="拖动滑块完成拼图")', 'd(description="拖动滑块完成拼图")']) if x > 0 and y > 0: # print(x, y) width, height = self.get_phone_width_height() x = x - width * 0.247 # print(x, y) dis = 0 for i in range(15): self.screen(self.ip + "screen.png") dis = pic_shear() if dis < 160: self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(description="javascript:;")']) self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(text="请点击此处重试")', 'd(description="请点击此处重试")']) time.sleep(random.uniform(1, 1.5)) else: self.d.swipe(x, y, x + dis, y) time.sleep(random.uniform(1, 1.5)) [x, y], k = self.multiple_find_picture([], 20, "不点击", "不返回", [], delem=['d(text="拖动滑块完成拼图")', 'd(text="向右拖动滑块填充拼图")', 'd(description="拖动滑块完成拼图")']) if x < 0 and y < 0: print("识别成功了") return else: dis = 0 print("距离为:%s" % dis) def locate_x_y(self, screen_img=None, pic=''): # 默认的截图比较的 def match_img(img_path1, img_path2): imgs = [] # 展示 sou_img1 = cv.imread(img_path1) sou_img2 = cv.imread(img_path2) # 最小阈值100,最大阈值500 # , 0 img1 = cv.imread(img_path1) blur1 = cv.GaussianBlur(img1, (3, 3), 0) canny1 = cv.Canny(blur1, 100, 500) cv.imwrite('temp1.png', canny1) # , 0 img2 = cv.imread(img_path2) blur2 = cv.GaussianBlur(img2, (3, 3), 0) canny2 = cv.Canny(blur2, 100, 500) cv.imwrite('temp2.png', canny2) target = cv.imread('temp1.png') template = cv.imread('temp2.png') # 调整大小 target_temp = cv.resize(sou_img1, (350, 200)) target_temp = cv.copyMakeBorder(target_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) template_temp = cv.resize(sou_img2, (200, 200)) template_temp = cv.copyMakeBorder(template_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) imgs.append(target_temp) imgs.append(template_temp) theight, twidth = template.shape[:2] # 匹配跟拼图 result = cv.matchTemplate(target, template, cv.TM_CCOEFF_NORMED) cv.normalize(result, result, 0, 1, cv.NORM_MINMAX, -1) min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) print("%s, %s, %s, %s" % (min_val, max_val, min_loc, max_loc)) # cv.rectangle(target, max_loc, (max_loc[0] + twidth, max_loc[1] + theight), (0, 0, 255), 2) # target_temp_n = cv.resize(target, (350, 200)) # target_temp_n = cv.copyMakeBorder(target_temp_n, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) # imgs.append(target_temp_n) # imstack = np.hstack(imgs) # # print("imstack=%s" % imstack) # cv.imshow('windows' + str(max_loc), imstack) # cv.waitKey(0) # cv.destroyAllWindows() # 计算距离 # distance = str(max_loc).split()[0].split('(')[1].split(',')[0] # x_dis = int(distance) + 150 # 150根据自己设备而定 # t(x_dis) # print("距离为%s" % x_dis) # ↓↓↓保存图片↓↓↓ x1 = max_loc[0] y1 = max_loc[1] x2 = max_loc[0] + twidth y2 = max_loc[1] + theight print("位置为:x1=%s, y1=%s,x2=%s,y2=%s" % (x1, y1, x2, y2)) # print("%s, %s, %s, %s" % (x1, y1, x2, y2)) return x1, y1, x2, y2 # pylint: disable=unsubscriptable-object def pic_shear(): # 1029:1162 指的从竖直方向截取 1029~1162(y轴) # 198:331 指的是从水平方向截取 198~331(x轴) screen_name_path = self.screen_path + 'phone_%s.png' % self.ip if screen_img: # 不为空时候,需要截图 self.screen('phone_%s.png' % self.ip) edge_detection_picture = self.screen_path + pic x1, y1, x2, y2 = match_img(screen_name_path, edge_detection_picture) img = cv.imread(screen_name_path) ocr_pic = self.screen_path + self.ip + "c1.png" # 保存需要ocr的小图片 cv.imwrite(ocr_pic, img[y1:y2, x1:x2]) # ocr识别图片 content1 = self.orc_l_x(ocr_pic) print_text = "[%s]pytesseract识别结果为%s,\n\t边缘检测图片[%s], \n\t截图为[%s], \n\tocr识别图片[%s]" \ % (self.ip, content1, edge_detection_picture, screen_name_path, ocr_pic) print(print_text) self.MySQLManager.write_file(print_text) return content1 content = pic_shear() return content def toast_get_message(self, max_time=2, ip='192.168.31.161', port='5555', adb_path = '/usr/local/bin/adb'): # 我发现连接uiautomator2.connect就输出空的, # import time # import threading # import subprocess # import queue # import re # try: # captured_output = queue.Queue() # 使用队列来存储捕获的输出 # def capture_output(proc, output_queue): # """ # 捕获并累积子进程的输出,并在结束时返回整个累积的输出。 # """ # while True: # line = proc.stdout.readline() # if not line: # break # output_queue.put(line) # # print(line) # 立即打印输出 # # print(adb_path, '-s', f'{ip}:{port.replace(":", "")}', 'shell', 'uiautomator', 'events') # proc = subprocess.Popen( # [adb_path, '-s', f'{ip}:{port.replace(":", "")}', 'shell', 'uiautomator', 'events'], # stdout=subprocess.PIPE, # stderr=subprocess.STDOUT, # bufsize=1, # 取消缓冲 # universal_newlines=True # ) # output_thread = threading.Thread(target=capture_output, args=(proc, captured_output)) # output_thread.start() # time.sleep(max_time) # proc.terminate() # proc.wait() # output_thread.join() # # 收集所有输出 # captured_output_str = '' # while not captured_output.empty(): # captured_output_str += captured_output.get() # print(captured_output_str) # pattern = r"android\.widget\.Toast; Text: \[(.*?)\];" # # Search the text # matches = re.findall(pattern, captured_output_str) # # Print the results # for match in matches: # if match: # print(match) # return captured_output_str # except: t1 = time.time() for i in range(100): try: text = self.d.toast.get_message(max_time, 2, "default message") if text != "default message": break except: text = "default message" time.sleep(0.1) t2 = time.time() if t2 - t1 >= max_time: break return text def kuai_slider_verification(self): def match_img(img_path1, img_path2): imgs = [] # 展示 sou_img1 = cv.imread(img_path1) sou_img2 = cv.imread(img_path2) # 最小阈值100,最大阈值500 img1 = cv.imread(img_path1) blur1 = cv.GaussianBlur(img1, (3, 3), 0) canny1 = cv.Canny(blur1, 100, 500) cv.imwrite('temp1.png', canny1) img2 = cv.imread(img_path2) blur2 = cv.GaussianBlur(img2, (3, 3), 0) canny2 = cv.Canny(blur2, 100, 500) cv.imwrite('temp2.png', canny2) target = cv.imread('temp1.png') template = cv.imread('temp2.png') # 调整大小 target_temp = cv.resize(sou_img1, (350, 200)) target_temp = cv.copyMakeBorder(target_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) template_temp = cv.resize(sou_img2, (200, 200)) template_temp = cv.copyMakeBorder(template_temp, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) imgs.append(target_temp) imgs.append(template_temp) # theight, twidth = template.shape[:2] # 匹配跟拼图 result = cv.matchTemplate(target, template, cv.TM_CCOEFF_NORMED) cv.normalize(result, result, 0, 1, cv.NORM_MINMAX, -1) min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) # # # 画圈 # cv.rectangle(target, max_loc, (max_loc[0] + twidth, max_loc[1] + theight), (0, 0, 255), 2) # target_temp_n = cv.resize(target, (350, 200)) # target_temp_n = cv.copyMakeBorder(target_temp_n, 5, 5, 5, 5, cv.BORDER_CONSTANT, value=[255, 255, 255]) # imgs.append(target_temp_n) # imstack = np.hstack(imgs) # # cv.imshow('windows' + str(max_loc), imstack) # cv.waitKey(0) # cv.destroyAllWindows() # 计算距离 # print(max_loc) distance = str(max_loc).split()[0].split('(')[1].split(',')[0] x_dis = int(distance) + 250 # 150根据自己设备而定 # t(x_dis) print("距离为%s" % x_dis) return x_dis # pylint: disable=unsubscriptable-object def pic_shear(): # 1029:1162 指的从竖直方向截取 1029~1162(y轴) # 198:331 指的是从水平方向截取 198~331(x轴) pic_path = self.screen_path + self.ip + "screen.png" c1 = self.screen_path + self.ip + "c1.png" c2 = self.screen_path + self.ip + "c2.png" img = cv.imread(pic_path) cv.imwrite(c1, img[273:842, 262:1032]) cv.imwrite(c2, img[278:839, 51:278]) dist = match_img(c1, c2) return dist # print("已截图") # d(text="拖动滑块完成拼图") [x, y], k = self.multiple_find_picture([], 20, "不点击", "不返回", [], delem=['d(text="拖动滑块完成拼图")', 'd(text="向右拖动滑块填充拼图")', 'd(description="拖动滑块完成拼图")']) if x > 0 and y > 0: # print(x, y) width, height = self.get_phone_width_height() x = x - width * 0.4 # print(x, y) dis = 0 for i in range(15): self.screen(self.ip + "screen.png") dis = pic_shear() if dis <= 250: self.multiple_find_picture([], 5, "点击", "不返回", [], delem=['d(textContains="cutPic").sibling(index=0)']) time.sleep(random.uniform(1, 1.5)) else: self.d.swipe(x, y, x + dis, y) break if x >= 0 and y >= 0: print("距离为:%s" % dis) # time.sleep(1) def recognize_text_dddocr(self,image_path: str) -> str: """ 使用 dddocr 识别图片中的文字,返回识别结果字符串。 参数: image_path: 图片文件路径(支持png/jpg等格式) 返回: 识别出的文字字符串 """ import ddddocr # 创建识别器实例,关闭广告打印(verbose=False) ocr = ddddocr.DdddOcr(show_ad=False) # 读取图片字节 with open(image_path, 'rb') as f: img_bytes = f.read() # 识别文字 result = ocr.classification(img_bytes) return result def ddddocr_slider_verification(self, image): print("验证") # d(text="请通过以下验证").child(index=0).child(index=1).child(index=0).child(index=0).child(index=0).child(index=0).info import ddddocr import time import os, base64 import random det = ddddocr.DdddOcr(det=True) # 滑块 time.sleep(2) with open(image, 'rb') as f: image_base64 = str(base64.b64encode(f.read()), encoding='utf-8') # print(image_base64) print('截图成功') poses = det.detection(img=image_base64) print('len(poses)=%s, poses=%s' % (len(poses),poses)) if len(poses) <= 1: return False else: pass # [[694, 33, 737, 74]] # [[691, 29, 740, 80]] offset_y = - 1 index = 0 source_pose = poses[0][0] distance = abs(poses[0][0] - poses[1][0]) elem = self.d(text="向右滑动滑块填充拼图").sibling(index=1) button = elem.info['bounds'] x, y = elem.center() x = int(x) y = int(y) print(button) py = (button.get('right') - button.get('left')) / 4 print(py) move = distance + py print('距离=%s,偏移=%s, 移动距离=%s' % (distance, py, move)) self.d.swipe(x, y, x + distance, y) return True def down_android_app(self, apk_name='应用宝', download_flag=0, means = 'go'): # apk_name = '应用宝' if "Windows" in self.system: main_dir = 'D:\\' project_path = self.read_yaml['win_computer']['project_path'] go_path = self.read_yaml['win_computer']['go_path'] gomodcache_path = self.read_yaml['win_computer']['gomodcache_path'] gocache_path = self.read_yaml['win_computer']['gocache_path'] elif "macOS" in self.system: print("mac系统") main_dir = self.read_yaml['mac_computer']['project_path'] project_path = self.read_yaml['mac_computer']['project_path'] go_path = self.read_yaml['main_ubantu_computer']['go_path'] gomodcache_path = self.read_yaml['main_ubantu_computer']['gomodcache_path'] gocache_path = self.read_yaml['main_ubantu_computer']['gocache_path'] else: main_dir = self.read_yaml['main_ubantu_computer']['share_path'] project_path = self.read_yaml['main_ubantu_computer']['project_path'] go_path = self.read_yaml['main_ubantu_computer']['go_path'] gomodcache_path = self.read_yaml['main_ubantu_computer']['gomodcache_path'] gocache_path = self.read_yaml['main_ubantu_computer']['gocache_path'] path = os.path.join(main_dir, 'android_apps') if not os.path.exists(path): os.makedirs(path) if not os.path.exists(go_path): means = 'python' # 方法1:go语言 if means == 'go': print("go语言") os.environ['PATH'] = os.path.normpath(go_path) os.environ['GOMODCACHE'] = gomodcache_path os.environ['GOCACHE'] = gocache_path os.environ['GOPROXY'] = 'https://mirrors.aliyun.com/goproxy/' # 切换到工程目录 os.chdir(project_path) print(os.getcwd()) def call_go_program(path, app_name): # go_path command = ['go', 'run', os.path.join(project_path, 'download_android_app.go'), path, app_name] result = subprocess.run(command, capture_output=True, text=True, encoding='utf-8') output = result.stdout.strip() # console = 'go run %s %s %s' % (os.path.join(project_path, 'download_android_app.go'), path, app_name) # output = os.popen(console).read() return output # 调用 Go 程序,并传递参数 output_data = call_go_program(path, apk_name) if not output_data: os.system('go mod init github.com/antchfx') time.sleep(0.5) os.system('go get github.com/antchfx/htmlquery') self.down_android_app(apk_name, means='python') print('output_data=', output_data) r_list = re.compile('【(.*?)】').findall(output_data) if r_list: path = r_list[0] if path: print(path) return path else: return False else: return False else: print("python语言") # 方法二:python file_name = os.path.join(path, apk_name+'.apk') if os.path.exists(file_name) and download_flag == 0: print("已下载无需下载%s" % apk_name) return file_name else: print("需要下载%s" % apk_name) headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36 Edg/113.0.1774.35", } params = { "pageContext": "appdetail", } one_url = 'https://sj.qq.com' search_url = one_url + "/search?q=%s" % apk_name response = requests.get(search_url, headers=headers) html = response.text # print(html) htmlElement = etree.HTML(html) result = htmlElement.xpath("//div[text()='"+apk_name+"']/../../..") if not result: print("没结果,需要模糊匹配") elem = "(//div[contains(text(), '"+apk_name+"')])[1]" result = htmlElement.xpath(elem +"/../../..") apk_name = htmlElement.xpath(elem)[0].text print('真实名字=', apk_name) # 获取文本 if result: href = result[0].get("href") one_url += href print("1.获取app连接%s" % one_url) try: response = requests.get(one_url, headers=headers, params=params) json_data = response.text except Exception as e: # print(e) if 'Expecting value' in str(e) and len(str(e)) < 300: qq_email("534640040@qq.com", 'reques版本过高' ,"request版本过高,默认2.27.1") json_data = e r_list = re.compile('"name":"'+apk_name+'".*?"download_url":"(.*?)"', re.S).findall(str(json_data)) print(r_list) if r_list: apk_url = r_list[0] print("2.获取app下载连接%s" % apk_url) # 发送GET请求,并获取响应对象 response = requests.get(apk_url, timeout=30) # 将响应内容写入本地文件 with open(file_name, 'wb') as f: f.write(response.content) # 输出下载完成的消息 print(f"APK文件已下载到 {file_name}") return file_name return False def click_element_by_xpath(self, xpath_expression='', d='',click=True, info = ''): """ 使用 XPath 定位元素并执行点击操作. Args: d: uiautomator2 device 对象 xpath_expression: 用于定位元素的 XPath 表达式 """ if not d: d = self.d xml_string = d.dump_hierarchy() root = etree.fromstring(xml_string.encode('utf-8')) elements = root.xpath(xpath_expression) if not elements: print(f"元素未找到, XPath: {xpath_expression}") return False # print(elements[0].get('text')) if info: for element in elements: text = element.get('text') if text: bounds_str = element.get('bounds') match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds_str) if not match: print(f"无法解析 bounds 属性: {bounds_str}") return False x1, y1, x2, y2 = map(int, match.groups()) center_x = (x1 + x2) // 2 #计算中心点 x 坐标 center_y = (y1 + y2) // 2 #计算中心点 y 坐标 return {'text': text, 'center': (center_x, center_y)} element = elements[0] bounds_str = element.get('bounds') # 获取边界属性,如 [0,0][100,200] print(f"Element bounds: {bounds_str}") if click: # 使用正则表达式从 bounds 字符串中提取坐标 match = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds_str) if not match: print(f"无法解析 bounds 属性: {bounds_str}") return False x1, y1, x2, y2 = map(int, match.groups()) center_x = (x1 + x2) // 2 #计算中心点 x 坐标 center_y = (y1 + y2) // 2 #计算中心点 y 坐标 print(f"Clicking at coordinates: ({center_x}, {center_y})") d.click(center_x, center_y) return True def get_dump(self, re_text='逛商品赚金币', mode='comp', select=0): dic = {} matches = [] # print(self.d.dump_hierarchy()) if mode == 'comp': pattern = re.compile(' 0 and y > 0: print("找垃圾清理") # d(descriptionContains="安装") # [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", [], delem=['d(textContains="旧版应用详情页已停止使用")']) # if x > 0 and y > 0: # # print(elem.info['text']) # print("无法安装垃圾清理") # self.MySQLManager.write_file("%s已统计手机容量" % (self.model + self.ip)) # return [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", [], delem=['d(descriptionContains="安装")', 'd(text="安装")', 'd(text="同意")']) if x > 0 and y > 0: if k == 0: print("没有安装垃圾清理,需要安装") self.d(description="已安装").exists(timeout=30) print("已安装") continue elif k == 1: print("外层安装") self.d(text="同意").exists(timeout=20) continue self.d(text="总共扫描出垃圾").exists(timeout=30) t3 = '' try: t1 = self.d(text="总共扫描出垃圾").sibling(index=0).info['text'] t2 = self.d(text="总共扫描出垃圾").sibling(index=1).info['text'] if not t1: t1 = self.d(resourceId="com.miui.cleanmaster:id/number").info['text'] t3 = t1 + t2 print("共扫描%s垃圾" % t3) except Exception as e: print(e) [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", [], delem=['d(resourceId="com.miui.cleanmaster:id/button_clean")', 'd(textContains="清理选中垃圾")']) if x > 0 and y > 0: print("找到清理选中垃圾") try: self.d(textContains="已用").exists(timeout=30) t = self.d(textContains="已用") if not t: t = self.d(textContains="共") t = t.info['text'] print(t) if t3 and t: text = "['%s', '手机管家清理', '%s']" % ((self.model + self.ip), "共扫描" + t3 + "垃圾" + ' ' + t) print(text) self.MySQLManager.write_file(text) except Exception as e: print(e) break self.multiple_find_picture([], 20, "点击", "不返回", [], delem=['d(text="确定")']) self.MySQLManager.write_file("%s已统计手机容量" % (self.model + self.ip)) # d(resourceId="com.miui.cleanmaster:id/button_clean") # 删除adb下安装包: print('删除adb下安装包') # self.console_input('shell find /sdcard -name "*.apk" -type f -delete') ret = self.console_input('shell find /storage/emulated -name "*.apk"', ret=1) texts = ret.split('\n') for text in texts: if 'Permission denied' not in text: print("删除%s" % text) self.console_input('shell rm %s' % text) print('删除结束') def task_done(self, r='', w='', user=''): if not user: user = self.tao_user + self.project if not user: return False # 这个函数可以用来判断任务是否已经完成 try: if r: have = self.MySQLManager.sql_search("content", "%s已经完成%s任务" % (user.replace("*", "\\\\*"), r)) if have: return True # 在这个例子中假设任务已经完成 else: return False # 在这个例子中假设任务未完成 if w: self.MySQLManager.write_file("%s已经完成%s任务" % (user, w)) except Exception as e: logging.error(e) def ui_layout(self, name='获取当前界面的布局层次结构', d=None, mode='', path=''): try: if self.d: d = self.d # 获取UI层次结构 hierarchy_xml = d.dump_hierarchy() # 准备要写入的代码部分 code_intro = '\nhierarchy_xml = """' code_outro = '"""' # XML解析部分代码 parse_and_query_code = ( '\ndef convert_xpath_lxml_to_uiautomator2(lxml_xpath):\n' '\t# 将XPath表达式拆分为多个部分,基于\']\'\n' '\timport re\n' '\tdef replacer(match):\n' '\t\tclass_name = match.group(1)\n' '\t\treturn class_name\n' '\t# 替换所有匹配的部分\n' '\tconverted_xpath = re.sub(r\'\\*\\[@class="([^"]+)"\\]\', replacer, lxml_xpath)\n' '\treturn converted_xpath\n' 'from lxml import etree\n' '# 解析XML结构\n' 'tree = etree.XML(hierarchy_xml.encode(\'utf-8\'))\n' '# 示例用法(不要写具体标签名,用*代替,如//*[]):\n' 'lxml_xpath = \'//*[@class="android.widget.RelativeLayout"]/*\'\n' '# 使用XPath查询具有特定资源ID的TextView元素\n' 'results = tree.xpath(lxml_xpath)\n' 'for result in results:\n' '\tprint(\'Text:\', result.attrib[\'text\'])\n' '\tprint(\'Bounds:\', result.attrib[\'bounds\'])\n' 'uiautomator2_xpath = convert_xpath_lxml_to_uiautomator2(lxml_xpath)\n' 'print(uiautomator2_xpath)\n' ) # 将所有部分拼接到一起以形成最终的字符串 full_code_string = code_intro + hierarchy_xml + code_outro + parse_and_query_code html_content = """
""" % full_code_string with allure.step(name): # Optional: 将层次结构信息附加到步骤 allure.attach(html_content, name=name, attachment_type=allure.attachment_type.HTML) if not path: path = self.screen('phonetimeout/' + self.model + 'ui.png') allure.attach.file(path,name=name, attachment_type=allure.attachment_type.PNG) if '失败' in mode: assert False, mode elif '跳过' in mode: pytest.skip(mode) elif '损坏' in mode: raise Exception(mode) except Exception as e: logging.error(e) def get_attribute_value(self, obj, attribute): try: value = ast.literal_eval(f'obj.{attribute}') return value except (SyntaxError, ValueError): return None def open_app1(self): self.multiple_find_picture([], 10, "点击", "不返回", delem=['d(resourceId="android.miui:id/app1")'], xy_percentage=True) def browse_task(self, key_list=['d(textContains="浏览完成")', 'd(description="任务完成")','d(text="任务完成")'], times=15, img: list = (), gep=0): for i in range(times): if i < 5: [x, y], k = self.multiple_find_picture([], 1, "点击", "不返回", delem=['d(text="打开")']) if gep: if i % gep != 0: # 多少次找一次元素 self.d.swipe_ext("up", random.uniform(0.4, 1)) continue time.sleep(random.uniform(2, 3)) # 浏览完成全部完成 [x, y], k = self.multiple_find_picture(img, 2, "不点击", "下滑",delem=key_list, is_print=False) if x > 0 and y > 0: break def back_task(self, key_dic={'d(text="每日任务")':"break", 'd(text="饿了么果园")': '点击', 'd(text="领水滴")':"点击"}, app_name=''): # d(text="每日任务") keys_list = list(key_dic.keys()) for i in range(6): [x, y], k = self.multiple_find_picture([], 2, "不点击", "不返回",delem=keys_list) if x > 0 and y > 0: value = key_dic.get(keys_list[k]) if value == '点击': self.click(x,y) time.sleep(5) elif value == 'break': return True elif value == '下滑': # 无需返回 # print("下滑") self.d.swipe_points([[0.8, 0.6], [0.8, 0.1]], 0.1) time.sleep(1) continue elif value == '右滑': # 无需返回 # print("下滑") self.d.swipe_points([[0.8, y], [0.1, y]], 0.1) time.sleep(1) continue # print("app_name:", app_name) else: self.back() time.sleep(2) if not app_name: app_name = self.app_name if i >= 4 and app_name: get_app_name = self.get_app_name() if get_app_name != app_name: print("不是本应用%s" % app_name) self.d.app_start(app_name) time.sleep(2) def calculate_sliding_distance(self, elem, y1=0.6, y2=0.55): # 计算距离确认是否有弹窗 if elem: center = elem.center() y1_loc = center[1] self.d.swipe_points([[0.8, y1], [0.8, y2]], 0.1) time.sleep(1) center = elem.center() y2_loc = center[1] distance = y1_loc - y2_loc # print(distance) if distance == 0: return False else: return distance else: return False def get_mobile_ipv6(self, ipv6_prefix="2409"): # 获取ipv6地址,已知小米13可以,其他手机没ipv6 # 方法2:adb shell "curl -s https://ipv6.ddnspod.com", 这个测试可以使用的ipv6 text = self.console_input('shell ip -6 addr show', ret=1) r_list = re.compile("(%s:\\S+)/64" % ipv6_prefix, re.S).findall(text) if r_list: print(r_list[0]) return r_list[0] def skip(self, z=3): if self.z >= z: pytest.skip("超过最大失败次数") ret = self.console_input('shell pm list packages', ret=1) if self.app_name not in ret: pytest.skip("没有安装app" + self.app_name) if not self.tao_user and not self.user_name: pytest.skip("没有安装获取到用户名" + self.model) def open_app(self, access_elem=[], name_elem=None,ad_elem=[], sleep_time = 1): for i in range(5): self.d.app_stop(self.app_name) time.sleep(1) print(self.app_name, self.activity) if self.activity: self.d.app_start(self.app_name, activity=self.activity) else: self.d.app_start(self.app_name) time.sleep(sleep_time) if not self.tao_user: print(" %s没有用户名需要获取用户名" % self.model) # d(resourceId="com.wuba:id/btn_ads") if ad_elem: self.multiple_find_picture([], 15, "点击", "不返回", [], delem=ad_elem, xy_percentage=True) time.sleep(2) # d.xpath('//*[@resource-id="com.jifen.qukan:id/app"]/android.widget.RelativeLayout[1]/android.widget.ImageView[1]') # 点击叉 [x, y], k = self.multiple_find_picture([], 20, "点击", "返回", [], delem=access_elem, xy_percentage=True) if x > 0 and y > 0: print("找到我的") for i in range(4): try: # d(resourceId="com.xunmeng.pinduoduo:id/pdd", text="yys534640040.club") if type(name_elem) == list: index = i % len(name_elem) # print('index=', index) elem = name_elem[index] else: elem = name_elem if elem.exists: text = elem.info['text'] if not text: # contentDescription text = elem.info['contentDescription'] self.tao_user = text.strip() print(self.tao_user) os.environ[self.ip] = str(self.tao_user) time.sleep(1) # self.multiple_find_picture([], 10, "点击", "不返回", [], delem=['d(text="取消")'], xy_percentage=True) # time.sleep(1) break time.sleep(1) except Exception as e: print(e) else: break if not self.tao_user: self.tb_timeout_option("找用户名失败") return False else: break def do_task_for_list(self, key_list=['去完成', '去领取'], title1_elem='sibling(index=0)', title2_elem = 'sibling(index=1)', app_name = 'com.eg.android.AlipayGphone', browse_key = {'关键字':['浏览', '指定动作奖励', '逛'], '成功元素':['d(textContains="浏览完成")', 'd(description="任务完成")','d(text="任务完成")'], '成功图片':[], '浏览次数':10}, back_key_dict = {'d(text="每日任务")':"break", 'd(text="饿了么果园")': '点击', 'd(text="领水滴")':"点击"}, get_rewards=['d(text="领取")', 'd(text="领奖")'], exclude_list=['多少运动币','外卖实付', '3个店铺'], watch_vd={}, other_task_dic={}, do_times=1, y_per_max=0.94, y_per_min=0.3, task_name='', delay = 6 ): # get_rewards: 任务完成后领取奖励按钮 # back_key_dict: 返回按钮 # watch_vd看视频的,如{'关键字':["看广告视频", '每日可完成7次'], "方法":self.watch_tv} # other_task_dic比如other_task_dic的扩展,多个字典{'惊喜水滴':self.other_task_dic} text2 = '' do_task = 0 # 没有一个满足key_list 则跳出循环 return_dic = {} # 统计没有完成的key_list max_ins = 5 for ins in range(15): # 初始索引到0-3,如果遇到不做的任务获准,做过的还有+1 for key_word in key_list: time.sleep(0.5) if ins <= 0: # 返回到做任务位置 is_true = self.back_task(key_dic=back_key_dict, app_name=app_name) if not is_true: return ins %= max_ins print('key_word=%s, ins=%s' % (key_word, ins)) try: # 计算return_dic all = len(self.d.xpath('//*[@text="%s"]' % key_word).all()) if all: return_dic[key_word] = all else: if key_word in return_dic: del return_dic[key_word] pass except Exception as e: pass # 查看是不是本应用 get_app_name = self.get_app_name() if get_app_name != app_name: print("不是本应用%s" % app_name) self.d.app_start(app_name) time.sleep(2) try: if '[%s]' in title1_elem: # print('2') click_elem = self.XPathElement(self.d, title1_elem % (key_word, ins + 1)) else: click_elem = self.d(text=key_word, instance=ins) # print(2, click_elem) if click_elem: is_numeric = lambda s: s.replace('.', '', 1).isdigit() if s.count('.') <= 1 else False if is_numeric(str(title1_elem).replace('-', '')) and is_numeric(str(title2_elem).replace('-', '')): # print('3') # 根据索引判断,is_numeric检查是否数字 ret = self.get_dump(re_text=key_word, select=ins) index = ret.get('index') + title1_elem text1 = self.d(index=index).info['text'] # title2 = eval(f"click_elem.{title2_elem}") index = ret.get('index') + title2_elem text2 = self.d(index=index).info['text'] # print(text1, text2) elif '//*' in title1_elem or '//com' in title1_elem: # xpath定位的情况 # print(self.d.xpath(title1_elem % key_word).all()[ins].info) if '[%s]' in title1_elem: # 带索引的xpath # self.XPathElement(self.d, title1 = self.XPathElement(self.d, title1_elem % (key_word, ins + 1)) text1 = title1.info['text'] if title1 else '' if title1_elem == title2_elem: # 相等就不需要获取标题2了 text2 = '' else: title2 = self.XPathElement(self.d, title2_elem % (key_word, ins + 1)) text2 = title2.info['text'] if title2 else '' else: title1 = self.d.xpath(title1_elem % key_word).all()[ins * 2] text1 = title1.info['text'] if title1 else '' if title1_elem == title2_elem: # 相等就不需要获取标题2了 text2 = '' else: title2 = self.d.xpath(title2_elem % key_word).all()[ins * 3] text2 = title2.info['text'] if title2 else '' else: # print("2323") # 使用getattr解析方法调用表达式字符串,获取兄弟元素 title1 = eval(f"click_elem.{title1_elem}") text1 = title1.info['text'] if title1 else '' title2 = eval(f"click_elem.{title2_elem}") text2 = title2.info['text'] if title2 else '' # print(text1, text2) text = text1 + text2 if any(i in text for i in exclude_list): print("排除任务【%s】" % text) continue user_search = self.tao_user # text_search = text.replace("(", "").replace(")", "").replace("+", "").replace(' ', '').replace('*', '') # print(text_search) text_search = "%s已经完成%s任务" % (user_search + self.project, text) value = self.redis_manager.redis_search(text_search) print('value:', value,text) # print() # value = self.MySQLManager.sql_search("content", "%s已经完成%s任务" % (user_search + self.project, text_search)) if value and int(value.decode('utf-8')) >= do_times: print("已经做过【%s】无需再做" % text) continue width, height = self.get_phone_width_height() # print(6) # print(click_elem.info) y_per = click_elem.center()[1] / height if y_per < y_per_min: print(y_per, "下滑") self.d.swipe_ext("down", 0.5) time.sleep(1) continue elif y_per > y_per_max: print(y_per, "上滑") self.d.swipe_ext("up", 0.5) continue else: print("无需滑动") print(y_per) do_task = 1 time.sleep(3) click_elem.click() # 点击词 time.sleep(1) self.redis_manager.redis_set(text_search,times='today', is_countable=True) # self.MySQLManager.write_file("%s已经完成%s任务" % (self.tao_user + self.project, text_search)) # 其他任务 # other_task_dic flag_other = 0 if other_task_dic: for action, params in other_task_dic.items(): if action in text: # print( action, params) try: if len(params) == 1: # 没有参数,直接调用函数 params[0]() else: # 有参数,解包元组后调用 params[0](*params[1:]) except Exception as e: params() flag_other = 1 # for i in other_task_dic: # if i in text: # dic = other_task_dic.get(i) # dic() if not flag_other: # 包含这些关键词的不需要再做了 # 浏览任务 if browse_key: key = browse_key.get('关键字') if key: succeed_key = browse_key.get('成功元素') succeed_pic = browse_key.get('成功图片') browse_times = browse_key.get('浏览次数') if key == '无' or any(i in text for i in key): self.browse_task(key_list=succeed_key, img=succeed_pic, times=browse_times) # 视频,广告 if watch_vd: key = watch_vd.get('关键字') if any(i in text for i in key): mean = watch_vd.get('方法') mean() # 直播 time.sleep(delay) # 返回 self.back_task(key_dic=back_key_dict, app_name=app_name) # 领取奖励 # pass if get_rewards: num = 4 flge = 0 for i in range(num): delem_list = [] pic_list = [] for type_elem in get_rewards: if 'd' in type_elem: delem_list.append(type_elem) else: pic_list.append(type_elem) [x, y], k = self.multiple_find_picture(pic_list, 3, "不点击", "不返回", delem=delem_list,xy_percentage=True) if x > 0 and y > 0: if y > 0.9: print(y, "上滑") if flge == 0: num += 5 flge = 1 self.d.swipe_ext("up", 0.5) else: self.click(x, y) time.sleep(4) pass else: break if flge == 1: for i in range(3): print("下滑") self.d.swipe_ext("down", 0.5) except Exception as e: print(e) if do_task == 0: self.d.swipe_ext("up", 0.5) if do_task == 0 and ins >= max_ins - 1: break if return_dic: try: # back_key_dict result_string = "" for key, value in return_dic.items(): result_string += f"{key}: {value}, " result_string = result_string.rstrip(', ') if back_key_dict: result_string += "_返回关键字:" +str(list(back_key_dict.keys())[0]).replace('"', '').replace("'", '') print("['%s','剩余任务次数','%s']" % (self.model + self.tao_user + self.project + task_name, result_string)) self.MySQLManager.write_file("['%s','剩余任务次数','%s']" % (self.model + self.tao_user + self.project + task_name, result_string)) except Exception as e: logging.error(e) def click_vd_cross(self, className="android.widget.ImageView", min_x=0.8, max_y = 0.1): for i in range(10): elem = self.d(resourceId="android:id/content").child(className=className, instance=i) if elem: center = elem.center() width, height = self.get_phone_width_height() y_per = center[1] / height x_per = center[0] / width # print(x_per, y_per) if x_per > min_x and y_per < max_y: # 叉子在左上角 print('点击叉', x_per, y_per) self.click(x_per, y_per) break def concatenate_images(self, image_paths, output_path): from PIL import Image # 打开所有图片并获取它们的尺寸 images = [Image.open(path) for path in image_paths] widths, heights = zip(*(img.size for img in images)) # 计算拼接后的总宽度和最大高度 total_width = sum(widths) max_height = max(heights) # 创建一个新图像,尺寸为拼接后图像的尺寸 concatenated_image = Image.new('RGB', (total_width, max_height)) # 将每张图片粘贴到新图像上 x_offset = 0 for img in images: concatenated_image.paste(img, (x_offset, 0)) x_offset += img.width # 保存拼接后的图片 concatenated_image.save(output_path) return output_path def click_ok_button(self): # git branch -d temp # '//*[@text="确定"]' # d.xpath('//*[@resource-id="android:id/button1"]') self.allure_screen("确定前") [x, y], k = self.multiple_find_picture([], 5, "点击", "不返回", xpath=['//*[@text="确定"]', '//*[@resource-id="android:id/button1"]'], xy_percentage=True) if x > 0 and y > 0: print("找到确定") self.allure_screen("确定后") def clear_app(self, app_name="com.jifen.qukan"): ret = self.console_input('shell pm clear %s' % app_name, ret=1) print('清除app%s数据' % app_name, ret) def yzm(self, times=8, swip=False): # d(text="请按照说明拖动滑块") mx = 0 i = 0 t1 = time.time() distance = 0 all_width = 1 for i in range(times + random.randint(0, 5)): try: [x, y], k = self.multiple_find_picture([], 10, "不点击", "不返回", delem=['d(resourceId="scratch-captcha-btn")', 'd(text="请按照说明拖动滑块")', 'd(text="验证码拦截")', 'd(text="访问被拒绝")'], xy_percentage=True) if x > 0 and y > 0: if k == 3: # 淘宝解决方案0 self.multiple_find_picture(['淘宝解决方案'], 2, "点击", "不返回") if swip: # get_canny # '淘宝验证码滑块按钮' [x, y], k = self.multiple_find_picture(['淘宝操作太频繁了','淘宝验证码滑块按钮'], 5, "不点击", "不返回", delem=['d(resourceId="scratch-captcha-btn")']) if x > 0 and y > 0: if k == 0: return False all_width, all_height = self.get_phone_width_height() width = all_width * 0.63 self.d.touch.down(x, y) time.sleep(0.3) self.d.touch.move(x+ width, y) time.sleep(0.5) # self.swipe(x, y, x + width, y) print("向右滑动总距离(方便截全图):", x + width) # 0.03 ,0.24 # # (y1(上):y2(下), x1(左):x2(右)) 0.37124999999999997 0.5812499999999999 0.1550925925925926 0.892 # print(y / all_height - 0.24, y / all_height - 0.01, x / all_width, 0.892, y / all_height) pic_path = self.screen(pic_name = self.ip + 'yzm.png', region=(y / all_height - 0.24, y / all_height - 0.02, x / all_width, 0.892)) x_list, path = self.find_and_show_rightmost_points(pic_path) # print(x_list) mx = random.choice(x_list) # print(i, ",回退,", x + mx, '占比', (mx / width) / width, ', mx=', mx) # 可以+一定距离(如x + mx + 10),我去掉了,看着能用 distance = x + mx print(i, '.图片选择的相对距离mx(不算按钮距离)',mx, ',绝对距离按钮',distance, ",swipe距离", x + width, y, distance , y, ', swipe_points距离比例:', [[(x + width)/all_width, y / all_height], [distance / all_width, y / all_height]]) # self.swipe(x + width, y, distance , y) # time.sleep(4) self.d.swipe_points([[(x + width)/all_width, y / all_height], [distance / all_width, y / all_height]], random.uniform(0.1, 0.8)) self.d.touch.up(width - mx, y) # self.d.swipe_points([[x, y], [x + mx, y]], random.uniform(0.1, 1)) time.sleep(1) pic_path = self.screen(pic_name = 'phonetimeout/' +self.ip + 'yzm.png') ret_path = self.concatenate_images([pic_path, path], self.screen_path + 'phonetimeout/' + self.ip + '合并.png') self.ui_layout(name='canny' + str(i) + "_SelectX_" + str(mx) + '_cost_%.2f' % (time.time() - t1), path=ret_path) num = random.randint(0, 2) # print(num) if num == 1: # 验证码点我反馈0 [x, y], k = self.multiple_find_picture(['验证码点我反馈'], 3, "点击", "不返回", xy_percentage=True) if x > 0 and y > 0: [x, y], k = self.multiple_find_picture([], 10, "点击", "不返回", delem=[random.choice(['d(text="滑动验证码后提示验证失败")', 'd(text="频繁看到该验证码")'])], xy_percentage=True) if x > 0 and y > 0: [x, y], k = self.multiple_find_picture([], 10, "点击", "不返回", delem=['d(text="提交")'], xy_percentage=True) self.back() time.sleep(5) else: try: ret = [distance / all_width, distance,i, time.time() - t1] if ret[0]: result = '距离比例%s, 距离%s,滑动次数%s,耗时%.2f秒' % (ret[0], ret[1], ret[2], ret[3]) print("['%s','淘宝滑动验证码结果','%s']" % (self.model + self.tao_user + self.project, result)) self.MySQLManager.write_file("['%s','淘宝滑动验证码结果','%s']" % (self.model + self.tao_user + self.project, result)) self.ui_layout(name=result) except Exception as e: print(e) return ret except Exception as e: print(e) def find_and_show_rightmost_points(self,image_path='/Users/yangyongsheng/nas/download/23.png', num_regions=5): # image_path = '/Users/yangyongsheng/nas/download/23.png' # 读取图像 img = cv.imread(image_path) if img is None: raise ValueError("无法读取图像,请检查图像路径。") # 转换为灰度图像 gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 应用高斯模糊 blurred = cv.GaussianBlur(gray, (5, 5), 0) # 应用Canny边缘检测 edges = cv.Canny(blurred, 50, 150) # 找到连通区域 num_labels, labels, stats, centroids = cv.connectedComponentsWithStats(edges, connectivity=8) # 排除背景,按面积排序,选出前num_regions个最大连通区域 largest_regions = np.argsort(stats[1:, cv.CC_STAT_AREA])[::-1][:num_regions] + 1 rightmost_points = [] # 计算每个连通区域的最右边点 for region in largest_regions: mask = (labels == region).astype(np.uint8) contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) if contours: rightmost_point = max(contours[0], key=lambda point: point[0][0]) rightmost_points.append(tuple(rightmost_point[0])) # 打印并标注最右边点 x_lis = [] for i, point in enumerate(rightmost_points): print(f"连通区域 {i+1} 的最右边点坐标: {point}") # 在图像上标注最右边点 cv.circle(img, point, 5, (0, 255, 0), -1) cv.putText(img, f"{point}", (point[0] + 10, point[1]), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) x_lis.append(point[0]) # 展示图像 # plt.figure(figsize=(10, 10)) # plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)) # plt.title("Rightmost Points") # plt.show() save_path = image_path.replace('.png', '_marked.png') cv.imwrite(save_path, img) print(f"标记后的图像已保存到 {save_path}") # print(x_lis) try: move = max(x_lis) x_lis.remove(move) except Exception as e: print(e) print(x_lis) return x_lis, save_path def get_appsize(self): # print(self.model, self.ip, "获取应用大小") catch_key = '%s今天已经统计应用大小' % (self.model + self.ip) have = self.redis_manager.redis_search(catch_key) print(catch_key, have) if have or have == b'': return # 小米应用商店 [x, y], k = self.multiple_find_picture([], 10, "点击", "返回", delem=['d(text="我的")']) if x > 0 and y > 0: [x, y], k = self.multiple_find_picture([], 6, "点击", "不返回", [], delem=['d(text="应用卸载")']) if x > 0 and y > 0: # elem = self.d(text="应用卸载") self.d(text="应用卸载").exists(timeout=3) # elem_zise = self.d(text="应用大小") # if not elem_zise: time.sleep(2) # 点击一下有用 try: self.d(resourceId="android:id/icon1") except Exception as e: self.d(resourceId="com.xiaomi.market:id/sort_mode_chooser").click() time.sleep(2) try: self.d(resourceId="android:id/text1", text="应用大小") except Exception as e: self.d(text="应用大小").click() d = self.d time.sleep(4) # 获取 hierarchy # 示例 XPath 查询 # 1. 根据 class 定位所有 TextView 元素 # d.xpath('//*[contains(@text="大小: ")]') text = '' for i in range(10): [x, y], k = self.multiple_find_picture([], 2, "不点击", "不返回", [], delem=['d(textContains="其他应用(")'], xy_percentage=True) if x > 0 and y > 0 : if y < 0.2: break print('其他应用(') self.d.swipe(0.5, 0.3 + random.uniform(-0.05, 0.1), 0.5, 0.2 + random.uniform(-0.05, 0.1)) time.sleep(0.5) else: print("没有找到其他应用(,停止上滑") break elem = self.d(resourceId="android:id/text1") if elem: text = elem.info['text'] print(text) if text and text != '应用大小': print("点击应用大小") elem.click() elem = self.d(text="应用大小") if elem: elem.click() time.sleep(3) print("获取 hierarchy") xml_string = d.dump_hierarchy() # print() # 解析 XML root = etree.fromstring(xml_string.encode('utf-8')) text_views = root.xpath('//node[contains(@text, "大小: ")]') for text_view in text_views: sibings = text_view.getparent().getchildren() size = text_view.get('text') # print(f"TextView Text: {size}") preceding_sibling = text_view.xpath('./preceding-sibling::node[1]') if preceding_sibling: app_name = preceding_sibling[0].get('text') # print(f"{app_name}{size}") text += f"{app_name} {size}
" # print(text) if text: print("['%s','应用大小排行','%s']" % (self.model + self.ip, text)) self.MySQLManager.write_file("['%s','应用大小排行','%s']" % (self.model + self.ip, text)) self.redis_manager.redis_set(catch_key, times='today') # 内存占用 # adb -s 192.168.31.182 shell "dumpsys meminfo | head -20" | awk ' # /K:/ { # gsub(",", "", $1) # gsub("K:", "", $1) # mb = int($1 / 1024) # printf "%6d MB: ", mb # for(i=2; i<=NF; i++) printf "%s ", $i # print "" # next # } # { print } # ' ret_oneline = self.get_phone_meminfo( top_n=20) if ret_oneline: print(ret_oneline) # 写库前压缩成单行,避免换行符破坏SQL self.MySQLManager.write_file( "['%s','内存占用','%s']" % (self.model + self.ip, ret_oneline) ) def get_phone_meminfo(self, top_n=20) -> str: """ 获取手机内存排行,自动适配 Linux / Windows。 返回已换算为 MB 的字符串,失败返回空字符串。 """ # Android shell 里的 pipe 由手机端执行,Linux/Win 均可用 raw = self.console_input(f'shell "dumpsys meminfo | head -{top_n}"', ret=1) # Windows 上 adb 有时返回 bytes,做一次保险解码 if isinstance(raw, bytes): raw = raw.decode('utf-8', errors='replace') if not raw or 'Applications Memory' not in raw: # 降级:不带 head,Python 侧截断(head 在极少数 Android 版本不存在) raw = self.console_input('shell dumpsys meminfo', ret=1) if isinstance(raw, bytes): raw = raw.decode('utf-8', errors='replace') if raw: raw = '\n'.join(raw.splitlines()[:top_n]) if not raw: return '' lines = [] for line in raw.splitlines(): m = re.match(r'^\s+([\d,]+)K:(.*)', line) if m: mb = int(m.group(1).replace(',', '')) // 1024 lines.append('%6d MB:%s' % (mb, m.group(2))) else: lines.append(line) result = '\n'.join(lines) return result.replace('\n', ' | ') def kill_heavy_memory_apps(self, top_n=20, threshold_mb=100, whitelist=None): """ 获取内存排行,强制停止占用超过 threshold_mb 的非系统 App。 whitelist: 额外保护的包名列表(不停止) """ # 系统级/必要进程白名单(不可停止) DEFAULT_WHITELIST = { 'system', 'app_process', 'com.android.systemui', 'com.android.phone', 'com.android.nfc', 'com.android.bluetooth', 'com.miui.home', 'com.miui.daemon', 'com.xiaomi.finddevice', 'com.tencent.mm', 'com.example.myaccessibility', # 你自己的无障碍服务 } if whitelist: DEFAULT_WHITELIST.update(whitelist) raw = self.get_phone_meminfo(top_n=top_n) if not raw: print("get_phone_meminfo 返回空,跳过") return # 解析 " 621 MB: com.miui.personalassistant (pid 7300)" MB_RE = re.compile(r'(\d+)\s+MB:\s+(\S+?)(?::[\w.]+)?\s+\(pid') stopped = [] skipped = [] for seg in raw.split('|'): m = MB_RE.search(seg) if not m: continue mb = int(m.group(1)) pkg = m.group(2).split(':')[0] # 去掉 :子进程 后缀 if mb < threshold_mb: continue # 内存低于阈值,不管 if pkg in DEFAULT_WHITELIST: skipped.append((pkg, mb)) continue try: self.console_input(f'shell am force-stop {pkg}') print(f"已停止: {pkg} ({mb} MB)") stopped.append((pkg, mb)) except Exception as e: print(f"停止 {pkg} 失败: {e}") print(f"\n共停止 {len(stopped)} 个 App,跳过 {len(skipped)} 个系统/白名单进程") for pkg, mb in stopped: print(f" ✓ {pkg} {mb} MB") for pkg, mb in skipped: print(f" - {pkg} {mb} MB (保留)") return stopped def stop_background_apps(self, ip, port): from concurrent.futures import ThreadPoolExecutor # 构建adb连接命令 adb_connect_command = f"{self.adb_path} connect {ip}:{port}" subprocess.run(adb_connect_command, shell=True) # 获取后台应用列表 get_background_apps_command = self.adb_path + " %s:%s shell dumpsys activity activities" % (ip, port.replace(":", '')) print(get_background_apps_command) # result = subprocess.run(get_background_apps_command, shell=True, capture_output=True, text=True) result = os.popen(get_background_apps_command).read() print(result) # 使用正则表达式获取包名 pattern = re.compile(r'ActivityRecord{.*? u0 (.*?)/') matches = pattern.findall(result.stdout) print("后台应用列表:", matches) # 停止所有后台应用 def stop_single_app(package_name): if "com.miui.home" not in package_name and "com.google.android.systemui" not in package_name: stop_app_command = f"{self.adb_path} {ip}:{port} shell am force-stop {package_name}" subprocess.run(stop_app_command, shell=True) print(f"应用 {package_name} 已停止") # 使用多线程并行停止应用 with ThreadPoolExecutor() as executor: executor.map(stop_single_app, matches) def open_accessibilit(self, ip=''): if ip: self.ip = ip self.port = "5555" self.read() # adb -s 192.168.31.182 shell am start -a android.settings.ACCESSIBILITY_SETTINGS self.console_input("shell am start -a android.settings.ACCESSIBILITY_SETTINGS") [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", delem=['d(text="已下载的应用")', 'd(text="已下载的服务")', 'd(text="更多已下载的服务")']) if x > 0 and y > 0: time.sleep(0.5) Accessibility_name = '我的Accessibility' text_list = [] elem2 = self.d(text="我的Accessibility").sibling(resourceId="android:id/summary") if elem2.exists: text = elem2.info["text"] # print(text) text_list.append(text) elem = self.d.xpath('//*[@text="%s"]/parent::*/following-sibling::*//android.widget.TextView' % Accessibility_name) if elem.exists: text = elem.info["text"] text_list.append(text) # print(text) self.allure_screen("是否关闭Accessibility") print(text_list) if '关闭' in text_list or "已关闭" in text_list: print("打开Accessibility") [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", delem=['d(text="%s")' % Accessibility_name]) if x > 0 and y > 0: time.sleep(1) [x, y], k = self.multiple_find_picture([], 20, "点击", "不返回", delem=['d(resourceId="android:id/switch_widget")', 'd(resourceId="android:id/widget_frame")']) if x > 0 and y > 0: for i in range(12): [x, y], k = self.multiple_find_picture([], 10, "不点击", "不返回", delem=['d(text="确定")']) if x > 0 and y > 0: # d(resourceId="com.miui.securitycenter:id/check_box") self.d(resourceId="com.miui.securitycenter:id/check_box").click() self.d(text="确定").click() time.sleep(1) self.d.press("home") break else: time.sleep(1) def inspect_power(self, ip, port, threshold=40): try: if ip: self.ip = ip if port: self.port = port self.read() output = self.console_input('shell dumpsys battery', ret=1) match = re.search(r'level: (\d+)', output) power = match.group(1) int_power = int(power) print(self.ip, '电量', int_power) if int_power <= threshold and self.ip != self.main_mobile_ip: if self.d: self.d.screen_off() # 关闭屏幕 self.create_cron_switch(40, mip=self.ip, this_power=str(int_power)) print("%s电量过低,等待40分钟充电" % self.ip) # time.sleep(1800) else: print("不满足充电") except Exception as e: print(e) def close_mobile(self, ip='', port=''): if ip: self.ip = ip if port: self.port = ':' + port self.read() # print(1) # 1.关闭所有app # 使用adb命令关闭所有后台进程 try: self.stop_background_apps(ip, port) except Exception as e: print(e) # print(2) # 2.回到桌面 self.d.press("home") # 3.获取手机电量 self.inspect_power(ip, port) time.sleep(1) # 4.关闭屏幕 self.d.screen_off() def open_location(self, app_name): # 打开定位 self.console_input("shell pm grant %s android.permission.ACCESS_FINE_LOCATION" % app_name) def china_yzm(self): for i in range(50): path = self.screen("yzm/%s.jpg" % (self.ip + 'qm4' + str(i)), elem=self.d(resourceId="captcha").child(className="android.view.View").child(className="android.view.View")) print(path) ret = pytesseract_pic_to_text(path, 'chinese') print(ret) self.d(text="换一换").click() time.sleep(2) # path = '

' % path # path = self.screen("phonetimeout/%s.jpg" % (self.ip + 'qm')) # path = '

' % path # 请依次点击: "虞" "庖" "快" "联" # d(text="换一换") # qq_email("", "%s%s七猫%s满足10元提现,请手动提现,有验证码" % (self.model, self.u2c.tao_user, flag), path) pass def get_allphone_temperature_max(self): win_mobile_ips = self.read_yaml['win_mobile_ip'] temperature_list = [] for dic in win_mobile_ips: mip = list(dic.values())[0] mport = ':5555' device_addr = f"{mip}{mport}" if mip == self.main_mobile_ip: print("不算主力手机") continue try: # 1. 使用 'grep' 替代 'findstr' # 2. 将连接和执行命令分开,方便调试 connect_cmd = f"{self.adb_path} connect {device_addr}" subprocess.run(connect_cmd, shell=True, check=True, capture_output=True, timeout=5) temp_cmd = f"{self.adb_path} -s {device_addr} shell dumpsys battery" process = subprocess.Popen(temp_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output_bytes, error_bytes = process.communicate(timeout=10) # 2. 将命令输出的字节串(bytes)解码(decode)为字符串(string) output_str = output_bytes.decode('utf-8') error_str = error_bytes.decode('utf-8') if error_str: print(f"命令执行出错 for {mip}: {error_str}") continue # 3. 在搜索前先检查是否匹配成功,避免AttributeError match = re.search(r'temperature:\s*(\d+)', output_str) if match: temperature_raw = match.group(1) temperature = int(temperature_raw) / 10 temperature_list.append(temperature) print(f"成功获取 {mip} 的温度: {temperature}°C") else: print(f"未能从 {mip} 的输出中找到温度信息") except subprocess.TimeoutExpired: print(f"连接或执行命令超时 for {mip}") except subprocess.CalledProcessError as e: print(f"ADB连接失败 for {mip}: {e.stderr.decode('utf-8')}") except Exception as e: print(f"发生未知错误 for {mip}: {e}") print("\n所有成功获取的手机温度:", temperature_list) return temperature_list def set_fan_speed(self): temp_list = self.get_allphone_temperature_max() # ESP32_fan ESP32_fan = self.read_yaml.get('switch_ip', {}).get('ESP32_fan', '') # http://ESP32_fan/setSpeed?value=1 max_temp = max(temp_list) if temp_list else 0 speed = 0 div = 1 hour = time.localtime().tm_hour if hour >= 22 or hour < 7: div = 2 if max_temp >= 50: speed = 180 // div elif max_temp >= 45: speed = 160 // div elif max_temp >= 40: speed = 1400 // div elif max_temp >= 35: speed = 120 // div elif max_temp >= 30: speed = 100 // div else: speed = 0 url = f'http://{ESP32_fan}/setSpeed?value={speed}' response = requests.get(url) print(f"设置风扇速度为 {speed}, 温度 {max_temp}, 结果: {response.text}") # print(f"设置风扇速度为 {speed}, 温度 {max_temp}") def check_pause_status(self): """ 带有频率限制的暂停检查 """ # 新增:检查环境变量,如果为"调试"模式,则跳过所有暂停逻辑 # win: set AUTOMATION_MODE=调试 $env:AUTOMATION_MODE = "调试" linux: export AUTOMATION_MODE=调试 if os.environ.get('AUTOMATION_MODE') == '调试': # print("[调试模式] 跳过暂停检查。") # 可以取消这行注释来确认是否进入了调试模式 return # 1. 检查是否在冷却时间内 current_time = time.time() if current_time - self.last_pause_check_time < self.pause_check_interval: return # 时间没到,直接跳过,不查 Redis,零消耗 # 2. 更新最后检查时间 self.last_pause_check_time = current_time # 3. 真正去查 Redis pause_key = f"STOP_DEVICE_{self.ip}" try: # 你的 redis_manager.redis_search 返回的是 value 或 None # 注意:你的类可能返回 bytes,需要判断 is_paused = self.redis_manager.redis_search(pause_key) if is_paused: print(f"🔴 [Redis信号] 检测到暂停指令!设备 {self.ip} 正在暂停...") self.MySQLManager.write_file(f"设备 {self.ip} 接收暂停指令,进入等待...") # 进入死循环等待 while True: time.sleep(5) # 暂停期间每5秒查一次,不消耗资源 # 暂停期间必须强制查 Redis,忽略冷却时间 check_again = self.redis_manager.redis_search(pause_key) if not check_again: print(f"🟢 [Redis信号] 暂停解除!设备 {self.ip} 恢复运行。") self.MySQLManager.write_file(f"设备 {self.ip} 恢复运行。") break except Exception as e: # Redis 连接失败不应该导致脚本崩溃,打印错误并跳过 print(f"检查暂停状态时 Redis 报错: {e}") def watch_video(self, times=30, watch_time=0, max_watch_time=5, timeout=70, close_delem=None, continue_delem=None, reward_img=None, reward_delem=None, exit_delem=None, live_delem=None, back_func=None, timeout_name=None, inspect_local=None, close_delem_click=True,close_cha_img=[], inspect_times=5,close_img=[], live_img=[] ): """ 通用视频观看方法 Args: times: 循环次数 watch_time: 当前递归次数(内部使用) max_watch_time: 最大递归次数 timeout: 超时时间(秒) close_delem: 关闭/完成标志元素列表 continue_delem: 继续观看弹窗元素列表 reward_img: 奖励图片列表 reward_delem: 奖励元素列表 exit_delem: 退出确认元素列表 live_delem: 直播间元素列表 back_func: 返回任务函数(必需) timeout_name: 超时任务名称 inspect_local: 查看当前不是看视频 close_delem_click: 是否点击关闭/完成标志 close_cha_img: 关闭/完叉图片 close_img: 关闭图片(优先级高于close_cha_img) live_img: 直播图片 Example: # 抖音 self.watch_video( close_delem=['d(resourceId="com.kuaishou.nebula:id/video_close_icon")', 'd(text="领取成功")'], continue_delem=['d(description="领取奖励")', 'd(description="坚持退出")'], reward_delem=['d(text="开心收下")'], exit_delem=['d(text="坚持退出")'], live_delem=['d(resourceId="com.ss.android.ugc.livelite:id/ctz")'], back_func=self.back_dy_task, timeout_name="看精彩视频赚更多" ) """ retrun_text = '正常结束' if not back_func: raise ValueError("必须提供 back_func 参数") # 设置默认值 close_delem = close_delem or [] continue_delem = continue_delem or [] reward_img = reward_img or [] reward_delem = reward_delem or [] exit_delem = exit_delem or [] live_delem = live_delem or [] # 递归次数检查 if watch_time >= max_watch_time: print(f"看视频次数达到{max_watch_time}次,返回") return t1 = time.time() for m in range(times): time.sleep(3 + random.uniform(1, 2)) span_time = time.time() - t1 if inspect_local: [x, y], k = self.multiple_find_picture([], 1, "不点击", "不返回", [], delem=inspect_local) if x > 0 and y > 0: print("当前不是看视频,返回") retrun_text = '当前不是看视频界面' break # 超时处理 if span_time > timeout: print(f"用时过多,已超过{timeout}秒") self.ui_layout("看视频用时过久") self.back() time.sleep(4) if exit_delem: self.multiple_find_picture([], 10, "点击", "不返回", [], delem=exit_delem, xy_percentage=True) if reward_img or reward_delem: self.multiple_find_picture([], 5, "点击", "不返回", reward_img, delem=reward_delem) back_func() break if self.get_app_name() != self.app_name: print("%s不是做任务的app %s" % (self.get_app_name(), self.app_name)) time.sleep(12) if self.activity: self.d.app_start(self.app_name, self.activity) else: self.d.app_start(self.app_name) # 检查视频是否结束 if close_delem: [x6, y6], k = self.multiple_find_picture(close_img, 1, "不点击", "不返回", [], delem=close_delem) if x6 > 0 and y6 > 0: print("检测到视频结束,close_delem_click=", close_delem_click) if close_delem_click is True: print("ture") self.click(x6, y6) elif close_delem_click and type(close_delem_click) == list: for i in range(2): print("点击对应元素") [x, y], k = self.multiple_find_picture(close_cha_img, 4, "点击", "不返回", [], delem=close_delem_click) if x > 0 and y > 0: time.sleep(2) else: back_func() time.sleep(2) # 处理继续观看弹窗 if continue_delem: [x4, y4], k_continue = self.multiple_find_picture([], 5, "点击", "不返回", delem=continue_delem) if x4 > 0 and y4 > 0 and k_continue == 0: watch_time += 1 print(f"满足第一个元素,再次看视频,次数{watch_time},耗时{span_time:.2f}秒") self.watch_video(times, watch_time, max_watch_time, timeout, close_delem, continue_delem, reward_img, reward_delem, exit_delem, live_delem, back_func, timeout_name, inspect_local, close_delem_click,close_cha_img, inspect_times, close_img, live_img) return retrun_text if back_func: back_func() if reward_img or reward_delem: self.multiple_find_picture(reward_img, 5, "点击", "不返回", delem=reward_delem) break # 直播间检查 if m > inspect_times: if live_img or live_delem: [x, y], k = self.multiple_find_picture(live_img, 1, "点击", "不返回", delem=live_delem, xy_percentage=True) if x > 0 and y > 0: time.sleep(5) print("退出直播间") else: print(f"循环结束,共{times}次") if timeout_name: self.tb_timeout_option(timeout_name) back_func() # 最后尝试领取奖励 if reward_img or reward_delem: self.multiple_find_picture([], 5, "点击", "不返回", reward_img, delem=reward_delem) return retrun_text def wx_key_word(self): y_py = 0 if self.model == '【MI 9 SE】': y_py = 0.03 print(self.model,"y偏移:", y_py) self.click(0.464, 0.785 + y_py) # 5 print("点击5") time.sleep(0.6) self.click(0.1, 0.712 + y_py) # 1 print("点击1") time.sleep(0.8) self.click(0.779, 0.823 + y_py) # 9 print("点击9") time.sleep(0.5) self.click(0.779, 0.823 + y_py) # 9 print("点击9") time.sleep(0.7) self.click(0.435, 0.824 + y_py) # 8 print("点击8") time.sleep(0.7) self.click(0.435, 0.712 + y_py) # 2 print("点击2") # 微信_支付成功 time.sleep(4) [x, y], k = self.multiple_find_picture(['微信_支付成功'], 10, "不点击", "不返回", xy_percentage=True) if x > 0 and y > 0: pic_path = self.screen("babanc.png") text = '

' % pic_path qq_email("", "%s,微信转账成功" % (self.ip + self.model), "[%s]
%s" % (self.model + self.ip, text)) def get_number_by_y(self, target_y, y0=638): """ 根据传入的 y 坐标返回对应的数字 :param target_y: 需要查询的 y 坐标 (比如传入431,应该返回2) :param y0: 数字 0 所在的基准 y 坐标,默认为 638 :return: 对应的数字 (int) """ # 计算每个数字之间的标准像素间距 # 根据 0(638) 和 5(121) 的距离得出: (638 - 121) / 5 = 103.4 step = 103.4 # 计算与基准坐标(数字0)的距离差 diff = y0 - target_y # 距离差除以步长,得到对应的数字 # 由于屏幕坐标点击可能存在1-2像素的误差,这里使用 round() 进行四舍五入取整 number = round(diff / step) return number class Test(UiAutomatr): def __init__(self, ip="", this_port=":5555", user='523040'): super(Test, self).__init__() self.ip = ip self.port = this_port self.z = 0 self.read() self.tao_user = user text = '' # self.wx_key_word() # time.sleep(10) # exit() # self.d.set_fastinput_ime(False) # self.d(description="请输入登录密码").click() # time.sleep(1) # self.d.shell("input text 519yang982") # print("输入密码结束") # # time.sleep(3) # # self.d.send_keys(str(10), clear=True) # self.d.shell("input text %s" % '10') # print("输入密码结束") # ret = self.d.dump_hierarchy() # print(ret) self.multiple_find_picture(['测试_小米运动'], 20, "点击", "不返回", delem=['d(textContains="个商品得奖励")'],xy_percentage=True) time.sleep(20) exit() # for i in range(5): # elem = self.d.xpath('//*[@text="天"]/preceding-sibling::android.view.View/*[%s]' % (i + 1)) # if elem.exists: # if elem.info.get('text') == '.': # text += '.' # else: # elem = self.d.xpath('//*[@text="天"]/preceding-sibling::android.view.View/*[%s]/android.widget.TextView' % (i + 1)) # if elem.exists: # top = elem.info.get('bounds', {}).get('top', 0) # print("top:", top) # nom = self.get_number_by_y(int(top)) # text += str(nom) # # text += elem.info.get('text', '') # else: # print("没有找到元素") # print(text) # screen_path = self.screen(self.ip + "xmshipin_screen.png", # elem=self.d(text="天").sibling(className="android.view.View")) # elem = self.d.xpath('//android.widget.LinearLayout/android.widget.TextView') # elem = self.d(className="android.widget.LinearLayout").child(className="android.widget.TextView") # print(elem.info) screen_path = self.screen(self.ip + "xmshipin_screen.png") # # # , binarize_num=190 ret = pytesseract_pic_to_text(screen_path, pic_cache=False,config='--psm 3', text_padding='\n') all_text = ret.get('all_text', '') print(all_text) if ret: all_text = ret.get('all_text', '') # # 0-08余额 0-08余额 # r_list = re.compile('(\\d+\\.\\d+)余额').findall(text.replace('-', '.').replace('00', '.0.0')) # if r_list: # money = float(r_list[0]) # for i in ret: # text = ret.get(i) # # print(text) # if "*" in text: # print(ret) # ret = self.recognize_text_dddocr(tjb_yzm) # if ret: # tao_user = ret time.sleep(399) # money = pytesseract_pic_to_text(tjb_yzm).get('all_text') # if money: # money = money.replace('#', '') # print(money) # tjb_yzm = self.screen('yzm/%s.jpg' % (self.ip + 'weixin2')) # all_text = pytesseract_pic_to_text(tjb_yzm).get('all_text') # print("available settings keys:", self.d.settings.available_keys()) # print("available settings keys:", self.d.settings.available_keys()) # # 或者从 __dict__ 里看内部存储 # print("settings __dict__:", self.d.settings.__dict__) # ret = self.d.dump_hierarchy() # # 正确调用:加上 self. # print("验证码是:", ret) # open(r'F:\备份\mi\hierarchy.xml', 'w', encoding='utf-8').write(ret) # print() # self.do_task_for_list() # self.clear_mobile() # self.click_vd_cross() # self.jurisdiction() # self.click_vd_cross() # self.get_mobile_ipv6() # print("打开清理软件") # d(text="浏览60秒") # self.open_accessibilit() # self.export_app() try: self.china_yzm() except Exception as e: print(e) # self.multiple_find_picture([], 20, "不点击", "不返回", delem=['d(textContains="个商品得奖励")'],xy_percentage=True) # self.d.app_start("com.miui.cleanmaster", activity="com.miui.optimizecenter.MainActivity") # self.d.app_start("com.jifen.qukan") # self.do_task_for_list(key_list=['去完成', '去领取', ' 去完成', ' 去领取'], # title1_elem='sibling(index=1).child(index=0)', # title2_elem = 'sibling(index=1).child(index=1)', # app_name = 'com.cainiao.wireless', # # '逛' # browse_key = {'关键字':['浏览'], '成功元素':['d(textContains="浏览完成")', 'd(description="任务完成")','d(text="任务完成")'], '成功图片':[], '浏览次数':10}, # back_key_dict = {'d(text