Bladeren bron

save

Co-authored-by: Copilot <copilot@github.com>
PUGE 1 maand geleden
bovenliggende
commit
98ea5c1c0c
3 gewijzigde bestanden met toevoegingen van 677 en 220 verwijderingen
  1. 341 220
      MuMu.py
  2. 1 0
      mumu_config.ini
  3. 335 0
      陪玩接单.py

+ 341 - 220
MuMu.py

@@ -10,11 +10,34 @@ import configparser
 from datetime import datetime
 
 class MuMuEmulatorManager:
+    # 类级别的剪贴板锁,所有实例共享
+    _clipboard_lock = threading.Lock()
+
     def __init__(self, manager_path=r"D:\MuMuPlayer\nx_main\MuMuManager.exe"):
         self.manager_path = manager_path
         if not os.path.exists(manager_path):
             raise FileNotFoundError(f"找不到 MuMuManager.exe: {manager_path}")
-    
+    def get_adb_port(self, index, log_callback=None):
+        """实时获取指定模拟器的 ADB 端口,获取不到就一直等待直到成功"""
+        while True:
+            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)
+                    adb_port = data.get('adb_port')
+                    if adb_port is not None:
+                        if log_callback:
+                            log_callback(f"✅ 模拟器 {index} ADB端口: {adb_port}")
+                        return adb_port
+                except:
+                    pass
+            
+            if log_callback:
+                log_callback(f"⏳ 模拟器 {index} 等待ADB端口...")
+            
+            time.sleep(3)  # 等待3秒后重试
     def get_emulator_list(self):
         """获取所有模拟器列表"""
         cmd = [self.manager_path, "info", "-v", "all"]
@@ -29,8 +52,6 @@ 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:
@@ -77,7 +98,7 @@ class MuMuEmulatorManager:
                 log_callback(f"❌ APK文件不存在: {apk_path}")
             return False
         
-        adb_port = 16384 + 32 * int(index)
+        adb_port = self.get_adb_port(index)
         target_device = f"127.0.0.1:{adb_port}"
         
         # 连接ADB
@@ -95,7 +116,7 @@ class MuMuEmulatorManager:
         
         # 安装APK
         if log_callback:
-            log_callback(f"正在安装APK: {os.path.basename(apk_path)}")
+            log_callback(f"正在安装APK: {os.path.basename(apk_path)} 端口: {adb_port}...")
         install_cmd = f"adb -s {target_device} install -r \"{apk_path}\""
         result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)
         
@@ -103,7 +124,7 @@ class MuMuEmulatorManager:
     
     def open_app(self, index, package_name, log_callback=None):
         """打开应用"""
-        adb_port = 16384 + 32 * int(index)
+        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)
@@ -118,37 +139,31 @@ class MuMuEmulatorManager:
             return True
         return False
     
+    # 替代方案:直接发送文本字符,不用剪贴板
     def paste_text(self, index, text, log_callback=None):
-        """粘贴文字"""
-        adb_port = 16384 + 32 * int(index)
+        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)
-        
-        # 复制到剪贴板
-        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)
         
-        # 执行粘贴
-        subprocess.run(f"adb -s {target_device} shell input keyevent 279", shell=True)
+        # 直接通过 ADB 输入文本(逐字符)
+        # 先确保输入框获得焦点(点击一下)
+        subprocess.run(f"adb -s {target_device} shell input tap 390 90", shell=True)
+        time.sleep(0.5)
+        
+        # 使用 adb shell input text 输入(会自动处理空格和特殊字符)
+        # 注意:需要用 %s 转义空格
+        safe_text = text.replace(' ', '%s').replace('&', '\\&')
+        subprocess.run(f"adb -s {target_device} shell input text '{safe_text}'", shell=True)
         
         if log_callback:
-            log_callback(f"✅ 已粘贴: {text[:50]}{'...' if len(text) > 50 else ''}")
+            log_callback(f"✅ 已输入: {text[:50]}{'...' if len(text) > 50 else ''}")
         return True
     
     def tap(self, index, x, y, log_callback=None):
         """点击坐标"""
-        adb_port = 16384 + 32 * int(index)
+        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)
@@ -163,31 +178,31 @@ class MuMuEmulatorManager:
         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)
+        """获取模拟器内指定坐标点的颜色"""
+        adb_port = self.get_adb_port(index)
         target_device = f"127.0.0.1:{adb_port}"
         
-        # 连接ADB
+        # 连接ADB并验证连接
         subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
         time.sleep(0.5)
         
+        # 验证设备是否在线
+        verify_cmd = f"adb -s {target_device} shell echo 1"
+        verify_result = subprocess.run(verify_cmd, shell=True, capture_output=True, text=True)
+        if verify_result.returncode != 0:
+            if log_callback:
+                log_callback(f"⚠️ 模拟器 {index} ADB 连接失败,重新连接...")
+            subprocess.run(f"adb disconnect {target_device}", shell=True, capture_output=True)
+            time.sleep(1)
+            subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
+            time.sleep(1)
+        
         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:
@@ -203,9 +218,10 @@ class MuMuEmulatorManager:
             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"
+            # 使用唯一的临时文件名(包含线程ID和时间戳)
+            import threading
+            thread_id = threading.current_thread().ident
+            temp_local_file = f"temp_screenshot_{thread_id}_{int(time.time()*1000)}.png"
             
             # 截图并保存到本地
             screenshot_cmd = f"adb -s {target_device} exec-out screencap -p > {temp_local_file}"
@@ -242,8 +258,10 @@ class MuMuEmulatorManager:
                     
                     color = f"#{r:02X}{g:02X}{b:02X}"
                     
-                    # 清理临时文件
-                    os.remove(temp_local_file)
+                    # 关闭图片并清理临时文件
+                    img.close()
+                    if os.path.exists(temp_local_file):
+                        os.remove(temp_local_file)
                     
                     if log_callback:
                         log_callback(f"🎨 坐标({x},{y}) 颜色: {color}")
@@ -252,22 +270,28 @@ class MuMuEmulatorManager:
                 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)
+                        try:
+                            os.remove(temp_local_file)
+                        except:
+                            pass
                     return None
             else:
                 if log_callback:
                     log_callback("❌ 截图失败")
+                if os.path.exists(temp_local_file):
+                    try:
+                        os.remove(temp_local_file)
+                    except:
+                        pass
                 return None
-                    
+                
         except Exception as e:
             if log_callback:
                 log_callback(f"❌ 获取颜色失败: {e}")
@@ -292,7 +316,9 @@ class MuMuAutoGUI:
         self.current_thread = None
         self.selected_emulators = []
 
-        
+        self.selected_emulators = []
+        self.thread_semaphore = None  # 添加信号量控制并发数
+
         self.load_btn = None
         self.start_read_btn = None
         self.start_comment_btn = None
@@ -448,6 +474,10 @@ class MuMuAutoGUI:
         self.load_btn = ttk.Button(button_frame, text="读取模拟器", command=self.load_emulators)
         self.load_btn.pack(side='left', padx=5)
         
+        # 添加全选按钮
+        self.select_all_btn = ttk.Button(button_frame, text="全选", command=self.select_all_emulators, width=6)
+        self.select_all_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)
 
@@ -465,21 +495,19 @@ class MuMuAutoGUI:
         tree_frame.pack(fill='both', expand=True, padx=5, pady=5)
         
         # 创建Treeview
-        columns = ("选择", "索引", "名称", "ADB端口", "状态")
+        columns = ("选择", "索引", "名称", "状态")
         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)
         
         # 添加滚动条
@@ -519,6 +547,14 @@ class MuMuAutoGUI:
         if path:
             self.apk_path_var.set(path)
     
+    def select_all_emulators(self):
+        """全选所有模拟器"""
+        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.log_message("已全选所有模拟器")
+    
     def load_emulators(self):
         """加载模拟器列表"""
         try:
@@ -532,7 +568,7 @@ class MuMuAutoGUI:
             # 添加模拟器
             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.emulator_tree.insert('', 'end', values=("□", emu.get('index'), emu.get('name'), status))
             
             self.log_message(f"已加载 {len(emulators)} 个模拟器")
         except Exception as e:
@@ -549,7 +585,7 @@ class MuMuAutoGUI:
                     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]))
+                    self.emulator_tree.item(item, values=(new_value, values[1], values[2], values[3]))
 
     def get_selected_emulators(self):
         """获取选中的模拟器"""
@@ -559,8 +595,7 @@ class MuMuAutoGUI:
             if values[0] == "☑":
                 selected.append({
                     'index': values[1],
-                    'name': values[2],
-                    'adb_port': values[3]
+                    'name': values[2]
                 })
         return selected
     
@@ -636,104 +671,177 @@ class MuMuAutoGUI:
             self.is_paused = False
             self.log_message("⏹ 正在停止任务...")
     
+    def openBook(self, manager, index):
+        # 执行操作
+        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} 等待搜索页面准备就绪...")
+            time.sleep(3)
+        time.sleep(3)
+        # 点输入框
+        manager.tap(index, 340, 90, self.log_message)
+        time.sleep(3)
+        # 黏贴
+        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":
+            self.log_message(f"模拟器 {index} 没有输入搜索内容...")
+            # 点输入框
+            manager.tap(index, 556, 87, self.log_message)
+            time.sleep(1)
+            manager.tap(index, 340, 90, self.log_message)
+            time.sleep(2)
+            # 黏贴
+            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":
+            self.log_message(f"模拟器 {index} 等待搜索结果...")
+            time.sleep(3)
+
+        # 进入书目
+        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":
+            self.log_message(f"模拟器 {index} 还在搜索结果页面,重新点击...")
+            manager.tap(index, 355, 333, self.log_message)
+            time.sleep(5)
+
     def run_task(self):
-        """执行任务"""
+        """执行任务(支持并发)"""
         try:
             manager = MuMuEmulatorManager(self.mumu_path_var.get())
             
-            for i, emu in enumerate(self.selected_emulators):
+            # 获取最大线程数
+            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.log_message(f"📌 最大并发数: {max_threads}")
+            
+            # 用于存储线程的列表
+            threads = []
+            results = {}  # 存储每个模拟器的执行结果
+            
+            def process_emulator(emu):
+                """处理单个模拟器的函数"""
+                with self.thread_semaphore:
+                    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)
+                        # 判断是否需要同意
+                        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)
+                            manager.tap(index, 640, 260, self.log_message)
+                            time.sleep(10)
+                            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)
+                        
+                        
+                        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:
+                                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, 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)
+                        
+                        self.log_message(f"✅ 模拟器 {index} 任务完成")
+                        results[index] = True
+                        
+                    except Exception as e:
+                        self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
+                        results[index] = False
+            
+            # 启动所有模拟器任务(信号量会自动控制并发数)
+            for emu in 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)
-                # 判断是否需要同意
+                # 等待直到有空闲槽位(信号量内部处理)
+                thread = threading.Thread(target=process_emulator, args=(emu,))
+                thread.start()
+                threads.append(thread)
+                # 稍微延迟一下,避免同时启动太多
                 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🎉 所有任务执行完毕!")
+            # 等待所有线程完成
+            for thread in threads:
+                thread.join()
+            
+            success_count = sum(1 for v in results.values() if v)
+            self.log_message(f"\n🎉 任务执行完毕!成功: {success_count}/{len(self.selected_emulators)}")
             
         except Exception as e:
             self.log_message(f"❌ 任务执行出错: {e}")
@@ -745,87 +853,100 @@ class MuMuAutoGUI:
             # 恢复按钮状态
             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):
+            # 获取最大线程数
+            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.log_message(f"📌 最大并发数: {max_threads}")
+            
+            # 用于存储线程的列表
+            threads = []
+            results = {}
+            
+            def process_emulator(emu):
+                """处理单个模拟器的函数"""
+                with self.thread_semaphore:
+                    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.openBook(manager, index)
+                        
+                        # 评价
+                        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
+                        
+                    except Exception as e:
+                        self.log_message(f"❌ 模拟器 {index} 执行出错: {e}")
+                        results[index] = False
+            
+            # 启动所有模拟器任务
+            for emu in 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)
+                thread = threading.Thread(target=process_emulator, args=(emu,))
+                thread.start()
+                threads.append(thread)
                 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🎉 所有任务执行完毕!")
+            # 等待所有线程完成
+            for thread in threads:
+                thread.join()
+            
+            success_count = sum(1 for v in results.values() if v)
+            self.log_message(f"\n🎉 任务执行完毕!成功: {success_count}/{len(self.selected_emulators)}")
             
         except Exception as e:
             self.log_message(f"❌ 任务执行出错: {e}")
@@ -855,6 +976,6 @@ class MuMuAutoGUI:
 
 
 if __name__ == "__main__":
-    if datetime.now() < datetime(2026, 4, 25):
+    if datetime.now() < datetime(2026, 5, 25):
         app = MuMuAutoGUI()
         app.run()

+ 1 - 0
mumu_config.ini

@@ -5,4 +5,5 @@ package_name = com.dragon.read
 search_content = 玄幻战神:开局就得到大佬的守护
 page_count_var = 5
 page_interval_var = 30-40
+max_threads_var = 2
 

+ 335 - 0
陪玩接单.py

@@ -0,0 +1,335 @@
+import tkinter as tk
+from tkinter import scrolledtext, messagebox
+import threading
+import time
+import json
+import requests
+import base64
+from datetime import datetime
+
+class OrderMonitorApp:
+    def __init__(self, root):
+        self.root = root
+        self.root.title("订单监控系统")
+        self.root.geometry("900x600")
+        self.root.resizable(True, True)
+
+        # 运行标志
+        self.running = False
+        self.thread = None
+
+        # 创建界面组件
+        self.create_widgets()
+
+        # 默认关闭时停止
+        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
+
+    def create_widgets(self):
+        # 顶部控制面板
+        control_frame = tk.Frame(self.root)
+        control_frame.pack(pady=10, padx=10, fill=tk.X)
+
+        # 请求间隔
+        tk.Label(control_frame, text="请求间隔(秒):", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)
+        self.interval_var = tk.StringVar(value="5")
+        self.interval_entry = tk.Entry(control_frame, textvariable=self.interval_var, width=8, font=("Arial", 12))
+        self.interval_entry.pack(side=tk.LEFT, padx=5)
+
+        # 抢单价格
+        tk.Label(control_frame, text="抢单价格(大于):", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)
+        self.buyPrice = tk.StringVar(value="180")
+        self.interval_entry = tk.Entry(control_frame, textvariable=self.buyPrice, width=8, font=("Arial", 12))
+        self.interval_entry.pack(side=tk.LEFT, padx=5)
+
+        # 备注过滤
+        tk.Label(control_frame, text="备注过滤(@隔开多个词):", font=("Arial", 12)).pack(side=tk.LEFT, padx=5)
+        self.remarkIgnore = tk.StringVar(value="双女@单女")
+        self.interval_entry = tk.Entry(control_frame, textvariable=self.remarkIgnore, width=24, font=("Arial", 12))
+        self.interval_entry.pack(side=tk.LEFT, padx=5)
+
+        control_frame2 = tk.Frame(self.root)
+        control_frame2.pack(pady=10, padx=10, fill=tk.X)
+
+        # 启动按钮
+        self.start_button = tk.Button(control_frame2, text="启动监控", command=self.start_monitor, bg="green", fg="white", font=("Arial", 12), width=12)
+        self.start_button.pack(side=tk.LEFT, padx=5)
+
+        # 停止按钮
+        self.stop_button = tk.Button(control_frame2, text="停止监控", command=self.stop_monitor, bg="red", fg="white", font=("Arial", 12), width=12, state=tk.DISABLED)
+        self.stop_button.pack(side=tk.LEFT, padx=5)
+
+        # 清空日志按钮
+        clear_button = tk.Button(control_frame2, text="清空日志", command=self.clear_log, bg="gray", fg="white", font=("Arial", 12), width=10)
+        clear_button.pack(side=tk.LEFT, padx=5)
+
+        # 日志框架
+        log_frame = tk.Frame(self.root)
+        log_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
+
+        tk.Label(log_frame, text="监控日志:", font=("Arial", 12), anchor="w").pack(anchor="w")
+        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=("Consolas", 10), height=25)
+        self.log_text.pack(fill=tk.BOTH, expand=True)
+
+        # 验证输入只能为整数
+        vcmd = (self.root.register(self.validate_int), '%P')
+        self.interval_entry.config(validate="key", validatecommand=vcmd)
+
+    def validate_int(self, new_value):
+        """验证输入是否为整数"""
+        if new_value == "":
+            return True
+        try:
+            int(new_value)
+            return True
+        except ValueError:
+            return False
+    def get_captcha(self):
+        try:
+            # 发送GET请求获取图片
+            response = requests.get("https://jiechidj.haiqigame.com/api/common/captcha?type=1&id=421052&timestamp=" + str(int(time.time() * 1000)), timeout=10)
+            
+            # 检查请求是否成功
+            response.raise_for_status()
+            
+            # 获取图片的二进制内容
+            image_data = response.content
+            
+            # 转换为base64编码
+            base64_str = base64.b64encode(image_data).decode('utf-8')
+            
+            # 如果需要带前缀的完整格式(用于HTML img标签)
+            # 根据响应的Content-Type判断图片类型
+            content_type = response.headers.get('content-type', 'image/png')
+            full_base64 = f"data:{content_type};base64,{base64_str}"
+            
+            return full_base64  # 或返回 base64_str,根据需求选择
+            
+        except requests.exceptions.RequestException as e:
+            print(f"请求图片失败: {e}")
+            return None
+        except Exception as e:
+            print(f"转换失败: {e}")
+            return None
+    def log_message(self, msg, level="INFO"):
+        """在日志框中添加消息"""
+        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        formatted_msg = f"[{timestamp}] [{level}] {msg}\n"
+        self.log_text.insert(tk.END, formatted_msg)
+        self.log_text.see(tk.END)  # 自动滚动到底部
+        self.root.update_idletasks()
+
+    def clear_log(self):
+        """清空日志"""
+        self.log_text.delete(1.0, tk.END)
+    
+    def ttshitu(self, imgBase64):
+        url = "https://api.ttshitu.com/predict"
+
+        payload = {"username": "puge","password": "mmit7750","typeid": 11,"remark": "测试","image": imgBase64}
+        headers = {
+            "Accept": "*/*",
+            "Accept-Encoding": "gzip, deflate, br",
+            "User-Agent": "PostmanRuntime-ApipostRuntime/1.1.0",
+            "Connection": "keep-alive",
+            "Content-Type": "application/json"
+        }
+
+        response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
+        jsonData = json.loads(response.text)
+        print('验证码识别结果:' + jsonData['data']['result'])
+        return jsonData['data']['result']
+
+    def sendConnect(self, order_sn, captcha):
+        """请求订单API"""
+        url = "https://jiechidj.haiqigame.com/addons/shop/api.order/sendConnect"
+
+        headers = {
+            "Host": "jiechidj.haiqigame.com",
+            "Connection": "keep-alive",
+            "content-type": "application/json",
+            "uid": "421052",
+            "platform": "ios",
+            "model": "iPhone 15<iPhone15,4>",
+            "sid": "uhir5qYEdlZYwE5SMC8jvpJrfLc5jL9x",
+            "token": "63d53066-ac50-4b39-b184-cf63f254afa2",
+            "brand": "iPhone",
+            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.71(0x1800472b) NetType/WIFI Language/zh_CN",
+            "Accept": "*/*",
+            "Accept-Encoding": "gzip, deflate, br"
+        }
+
+        try:
+            response = requests.post(url, headers=headers, data=json.dumps({"order_sn":order_sn,"captcha":captcha,"__token__":"5c8f6680ff6a1732b42ed36ebed7d1d2"}), timeout=10)
+            response.raise_for_status()
+            print(response.json())
+            return response.json()
+        except requests.exceptions.RequestException as e:
+            self.log_message(f"请求失败: {str(e)}", "ERROR")
+            return None
+        except ValueError as e:
+            self.log_message(f"解析JSON失败: {str(e)}", "ERROR")
+            return None
+
+    def fetch_orders(self):
+        """请求订单API"""
+        url = "https://jiechidj.haiqigame.com/addons/shop/api.order/index"
+        querystring = {
+            "connect": "true",
+            "company_id": "131",
+            "page": "1",
+            "orderstate": "0",
+            "paystate": "1",
+            "shippingstate": "0"
+        }
+        headers = {
+            "Host": "jiechidj.haiqigame.com",
+            "Connection": "keep-alive",
+            "content-type": "application/json",
+            "uid": "421052",
+            "platform": "ios",
+            "model": "iPhone 15<iPhone15,4>",
+            "sid": "uhir5qYEdlZYwE5SMC8jvpJrfLc5jL9x",
+            "token": "63d53066-ac50-4b39-b184-cf63f254afa2",
+            "brand": "iPhone",
+            "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_4_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.71(0x1800472b) NetType/WIFI Language/zh_CN",
+            "Accept": "*/*",
+            "Accept-Encoding": "gzip, deflate, br"
+        }
+
+        try:
+            response = requests.get(url, headers=headers, params=querystring, timeout=10)
+            response.raise_for_status()
+            return response.json()
+        except requests.exceptions.RequestException as e:
+            self.log_message(f"请求失败: {str(e)}", "ERROR")
+            return None
+        except ValueError as e:
+            self.log_message(f"解析JSON失败: {str(e)}", "ERROR")
+            return None
+
+    def process_orders(self, data):
+        
+        """处理订单数据并显示关键信息"""
+        if not data or data.get("code") != 1:
+            self.log_message("API返回错误: " + str(data.get("msg", "未知错误")), "WARNING")
+            return
+
+        orders = data.get("data", {}).get("data", [])
+        if not orders:
+            self.log_message("当前无新订单", "INFO")
+            return
+
+        self.log_message(f"获取到 {len(orders)} 个订单", "INFO")
+        for order in orders:
+            # 提取订单基本信息
+            order_sn = order.get("order_sn", "未知订单号")
+            client = order.get("client", "未知")
+            self.log_message(f"--- 订单号: {order_sn} (客户端: {client}) ---", "INFO")
+
+            # 提取 order_goods 中的 title, attrdata, price
+            order_goods = order.get("order_goods", [])
+            for goods in order_goods:
+                title = goods.get("title", "无标题")
+                attrdata = goods.get("attrdata", "无规格")
+                price = goods.get("price", "0.00")
+                self.log_message(f"  商品: {title}", "INFO")
+                self.log_message(f"  规格: {attrdata}", "INFO")
+                self.log_message(f"  价格: ¥{price}", "INFO")
+
+            # 提取接单要求 (order_desc 中 title="接单要求" 的 content)
+            game_type = order.get("client", "未指定")
+            self.log_message(f"  接单要求(端游/手游): {game_type}", "INFO")
+
+            # 提取订单备注 (order_desc 中其他备注内容)
+            remark_content = order.get("memo", "无备注")
+            self.log_message(f"  订单备注: {remark_content}", "INFO")
+            self.log_message("-" * 50, "INFO")
+            # 抢单
+            # 判断备注里面有没有关键词
+            remarkGood = True
+            remarkText = self.remarkIgnore.get()
+            for item in remarkText.split('@'):
+                if (item in remark_content):
+                    print('备注不符合!')
+                    remarkGood = False
+            if (remarkGood):
+                print('订单价格:' + price + ' 设置价格:' + self.buyPrice.get())
+                if (float(price) > float(self.buyPrice.get())):
+                    print('定单价格符合!')
+                    yzmImg = self.get_captcha()
+                    yzmCode = self.ttshitu(yzmImg.split(',')[1])
+                    self.sendConnect(order_sn, yzmCode)
+                    break
+
+    def monitor_loop(self):
+        """监控循环,在后台线程中运行"""
+        while self.running:
+            try:
+                interval = int(self.interval_var.get())
+                if interval <= 0:
+                    interval = 5
+            except ValueError:
+                interval = 5
+
+            self.log_message("正在请求订单数据...", "INFO")
+            data = self.fetch_orders()
+            if data:
+                self.process_orders(data)
+            else:
+                self.log_message("请求失败,请检查网络或API配置", "ERROR")
+
+            # 等待间隔时间,但需要检查运行标志
+            for _ in range(interval):
+                if not self.running:
+                    break
+                time.sleep(1)
+
+    def start_monitor(self):
+        """启动监控线程"""
+        # 验证间隔输入
+        try:
+            interval = int(self.interval_var.get())
+            if interval <= 0:
+                raise ValueError
+        except ValueError:
+            messagebox.showerror("错误", "请求间隔必须是正整数!")
+            return
+
+        if self.running:
+            messagebox.showwarning("警告", "监控已在运行中")
+            return
+
+        self.running = True
+        self.thread = threading.Thread(target=self.monitor_loop, daemon=True)
+        self.thread.start()
+
+        self.start_button.config(state=tk.DISABLED)
+        self.stop_button.config(state=tk.NORMAL)
+        self.log_message("监控已启动", "SUCCESS")
+
+    def stop_monitor(self):
+        """停止监控"""
+        if not self.running:
+            return
+
+        self.running = False
+        if self.thread and self.thread.is_alive():
+            self.thread.join(timeout=2)
+
+        self.start_button.config(state=tk.NORMAL)
+        self.stop_button.config(state=tk.DISABLED)
+        self.log_message("监控已停止", "SUCCESS")
+
+    def on_closing(self):
+        """关闭窗口时的清理"""
+        if self.running:
+            self.stop_monitor()
+        self.root.destroy()
+
+def main():
+    root = tk.Tk()
+    app = OrderMonitorApp(root)
+    root.mainloop()
+
+if __name__ == "__main__":
+    main()