|
|
@@ -1,8 +1,13 @@
|
|
|
+import random
|
|
|
import subprocess
|
|
|
import json
|
|
|
import time
|
|
|
import os
|
|
|
import tkinter as tk
|
|
|
+from tkinter import ttk, scrolledtext, messagebox, filedialog
|
|
|
+import threading
|
|
|
+import configparser
|
|
|
+from datetime import datetime
|
|
|
|
|
|
class MuMuEmulatorManager:
|
|
|
def __init__(self, manager_path=r"D:\MuMuPlayer\nx_main\MuMuManager.exe"):
|
|
|
@@ -16,7 +21,6 @@ class MuMuEmulatorManager:
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
|
|
|
|
|
if result.returncode != 0:
|
|
|
- print(f"获取列表失败: {result.stderr}")
|
|
|
return []
|
|
|
|
|
|
try:
|
|
|
@@ -25,283 +29,832 @@ class MuMuEmulatorManager:
|
|
|
for key, value in data.items():
|
|
|
if isinstance(value, dict):
|
|
|
value['index'] = key
|
|
|
+ # 计算ADB端口
|
|
|
+ value['adb_port'] = 16384 + 32 * int(key)
|
|
|
emulators.append(value)
|
|
|
return emulators
|
|
|
- except json.JSONDecodeError as e:
|
|
|
- print(f"JSON解析失败: {e}")
|
|
|
+ except json.JSONDecodeError:
|
|
|
return []
|
|
|
|
|
|
def start_emulator(self, index):
|
|
|
- """启动指定索引的模拟器 - 使用正确的 launch 命令"""
|
|
|
- print(f"正在启动模拟器 {index}...")
|
|
|
-
|
|
|
- # 正确的命令格式:control -v <index> launch
|
|
|
+ """启动指定索引的模拟器"""
|
|
|
cmd = [self.manager_path, "control", "-v", str(index), "launch"]
|
|
|
- print(f"执行命令: {' '.join(cmd)}")
|
|
|
-
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
|
|
-
|
|
|
- if result.returncode == 0:
|
|
|
- print(f"✅ 模拟器 {index} 启动命令已发送")
|
|
|
- return True
|
|
|
- else:
|
|
|
- print(f"❌ 启动失败")
|
|
|
- if result.stderr:
|
|
|
- print(f"错误信息: {result.stderr}")
|
|
|
- if result.stdout:
|
|
|
- print(f"输出: {result.stdout}")
|
|
|
- return False
|
|
|
+ return result.returncode == 0
|
|
|
|
|
|
- def wait_for_emulator_ready(self, index, timeout=120, check_interval=3):
|
|
|
- """等待模拟器启动完成(Android 系统就绪)"""
|
|
|
- print(f"\n等待模拟器 {index} 启动完成...")
|
|
|
+ def wait_for_emulator_ready(self, index, timeout=120, check_interval=3, log_callback=None):
|
|
|
+ """等待模拟器启动完成"""
|
|
|
start_time = time.time()
|
|
|
|
|
|
while time.time() - start_time < timeout:
|
|
|
- # 获取模拟器状态
|
|
|
cmd = [self.manager_path, "info", "-v", str(index)]
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
try:
|
|
|
data = json.loads(result.stdout)
|
|
|
- # 检查 Android 是否已启动
|
|
|
if data.get('is_android_started') == True:
|
|
|
- elapsed = time.time() - start_time
|
|
|
- print(f"✅ 模拟器 {index} 已就绪!耗时: {elapsed:.1f} 秒")
|
|
|
return True
|
|
|
- elif data.get('is_process_started') == True:
|
|
|
- print(f"⏳ 进程已启动,等待 Android 系统... ({int(time.time() - start_time)}秒)")
|
|
|
- else:
|
|
|
- print(f"⏳ 等待进程启动... ({int(time.time() - start_time)}秒)")
|
|
|
except:
|
|
|
pass
|
|
|
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"等待模拟器 {index} 启动... ({int(time.time() - start_time)}秒)")
|
|
|
time.sleep(check_interval)
|
|
|
|
|
|
- print(f"❌ 超时!模拟器 {index} 在 {timeout} 秒内未就绪")
|
|
|
return False
|
|
|
|
|
|
- def get_adb_port(self, index):
|
|
|
- """获取模拟器的 ADB 端口"""
|
|
|
- # 通过 MuMuManager 获取
|
|
|
- cmd = [self.manager_path, "info", "-v", str(index)]
|
|
|
+ def stop_emulator(self, index):
|
|
|
+ """关闭模拟器"""
|
|
|
+ cmd = [self.manager_path, "control", "-v", str(index), "shutdown"]
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
|
|
|
-
|
|
|
- if result.returncode == 0:
|
|
|
- try:
|
|
|
- data = json.loads(result.stdout)
|
|
|
- # 注意:从之前的输出看,info 命令没有直接返回 adb_port
|
|
|
- # 可能需要通过其他方式获取
|
|
|
- pass
|
|
|
- except:
|
|
|
- pass
|
|
|
-
|
|
|
- # 使用 MuMu 默认端口规律:16384 + 32 * index
|
|
|
- default_port = 16384 + 32 * int(index)
|
|
|
- print(f"估算 ADB 端口: {default_port}")
|
|
|
- return default_port
|
|
|
+ return result.returncode == 0
|
|
|
|
|
|
- def install_apk(self, index, apk_path):
|
|
|
- """通过 ADB 安装 APK 到模拟器"""
|
|
|
- # 检查 APK 是否存在
|
|
|
+ def install_apk(self, index, apk_path, log_callback=None):
|
|
|
+ """安装APK"""
|
|
|
if not os.path.exists(apk_path):
|
|
|
- print(f"❌ 错误:APK 文件不存在: {apk_path}")
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"❌ APK文件不存在: {apk_path}")
|
|
|
return False
|
|
|
|
|
|
- # 获取 ADB 端口
|
|
|
- adb_port = self.get_adb_port(index)
|
|
|
+ adb_port = 16384 + 32 * int(index)
|
|
|
target_device = f"127.0.0.1:{adb_port}"
|
|
|
|
|
|
- # 连接 ADB
|
|
|
- print(f"\n连接 ADB {target_device}...")
|
|
|
- connect_result = subprocess.run(f"adb connect {target_device}",
|
|
|
- shell=True, capture_output=True, text=True)
|
|
|
- print(connect_result.stdout.strip())
|
|
|
-
|
|
|
- # 等待连接稳定
|
|
|
+ # 连接ADB
|
|
|
+ subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
time.sleep(2)
|
|
|
|
|
|
- # 检查设备连接
|
|
|
- devices_result = subprocess.run("adb devices", shell=True, capture_output=True, text=True)
|
|
|
- print(f"\n当前设备列表:\n{devices_result.stdout}")
|
|
|
-
|
|
|
- # 检查是否已安装 com.dragon.read
|
|
|
- print(f"\n检查是否已安装 com.dragon.read...")
|
|
|
+ # 检查是否已安装
|
|
|
check_cmd = f"adb -s {target_device} shell pm list packages | findstr \"com.dragon.read\""
|
|
|
check_result = subprocess.run(check_cmd, shell=True, capture_output=True, text=True)
|
|
|
|
|
|
if "com.dragon.read" in check_result.stdout:
|
|
|
- print("✅ com.dragon.read 已安装,跳过安装步骤")
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"✅ com.dragon.read 已安装,跳过安装步骤")
|
|
|
return True
|
|
|
|
|
|
- # 安装 APK
|
|
|
- print(f"\n正在安装 APK: {os.path.basename(apk_path)}")
|
|
|
+ # 安装APK
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"正在安装APK: {os.path.basename(apk_path)}")
|
|
|
install_cmd = f"adb -s {target_device} install -r \"{apk_path}\""
|
|
|
result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)
|
|
|
|
|
|
- if "Success" in result.stdout:
|
|
|
- print("✅ APK 安装成功!")
|
|
|
- return True
|
|
|
- else:
|
|
|
- print("❌ APK 安装失败")
|
|
|
- print(f"输出: {result.stdout}")
|
|
|
- if result.stderr:
|
|
|
- print(f"错误: {result.stderr}")
|
|
|
- return False
|
|
|
+ return "Success" in result.stdout
|
|
|
|
|
|
- def display_emulator_list(self):
|
|
|
- """显示模拟器列表"""
|
|
|
- emulators = self.get_emulator_list()
|
|
|
-
|
|
|
- if not emulators:
|
|
|
- print("未找到任何模拟器")
|
|
|
- return
|
|
|
-
|
|
|
- print(f"\n{'='*60}")
|
|
|
- print(f"找到 {len(emulators)} 个模拟器:")
|
|
|
- print(f"{'='*60}\n")
|
|
|
-
|
|
|
- for i, emu in enumerate(emulators, 1):
|
|
|
- status = "🟢 运行中" if emu.get('is_process_started') else "🔴 未运行"
|
|
|
- android_status = "✅ 已启动" if emu.get('is_android_started') else "⏸️ 未启动"
|
|
|
- print(f"{i}. 索引 {emu.get('index')}: {emu.get('name')}")
|
|
|
- print(f" 进程状态: {status}")
|
|
|
- print(f" Android: {android_status}")
|
|
|
- print()
|
|
|
- def open_app(self, index, package_name="com.dragon.read"):
|
|
|
- """打开指定包名的应用"""
|
|
|
- # 获取 ADB 端口
|
|
|
- adb_port = self.get_adb_port(index)
|
|
|
+ def open_app(self, index, package_name, log_callback=None):
|
|
|
+ """打开应用"""
|
|
|
+ adb_port = 16384 + 32 * int(index)
|
|
|
target_device = f"127.0.0.1:{adb_port}"
|
|
|
|
|
|
- # 连接 ADB
|
|
|
- print(f"\n连接 ADB {target_device}...")
|
|
|
subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
time.sleep(1)
|
|
|
|
|
|
- # 打开应用(使用 monkey 命令,最通用)
|
|
|
- print(f"\n正在打开应用: {package_name}")
|
|
|
cmd = f"adb -s {target_device} shell monkey -p {package_name} -c android.intent.category.LAUNCHER 1"
|
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
|
|
|
|
if "Events injected" in result.stdout or result.returncode == 0:
|
|
|
- print(f"✅ 应用已打开: {package_name}")
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"✅ 已打开应用: {package_name}")
|
|
|
return True
|
|
|
- else:
|
|
|
- print(f"❌ 打开失败")
|
|
|
- print(f"输出: {result.stdout}")
|
|
|
- if result.stderr:
|
|
|
- print(f"错误: {result.stderr}")
|
|
|
- return False
|
|
|
- def paste_text(self, index, text):
|
|
|
- """将文字放到系统剪贴板并粘贴"""
|
|
|
- # 获取 ADB 端口
|
|
|
- adb_port = self.get_adb_port(index)
|
|
|
+ return False
|
|
|
+
|
|
|
+ def paste_text(self, index, text, log_callback=None):
|
|
|
+ """粘贴文字"""
|
|
|
+ adb_port = 16384 + 32 * int(index)
|
|
|
target_device = f"127.0.0.1:{adb_port}"
|
|
|
|
|
|
- # 连接 ADB
|
|
|
subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
time.sleep(0.5)
|
|
|
- # 1. 复制到电脑剪贴板
|
|
|
- """使用 Windows clip 命令复制到剪贴板"""
|
|
|
+
|
|
|
+ # 复制到剪贴板
|
|
|
root = tk.Tk()
|
|
|
root.withdraw()
|
|
|
root.clipboard_clear()
|
|
|
root.clipboard_append(text)
|
|
|
root.update()
|
|
|
root.destroy()
|
|
|
- # 将文字设置到剪贴板
|
|
|
|
|
|
escape_text = text.replace('"', '\\"').replace("'", "\\'")
|
|
|
set_clipboard_cmd = f'adb -s {target_device} shell am broadcast -a clipper.set -e text "{escape_text}"'
|
|
|
subprocess.run(set_clipboard_cmd, shell=True, capture_output=True)
|
|
|
time.sleep(0.3)
|
|
|
|
|
|
- # 执行粘贴 (Ctrl+V)
|
|
|
+ # 执行粘贴
|
|
|
subprocess.run(f"adb -s {target_device} shell input keyevent 279", shell=True)
|
|
|
|
|
|
- print(f"✅ 已粘贴: {text[:50]}{'...' if len(text) > 50 else ''}")
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"✅ 已粘贴: {text[:50]}{'...' if len(text) > 50 else ''}")
|
|
|
return True
|
|
|
- def tap(self, index, x, y):
|
|
|
- """点击指定坐标"""
|
|
|
- # 获取 ADB 端口
|
|
|
- adb_port = self.get_adb_port(index)
|
|
|
+
|
|
|
+ def tap(self, index, x, y, log_callback=None):
|
|
|
+ """点击坐标"""
|
|
|
+ adb_port = 16384 + 32 * int(index)
|
|
|
target_device = f"127.0.0.1:{adb_port}"
|
|
|
|
|
|
- # 连接 ADB
|
|
|
subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
|
|
|
- # 点击坐标
|
|
|
cmd = f"adb -s {target_device} shell input tap {x} {y}"
|
|
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
- print(f"✅ 点击坐标 ({x}, {y})")
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"✅ 点击坐标 ({x}, {y})")
|
|
|
return True
|
|
|
+ return False
|
|
|
+
|
|
|
+ def get_pixel_color(self, index, x, y, log_callback=None):
|
|
|
+ """获取模拟器内指定坐标点的颜色
|
|
|
+
|
|
|
+ Args:
|
|
|
+ index: 模拟器索引
|
|
|
+ x: X坐标
|
|
|
+ y: Y坐标
|
|
|
+ log_callback: 日志回调函数
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ 颜色值字符串,格式如 "#FFFFFF",失败返回 None
|
|
|
+ """
|
|
|
+ adb_port = 16384 + 32 * int(index)
|
|
|
+ target_device = f"127.0.0.1:{adb_port}"
|
|
|
+
|
|
|
+ # 连接ADB
|
|
|
+ subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
|
|
|
+ time.sleep(0.5)
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 获取屏幕分辨率
|
|
|
+ get_resolution_cmd = f"adb -s {target_device} shell wm size"
|
|
|
+ resolution_result = subprocess.run(get_resolution_cmd, shell=True, capture_output=True, text=True)
|
|
|
+
|
|
|
+ if resolution_result.stdout:
|
|
|
+ # 解析分辨率,格式如 "Physical size: 720x1280"
|
|
|
+ import re
|
|
|
+ match = re.search(r'(\d+)x(\d+)', resolution_result.stdout)
|
|
|
+ if match:
|
|
|
+ screen_width = int(match.group(1))
|
|
|
+ screen_height = int(match.group(2))
|
|
|
+ else:
|
|
|
+ screen_width = 720
|
|
|
+ screen_height = 1280
|
|
|
+ else:
|
|
|
+ screen_width = 720
|
|
|
+ screen_height = 1280
|
|
|
+
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"📱 屏幕分辨率: {screen_width}x{screen_height}")
|
|
|
+
|
|
|
+ # 使用 screencap -p 获取 PNG 格式,然后通过 Python 解析
|
|
|
+ # 创建本地临时文件
|
|
|
+ temp_local_file = f"temp_screenshot_{int(time.time())}.png"
|
|
|
+
|
|
|
+ # 截图并保存到本地
|
|
|
+ screenshot_cmd = f"adb -s {target_device} exec-out screencap -p > {temp_local_file}"
|
|
|
+ subprocess.run(screenshot_cmd, shell=True, capture_output=True, text=True)
|
|
|
+ time.sleep(0.3)
|
|
|
+
|
|
|
+ # 检查文件是否存在且不为空
|
|
|
+ if os.path.exists(temp_local_file) and os.path.getsize(temp_local_file) > 0:
|
|
|
+ try:
|
|
|
+ from PIL import Image
|
|
|
+
|
|
|
+ # 打开图片
|
|
|
+ img = Image.open(temp_local_file)
|
|
|
+
|
|
|
+ # 确保坐标在范围内
|
|
|
+ width, height = img.size
|
|
|
+ if x < 0 or x >= width or y < 0 or y >= height:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"❌ 坐标({x},{y})超出屏幕范围 {width}x{height}")
|
|
|
+ os.remove(temp_local_file)
|
|
|
+ return None
|
|
|
+
|
|
|
+ # 获取像素颜色
|
|
|
+ pixel = img.getpixel((x, y))
|
|
|
+
|
|
|
+ # 转换为十六进制颜色值
|
|
|
+ if isinstance(pixel, tuple):
|
|
|
+ if len(pixel) >= 3:
|
|
|
+ r, g, b = pixel[0], pixel[1], pixel[2]
|
|
|
+ else:
|
|
|
+ r, g, b = pixel, pixel, pixel
|
|
|
+ else:
|
|
|
+ r = g = b = pixel
|
|
|
+
|
|
|
+ color = f"#{r:02X}{g:02X}{b:02X}"
|
|
|
+
|
|
|
+ # 清理临时文件
|
|
|
+ os.remove(temp_local_file)
|
|
|
+
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"🎨 坐标({x},{y}) 颜色: {color}")
|
|
|
+ return color
|
|
|
+
|
|
|
+ except ImportError:
|
|
|
+ if log_callback:
|
|
|
+ log_callback("❌ 请先安装PIL库: pip install Pillow")
|
|
|
+ # 清理临时文件
|
|
|
+ if os.path.exists(temp_local_file):
|
|
|
+ os.remove(temp_local_file)
|
|
|
+ return None
|
|
|
+ except Exception as e:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"❌ 解析图片失败: {e}")
|
|
|
+ # 清理临时文件
|
|
|
+ if os.path.exists(temp_local_file):
|
|
|
+ os.remove(temp_local_file)
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ if log_callback:
|
|
|
+ log_callback("❌ 截图失败")
|
|
|
+ return None
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ if log_callback:
|
|
|
+ log_callback(f"❌ 获取颜色失败: {e}")
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+class MuMuAutoGUI:
|
|
|
+ def __init__(self):
|
|
|
+ self.root = tk.Tk()
|
|
|
+ self.root.title("MuMu模拟器自动化工具")
|
|
|
+ self.root.geometry("700x360")
|
|
|
+
|
|
|
+ # 配置文件
|
|
|
+ self.config_file = "mumu_config.ini"
|
|
|
+ self.config = configparser.ConfigParser()
|
|
|
+ self.load_config()
|
|
|
+
|
|
|
+ # 运行状态
|
|
|
+ self.is_running = False
|
|
|
+ self.is_paused = False
|
|
|
+ self.should_stop = False
|
|
|
+ self.current_thread = None
|
|
|
+ self.selected_emulators = []
|
|
|
+
|
|
|
+
|
|
|
+ self.load_btn = None
|
|
|
+ self.start_read_btn = None
|
|
|
+ self.start_comment_btn = None
|
|
|
+
|
|
|
+ # 创建界面
|
|
|
+ self.create_widgets()
|
|
|
+
|
|
|
+ # 加载保存的配置
|
|
|
+ self.load_settings()
|
|
|
+
|
|
|
+ def load_config(self):
|
|
|
+ """加载配置文件"""
|
|
|
+ if os.path.exists(self.config_file):
|
|
|
+ self.config.read(self.config_file, encoding='utf-8')
|
|
|
else:
|
|
|
- print(f"❌ 点击失败: {result.stderr}")
|
|
|
- return False
|
|
|
-def main():
|
|
|
- # 配置
|
|
|
- MANAGER_PATH = r"D:\MuMuPlayer\nx_main\MuMuManager.exe"
|
|
|
- APK_PATH = r"fanqie.apk" # 请修改为你的 APK 实际路径
|
|
|
- TARGET_INDEX = 1 # 第二个模拟器的索引(索引从0开始,1是第二个)
|
|
|
+ self.config['Settings'] = {
|
|
|
+ 'mumu_path': r'D:\MuMuPlayer\nx_main\MuMuManager.exe',
|
|
|
+ 'apk_path': 'fanqie.apk',
|
|
|
+ 'package_name': 'com.dragon.read',
|
|
|
+ 'search_content': '玄幻战神:开局就得到大佬的守护'
|
|
|
+ }
|
|
|
|
|
|
- # 创建管理器实例
|
|
|
- try:
|
|
|
- manager = MuMuEmulatorManager(MANAGER_PATH)
|
|
|
- except FileNotFoundError as e:
|
|
|
- print(e)
|
|
|
- return
|
|
|
+ def save_config(self):
|
|
|
+ """保存配置文件"""
|
|
|
+ with open(self.config_file, 'w', encoding='utf-8') as f:
|
|
|
+ self.config.write(f)
|
|
|
|
|
|
- # 1. 显示所有模拟器列表
|
|
|
- print("步骤1: 获取模拟器列表")
|
|
|
- manager.display_emulator_list()
|
|
|
+ def load_settings(self):
|
|
|
+ """加载设置到界面"""
|
|
|
+ self.mumu_path_var.set(self.config['Settings']['mumu_path'] if 'mumu_path' in self.config['Settings'] else '')
|
|
|
+ self.apk_path_var.set(self.config['Settings']['apk_path'] if 'apk_path' in self.config['Settings'] else '')
|
|
|
+ self.package_name_var.set(self.config['Settings']['package_name'] if 'package_name' in self.config['Settings'] else '')
|
|
|
+ self.search_content_var.set(self.config['Settings']['search_content'] if 'search_content' in self.config['Settings'] else '盗墓笔记')
|
|
|
+ self.page_count_var.set(self.config['Settings']['page_count_var'] if 'page_count_var' in self.config['Settings'] else '10-30')
|
|
|
+ self.page_interval_var.set(self.config['Settings']['page_interval_var'] if 'page_interval_var' in self.config['Settings'] else '5')
|
|
|
+ self.max_threads_var.set(self.config['Settings']['max_threads_var'] if 'max_threads_var' in self.config['Settings'] else '1')
|
|
|
+
|
|
|
|
|
|
- # 2. 启动第二个模拟器(索引为1)
|
|
|
- print(f"\n步骤2: 启动模拟器 {TARGET_INDEX}")
|
|
|
- if not manager.start_emulator(TARGET_INDEX):
|
|
|
- print("启动失败,退出")
|
|
|
- return
|
|
|
+ def save_settings(self):
|
|
|
+ """保存界面设置到文件"""
|
|
|
+ self.config['Settings']['mumu_path'] = self.mumu_path_var.get()
|
|
|
+ self.config['Settings']['apk_path'] = self.apk_path_var.get()
|
|
|
+ self.config['Settings']['package_name'] = self.package_name_var.get()
|
|
|
+ self.config['Settings']['search_content'] = self.search_content_var.get()
|
|
|
+ self.config['Settings']['page_count_var'] = self.page_count_var.get()
|
|
|
+ self.config['Settings']['page_interval_var'] = self.page_interval_var.get()
|
|
|
+ self.config['Settings']['max_threads_var'] = self.max_threads_var.get()
|
|
|
+
|
|
|
+ self.save_config()
|
|
|
+ self.log_message("✅ 配置已保存")
|
|
|
|
|
|
- # 3. 等待模拟器启动完成
|
|
|
- print(f"\n步骤3: 等待模拟器就绪")
|
|
|
- if not manager.wait_for_emulator_ready(TARGET_INDEX, timeout=180):
|
|
|
- print("模拟器未能在规定时间内就绪,退出")
|
|
|
- return
|
|
|
- time.sleep(5)
|
|
|
- # 4. 安装 APK
|
|
|
- print(f"\n步骤4: 安装 APK")
|
|
|
- if manager.install_apk(TARGET_INDEX, APK_PATH):
|
|
|
- print("\n🎉 全部步骤完成!")
|
|
|
- manager.open_app(TARGET_INDEX, package_name="com.dragon.read")
|
|
|
- time.sleep(40)
|
|
|
- manager.tap(TARGET_INDEX, 390, 90) # 点击输入框坐标(示例)
|
|
|
- time.sleep(2)
|
|
|
- manager.paste_text(TARGET_INDEX, "玄幻战神:开局就得到大佬的守护")
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(TARGET_INDEX, 655, 92)
|
|
|
- # 进入数目
|
|
|
- time.sleep(5)
|
|
|
- manager.tap(TARGET_INDEX, 355, 333)
|
|
|
- ind = 0
|
|
|
- while ind < 10:
|
|
|
- time.sleep(30)
|
|
|
- manager.tap(TARGET_INDEX, 710, 632)
|
|
|
- ind += 1
|
|
|
- # 评价
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(TARGET_INDEX, 360, 690)
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(TARGET_INDEX, 680, 95)
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(TARGET_INDEX, 633, 930)
|
|
|
- # 发表
|
|
|
- time.sleep(2)
|
|
|
- manager.tap(TARGET_INDEX, 640, 95)
|
|
|
- else:
|
|
|
- print("\n❌ 运行失败")
|
|
|
+ def create_widgets(self):
|
|
|
+ """创建界面组件"""
|
|
|
+ # 创建选项卡
|
|
|
+ self.notebook = ttk.Notebook(self.root)
|
|
|
+ self.notebook.pack(fill='both', expand=True, padx=5, pady=5)
|
|
|
+
|
|
|
+ # 配置选项卡
|
|
|
+ self.create_config_tab()
|
|
|
+
|
|
|
+ # 任务选项卡
|
|
|
+ self.create_task_tab()
|
|
|
+
|
|
|
+ # 日志选项卡
|
|
|
+ self.create_log_tab()
|
|
|
+
|
|
|
+ def create_config_tab(self):
|
|
|
+ """创建配置选项卡"""
|
|
|
+ config_frame = ttk.Frame(self.notebook)
|
|
|
+ self.notebook.add(config_frame, text="配置")
|
|
|
+
|
|
|
+ # 创建滚动框架
|
|
|
+ canvas = tk.Canvas(config_frame)
|
|
|
+ scrollbar = ttk.Scrollbar(config_frame, orient="vertical", command=canvas.yview)
|
|
|
+ scrollable_frame = ttk.Frame(canvas)
|
|
|
+
|
|
|
+ scrollable_frame.bind(
|
|
|
+ "<Configure>",
|
|
|
+ lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
|
|
+ )
|
|
|
+
|
|
|
+ canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
|
|
+ canvas.configure(yscrollcommand=scrollbar.set)
|
|
|
+
|
|
|
+ # 配置项
|
|
|
+ row = 0
|
|
|
+
|
|
|
+ # MuMu路径
|
|
|
+ ttk.Label(scrollable_frame, text="MuMuManager路径:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.mumu_path_var = tk.StringVar()
|
|
|
+ mumu_entry = ttk.Entry(scrollable_frame, textvariable=self.mumu_path_var, width=60)
|
|
|
+ mumu_entry.grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ ttk.Button(scrollable_frame, text="浏览", command=self.browse_mumu_path).grid(row=row, column=2, padx=5, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # APK路径
|
|
|
+ ttk.Label(scrollable_frame, text="APK文件路径:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.apk_path_var = tk.StringVar()
|
|
|
+ apk_entry = ttk.Entry(scrollable_frame, textvariable=self.apk_path_var, width=60)
|
|
|
+ apk_entry.grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ ttk.Button(scrollable_frame, text="浏览", command=self.browse_apk_path).grid(row=row, column=2, padx=5, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 包名
|
|
|
+ ttk.Label(scrollable_frame, text="应用包名:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.package_name_var = tk.StringVar()
|
|
|
+ ttk.Entry(scrollable_frame, textvariable=self.package_name_var, width=40).grid(row=row, column=1, sticky='w', padx=10, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 搜索内容
|
|
|
+ ttk.Label(scrollable_frame, text="搜索内容:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.search_content_var = tk.StringVar()
|
|
|
+ ttk.Entry(scrollable_frame, textvariable=self.search_content_var, width=60).grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 翻页间隔
|
|
|
+ ttk.Label(scrollable_frame, text="翻页间隔(秒):").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.page_interval_var = tk.StringVar()
|
|
|
+ ttk.Entry(scrollable_frame, textvariable=self.page_interval_var, width=60).grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 阅读页数
|
|
|
+ ttk.Label(scrollable_frame, text="阅读页数:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.page_count_var = tk.StringVar()
|
|
|
+ ttk.Entry(scrollable_frame, textvariable=self.page_count_var, width=60).grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 最大线程
|
|
|
+ ttk.Label(scrollable_frame, text="最大线程:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
|
|
|
+ self.max_threads_var = tk.StringVar()
|
|
|
+ ttk.Entry(scrollable_frame, textvariable=self.max_threads_var, width=60).grid(row=row, column=1, padx=10, pady=5)
|
|
|
+ row += 1
|
|
|
+
|
|
|
+ # 保存按钮
|
|
|
+ ttk.Button(scrollable_frame, text="保存配置", command=self.save_settings).grid(row=row, column=0, columnspan=3, pady=20)
|
|
|
+
|
|
|
+ canvas.pack(side="left", fill="both", expand=True)
|
|
|
+ scrollbar.pack(side="right", fill="y")
|
|
|
+
|
|
|
+ def create_task_tab(self):
|
|
|
+ """创建任务选项卡"""
|
|
|
+ task_frame = ttk.Frame(self.notebook)
|
|
|
+ self.notebook.add(task_frame, text="任务")
|
|
|
+
|
|
|
+ # 上部:模拟器列表
|
|
|
+ list_frame = ttk.LabelFrame(task_frame, text="任务控制")
|
|
|
+ list_frame.pack(fill='both', expand=True, padx=5, pady=5)
|
|
|
+
|
|
|
+ # 按钮栏
|
|
|
+ button_frame = ttk.Frame(list_frame)
|
|
|
+ button_frame.pack(fill='x', padx=5, pady=5)
|
|
|
+
|
|
|
+ self.load_btn = ttk.Button(button_frame, text="读取模拟器", command=self.load_emulators)
|
|
|
+ self.load_btn.pack(side='left', padx=5)
|
|
|
+
|
|
|
+ self.start_read_btn = ttk.Button(button_frame, text="开始阅读", command=self.start_task, width=10)
|
|
|
+ self.start_read_btn.pack(side='left', padx=10)
|
|
|
+
|
|
|
+ self.start_comment_btn = ttk.Button(button_frame, text="开始评价", command=self.start_task2, width=10)
|
|
|
+ self.start_comment_btn.pack(side='left', padx=10)
|
|
|
+
|
|
|
+ self.pause_btn = ttk.Button(button_frame, text="暂停", command=self.pause_task, width=10, state='disabled')
|
|
|
+ self.pause_btn.pack(side='left', padx=10)
|
|
|
+
|
|
|
+ self.stop_btn = ttk.Button(button_frame, text="停止", command=self.stop_task, width=10, state='disabled')
|
|
|
+ self.stop_btn.pack(side='left', padx=10)
|
|
|
+
|
|
|
+ # 模拟器列表(带复选框)
|
|
|
+ tree_frame = ttk.Frame(list_frame)
|
|
|
+ tree_frame.pack(fill='both', expand=True, padx=5, pady=5)
|
|
|
+
|
|
|
+ # 创建Treeview
|
|
|
+ columns = ("选择", "索引", "名称", "ADB端口", "状态")
|
|
|
+ self.emulator_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=8)
|
|
|
+
|
|
|
+ # 设置列标题
|
|
|
+ self.emulator_tree.heading("选择", text="选择")
|
|
|
+ self.emulator_tree.heading("索引", text="索引")
|
|
|
+ self.emulator_tree.heading("名称", text="名称")
|
|
|
+ self.emulator_tree.heading("ADB端口", text="ADB端口")
|
|
|
+ self.emulator_tree.heading("状态", text="状态")
|
|
|
+
|
|
|
+ # 设置列宽
|
|
|
+ self.emulator_tree.column("选择", width=50)
|
|
|
+ self.emulator_tree.column("索引", width=50)
|
|
|
+ self.emulator_tree.column("名称", width=150)
|
|
|
+ self.emulator_tree.column("ADB端口", width=80)
|
|
|
+ self.emulator_tree.column("状态", width=100)
|
|
|
+
|
|
|
+ # 添加滚动条
|
|
|
+ vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.emulator_tree.yview)
|
|
|
+ self.emulator_tree.configure(yscrollcommand=vsb.set)
|
|
|
+
|
|
|
+ self.emulator_tree.pack(side='left', fill='both', expand=True)
|
|
|
+ vsb.pack(side='right', fill='y')
|
|
|
+
|
|
|
+ # 绑定双击选择
|
|
|
+ self.emulator_tree.bind('<Button-1>', self.on_tree_click)
|
|
|
+
|
|
|
+
|
|
|
+ def create_log_tab(self):
|
|
|
+ """创建日志选项卡"""
|
|
|
+ log_frame = ttk.Frame(self.notebook)
|
|
|
+ self.notebook.add(log_frame, text="日志")
|
|
|
+
|
|
|
+ # 日志文本框
|
|
|
+ self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
|
|
|
+ self.log_text.pack(fill='both', expand=True, padx=5, pady=5)
|
|
|
+
|
|
|
+ # 清空按钮
|
|
|
+ btn_frame = ttk.Frame(log_frame)
|
|
|
+ btn_frame.pack(fill='x', padx=5, pady=5)
|
|
|
+ ttk.Button(btn_frame, text="清空日志", command=self.clear_log).pack(side='right')
|
|
|
+
|
|
|
+ def browse_mumu_path(self):
|
|
|
+ """浏览MuMuManager路径"""
|
|
|
+ path = filedialog.askopenfilename(title="选择MuMuManager.exe", filetypes=[("Executable", "*.exe")])
|
|
|
+ if path:
|
|
|
+ self.mumu_path_var.set(path)
|
|
|
+
|
|
|
+ def browse_apk_path(self):
|
|
|
+ """浏览APK文件"""
|
|
|
+ path = filedialog.askopenfilename(title="选择APK文件", filetypes=[("APK", "*.apk")])
|
|
|
+ if path:
|
|
|
+ self.apk_path_var.set(path)
|
|
|
+
|
|
|
+ def load_emulators(self):
|
|
|
+ """加载模拟器列表"""
|
|
|
+ try:
|
|
|
+ manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
+ emulators = manager.get_emulator_list()
|
|
|
+
|
|
|
+ # 清空现有列表
|
|
|
+ for item in self.emulator_tree.get_children():
|
|
|
+ self.emulator_tree.delete(item)
|
|
|
+
|
|
|
+ # 添加模拟器
|
|
|
+ for emu in emulators:
|
|
|
+ status = "运行中" if emu.get('is_process_started') else "未运行"
|
|
|
+ self.emulator_tree.insert('', 'end', values=("□", emu.get('index'), emu.get('name'), emu.get('adb_port'), status))
|
|
|
+
|
|
|
+ self.log_message(f"已加载 {len(emulators)} 个模拟器")
|
|
|
+ except Exception as e:
|
|
|
+ self.log_message(f"加载模拟器失败: {e}")
|
|
|
+
|
|
|
+ def on_tree_click(self, event):
|
|
|
+ """处理列表点击选择"""
|
|
|
+ region = self.emulator_tree.identify_region(event.x, event.y)
|
|
|
+ if region == "cell":
|
|
|
+ column = self.emulator_tree.identify_column(event.x)
|
|
|
+ if column == "#1": # 选择列
|
|
|
+ item = self.emulator_tree.identify_row(event.y)
|
|
|
+ if item:
|
|
|
+ values = self.emulator_tree.item(item, 'values')
|
|
|
+ current = values[0]
|
|
|
+ new_value = "☑" if current == "□" else "□"
|
|
|
+ self.emulator_tree.item(item, values=(new_value, values[1], values[2], values[3], values[4]))
|
|
|
+
|
|
|
+ def get_selected_emulators(self):
|
|
|
+ """获取选中的模拟器"""
|
|
|
+ selected = []
|
|
|
+ for item in self.emulator_tree.get_children():
|
|
|
+ values = self.emulator_tree.item(item, 'values')
|
|
|
+ if values[0] == "☑":
|
|
|
+ selected.append({
|
|
|
+ 'index': values[1],
|
|
|
+ 'name': values[2],
|
|
|
+ 'adb_port': values[3]
|
|
|
+ })
|
|
|
+ return selected
|
|
|
+
|
|
|
+ def log_message(self, message):
|
|
|
+ """添加日志"""
|
|
|
+ timestamp = datetime.now().strftime("%H:%M:%S")
|
|
|
+ self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
|
|
|
+ self.log_text.see(tk.END)
|
|
|
+ self.root.update()
|
|
|
+
|
|
|
+ def start_task(self):
|
|
|
+ """开始阅读任务"""
|
|
|
+ selected = self.get_selected_emulators()
|
|
|
+ if not selected:
|
|
|
+ messagebox.showwarning("警告", "请至少选择一个模拟器")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.selected_emulators = selected
|
|
|
+ self.is_running = True
|
|
|
+ self.is_paused = False
|
|
|
+ self.should_stop = False
|
|
|
+
|
|
|
+ # 禁用相关按钮
|
|
|
+ self.load_btn.config(state='disabled')
|
|
|
+ self.start_read_btn.config(state='disabled')
|
|
|
+ self.start_comment_btn.config(state='disabled')
|
|
|
+ self.pause_btn.config(state='normal')
|
|
|
+ self.stop_btn.config(state='normal')
|
|
|
+
|
|
|
+ # 在新线程中运行任务
|
|
|
+ self.current_thread = threading.Thread(target=self.run_task, daemon=True)
|
|
|
+ self.current_thread.start()
|
|
|
+
|
|
|
+ def start_task2(self):
|
|
|
+ """开始评价任务"""
|
|
|
+ selected = self.get_selected_emulators()
|
|
|
+ if not selected:
|
|
|
+ messagebox.showwarning("警告", "请至少选择一个模拟器")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.selected_emulators = selected
|
|
|
+ self.is_running = True
|
|
|
+ self.is_paused = False
|
|
|
+ self.should_stop = False
|
|
|
+
|
|
|
+ # 禁用相关按钮
|
|
|
+ self.load_btn.config(state='disabled')
|
|
|
+ self.start_read_btn.config(state='disabled')
|
|
|
+ self.start_comment_btn.config(state='disabled')
|
|
|
+ self.pause_btn.config(state='normal')
|
|
|
+ self.stop_btn.config(state='normal')
|
|
|
+
|
|
|
+ # 在新线程中运行评价任务
|
|
|
+ self.current_thread = threading.Thread(target=self.run_task2, daemon=True)
|
|
|
+ self.current_thread.start()
|
|
|
+
|
|
|
+ def pause_task(self):
|
|
|
+ """暂停任务"""
|
|
|
+ if self.is_running and not self.is_paused:
|
|
|
+ self.is_paused = True
|
|
|
+ self.pause_btn.config(text="继续")
|
|
|
+ self.log_message("⏸ 任务已暂停")
|
|
|
+ elif self.is_running and self.is_paused:
|
|
|
+ self.is_paused = False
|
|
|
+ self.pause_btn.config(text="暂停")
|
|
|
+ self.log_message("▶️ 任务已继续")
|
|
|
+
|
|
|
+ def stop_task(self):
|
|
|
+ """停止任务"""
|
|
|
+ if self.is_running:
|
|
|
+ self.should_stop = True
|
|
|
+ self.is_running = False
|
|
|
+ self.is_paused = False
|
|
|
+ self.log_message("⏹ 正在停止任务...")
|
|
|
+
|
|
|
+ def run_task(self):
|
|
|
+ """执行任务"""
|
|
|
+ try:
|
|
|
+ manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
+
|
|
|
+ for i, emu in enumerate(self.selected_emulators):
|
|
|
+ if self.should_stop:
|
|
|
+ self.log_message("任务已停止")
|
|
|
+ break
|
|
|
+
|
|
|
+ while self.is_paused and not self.should_stop:
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ index = emu['index']
|
|
|
+ self.log_message(f"\n{'='*50}")
|
|
|
+ self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
+ self.log_message(f"{'='*50}")
|
|
|
+
|
|
|
+ # 启动模拟器
|
|
|
+ self.log_message(f"正在启动模拟器 {index}...")
|
|
|
+ if not manager.start_emulator(index):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 等待就绪
|
|
|
+ if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
+ continue
|
|
|
+
|
|
|
+ time.sleep(5)
|
|
|
+
|
|
|
+ # 安装APK
|
|
|
+ if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
+ self.log_message(f"❌ APK安装失败")
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 打开应用
|
|
|
+ manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
+ # 判断是否需要同意
|
|
|
+ time.sleep(10)
|
|
|
+ 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(70)
|
|
|
+ manager.tap(index, 640, 260, self.log_message)
|
|
|
+ time.sleep(5)
|
|
|
+ manager.tap(index, 57, 193, self.log_message)
|
|
|
+ time.sleep(5)
|
|
|
+ else:
|
|
|
+ time.sleep(30)
|
|
|
+
|
|
|
+ # 执行操作
|
|
|
+ manager.tap(index, 390, 90, self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.paste_text(index, self.search_content_var.get(), self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.tap(index, 655, 92, self.log_message)
|
|
|
+
|
|
|
+ # 进入书目
|
|
|
+ time.sleep(5)
|
|
|
+ manager.tap(index, 355, 333, self.log_message)
|
|
|
+
|
|
|
+ time.sleep(8)
|
|
|
+ # 翻页10次
|
|
|
+ for page in range(int(self.page_count_var.get()) if self.page_count_var.get().isdigit() else 5):
|
|
|
+ if self.should_stop or self.is_paused:
|
|
|
+ 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)
|
|
|
+ else:
|
|
|
+ intervalNum = int(self.page_interval_var.get()) if self.page_interval_var.get().isdigit() else 30
|
|
|
+ time.sleep(intervalNum)
|
|
|
+ manager.tap(index, 710, 632, self.log_message)
|
|
|
+
|
|
|
+
|
|
|
+ time.sleep(4)
|
|
|
+ # 关闭模拟器
|
|
|
+ self.log_message(f"正在关闭模拟器 {index}...")
|
|
|
+ manager.stop_emulator(index)
|
|
|
+
|
|
|
+ self.log_message(f"✅ 模拟器 {index} 任务完成")
|
|
|
+
|
|
|
+ # 如果不是最后一个,等待一段时间再启动下一个
|
|
|
+ if i < len(self.selected_emulators) - 1:
|
|
|
+ wait_time = 10
|
|
|
+ self.log_message(f"等待 {wait_time} 秒后处理下一个模拟器...")
|
|
|
+ for _ in range(wait_time):
|
|
|
+ if self.should_stop:
|
|
|
+ break
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ self.log_message("\n🎉 所有任务执行完毕!")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log_message(f"❌ 任务执行出错: {e}")
|
|
|
+ finally:
|
|
|
+ self.is_running = False
|
|
|
+ self.is_paused = False
|
|
|
+ self.pause_btn.config(text="暂停")
|
|
|
+
|
|
|
+ # 恢复按钮状态
|
|
|
+ self.root.after(0, self.reset_buttons)
|
|
|
+
|
|
|
+ def run_task2(self):
|
|
|
+ """执行任务"""
|
|
|
+ try:
|
|
|
+ manager = MuMuEmulatorManager(self.mumu_path_var.get())
|
|
|
+
|
|
|
+ for i, emu in enumerate(self.selected_emulators):
|
|
|
+ if self.should_stop:
|
|
|
+ self.log_message("任务已停止")
|
|
|
+ break
|
|
|
+
|
|
|
+ while self.is_paused and not self.should_stop:
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ index = emu['index']
|
|
|
+ self.log_message(f"\n{'='*50}")
|
|
|
+ self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
|
|
|
+ self.log_message(f"{'='*50}")
|
|
|
+
|
|
|
+ # 启动模拟器
|
|
|
+ self.log_message(f"正在启动模拟器 {index}...")
|
|
|
+ if not manager.start_emulator(index):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动失败")
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 等待就绪
|
|
|
+ if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
|
|
|
+ self.log_message(f"❌ 模拟器 {index} 启动超时")
|
|
|
+ continue
|
|
|
+
|
|
|
+ time.sleep(5)
|
|
|
+
|
|
|
+ # 安装APK
|
|
|
+ if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
|
|
|
+ self.log_message(f"❌ APK安装失败")
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 打开应用
|
|
|
+ manager.open_app(index, self.package_name_var.get(), self.log_message)
|
|
|
+ time.sleep(40)
|
|
|
+
|
|
|
+ # 执行操作
|
|
|
+ manager.tap(index, 390, 90, self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.paste_text(index, self.search_content_var.get(), self.log_message)
|
|
|
+ time.sleep(2)
|
|
|
+ manager.tap(index, 655, 92, self.log_message)
|
|
|
+
|
|
|
+ # 进入书目
|
|
|
+ time.sleep(5)
|
|
|
+ manager.tap(index, 355, 333, self.log_message)
|
|
|
+
|
|
|
+
|
|
|
+ # 评价
|
|
|
+ # time.sleep(2)
|
|
|
+ # manager.tap(index, 360, 690, 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)
|
|
|
+
|
|
|
+ # 发表
|
|
|
+ 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} 任务完成")
|
|
|
+
|
|
|
+ # 如果不是最后一个,等待一段时间再启动下一个
|
|
|
+ if i < len(self.selected_emulators) - 1:
|
|
|
+ wait_time = 10
|
|
|
+ self.log_message(f"等待 {wait_time} 秒后处理下一个模拟器...")
|
|
|
+ for _ in range(wait_time):
|
|
|
+ if self.should_stop:
|
|
|
+ break
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ self.log_message("\n🎉 所有任务执行完毕!")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log_message(f"❌ 任务执行出错: {e}")
|
|
|
+ finally:
|
|
|
+ self.is_running = False
|
|
|
+ self.is_paused = False
|
|
|
+ self.pause_btn.config(text="暂停")
|
|
|
+
|
|
|
+ # 恢复按钮状态
|
|
|
+ self.root.after(0, self.reset_buttons)
|
|
|
+
|
|
|
+ def reset_buttons(self):
|
|
|
+ """重置按钮状态"""
|
|
|
+ self.load_btn.config(state='normal')
|
|
|
+ self.start_read_btn.config(state='normal')
|
|
|
+ self.start_comment_btn.config(state='normal')
|
|
|
+ self.pause_btn.config(state='disabled', text="暂停")
|
|
|
+ self.stop_btn.config(state='disabled')
|
|
|
+
|
|
|
+ def clear_log(self):
|
|
|
+ """清空日志"""
|
|
|
+ self.log_text.delete(1.0, tk.END)
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ """运行GUI"""
|
|
|
+ self.root.mainloop()
|
|
|
+
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
- main()
|
|
|
+ if datetime.now() < datetime(2026, 4, 25):
|
|
|
+ app = MuMuAutoGUI()
|
|
|
+ app.run()
|