MuMu.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. import random
  2. import subprocess
  3. import json
  4. import time
  5. import os
  6. import tkinter as tk
  7. from tkinter import ttk, scrolledtext, messagebox, filedialog
  8. import threading
  9. import configparser
  10. from datetime import datetime
  11. class MuMuEmulatorManager:
  12. def __init__(self, manager_path=r"D:\MuMuPlayer\nx_main\MuMuManager.exe"):
  13. self.manager_path = manager_path
  14. if not os.path.exists(manager_path):
  15. raise FileNotFoundError(f"找不到 MuMuManager.exe: {manager_path}")
  16. def get_emulator_list(self):
  17. """获取所有模拟器列表"""
  18. cmd = [self.manager_path, "info", "-v", "all"]
  19. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  20. if result.returncode != 0:
  21. return []
  22. try:
  23. data = json.loads(result.stdout)
  24. emulators = []
  25. for key, value in data.items():
  26. if isinstance(value, dict):
  27. value['index'] = key
  28. # 计算ADB端口
  29. value['adb_port'] = 16384 + 32 * int(key)
  30. emulators.append(value)
  31. return emulators
  32. except json.JSONDecodeError:
  33. return []
  34. def start_emulator(self, index):
  35. """启动指定索引的模拟器"""
  36. cmd = [self.manager_path, "control", "-v", str(index), "launch"]
  37. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  38. return result.returncode == 0
  39. def wait_for_emulator_ready(self, index, timeout=120, check_interval=3, log_callback=None):
  40. """等待模拟器启动完成"""
  41. start_time = time.time()
  42. while time.time() - start_time < timeout:
  43. cmd = [self.manager_path, "info", "-v", str(index)]
  44. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  45. if result.returncode == 0:
  46. try:
  47. data = json.loads(result.stdout)
  48. if data.get('is_android_started') == True:
  49. return True
  50. except:
  51. pass
  52. if log_callback:
  53. log_callback(f"等待模拟器 {index} 启动... ({int(time.time() - start_time)}秒)")
  54. time.sleep(check_interval)
  55. return False
  56. def stop_emulator(self, index):
  57. """关闭模拟器"""
  58. cmd = [self.manager_path, "control", "-v", str(index), "shutdown"]
  59. result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
  60. return result.returncode == 0
  61. def install_apk(self, index, apk_path, log_callback=None):
  62. """安装APK"""
  63. if not os.path.exists(apk_path):
  64. if log_callback:
  65. log_callback(f"❌ APK文件不存在: {apk_path}")
  66. return False
  67. adb_port = 16384 + 32 * int(index)
  68. target_device = f"127.0.0.1:{adb_port}"
  69. # 连接ADB
  70. subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
  71. time.sleep(2)
  72. # 检查是否已安装
  73. check_cmd = f"adb -s {target_device} shell pm list packages | findstr \"com.dragon.read\""
  74. check_result = subprocess.run(check_cmd, shell=True, capture_output=True, text=True)
  75. if "com.dragon.read" in check_result.stdout:
  76. if log_callback:
  77. log_callback(f"✅ com.dragon.read 已安装,跳过安装步骤")
  78. return True
  79. # 安装APK
  80. if log_callback:
  81. log_callback(f"正在安装APK: {os.path.basename(apk_path)}")
  82. install_cmd = f"adb -s {target_device} install -r \"{apk_path}\""
  83. result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)
  84. return "Success" in result.stdout
  85. def open_app(self, index, package_name, log_callback=None):
  86. """打开应用"""
  87. adb_port = 16384 + 32 * int(index)
  88. target_device = f"127.0.0.1:{adb_port}"
  89. subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
  90. time.sleep(1)
  91. cmd = f"adb -s {target_device} shell monkey -p {package_name} -c android.intent.category.LAUNCHER 1"
  92. result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
  93. if "Events injected" in result.stdout or result.returncode == 0:
  94. if log_callback:
  95. log_callback(f"✅ 已打开应用: {package_name}")
  96. return True
  97. return False
  98. def paste_text(self, index, text, log_callback=None):
  99. """粘贴文字"""
  100. adb_port = 16384 + 32 * int(index)
  101. target_device = f"127.0.0.1:{adb_port}"
  102. subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
  103. time.sleep(0.5)
  104. # 复制到剪贴板
  105. root = tk.Tk()
  106. root.withdraw()
  107. root.clipboard_clear()
  108. root.clipboard_append(text)
  109. root.update()
  110. root.destroy()
  111. escape_text = text.replace('"', '\\"').replace("'", "\\'")
  112. set_clipboard_cmd = f'adb -s {target_device} shell am broadcast -a clipper.set -e text "{escape_text}"'
  113. subprocess.run(set_clipboard_cmd, shell=True, capture_output=True)
  114. time.sleep(0.3)
  115. # 执行粘贴
  116. subprocess.run(f"adb -s {target_device} shell input keyevent 279", shell=True)
  117. if log_callback:
  118. log_callback(f"✅ 已粘贴: {text[:50]}{'...' if len(text) > 50 else ''}")
  119. return True
  120. def tap(self, index, x, y, log_callback=None):
  121. """点击坐标"""
  122. adb_port = 16384 + 32 * int(index)
  123. target_device = f"127.0.0.1:{adb_port}"
  124. subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
  125. cmd = f"adb -s {target_device} shell input tap {x} {y}"
  126. result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
  127. if result.returncode == 0:
  128. if log_callback:
  129. log_callback(f"✅ 点击坐标 ({x}, {y})")
  130. return True
  131. return False
  132. def get_pixel_color(self, index, x, y, log_callback=None):
  133. """获取模拟器内指定坐标点的颜色
  134. Args:
  135. index: 模拟器索引
  136. x: X坐标
  137. y: Y坐标
  138. log_callback: 日志回调函数
  139. Returns:
  140. 颜色值字符串,格式如 "#FFFFFF",失败返回 None
  141. """
  142. adb_port = 16384 + 32 * int(index)
  143. target_device = f"127.0.0.1:{adb_port}"
  144. # 连接ADB
  145. subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
  146. time.sleep(0.5)
  147. try:
  148. # 获取屏幕分辨率
  149. get_resolution_cmd = f"adb -s {target_device} shell wm size"
  150. resolution_result = subprocess.run(get_resolution_cmd, shell=True, capture_output=True, text=True)
  151. if resolution_result.stdout:
  152. # 解析分辨率,格式如 "Physical size: 720x1280"
  153. import re
  154. match = re.search(r'(\d+)x(\d+)', resolution_result.stdout)
  155. if match:
  156. screen_width = int(match.group(1))
  157. screen_height = int(match.group(2))
  158. else:
  159. screen_width = 720
  160. screen_height = 1280
  161. else:
  162. screen_width = 720
  163. screen_height = 1280
  164. if log_callback:
  165. log_callback(f"📱 屏幕分辨率: {screen_width}x{screen_height}")
  166. # 使用 screencap -p 获取 PNG 格式,然后通过 Python 解析
  167. # 创建本地临时文件
  168. temp_local_file = f"temp_screenshot_{int(time.time())}.png"
  169. # 截图并保存到本地
  170. screenshot_cmd = f"adb -s {target_device} exec-out screencap -p > {temp_local_file}"
  171. subprocess.run(screenshot_cmd, shell=True, capture_output=True, text=True)
  172. time.sleep(0.3)
  173. # 检查文件是否存在且不为空
  174. if os.path.exists(temp_local_file) and os.path.getsize(temp_local_file) > 0:
  175. try:
  176. from PIL import Image
  177. # 打开图片
  178. img = Image.open(temp_local_file)
  179. # 确保坐标在范围内
  180. width, height = img.size
  181. if x < 0 or x >= width or y < 0 or y >= height:
  182. if log_callback:
  183. log_callback(f"❌ 坐标({x},{y})超出屏幕范围 {width}x{height}")
  184. os.remove(temp_local_file)
  185. return None
  186. # 获取像素颜色
  187. pixel = img.getpixel((x, y))
  188. # 转换为十六进制颜色值
  189. if isinstance(pixel, tuple):
  190. if len(pixel) >= 3:
  191. r, g, b = pixel[0], pixel[1], pixel[2]
  192. else:
  193. r, g, b = pixel, pixel, pixel
  194. else:
  195. r = g = b = pixel
  196. color = f"#{r:02X}{g:02X}{b:02X}"
  197. # 清理临时文件
  198. os.remove(temp_local_file)
  199. if log_callback:
  200. log_callback(f"🎨 坐标({x},{y}) 颜色: {color}")
  201. return color
  202. except ImportError:
  203. if log_callback:
  204. log_callback("❌ 请先安装PIL库: pip install Pillow")
  205. # 清理临时文件
  206. if os.path.exists(temp_local_file):
  207. os.remove(temp_local_file)
  208. return None
  209. except Exception as e:
  210. if log_callback:
  211. log_callback(f"❌ 解析图片失败: {e}")
  212. # 清理临时文件
  213. if os.path.exists(temp_local_file):
  214. os.remove(temp_local_file)
  215. return None
  216. else:
  217. if log_callback:
  218. log_callback("❌ 截图失败")
  219. return None
  220. except Exception as e:
  221. if log_callback:
  222. log_callback(f"❌ 获取颜色失败: {e}")
  223. return None
  224. class MuMuAutoGUI:
  225. def __init__(self):
  226. self.root = tk.Tk()
  227. self.root.title("MuMu模拟器自动化工具")
  228. self.root.geometry("700x360")
  229. # 配置文件
  230. self.config_file = "mumu_config.ini"
  231. self.config = configparser.ConfigParser()
  232. self.load_config()
  233. # 运行状态
  234. self.is_running = False
  235. self.is_paused = False
  236. self.should_stop = False
  237. self.current_thread = None
  238. self.selected_emulators = []
  239. self.load_btn = None
  240. self.start_read_btn = None
  241. self.start_comment_btn = None
  242. # 创建界面
  243. self.create_widgets()
  244. # 加载保存的配置
  245. self.load_settings()
  246. def load_config(self):
  247. """加载配置文件"""
  248. if os.path.exists(self.config_file):
  249. self.config.read(self.config_file, encoding='utf-8')
  250. else:
  251. self.config['Settings'] = {
  252. 'mumu_path': r'D:\MuMuPlayer\nx_main\MuMuManager.exe',
  253. 'apk_path': 'fanqie.apk',
  254. 'package_name': 'com.dragon.read',
  255. 'search_content': '玄幻战神:开局就得到大佬的守护'
  256. }
  257. def save_config(self):
  258. """保存配置文件"""
  259. with open(self.config_file, 'w', encoding='utf-8') as f:
  260. self.config.write(f)
  261. def load_settings(self):
  262. """加载设置到界面"""
  263. self.mumu_path_var.set(self.config['Settings']['mumu_path'] if 'mumu_path' in self.config['Settings'] else '')
  264. self.apk_path_var.set(self.config['Settings']['apk_path'] if 'apk_path' in self.config['Settings'] else '')
  265. self.package_name_var.set(self.config['Settings']['package_name'] if 'package_name' in self.config['Settings'] else '')
  266. self.search_content_var.set(self.config['Settings']['search_content'] if 'search_content' in self.config['Settings'] else '盗墓笔记')
  267. self.page_count_var.set(self.config['Settings']['page_count_var'] if 'page_count_var' in self.config['Settings'] else '10-30')
  268. self.page_interval_var.set(self.config['Settings']['page_interval_var'] if 'page_interval_var' in self.config['Settings'] else '5')
  269. self.max_threads_var.set(self.config['Settings']['max_threads_var'] if 'max_threads_var' in self.config['Settings'] else '1')
  270. def save_settings(self):
  271. """保存界面设置到文件"""
  272. self.config['Settings']['mumu_path'] = self.mumu_path_var.get()
  273. self.config['Settings']['apk_path'] = self.apk_path_var.get()
  274. self.config['Settings']['package_name'] = self.package_name_var.get()
  275. self.config['Settings']['search_content'] = self.search_content_var.get()
  276. self.config['Settings']['page_count_var'] = self.page_count_var.get()
  277. self.config['Settings']['page_interval_var'] = self.page_interval_var.get()
  278. self.config['Settings']['max_threads_var'] = self.max_threads_var.get()
  279. self.save_config()
  280. self.log_message("✅ 配置已保存")
  281. def create_widgets(self):
  282. """创建界面组件"""
  283. # 创建选项卡
  284. self.notebook = ttk.Notebook(self.root)
  285. self.notebook.pack(fill='both', expand=True, padx=5, pady=5)
  286. # 配置选项卡
  287. self.create_config_tab()
  288. # 任务选项卡
  289. self.create_task_tab()
  290. # 日志选项卡
  291. self.create_log_tab()
  292. def create_config_tab(self):
  293. """创建配置选项卡"""
  294. config_frame = ttk.Frame(self.notebook)
  295. self.notebook.add(config_frame, text="配置")
  296. # 创建滚动框架
  297. canvas = tk.Canvas(config_frame)
  298. scrollbar = ttk.Scrollbar(config_frame, orient="vertical", command=canvas.yview)
  299. scrollable_frame = ttk.Frame(canvas)
  300. scrollable_frame.bind(
  301. "<Configure>",
  302. lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
  303. )
  304. canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
  305. canvas.configure(yscrollcommand=scrollbar.set)
  306. # 配置项
  307. row = 0
  308. # MuMu路径
  309. ttk.Label(scrollable_frame, text="MuMuManager路径:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  310. self.mumu_path_var = tk.StringVar()
  311. mumu_entry = ttk.Entry(scrollable_frame, textvariable=self.mumu_path_var, width=60)
  312. mumu_entry.grid(row=row, column=1, padx=10, pady=5)
  313. ttk.Button(scrollable_frame, text="浏览", command=self.browse_mumu_path).grid(row=row, column=2, padx=5, pady=5)
  314. row += 1
  315. # APK路径
  316. ttk.Label(scrollable_frame, text="APK文件路径:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  317. self.apk_path_var = tk.StringVar()
  318. apk_entry = ttk.Entry(scrollable_frame, textvariable=self.apk_path_var, width=60)
  319. apk_entry.grid(row=row, column=1, padx=10, pady=5)
  320. ttk.Button(scrollable_frame, text="浏览", command=self.browse_apk_path).grid(row=row, column=2, padx=5, pady=5)
  321. row += 1
  322. # 包名
  323. ttk.Label(scrollable_frame, text="应用包名:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  324. self.package_name_var = tk.StringVar()
  325. ttk.Entry(scrollable_frame, textvariable=self.package_name_var, width=40).grid(row=row, column=1, sticky='w', padx=10, pady=5)
  326. row += 1
  327. # 搜索内容
  328. ttk.Label(scrollable_frame, text="搜索内容:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  329. self.search_content_var = tk.StringVar()
  330. ttk.Entry(scrollable_frame, textvariable=self.search_content_var, width=60).grid(row=row, column=1, padx=10, pady=5)
  331. row += 1
  332. # 翻页间隔
  333. ttk.Label(scrollable_frame, text="翻页间隔(秒):").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  334. self.page_interval_var = tk.StringVar()
  335. ttk.Entry(scrollable_frame, textvariable=self.page_interval_var, width=60).grid(row=row, column=1, padx=10, pady=5)
  336. row += 1
  337. # 阅读页数
  338. ttk.Label(scrollable_frame, text="阅读页数:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  339. self.page_count_var = tk.StringVar()
  340. ttk.Entry(scrollable_frame, textvariable=self.page_count_var, width=60).grid(row=row, column=1, padx=10, pady=5)
  341. row += 1
  342. # 最大线程
  343. ttk.Label(scrollable_frame, text="最大线程:").grid(row=row, column=0, sticky='w', padx=10, pady=5)
  344. self.max_threads_var = tk.StringVar()
  345. ttk.Entry(scrollable_frame, textvariable=self.max_threads_var, width=60).grid(row=row, column=1, padx=10, pady=5)
  346. row += 1
  347. # 保存按钮
  348. ttk.Button(scrollable_frame, text="保存配置", command=self.save_settings).grid(row=row, column=0, columnspan=3, pady=20)
  349. canvas.pack(side="left", fill="both", expand=True)
  350. scrollbar.pack(side="right", fill="y")
  351. def create_task_tab(self):
  352. """创建任务选项卡"""
  353. task_frame = ttk.Frame(self.notebook)
  354. self.notebook.add(task_frame, text="任务")
  355. # 上部:模拟器列表
  356. list_frame = ttk.LabelFrame(task_frame, text="任务控制")
  357. list_frame.pack(fill='both', expand=True, padx=5, pady=5)
  358. # 按钮栏
  359. button_frame = ttk.Frame(list_frame)
  360. button_frame.pack(fill='x', padx=5, pady=5)
  361. self.load_btn = ttk.Button(button_frame, text="读取模拟器", command=self.load_emulators)
  362. self.load_btn.pack(side='left', padx=5)
  363. self.start_read_btn = ttk.Button(button_frame, text="开始阅读", command=self.start_task, width=10)
  364. self.start_read_btn.pack(side='left', padx=10)
  365. self.start_comment_btn = ttk.Button(button_frame, text="开始评价", command=self.start_task2, width=10)
  366. self.start_comment_btn.pack(side='left', padx=10)
  367. self.pause_btn = ttk.Button(button_frame, text="暂停", command=self.pause_task, width=10, state='disabled')
  368. self.pause_btn.pack(side='left', padx=10)
  369. self.stop_btn = ttk.Button(button_frame, text="停止", command=self.stop_task, width=10, state='disabled')
  370. self.stop_btn.pack(side='left', padx=10)
  371. # 模拟器列表(带复选框)
  372. tree_frame = ttk.Frame(list_frame)
  373. tree_frame.pack(fill='both', expand=True, padx=5, pady=5)
  374. # 创建Treeview
  375. columns = ("选择", "索引", "名称", "ADB端口", "状态")
  376. self.emulator_tree = ttk.Treeview(tree_frame, columns=columns, show='headings', height=8)
  377. # 设置列标题
  378. self.emulator_tree.heading("选择", text="选择")
  379. self.emulator_tree.heading("索引", text="索引")
  380. self.emulator_tree.heading("名称", text="名称")
  381. self.emulator_tree.heading("ADB端口", text="ADB端口")
  382. self.emulator_tree.heading("状态", text="状态")
  383. # 设置列宽
  384. self.emulator_tree.column("选择", width=50)
  385. self.emulator_tree.column("索引", width=50)
  386. self.emulator_tree.column("名称", width=150)
  387. self.emulator_tree.column("ADB端口", width=80)
  388. self.emulator_tree.column("状态", width=100)
  389. # 添加滚动条
  390. vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.emulator_tree.yview)
  391. self.emulator_tree.configure(yscrollcommand=vsb.set)
  392. self.emulator_tree.pack(side='left', fill='both', expand=True)
  393. vsb.pack(side='right', fill='y')
  394. # 绑定双击选择
  395. self.emulator_tree.bind('<Button-1>', self.on_tree_click)
  396. def create_log_tab(self):
  397. """创建日志选项卡"""
  398. log_frame = ttk.Frame(self.notebook)
  399. self.notebook.add(log_frame, text="日志")
  400. # 日志文本框
  401. self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
  402. self.log_text.pack(fill='both', expand=True, padx=5, pady=5)
  403. # 清空按钮
  404. btn_frame = ttk.Frame(log_frame)
  405. btn_frame.pack(fill='x', padx=5, pady=5)
  406. ttk.Button(btn_frame, text="清空日志", command=self.clear_log).pack(side='right')
  407. def browse_mumu_path(self):
  408. """浏览MuMuManager路径"""
  409. path = filedialog.askopenfilename(title="选择MuMuManager.exe", filetypes=[("Executable", "*.exe")])
  410. if path:
  411. self.mumu_path_var.set(path)
  412. def browse_apk_path(self):
  413. """浏览APK文件"""
  414. path = filedialog.askopenfilename(title="选择APK文件", filetypes=[("APK", "*.apk")])
  415. if path:
  416. self.apk_path_var.set(path)
  417. def load_emulators(self):
  418. """加载模拟器列表"""
  419. try:
  420. manager = MuMuEmulatorManager(self.mumu_path_var.get())
  421. emulators = manager.get_emulator_list()
  422. # 清空现有列表
  423. for item in self.emulator_tree.get_children():
  424. self.emulator_tree.delete(item)
  425. # 添加模拟器
  426. for emu in emulators:
  427. status = "运行中" if emu.get('is_process_started') else "未运行"
  428. self.emulator_tree.insert('', 'end', values=("□", emu.get('index'), emu.get('name'), emu.get('adb_port'), status))
  429. self.log_message(f"已加载 {len(emulators)} 个模拟器")
  430. except Exception as e:
  431. self.log_message(f"加载模拟器失败: {e}")
  432. def on_tree_click(self, event):
  433. """处理列表点击选择"""
  434. region = self.emulator_tree.identify_region(event.x, event.y)
  435. if region == "cell":
  436. column = self.emulator_tree.identify_column(event.x)
  437. if column == "#1": # 选择列
  438. item = self.emulator_tree.identify_row(event.y)
  439. if item:
  440. values = self.emulator_tree.item(item, 'values')
  441. current = values[0]
  442. new_value = "☑" if current == "□" else "□"
  443. self.emulator_tree.item(item, values=(new_value, values[1], values[2], values[3], values[4]))
  444. def get_selected_emulators(self):
  445. """获取选中的模拟器"""
  446. selected = []
  447. for item in self.emulator_tree.get_children():
  448. values = self.emulator_tree.item(item, 'values')
  449. if values[0] == "☑":
  450. selected.append({
  451. 'index': values[1],
  452. 'name': values[2],
  453. 'adb_port': values[3]
  454. })
  455. return selected
  456. def log_message(self, message):
  457. """添加日志"""
  458. timestamp = datetime.now().strftime("%H:%M:%S")
  459. self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
  460. self.log_text.see(tk.END)
  461. self.root.update()
  462. def start_task(self):
  463. """开始阅读任务"""
  464. selected = self.get_selected_emulators()
  465. if not selected:
  466. messagebox.showwarning("警告", "请至少选择一个模拟器")
  467. return
  468. self.selected_emulators = selected
  469. self.is_running = True
  470. self.is_paused = False
  471. self.should_stop = False
  472. # 禁用相关按钮
  473. self.load_btn.config(state='disabled')
  474. self.start_read_btn.config(state='disabled')
  475. self.start_comment_btn.config(state='disabled')
  476. self.pause_btn.config(state='normal')
  477. self.stop_btn.config(state='normal')
  478. # 在新线程中运行任务
  479. self.current_thread = threading.Thread(target=self.run_task, daemon=True)
  480. self.current_thread.start()
  481. def start_task2(self):
  482. """开始评价任务"""
  483. selected = self.get_selected_emulators()
  484. if not selected:
  485. messagebox.showwarning("警告", "请至少选择一个模拟器")
  486. return
  487. self.selected_emulators = selected
  488. self.is_running = True
  489. self.is_paused = False
  490. self.should_stop = False
  491. # 禁用相关按钮
  492. self.load_btn.config(state='disabled')
  493. self.start_read_btn.config(state='disabled')
  494. self.start_comment_btn.config(state='disabled')
  495. self.pause_btn.config(state='normal')
  496. self.stop_btn.config(state='normal')
  497. # 在新线程中运行评价任务
  498. self.current_thread = threading.Thread(target=self.run_task2, daemon=True)
  499. self.current_thread.start()
  500. def pause_task(self):
  501. """暂停任务"""
  502. if self.is_running and not self.is_paused:
  503. self.is_paused = True
  504. self.pause_btn.config(text="继续")
  505. self.log_message("⏸ 任务已暂停")
  506. elif self.is_running and self.is_paused:
  507. self.is_paused = False
  508. self.pause_btn.config(text="暂停")
  509. self.log_message("▶️ 任务已继续")
  510. def stop_task(self):
  511. """停止任务"""
  512. if self.is_running:
  513. self.should_stop = True
  514. self.is_running = False
  515. self.is_paused = False
  516. self.log_message("⏹ 正在停止任务...")
  517. def run_task(self):
  518. """执行任务"""
  519. try:
  520. manager = MuMuEmulatorManager(self.mumu_path_var.get())
  521. for i, emu in enumerate(self.selected_emulators):
  522. if self.should_stop:
  523. self.log_message("任务已停止")
  524. break
  525. while self.is_paused and not self.should_stop:
  526. time.sleep(1)
  527. index = emu['index']
  528. self.log_message(f"\n{'='*50}")
  529. self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
  530. self.log_message(f"{'='*50}")
  531. # 启动模拟器
  532. self.log_message(f"正在启动模拟器 {index}...")
  533. if not manager.start_emulator(index):
  534. self.log_message(f"❌ 模拟器 {index} 启动失败")
  535. continue
  536. # 等待就绪
  537. if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
  538. self.log_message(f"❌ 模拟器 {index} 启动超时")
  539. continue
  540. time.sleep(5)
  541. # 安装APK
  542. if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
  543. self.log_message(f"❌ APK安装失败")
  544. continue
  545. # 打开应用
  546. manager.open_app(index, self.package_name_var.get(), self.log_message)
  547. # 判断是否需要同意
  548. time.sleep(10)
  549. button_color = manager.get_pixel_color(index, 394, 846, log_callback=self.log_message)
  550. self.log_message(button_color)
  551. if (button_color and button_color.upper() == "#FC7838"):
  552. manager.tap(index, 394, 846, self.log_message)
  553. time.sleep(70)
  554. manager.tap(index, 640, 260, self.log_message)
  555. time.sleep(5)
  556. manager.tap(index, 57, 193, self.log_message)
  557. time.sleep(5)
  558. else:
  559. time.sleep(30)
  560. # 执行操作
  561. manager.tap(index, 390, 90, self.log_message)
  562. time.sleep(2)
  563. manager.paste_text(index, self.search_content_var.get(), self.log_message)
  564. time.sleep(2)
  565. manager.tap(index, 655, 92, self.log_message)
  566. # 进入书目
  567. time.sleep(5)
  568. manager.tap(index, 355, 333, self.log_message)
  569. time.sleep(8)
  570. # 翻页10次
  571. for page in range(int(self.page_count_var.get()) if self.page_count_var.get().isdigit() else 5):
  572. if self.should_stop or self.is_paused:
  573. break
  574. intervalNum = 30
  575. if ('-' in self.page_interval_var.get()):
  576. parts = self.page_interval_var.get().split('-')
  577. if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
  578. min_interval = int(parts[0])
  579. max_interval = int(parts[1])
  580. intervalNum = random.randint(min_interval, max_interval)
  581. else:
  582. intervalNum = int(self.page_interval_var.get()) if self.page_interval_var.get().isdigit() else 30
  583. time.sleep(intervalNum)
  584. manager.tap(index, 710, 632, self.log_message)
  585. time.sleep(4)
  586. # 关闭模拟器
  587. self.log_message(f"正在关闭模拟器 {index}...")
  588. manager.stop_emulator(index)
  589. self.log_message(f"✅ 模拟器 {index} 任务完成")
  590. # 如果不是最后一个,等待一段时间再启动下一个
  591. if i < len(self.selected_emulators) - 1:
  592. wait_time = 10
  593. self.log_message(f"等待 {wait_time} 秒后处理下一个模拟器...")
  594. for _ in range(wait_time):
  595. if self.should_stop:
  596. break
  597. time.sleep(1)
  598. self.log_message("\n🎉 所有任务执行完毕!")
  599. except Exception as e:
  600. self.log_message(f"❌ 任务执行出错: {e}")
  601. finally:
  602. self.is_running = False
  603. self.is_paused = False
  604. self.pause_btn.config(text="暂停")
  605. # 恢复按钮状态
  606. self.root.after(0, self.reset_buttons)
  607. def run_task2(self):
  608. """执行任务"""
  609. try:
  610. manager = MuMuEmulatorManager(self.mumu_path_var.get())
  611. for i, emu in enumerate(self.selected_emulators):
  612. if self.should_stop:
  613. self.log_message("任务已停止")
  614. break
  615. while self.is_paused and not self.should_stop:
  616. time.sleep(1)
  617. index = emu['index']
  618. self.log_message(f"\n{'='*50}")
  619. self.log_message(f"开始处理模拟器 {index} ({emu['name']})")
  620. self.log_message(f"{'='*50}")
  621. # 启动模拟器
  622. self.log_message(f"正在启动模拟器 {index}...")
  623. if not manager.start_emulator(index):
  624. self.log_message(f"❌ 模拟器 {index} 启动失败")
  625. continue
  626. # 等待就绪
  627. if not manager.wait_for_emulator_ready(index, timeout=180, log_callback=self.log_message):
  628. self.log_message(f"❌ 模拟器 {index} 启动超时")
  629. continue
  630. time.sleep(5)
  631. # 安装APK
  632. if not manager.install_apk(index, self.apk_path_var.get(), self.log_message):
  633. self.log_message(f"❌ APK安装失败")
  634. continue
  635. # 打开应用
  636. manager.open_app(index, self.package_name_var.get(), self.log_message)
  637. time.sleep(40)
  638. # 执行操作
  639. manager.tap(index, 390, 90, self.log_message)
  640. time.sleep(2)
  641. manager.paste_text(index, self.search_content_var.get(), self.log_message)
  642. time.sleep(2)
  643. manager.tap(index, 655, 92, self.log_message)
  644. # 进入书目
  645. time.sleep(5)
  646. manager.tap(index, 355, 333, self.log_message)
  647. # 评价
  648. # time.sleep(2)
  649. # manager.tap(index, 360, 690, self.log_message)
  650. time.sleep(8)
  651. manager.tap(index, 680, 95, self.log_message)
  652. time.sleep(2)
  653. manager.tap(index, 633, 930, self.log_message)
  654. # 发表
  655. time.sleep(5)
  656. manager.tap(index, 640, 95, self.log_message)
  657. time.sleep(4)
  658. # 关闭模拟器
  659. self.log_message(f"正在关闭模拟器 {index}...")
  660. manager.stop_emulator(index)
  661. self.log_message(f"✅ 模拟器 {index} 任务完成")
  662. # 如果不是最后一个,等待一段时间再启动下一个
  663. if i < len(self.selected_emulators) - 1:
  664. wait_time = 10
  665. self.log_message(f"等待 {wait_time} 秒后处理下一个模拟器...")
  666. for _ in range(wait_time):
  667. if self.should_stop:
  668. break
  669. time.sleep(1)
  670. self.log_message("\n🎉 所有任务执行完毕!")
  671. except Exception as e:
  672. self.log_message(f"❌ 任务执行出错: {e}")
  673. finally:
  674. self.is_running = False
  675. self.is_paused = False
  676. self.pause_btn.config(text="暂停")
  677. # 恢复按钮状态
  678. self.root.after(0, self.reset_buttons)
  679. def reset_buttons(self):
  680. """重置按钮状态"""
  681. self.load_btn.config(state='normal')
  682. self.start_read_btn.config(state='normal')
  683. self.start_comment_btn.config(state='normal')
  684. self.pause_btn.config(state='disabled', text="暂停")
  685. self.stop_btn.config(state='disabled')
  686. def clear_log(self):
  687. """清空日志"""
  688. self.log_text.delete(1.0, tk.END)
  689. def run(self):
  690. """运行GUI"""
  691. self.root.mainloop()
  692. if __name__ == "__main__":
  693. if datetime.now() < datetime(2026, 4, 25):
  694. app = MuMuAutoGUI()
  695. app.run()