|
|
@@ -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):
|
|
|
"""获取模拟器内指定坐标点的颜色"""
|
|
|
@@ -552,9 +604,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 +734,38 @@ class MuMuAutoGUI:
|
|
|
self.is_running = False
|
|
|
self.is_paused = False
|
|
|
self.log_message("⏹ 正在停止任务...")
|
|
|
-
|
|
|
+
|
|
|
def openBook(self, manager, index):
|
|
|
+ global results
|
|
|
# 执行操作
|
|
|
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} 等待搜索页面准备就绪...")
|
|
|
+ color56065 = manager.get_pixel_color(index, 560, 65, log_callback=self.log_message)
|
|
|
+ errNumber = 0
|
|
|
+ while color56065 != "#F7F7F7":
|
|
|
+ if color56065 == "#EBF8EC":
|
|
|
+ manager.tap(index, 390, 90, self.log_message)
|
|
|
+ self.log_message(f"模拟器 {index} 尝试重新点击搜索...")
|
|
|
+ if 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)
|
|
|
+ if 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} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 错误退出")
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "遇到错误"))
|
|
|
+ self.log_message(f"模拟器 {index} 等待搜索页面准备就绪...")
|
|
|
time.sleep(3)
|
|
|
+ color56065 = manager.get_pixel_color(index, 560, 65, log_callback=self.log_message)
|
|
|
time.sleep(3)
|
|
|
# 点输入框
|
|
|
manager.tap(index, 340, 90, self.log_message)
|
|
|
@@ -684,7 +773,17 @@ class MuMuAutoGUI:
|
|
|
# 黏贴
|
|
|
manager.paste_text(index, self.search_content_var.get(), self.log_message)
|
|
|
time.sleep(3)
|
|
|
+ errNumber = 0
|
|
|
while manager.get_pixel_color(index, 435, 880, log_callback=self.log_message) != "#FFFFFF":
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 30:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 错误退出")
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "遇到错误"))
|
|
|
self.log_message(f"模拟器 {index} 没有输入搜索内容...")
|
|
|
# 点输入框
|
|
|
manager.tap(index, 556, 87, self.log_message)
|
|
|
@@ -696,7 +795,30 @@ class MuMuAutoGUI:
|
|
|
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":
|
|
|
+
|
|
|
+ while True:
|
|
|
+ color630235 = manager.get_pixel_color(index, 630, 235, log_callback=self.log_message)
|
|
|
+ print(color630235)
|
|
|
+ errNumber = 0
|
|
|
+ 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} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 错误退出")
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "遇到错误"))
|
|
|
self.log_message(f"模拟器 {index} 等待搜索结果...")
|
|
|
time.sleep(3)
|
|
|
|
|
|
@@ -704,12 +826,30 @@ 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)
|
|
|
+ print(color630235)
|
|
|
+ if color630235 in ["#E8E3CE", "#E0DBC6", "#CCCBCB", "#DFDAC5", "#141000", "#E3DEC9"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经在看书目录界面,继续...")
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 20:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到搜索页面,设置错误并退出...")
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+ results[index] = False
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 错误退出")
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "遇到错误"))
|
|
|
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())
|
|
|
@@ -719,9 +859,7 @@ class MuMuAutoGUI:
|
|
|
self.thread_semaphore = threading.Semaphore(max_threads)
|
|
|
self.log_message(f"📌 最大并发数: {max_threads}")
|
|
|
|
|
|
- # 用于存储线程的列表
|
|
|
- threads = []
|
|
|
- results = {} # 存储每个模拟器的执行结果
|
|
|
+
|
|
|
|
|
|
def process_emulator(emu):
|
|
|
"""处理单个模拟器的函数"""
|
|
|
@@ -734,6 +872,9 @@ class MuMuAutoGUI:
|
|
|
self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
self.log_message(f"{'='*50}")
|
|
|
|
|
|
+ # 更新状态为运行中
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "运行中"))
|
|
|
+
|
|
|
try:
|
|
|
# 启动模拟器
|
|
|
self.log_message(f"正在启动模拟器 {index}...")
|
|
|
@@ -749,7 +890,12 @@ class MuMuAutoGUI:
|
|
|
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安装失败")
|
|
|
@@ -765,31 +911,72 @@ class MuMuAutoGUI:
|
|
|
# 是否是第一次进入
|
|
|
if (button_color and button_color.upper() == "#FC7838"):
|
|
|
manager.tap(index, 394, 846, self.log_message)
|
|
|
- time.sleep(90)
|
|
|
+ time.sleep(120)
|
|
|
+ boxColor = manager.get_pixel_color(index, 640, 260, log_callback=self.log_message)
|
|
|
manager.tap(index, 640, 260, self.log_message)
|
|
|
- time.sleep(10)
|
|
|
+ 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":
|
|
|
+ while True:
|
|
|
+ color560130 = manager.get_pixel_color(index, 560, 130, log_callback=self.log_message)
|
|
|
+ if color560130 in ["#EEF8EE", "#F7F7F7"]:
|
|
|
+ self.log_message(f"模拟器 {index} 已经进入主界面,继续...")
|
|
|
+ break
|
|
|
+ if 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)
|
|
|
+ if color560130 in ["#CED0D2"]:
|
|
|
+ self.log_message(f"模拟器 {index} 需要关闭广告弹窗...")
|
|
|
+ manager.tap(index, 200, 80, self.log_message)
|
|
|
+
|
|
|
+ # F3CEA9可能是推荐榜
|
|
|
+ if 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)
|
|
|
self.log_message(f"模拟器 {index} 等待进入主界面中...")
|
|
|
- manager.get_pixel_color(index, 640, 260, log_callback=self.log_message)
|
|
|
+ # manager.get_pixel_color(index, 640, 260, log_callback=self.log_message)
|
|
|
time.sleep(5)
|
|
|
# 执行操作
|
|
|
self.openBook(manager, index)
|
|
|
|
|
|
|
|
|
time.sleep(8)
|
|
|
+ errNumber = 0
|
|
|
# 翻页
|
|
|
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"]:
|
|
|
+ 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
|
|
|
+ if color7001000 in [ "#F9F9FC"]:
|
|
|
+ self.log_message(f"模拟器 {index} 遇到广告,点击跳过...")
|
|
|
+ manager.tap(index, 700, 300, self.log_message)
|
|
|
self.log_message(f"模拟器 {index} 不在看书目录界面,等待中...")
|
|
|
+ errNumber = errNumber + 1
|
|
|
+ if errNumber > 10:
|
|
|
+ self.log_message(f"模拟器 {index} 连续多次未检测到目录界面,设置错误并退出...")
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 错误退出")
|
|
|
+ results[index] = False
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "遇到错误"))
|
|
|
# 判断是否有广告
|
|
|
time.sleep(2)
|
|
|
- if manager.get_pixel_color(index, 700, 1200, log_callback=self.log_message) in ["#e8e3ce"]:
|
|
|
+ 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)
|
|
|
@@ -806,13 +993,16 @@ class MuMuAutoGUI:
|
|
|
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)
|
|
|
+ # 清空错误信息
|
|
|
+ errNumber = 0
|
|
|
+ # manager.tap(index, 700, 1000, self.log_message)
|
|
|
+ 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, 97, self.log_message)
|
|
|
+ manager.tap(index, 255, 84, self.log_message)
|
|
|
time.sleep(3)
|
|
|
# 关闭模拟器
|
|
|
self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
@@ -820,10 +1010,11 @@ class MuMuAutoGUI:
|
|
|
|
|
|
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, "执行出错"))
|
|
|
|
|
|
# 启动所有模拟器任务(信号量会自动控制并发数)
|
|
|
for emu in self.selected_emulators:
|
|
|
@@ -879,6 +1070,9 @@ class MuMuAutoGUI:
|
|
|
self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
self.log_message(f"{'='*50}")
|
|
|
|
|
|
+ # 更新状态为运行中
|
|
|
+ self.root.after(0, lambda: self.update_emulator_status(index, "运行中"))
|
|
|
+
|
|
|
try:
|
|
|
# 启动模拟器
|
|
|
self.log_message(f"正在启动模拟器 {index}...")
|
|
|
@@ -894,7 +1088,12 @@ class MuMuAutoGUI:
|
|
|
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安装失败")
|
|
|
@@ -927,10 +1126,11 @@ class MuMuAutoGUI:
|
|
|
|
|
|
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, "执行出错"))
|
|
|
|
|
|
# 启动所有模拟器任务
|
|
|
for emu in self.selected_emulators:
|