|
|
@@ -9,6 +9,10 @@ import threading
|
|
|
import configparser
|
|
|
from datetime import datetime
|
|
|
|
|
|
+# 用于存储线程的列表
|
|
|
+threads = []
|
|
|
+results = {} # 存储每个模拟器的执行结果
|
|
|
+
|
|
|
class MuMuEmulatorManager:
|
|
|
# 类级别的剪贴板锁,所有实例共享
|
|
|
_clipboard_lock = threading.Lock()
|
|
|
@@ -176,6 +180,54 @@ class MuMuEmulatorManager:
|
|
|
log_callback(f"✅ 点击坐标 ({x}, {y})")
|
|
|
return True
|
|
|
return False
|
|
|
+ def swipe(self, index, x1, y1, x2, y2, duration_ms=300, log_callback=None):
|
|
|
+ """从坐标 (x1, y1) 滑动到 (x2, y2)"""
|
|
|
+ adb_port = self.get_adb_port(index)
|
|
|
+ target_device = f"127.0.0.1:{adb_port}"
|
|
|
+
|
|
|
+ subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
+
|
|
|
+ cmd = f"adb -s {target_device} shell input swipe {x1} {y1} {x2} {y2} {duration_ms}"
|
|
|
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
|
+
|
|
|
+ if result.returncode == 0:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"✅ 滑动从 ({x1}, {y1}) 到 ({x2}, {y2}),耗时 {duration_ms}ms")
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"❌ 滑动失败: {result.stderr}")
|
|
|
+ return False
|
|
|
+ def get_screen_size(self, index, log_callback=None):
|
|
|
+ """获取模拟器屏幕分辨率,返回 (width, height)"""
|
|
|
+ adb_port = self.get_adb_port(index)
|
|
|
+ target_device = f"127.0.0.1:{adb_port}"
|
|
|
+
|
|
|
+ subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
+ time.sleep(0.5)
|
|
|
+
|
|
|
+ get_resolution_cmd = f"adb -s {target_device} shell wm size"
|
|
|
+ result = subprocess.run(get_resolution_cmd, shell=True, capture_output=True, text=True)
|
|
|
+
|
|
|
+ if result.stdout:
|
|
|
+ import re
|
|
|
+ match = re.search(r'(\d+)x(\d+)', result.stdout)
|
|
|
+ if match:
|
|
|
+ width = int(match.group(1))
|
|
|
+ height = int(match.group(2))
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"📱 模拟器 {index} 分辨率: {width}x{height}")
|
|
|
+ return width, height
|
|
|
+ return None, None
|
|
|
+
|
|
|
+ def check_resolution(self, index, expected_width=720, log_callback=None):
|
|
|
+ """检查分辨率宽度是否符合要求,返回 True/False"""
|
|
|
+ width, height = self.get_screen_size(index, log_callback)
|
|
|
+ if width is None:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"⚠️ 模拟器 {index} 无法获取分辨率")
|
|
|
+ return False
|
|
|
+ return width == expected_width
|
|
|
|
|
|
def get_pixel_color(self, index, x, y, log_callback=None):
|
|
|
"""获取模拟器内指定坐标点的颜色"""
|
|
|
@@ -318,6 +370,8 @@ class MuMuAutoGUI:
|
|
|
|
|
|
self.selected_emulators = []
|
|
|
self.thread_semaphore = None # 添加信号量控制并发数
|
|
|
+ self.active_threads = 0 # 记录当前活跃线程数
|
|
|
+ self.threads_lock = threading.Lock() # 线程锁
|
|
|
|
|
|
self.load_btn = None
|
|
|
self.start_read_btn = None
|
|
|
@@ -552,9 +606,21 @@ class MuMuAutoGUI:
|
|
|
for item in self.emulator_tree.get_children():
|
|
|
values = self.emulator_tree.item(item, 'values')
|
|
|
if values[0] == "□":
|
|
|
- self.emulator_tree.item(item, values=("☑", values[1], values[2], values[3], values[4]))
|
|
|
+ self.emulator_tree.item(item, values=("☑", values[1], values[2], values[3]))
|
|
|
self.log_message("已全选所有模拟器")
|
|
|
|
|
|
+ def update_emulator_status(self, index, status):
|
|
|
+ """更新指定索引的模拟器状态显示
|
|
|
+ Args:
|
|
|
+ index: 模拟器索引(字符串)
|
|
|
+ status: 状态文本,如 "运行中" 或 "未运行"
|
|
|
+ """
|
|
|
+ for item in self.emulator_tree.get_children():
|
|
|
+ values = self.emulator_tree.item(item, 'values')
|
|
|
+ if values[1] == str(index):
|
|
|
+ self.emulator_tree.item(item, values=(values[0], values[1], values[2], status))
|
|
|
+ break
|
|
|
+
|
|
|
def load_emulators(self):
|
|
|
"""加载模拟器列表"""
|
|
|
try:
|
|
|
@@ -670,13 +736,36 @@ class MuMuAutoGUI:
|
|
|
self.is_running = False
|
|
|
self.is_paused = False
|
|
|
self.log_message("⏹ 正在停止任务...")
|
|
|
-
|
|
|
+
|
|
|
def openBook(self, manager, index):
|
|
|
+ """打开书籍,遇到错误返回False"""
|
|
|
# 执行操作
|
|
|
manager.tap(index, 390, 90, self.log_message)
|
|
|
- while manager.get_pixel_color(index, 560, 65, log_callback=self.log_message) != "#F7F7F7":
|
|
|
- self.log_message(f"模拟器 {index} 等待搜索页面准备就绪...")
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ color56065 = manager.get_pixel_color(index, 560, 65, log_callback=self.log_message)
|
|
|
+ if color56065 == "#F7F7F7":
|
|
|
+ break
|
|
|
+ elif color56065 == "#EBF8EC":
|
|
|
+ manager.tap(index, 390, 90, self.log_message)
|
|
|
+ self.log_message(f"模拟器 {index} 尝试重新点击搜索...")
|
|
|
+ elif color56065 == "#5E635E":
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
|
|
|
+ manager.tap(index, 640, 260, self.log_message)
|
|
|
+ time.sleep(20)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
+ elif color56065 in ["#EBE8E4", "#CDD0D1"]:
|
|
|
+ self.log_message(f"模拟器 {index} 进入错误页面,返回...")
|
|
|
+ manager.tap(index, 44, 92, self.log_message)
|
|
|
+ else:
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ return False
|
|
|
+ self.log_message(f"模拟器 {index} 等待搜索页面准备就绪...")
|
|
|
time.sleep(3)
|
|
|
+
|
|
|
time.sleep(3)
|
|
|
# 点输入框
|
|
|
manager.tap(index, 340, 90, self.log_message)
|
|
|
@@ -684,7 +773,16 @@ class MuMuAutoGUI:
|
|
|
# 黏贴
|
|
|
manager.paste_text(index, self.search_content_var.get(), self.log_message)
|
|
|
time.sleep(3)
|
|
|
- while manager.get_pixel_color(index, 435, 880, log_callback=self.log_message) != "#FFFFFF":
|
|
|
+
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ if manager.get_pixel_color(index, 435, 880, log_callback=self.log_message) == "#FFFFFF":
|
|
|
+ break
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ return False
|
|
|
self.log_message(f"模拟器 {index} 没有输入搜索内容...")
|
|
|
# 点输入框
|
|
|
manager.tap(index, 556, 87, self.log_message)
|
|
|
@@ -694,9 +792,28 @@ class MuMuAutoGUI:
|
|
|
# 黏贴
|
|
|
manager.paste_text(index, self.search_content_var.get(), self.log_message)
|
|
|
time.sleep(3)
|
|
|
+
|
|
|
manager.tap(index, 655, 92, self.log_message)
|
|
|
time.sleep(6)
|
|
|
- while manager.get_pixel_color(index, 630, 235, log_callback=self.log_message) != "#FFFFFF":
|
|
|
+
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ color630235 = manager.get_pixel_color(index, 630, 235, log_callback=self.log_message)
|
|
|
+ if color630235 in ["#FFFFFF"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经在搜索结果页面,继续...")
|
|
|
+ break
|
|
|
+ elif color630235 in ["#E8E3CE", "#E0DBC6", "#CCCBCB", "#DFDAC5", "#141000"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经在看书目录界面,继续...")
|
|
|
+ break
|
|
|
+ elif color630235 in ["#F9F9FC"]:
|
|
|
+ self.log_message(f"模拟器 {index} 关闭广告弹窗,继续...")
|
|
|
+ manager.tap(index, 634, 123, self.log_message)
|
|
|
+ else:
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ return False
|
|
|
self.log_message(f"模拟器 {index} 等待搜索结果...")
|
|
|
time.sleep(3)
|
|
|
|
|
|
@@ -704,137 +821,256 @@ class MuMuAutoGUI:
|
|
|
time.sleep(6)
|
|
|
manager.tap(index, 355, 333, self.log_message)
|
|
|
time.sleep(5)
|
|
|
- while manager.get_pixel_color(index, 630, 235, log_callback=self.log_message) == "#FFFFFF":
|
|
|
+
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ color630235 = manager.get_pixel_color(index, 630, 235, log_callback=self.log_message)
|
|
|
+ if color630235 in ["#E8E3CE", "#E0DBC6", "#CCCBCB", "#DFDAC5", "#141000", "#E3DEC9"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经在看书目录界面,继续...")
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 20:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ return False
|
|
|
self.log_message(f"模拟器 {index} 还在搜索结果页面,重新点击...")
|
|
|
manager.tap(index, 355, 333, self.log_message)
|
|
|
time.sleep(5)
|
|
|
|
|
|
def run_task(self):
|
|
|
- """执行任务(支持并发)"""
|
|
|
+ """执行任务(支持并发,出错直接退出不重试)"""
|
|
|
+ global results
|
|
|
+ global threads
|
|
|
+
|
|
|
try:
|
|
|
- manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
-
|
|
|
# 获取最大线程数
|
|
|
max_threads = int(self.max_threads_var.get()) if self.max_threads_var.get().isdigit() else 1
|
|
|
self.thread_semaphore = threading.Semaphore(max_threads)
|
|
|
+ self.active_threads = 0
|
|
|
self.log_message(f"📌 最大并发数: {max_threads}")
|
|
|
|
|
|
- # 用于存储线程的列表
|
|
|
threads = []
|
|
|
- results = {} # 存储每个模拟器的执行结果
|
|
|
+ results = {}
|
|
|
|
|
|
def process_emulator(emu):
|
|
|
- """处理单个模拟器的函数"""
|
|
|
- with self.thread_semaphore:
|
|
|
+ """处理单个模拟器的函数,出错直接退出"""
|
|
|
+ # 获取信号量,控制并发数
|
|
|
+ self.thread_semaphore.acquire()
|
|
|
+
|
|
|
+ # 增加活跃线程计数
|
|
|
+ with self.threads_lock:
|
|
|
+ self.active_threads += 1
|
|
|
+ current_active = self.active_threads
|
|
|
+ self.log_message(f"📊 当前活跃线程数: {current_active}/{max_threads}")
|
|
|
+
|
|
|
+ index = emu['index']
|
|
|
+
|
|
|
+ try:
|
|
|
if self.should_stop:
|
|
|
return
|
|
|
|
|
|
- index = emu['index']
|
|
|
self.log_message(f"\n{'='*50}")
|
|
|
self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
self.log_message(f"{'='*50}")
|
|
|
|
|
|
- try:
|
|
|
- # 启动模拟器
|
|
|
- self.log_message(f"正在启动模拟器 {index}...")
|
|
|
- if not manager.start_emulator(index):
|
|
|
- self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
- results[index] = False
|
|
|
- return
|
|
|
-
|
|
|
- # 等待就绪
|
|
|
- if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
- self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
- results[index] = False
|
|
|
- return
|
|
|
-
|
|
|
+ # 更新状态为运行中
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "运行中"))
|
|
|
+
|
|
|
+ manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
+
|
|
|
+ # 启动模拟器
|
|
|
+ self.log_message(f"正在启动模拟器 {index}...")
|
|
|
+ if not manager.start_emulator(index):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "启动失败"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 等待就绪
|
|
|
+ if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "启动超时"))
|
|
|
+ return
|
|
|
+
|
|
|
+ time.sleep(5)
|
|
|
+ if not manager.check_resolution(index, 720, self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 分辨率不是720,停止任务并关闭模拟器")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "分辨率错误"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 安装APK
|
|
|
+ if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
+ self.log_message(f"⚠️ APK安装失败,但继续执行...")
|
|
|
+
|
|
|
+ # 打开应用
|
|
|
+ manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
+
|
|
|
+ # 判断是否需要同意
|
|
|
+ time.sleep(20)
|
|
|
+ button_color = manager.get_pixel_color(index, 394, 846, log_callback=self.log_message)
|
|
|
+
|
|
|
+ # 是否是第一次进入
|
|
|
+ if button_color and button_color.upper() == "#FC7838":
|
|
|
+ manager.tap(index, 394, 846, self.log_message)
|
|
|
+ time.sleep(120)
|
|
|
+ manager.tap(index, 640, 260, self.log_message)
|
|
|
+ time.sleep(20)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
time.sleep(5)
|
|
|
+ else:
|
|
|
+ time.sleep(10)
|
|
|
+
|
|
|
+ # 等待进入主界面
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ if self.should_stop or self.is_paused:
|
|
|
+ # 处理暂停
|
|
|
+ while self.is_paused and not self.should_stop:
|
|
|
+ time.sleep(1)
|
|
|
+ if self.should_stop:
|
|
|
+ return
|
|
|
|
|
|
- # 安装APK
|
|
|
- if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
- self.log_message(f"❌ APK安装失败")
|
|
|
- # results[index] = False
|
|
|
- # return
|
|
|
+ color560130 = manager.get_pixel_color(index, 560, 130, log_callback=self.log_message)
|
|
|
|
|
|
- # 打开应用
|
|
|
- manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
- # 判断是否需要同意
|
|
|
- time.sleep(20)
|
|
|
- button_color = manager.get_pixel_color(index, 394, 846, log_callback=self.log_message)
|
|
|
- self.log_message(button_color)
|
|
|
- # 是否是第一次进入
|
|
|
- if (button_color and button_color.upper() == "#FC7838"):
|
|
|
- manager.tap(index, 394, 846, self.log_message)
|
|
|
- time.sleep(90)
|
|
|
+ if color560130 in ["#EEF8EE", "#F7F7F7"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经进入主界面,继续...")
|
|
|
+ break
|
|
|
+ elif color560130 in ["#5F635F"]:
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
|
|
|
manager.tap(index, 640, 260, self.log_message)
|
|
|
- time.sleep(10)
|
|
|
+ time.sleep(20)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
+ elif color560130 in ["#CED0D2"]:
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭广告弹窗...")
|
|
|
+ manager.tap(index, 200, 80, self.log_message)
|
|
|
+ elif color560130 in ["#F3CEA9"]:
|
|
|
+ self.log_message(f"模拟器 {index} 进入错误页面,返回...")
|
|
|
+ manager.tap(index, 44, 92, self.log_message)
|
|
|
+ time.sleep(20)
|
|
|
manager.tap(index, 57, 193, self.log_message)
|
|
|
- time.sleep(5)
|
|
|
else:
|
|
|
- time.sleep(10)
|
|
|
-
|
|
|
- while manager.get_pixel_color(index, 560, 130, log_callback=self.log_message) != "#EEF8EE":
|
|
|
- self.log_message(f"模拟器 {index} 等待进入主界面中...")
|
|
|
- manager.get_pixel_color(index, 640, 260, log_callback=self.log_message)
|
|
|
- time.sleep(5)
|
|
|
- # 执行操作
|
|
|
- self.openBook(manager, index)
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未进入主界面,退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "进入主界面失败"))
|
|
|
+ return
|
|
|
|
|
|
+ self.log_message(f"模拟器 {index} 等待进入主界面中...")
|
|
|
+ time.sleep(5)
|
|
|
+
|
|
|
+ # 执行打开书籍
|
|
|
+ if not self.openBook(manager, index):
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "打开书籍失败"))
|
|
|
+ return
|
|
|
+
|
|
|
+ time.sleep(8)
|
|
|
+
|
|
|
+ # 翻页
|
|
|
+ page_range = self.page_count_var.get()
|
|
|
+ if '-' in page_range:
|
|
|
+ parts = page_range.split('-')
|
|
|
+ if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
|
|
+ page_count = random.randint(int(parts[0]), int(parts[1]))
|
|
|
+ else:
|
|
|
+ page_count = 10
|
|
|
+ else:
|
|
|
+ page_count = int(page_range) if page_range.isdigit() else 10
|
|
|
+
|
|
|
+ for page in range(page_count):
|
|
|
+ if self.should_stop or self.is_paused:
|
|
|
+ # 处理暂停
|
|
|
+ while self.is_paused and not self.should_stop:
|
|
|
+ time.sleep(1)
|
|
|
+ if self.should_stop:
|
|
|
+ return
|
|
|
|
|
|
- time.sleep(8)
|
|
|
- # 翻页
|
|
|
- for page in range(int(self.page_count_var.get()) if self.page_count_var.get().isdigit() else 5):
|
|
|
- # 判断是否在看书目录界面
|
|
|
- while manager.get_pixel_color(index, 712, 147, log_callback=self.log_message) not in ["#E8E3CE", "#E0DBC6"]:
|
|
|
- self.log_message(f"模拟器 {index} 不在看书目录界面,等待中...")
|
|
|
- # 判断是否有广告
|
|
|
- time.sleep(2)
|
|
|
- if manager.get_pixel_color(index, 700, 1200, log_callback=self.log_message) in ["#e8e3ce"]:
|
|
|
- self.log_message(f"模拟器 {index} 点击跳过广告!")
|
|
|
- manager.tap(index, 700, 1200, self.log_message)
|
|
|
- time.sleep(3)
|
|
|
- if self.should_stop or self.is_paused:
|
|
|
+ # 判断是否在看书目录界面
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ color7001000 = manager.get_pixel_color(index, 700, 1000, log_callback=self.log_message)
|
|
|
+ if color7001000 in ["#E8E3CE", "#E0DBC6", "#CCCBCB"]:
|
|
|
+ self.log_message(f"模拟器 {index} 在看书目录界面,继续翻页...")
|
|
|
break
|
|
|
- intervalNum = 30
|
|
|
- if ('-' in self.page_interval_var.get()):
|
|
|
- parts = self.page_interval_var.get().split('-')
|
|
|
- if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
|
|
- min_interval = int(parts[0])
|
|
|
- max_interval = int(parts[1])
|
|
|
- intervalNum = random.randint(min_interval, max_interval)
|
|
|
+ elif color7001000 in ["#F9F9FC"]:
|
|
|
+ self.log_message(f"模拟器 {index} 遇到广告,点击跳过...")
|
|
|
+ manager.tap(index, 700, 300, self.log_message)
|
|
|
else:
|
|
|
- intervalNum = int(self.page_interval_var.get()) if self.page_interval_var.get().isdigit() else 30
|
|
|
- time.sleep(intervalNum)
|
|
|
- # manager.tap(index, 700, 1055, self.log_message)
|
|
|
- manager.tap(index, 700, 1055, self.log_message)
|
|
|
- time.sleep(2)
|
|
|
- # 加入书签
|
|
|
- self.log_message(f"正在加入书架 {index}...")
|
|
|
- manager.tap(index, 360, 600, self.log_message)
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(index, 255, 97, self.log_message)
|
|
|
- time.sleep(3)
|
|
|
- # 关闭模拟器
|
|
|
- self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
- manager.stop_emulator(index)
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 10:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到目录界面,退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "翻页失败"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 判断是否有广告
|
|
|
+ if manager.get_pixel_color(index, 700, 1200, log_callback=self.log_message) in ["#E8E3CE"]:
|
|
|
+ self.log_message(f"模拟器 {index} 点击跳过广告!")
|
|
|
+ manager.tap(index, 700, 1200, self.log_message)
|
|
|
+
|
|
|
+ time.sleep(2)
|
|
|
|
|
|
- self.log_message(f"✅ 模拟器 {index} 任务完成")
|
|
|
- results[index] = True
|
|
|
+ # 随机翻页间隔
|
|
|
+ interval_str = self.page_interval_var.get()
|
|
|
+ if '-' in interval_str:
|
|
|
+ parts = interval_str.split('-')
|
|
|
+ if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
|
|
+ intervalNum = random.randint(int(parts[0]), int(parts[1]))
|
|
|
+ else:
|
|
|
+ intervalNum = 30
|
|
|
+ else:
|
|
|
+ intervalNum = int(interval_str) if interval_str.isdigit() else 30
|
|
|
|
|
|
- except Exception as e:
|
|
|
- self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
|
|
|
- results[index] = False
|
|
|
+ time.sleep(intervalNum)
|
|
|
+ manager.swipe(index, 700, 700, 200, 700, 500, self.log_message)
|
|
|
+
|
|
|
+ time.sleep(2)
|
|
|
+
|
|
|
+ # 加入书签
|
|
|
+ self.log_message(f"正在加入书架 {index}...")
|
|
|
+ manager.tap(index, 360, 600, self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.tap(index, 255, 84, self.log_message)
|
|
|
+ time.sleep(3)
|
|
|
+
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 任务完成")
|
|
|
+ results[index] = True
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "任务完成"))
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "执行出错"))
|
|
|
+ finally:
|
|
|
+ # 减少活跃线程计数
|
|
|
+ with self.threads_lock:
|
|
|
+ self.active_threads -= 1
|
|
|
+ current_active = self.active_threads
|
|
|
+ self.log_message(f"📊 当前活跃线程数: {current_active}/{max_threads}")
|
|
|
+
|
|
|
+ # 释放信号量,让下一个等待的线程开始
|
|
|
+ self.thread_semaphore.release()
|
|
|
|
|
|
# 启动所有模拟器任务(信号量会自动控制并发数)
|
|
|
for emu in self.selected_emulators:
|
|
|
if self.should_stop:
|
|
|
break
|
|
|
- # 等待直到有空闲槽位(信号量内部处理)
|
|
|
thread = threading.Thread(target=process_emulator, args=(emu,))
|
|
|
thread.start()
|
|
|
threads.append(thread)
|
|
|
# 稍微延迟一下,避免同时启动太多
|
|
|
- time.sleep(10)
|
|
|
+ time.sleep(2)
|
|
|
|
|
|
# 等待所有线程完成
|
|
|
for thread in threads:
|
|
|
@@ -855,82 +1091,151 @@ class MuMuAutoGUI:
|
|
|
|
|
|
|
|
|
def run_task2(self):
|
|
|
- """执行评价任务(支持并发)"""
|
|
|
+ """执行评价任务(支持并发,出错直接退出不重试)"""
|
|
|
try:
|
|
|
- manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
-
|
|
|
# 获取最大线程数
|
|
|
max_threads = int(self.max_threads_var.get()) if self.max_threads_var.get().isdigit() else 1
|
|
|
self.thread_semaphore = threading.Semaphore(max_threads)
|
|
|
+ self.active_threads = 0
|
|
|
self.log_message(f"📌 最大并发数: {max_threads}")
|
|
|
|
|
|
- # 用于存储线程的列表
|
|
|
threads = []
|
|
|
results = {}
|
|
|
|
|
|
def process_emulator(emu):
|
|
|
- """处理单个模拟器的函数"""
|
|
|
- with self.thread_semaphore:
|
|
|
+ """处理单个模拟器的函数,出错直接退出"""
|
|
|
+ # 获取信号量,控制并发数
|
|
|
+ self.thread_semaphore.acquire()
|
|
|
+
|
|
|
+ # 增加活跃线程计数
|
|
|
+ with self.threads_lock:
|
|
|
+ self.active_threads += 1
|
|
|
+ current_active = self.active_threads
|
|
|
+ self.log_message(f"📊 当前活跃线程数: {current_active}/{max_threads}")
|
|
|
+
|
|
|
+ index = emu['index']
|
|
|
+
|
|
|
+ try:
|
|
|
if self.should_stop:
|
|
|
return
|
|
|
|
|
|
- index = emu['index']
|
|
|
self.log_message(f"\n{'='*50}")
|
|
|
self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
self.log_message(f"{'='*50}")
|
|
|
|
|
|
- try:
|
|
|
- # 启动模拟器
|
|
|
- self.log_message(f"正在启动模拟器 {index}...")
|
|
|
- if not manager.start_emulator(index):
|
|
|
- self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
- results[index] = False
|
|
|
- return
|
|
|
-
|
|
|
- # 等待就绪
|
|
|
- if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
- self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
- results[index] = False
|
|
|
- return
|
|
|
-
|
|
|
- time.sleep(5)
|
|
|
-
|
|
|
- # 安装APK
|
|
|
- if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
- self.log_message(f"❌ APK安装失败")
|
|
|
- # results[index] = False
|
|
|
- # return
|
|
|
-
|
|
|
- # 打开应用
|
|
|
- manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
- while manager.get_pixel_color(index, 560, 130, log_callback=self.log_message) != "#EEF8EE" or manager.get_pixel_color(index, 533, 60, log_callback=self.log_message) != "#FFFFFF":
|
|
|
- self.log_message(f"模拟器 {index} 等待进入主界面中...")
|
|
|
- time.sleep(5)
|
|
|
+ # 更新状态为运行中
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "运行中"))
|
|
|
+
|
|
|
+ manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
+
|
|
|
+ # 启动模拟器
|
|
|
+ self.log_message(f"正在启动模拟器 {index}...")
|
|
|
+ if not manager.start_emulator(index):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "启动失败"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 等待就绪
|
|
|
+ if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "启动超时"))
|
|
|
+ return
|
|
|
+
|
|
|
+ time.sleep(5)
|
|
|
+ if not manager.check_resolution(index, 720, self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 分辨率不是720,停止任务并关闭模拟器")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "分辨率错误"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 安装APK
|
|
|
+ if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
+ self.log_message(f"⚠️ APK安装失败,但继续执行...")
|
|
|
+
|
|
|
+ # 打开应用
|
|
|
+ manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
+
|
|
|
+ # 等待进入主界面
|
|
|
+ errNumber = 0
|
|
|
+ while True:
|
|
|
+ if self.should_stop or self.is_paused:
|
|
|
+ while self.is_paused and not self.should_stop:
|
|
|
+ time.sleep(1)
|
|
|
+ if self.should_stop:
|
|
|
+ return
|
|
|
|
|
|
- # 执行操作
|
|
|
- self.openBook(manager, index)
|
|
|
+ color560130 = manager.get_pixel_color(index, 560, 130, log_callback=self.log_message)
|
|
|
|
|
|
- # 评价
|
|
|
- time.sleep(8)
|
|
|
- manager.tap(index, 680, 95, self.log_message)
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(index, 633, 930, self.log_message)
|
|
|
+ if color560130 in ["#EEF8EE", "#F7F7F7"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经进入主界面,继续...")
|
|
|
+ break
|
|
|
+ elif color560130 in ["#5F635F"]:
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
|
|
|
+ manager.tap(index, 640, 260, self.log_message)
|
|
|
+ time.sleep(20)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
+ elif color560130 in ["#CED0D2"]:
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭广告弹窗...")
|
|
|
+ manager.tap(index, 200, 80, self.log_message)
|
|
|
+ elif color560130 in ["#F3CEA9"]:
|
|
|
+ self.log_message(f"模拟器 {index} 进入错误页面,返回...")
|
|
|
+ manager.tap(index, 44, 92, self.log_message)
|
|
|
+ time.sleep(20)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
+ else:
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未进入主界面,退出...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "进入主界面失败"))
|
|
|
+ return
|
|
|
|
|
|
- # 发表
|
|
|
+ self.log_message(f"模拟器 {index} 等待进入主界面中...")
|
|
|
time.sleep(5)
|
|
|
- manager.tap(index, 640, 95, self.log_message)
|
|
|
-
|
|
|
- time.sleep(4)
|
|
|
- # 关闭模拟器
|
|
|
- self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
- manager.stop_emulator(index)
|
|
|
-
|
|
|
- self.log_message(f"✅ 模拟器 {index} 任务完成")
|
|
|
- results[index] = True
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
|
|
|
+
|
|
|
+ # 执行打开书籍
|
|
|
+ if not self.openBook(manager, index):
|
|
|
results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "打开书籍失败"))
|
|
|
+ return
|
|
|
+
|
|
|
+ # 评价
|
|
|
+ time.sleep(8)
|
|
|
+ manager.tap(index, 680, 95, self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.tap(index, 633, 930, self.log_message)
|
|
|
+
|
|
|
+ # 发表
|
|
|
+ time.sleep(5)
|
|
|
+ manager.tap(index, 640, 95, self.log_message)
|
|
|
+
|
|
|
+ time.sleep(4)
|
|
|
+
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 任务完成")
|
|
|
+ results[index] = True
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "任务完成"))
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "执行出错"))
|
|
|
+ finally:
|
|
|
+ # 减少活跃线程计数
|
|
|
+ with self.threads_lock:
|
|
|
+ self.active_threads -= 1
|
|
|
+ current_active = self.active_threads
|
|
|
+ self.log_message(f"📊 当前活跃线程数: {current_active}/{max_threads}")
|
|
|
+
|
|
|
+ # 释放信号量
|
|
|
+ self.thread_semaphore.release()
|
|
|
|
|
|
# 启动所有模拟器任务
|
|
|
for emu in self.selected_emulators:
|