| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- import tkinter as tk
- from tkinter import ttk, scrolledtext, messagebox, filedialog
- import subprocess
- import threading
- import json
- import os
- import time
- import random
- import requests
- from datetime import datetime
- import win32gui
- import win32con
- import win32api
- import tkinter as tk
- from PIL import Image, ImageTk
- def drag_in_emulator(manager_path, emu_index, x, y1, y2, duration=400):
- """
- 在模拟器中从 (x, y1) 拖拽到 (x, y2)
- """
- # 获取adb端口
- cmd = [manager_path, "info", "-v", str(emu_index)]
- result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
-
- if result.returncode != 0:
- return False
-
- try:
- data = json.loads(result.stdout)
- adb_port = data.get('adb_port')
- if not adb_port:
- return False
- except:
- return False
-
- # 连接adb
- target_device = f"127.0.0.1:{adb_port}"
- subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
- time.sleep(0.3)
-
- # 执行滑动
- cmd = f"adb -s {target_device} shell input swipe {x} {y1} {x} {y2} {duration}"
- result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
-
- return result.returncode == 0
- def input_text_to_emulator(manager_path, emu_index, text):
- """向模拟器输入文本"""
- # 获取adb端口
- cmd = [manager_path, "info", "-v", str(emu_index)]
- result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
-
- if result.returncode != 0:
- return False
-
- try:
- data = json.loads(result.stdout)
- adb_port = data.get('adb_port')
- if not adb_port:
- return False
- except:
- return False
-
- target_device = f"127.0.0.1:{adb_port}"
- subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
-
- # 先删除
- subprocess.run(f"adb -s {target_device} shell input keyevent KEYCODE_DEL", shell=True)
-
- # 输入文本
- subprocess.run(f"adb -s {target_device} shell input text \"{text}\"", shell=True)
-
- return True
- def get_emulator_pixel_color(manager_path, emu_index, x, y):
- """
- 获取模拟器指定坐标的颜色
-
- Args:
- manager_path: MuMuManager.exe路径
- emu_index: 模拟器索引
- x, y: 坐标
-
- Returns:
- 颜色代码,如 "#FFFFFF",失败返回 None
- """
- # 获取adb端口
- cmd = [manager_path, "info", "-v", str(emu_index)]
- result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
-
- if result.returncode != 0:
- return None
-
- try:
- data = json.loads(result.stdout)
- adb_port = data.get('adb_port')
- if not adb_port:
- return None
- except:
- return None
-
- # 连接adb
- target_device = f"127.0.0.1:{adb_port}"
- subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
- time.sleep(0.3)
-
- # 截图并获取颜色
- temp_file = f"temp_screenshot_{emu_index}.png"
- subprocess.run(f"adb -s {target_device} exec-out screencap -p > {temp_file}", shell=True)
- time.sleep(0.3)
-
- try:
- from PIL import Image
- img = Image.open(temp_file)
- pixel = img.getpixel((x, y))
- img.close()
-
- # 转换为十六进制颜色
- if isinstance(pixel, tuple):
- r, g, b = pixel[0], pixel[1], pixel[2]
- else:
- r = g = b = pixel
-
- color = f"#{r:02X}{g:02X}{b:02X}"
- return color
-
- except Exception as e:
- return None
- finally:
- import os
- if os.path.exists(temp_file):
- os.remove(temp_file)
- def show_image_at_position(image_path, x, y, width=250, height=250):
- """
- 在指定位置创建窗口显示图片并激活
- """
- # 创建窗口
- window = tk.Toplevel()
- window.title("图片显示")
-
- # 设置窗口位置和大小
- window.geometry(f"{width}x{height}+{x}+{y}")
-
- # 加载图片
- image = Image.open(image_path)
- image = image.resize((width, height), Image.Resampling.LANCZOS)
- photo = ImageTk.PhotoImage(image)
-
- # 显示图片
- label = tk.Label(window, image=photo)
- label.image = photo
- label.pack(fill=tk.BOTH, expand=True)
-
- # 刷新窗口
- window.update()
-
- # 获取窗口句柄并激活
- hwnd = win32gui.FindWindow(None, "图片显示")
- if hwnd:
- win32gui.SetForegroundWindow(hwnd)
-
- return window
- def click_screen(x, y):
- """
- 在屏幕坐标(x, y)处点击鼠标左键
- """
- # 移动鼠标到指定位置
- win32api.SetCursorPos((x, y))
-
- # 按下左键
- win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
-
- # 抬起左键
- win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
- def activate_window_and_get_position(window_title):
- """
- 激活窗口并返回左上角坐标
- """
- # 查找窗口
- hwnd = win32gui.FindWindow(None, window_title)
-
- if hwnd == 0:
- print(f"未找到窗口: {window_title}")
- return None, None
-
- # 如果窗口最小化,恢复它
- if win32gui.IsIconic(hwnd):
- win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
-
- # 激活窗口
- win32gui.SetForegroundWindow(hwnd)
-
- # 获取窗口位置
- rect = win32gui.GetWindowRect(hwnd)
- x = rect[0]
- y = rect[1]
-
- return x, y
- class MuMuEmulatorManager:
- def __init__(self, manager_path):
- self.manager_path = manager_path
-
- def get_adb_port(self, index, log_callback=None):
- """获取指定模拟器的 ADB 端口"""
- 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
- return None
-
- def tap(self, index, x, y, log_callback=None):
- """点击坐标"""
- adb_port = self.get_adb_port(index, log_callback)
- if not adb_port:
- if log_callback:
- log_callback(f"❌ 无法获取模拟器 {index} 的ADB端口")
- return False
-
- 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)
-
- # 执行点击
- 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:
- if log_callback:
- log_callback(f"✅ 点击坐标 ({x}, {y}) 成功")
- return True
- else:
- if log_callback:
- log_callback(f"❌ 点击失败: {result.stderr}")
- return False
-
- def get_emulator_list(self):
- """获取所有模拟器列表"""
- cmd = [self.manager_path, "info", "-v", "all"]
- result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
-
- if result.returncode != 0:
- return []
-
- try:
- # 清理输出(可能包含非JSON内容)
- output = result.stdout.strip()
- json_start = output.find('{')
- if json_start != -1:
- output = output[json_start:]
-
- data = json.loads(output)
- emulators = []
- for key, value in data.items():
- if isinstance(value, dict):
- value['index'] = key
- # 只返回运行中的模拟器
- if value.get('is_process_started', False):
- emulators.append(value)
- return emulators
- except json.JSONDecodeError:
- return []
- class SimpleMuMuManager:
- def __init__(self, root):
- self.root = root
- self.root.title("MuMu模拟器管理工具")
- self.root.geometry("650x450")
-
- # 配置文件
- self.config_file = "mumu_config.json"
- self.mumu_manager_path = None
- self.emulators = []
-
- # 创建界面
- self.create_widgets()
-
- # 加载配置
- self.load_config()
-
- # 如果路径存在,自动刷新
- if self.mumu_manager_path and os.path.exists(self.mumu_manager_path):
- self.refresh_emulators()
-
- def create_widgets(self):
- # 主框架
- main_frame = ttk.Frame(self.root, padding="10")
- main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
-
- # 配置区域
- config_frame = ttk.LabelFrame(main_frame, text="MuMuManager配置", padding="10")
- config_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5)
-
- self.path_var = tk.StringVar()
- self.path_entry = ttk.Entry(config_frame, textvariable=self.path_var, width=55)
- self.path_entry.grid(row=0, column=1, padx=5)
- self.path_entry.bind('<FocusOut>', self.on_path_changed)
-
- browse_btn = ttk.Button(config_frame, text="浏览", command=self.browse_mumu_manager)
- browse_btn.grid(row=0, column=2, padx=5)
-
- refresh_btn = ttk.Button(config_frame, text="刷新列表", command=self.refresh_emulators)
- refresh_btn.grid(row=0, column=3, padx=5)
-
- # 模拟器选择区域 - 一行两个
- selector_frame = ttk.Frame(main_frame)
- selector_frame.grid(row=1, column=0, pady=20)
-
- # 发货手机
- send_frame = ttk.LabelFrame(selector_frame, text="发货手机", padding="10")
- send_frame.grid(row=0, column=0, padx=10)
-
- self.send_var = tk.StringVar()
- self.send_combo = ttk.Combobox(send_frame, textvariable=self.send_var, width=35, state="readonly")
- self.send_combo.grid(row=0, column=0)
-
- # 收货手机
- receive_frame = ttk.LabelFrame(selector_frame, text="收货手机", padding="10")
- receive_frame.grid(row=0, column=1, padx=10)
-
- self.receive_var = tk.StringVar()
- self.receive_combo = ttk.Combobox(receive_frame, textvariable=self.receive_var, width=35, state="readonly")
- self.receive_combo.grid(row=0, column=0)
-
- # 日志区域
- log_frame = ttk.LabelFrame(main_frame, text="日志", padding="10")
- log_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
-
- self.log_text = scrolledtext.ScrolledText(log_frame, width=75, height=15, wrap=tk.WORD)
- self.log_text.grid(row=0, column=0)
-
- # 测试按钮
- # test_btn = ttk.Button(main_frame, text="测试发货", command=self.test_click, width=20)
- # test_btn.grid(row=3, column=0, pady=1)
- test_btn2 = ttk.Button(main_frame, text="测试收货", command=self.test_click2, width=20)
- test_btn2.grid(row=3, column=0, pady=1)
-
- # 配置权重
- self.root.columnconfigure(0, weight=1)
- self.root.rowconfigure(0, weight=1)
- main_frame.columnconfigure(0, weight=1)
- main_frame.rowconfigure(2, weight=1)
- log_frame.columnconfigure(0, weight=1)
- log_frame.rowconfigure(0, weight=1)
-
- def browse_mumu_manager(self):
- """浏览选择MuMuManager.exe"""
- file_path = filedialog.askopenfilename(
- title="选择MuMuManager.exe",
- filetypes=[("Executable files", "*.exe"), ("All files", "*.*")]
- )
- if file_path:
- self.path_var.set(file_path)
- self.on_path_changed()
-
- def on_path_changed(self, event=None):
- """路径改变时自动保存并刷新"""
- new_path = self.path_var.get()
- if new_path and os.path.exists(new_path):
- self.mumu_manager_path = new_path
- self.save_config()
- self.log_message("配置已自动保存")
- self.refresh_emulators()
- elif new_path:
- self.log_message(f"路径不存在: {new_path}", "WARNING")
-
- def save_config(self):
- """保存配置"""
- config = {'mumu_manager_path': self.mumu_manager_path}
- try:
- with open(self.config_file, 'w', encoding='utf-8') as f:
- json.dump(config, f, indent=4)
- except Exception as e:
- self.log_message(f"保存配置失败: {e}", "ERROR")
-
- def load_config(self):
- """加载配置"""
- if os.path.exists(self.config_file):
- try:
- with open(self.config_file, 'r', encoding='utf-8') as f:
- config = json.load(f)
- path = config.get('mumu_manager_path')
- if path and os.path.exists(path):
- self.mumu_manager_path = path
- self.path_var.set(path)
- self.log_message(f"已加载配置: {path}")
- elif path:
- self.log_message(f"配置路径不存在: {path}", "WARNING")
- except Exception as e:
- self.log_message(f"加载配置失败: {e}", "ERROR")
-
- def log_message(self, message, level="INFO"):
- """添加日志"""
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- log_entry = f"[{timestamp}] [{level}] {message}\n"
- self.log_text.insert(tk.END, log_entry)
- self.log_text.see(tk.END)
- self.root.update()
-
- def refresh_emulators(self):
- """刷新模拟器列表"""
- if not self.mumu_manager_path or not os.path.exists(self.mumu_manager_path):
- self.log_message("请先设置正确的 MuMuManager.exe 路径", "WARNING")
- return
-
- self.log_message("正在获取模拟器列表...")
-
- try:
- manager = MuMuEmulatorManager(self.mumu_manager_path)
- self.emulators = manager.get_emulator_list()
-
- if not self.emulators:
- self.log_message("未发现运行中的模拟器,请确保 MuMu 模拟器已启动", "WARNING")
- self.send_combo['values'] = []
- self.receive_combo['values'] = []
- return
-
- # 构建显示名称列表
- emulator_names = []
- for emu in self.emulators:
- name = emu.get('name', f"模拟器_{emu.get('index')}")
- display_name = f"{name} (索引:{emu.get('index')})"
- emulator_names.append(display_name)
- self.log_message(f"发现模拟器: {display_name}")
-
- # 更新下拉框
- self.send_combo['values'] = emulator_names
- self.receive_combo['values'] = emulator_names
-
- self.log_message(f"✅ 已加载 {len(self.emulators)} 个运行中的模拟器")
-
- except Exception as e:
- self.log_message(f"获取模拟器列表失败: {e}", "ERROR")
-
- def get_emulator_index(self, display_name):
- """从显示名称获取模拟器索引"""
- if not display_name:
- return None
- # 提取索引,格式如 "名称 (索引:0)"
- import re
- match = re.search(r'索引:(\d+)', display_name)
- if match:
- return match.group(1)
- return None
-
- def test_click(self):
- url = "https://task.port.run/acquire/bdxkjzc"
- payload = ""
- headers = {
- "Content-Type": "application/json",
- "Accept": "*/*",
- "Accept-Encoding": "gzip, deflate, br",
- "User-Agent": "PostmanRuntime-ApipostRuntime/1.1.0",
- "Connection": "keep-alive"
- }
- response = requests.request("GET", url, data=payload, headers=headers)
- print(response.text)
- repData = json.loads(response.text)
- if ("task" in repData and "content" in repData['task']):
- """测试点击"""
- send_selection = self.send_var.get()
-
- if not send_selection:
- self.log_message("请选择发货手机模拟器", "ERROR")
- messagebox.showwarning("警告", "请选择发货手机模拟器")
- return
- emu_index = self.get_emulator_index(send_selection)
- if not emu_index:
- self.log_message("无法获取模拟器索引", "ERROR")
- return
-
- # 在新线程中执行点击
- thread = threading.Thread(target=self.perform_click, args=(emu_index, send_selection.split(" (")[0], repData['task']["content"]))
- thread.daemon = True
- thread.start()
- else:
- self.log_message("未获取到有效任务数据", "ERROR")
- messagebox.showerror("错误", "未获取到有效任务数据")
-
- def test_click2(self):
- url = "https://task.port.run/acquire/bdxkjxc"
- payload = ""
- headers = {
- "Content-Type": "application/json",
- "Accept": "*/*",
- "Accept-Encoding": "gzip, deflate, br",
- "User-Agent": "PostmanRuntime-ApipostRuntime/1.1.0",
- "Connection": "keep-alive"
- }
- response = requests.request("GET", url, data=payload, headers=headers)
- print(response.text)
- repData = json.loads(response.text)
- if ("task" in repData and "content" in repData['task']):
- """测试点击"""
- receive_selection = self.receive_var.get()
-
- if not receive_selection:
- self.log_message("请选择收货手机模拟器", "ERROR")
- messagebox.showwarning("警告", "请选择收货手机模拟器")
- return
- emu_index = self.get_emulator_index(receive_selection)
- if not emu_index:
- self.log_message("无法获取模拟器索引", "ERROR")
- return
-
- # 在新线程中执行点击
- thread = threading.Thread(target=self.perform_click2, args=(emu_index, receive_selection.split(" (")[0], repData['task']["content"]))
- thread.daemon = True
- thread.start()
- else:
- self.log_message("未获取到有效任务数据", "ERROR")
- messagebox.showerror("错误", "未获取到有效任务数据")
- def perform_click(self, emu_index, window_title, content):
- """执行点击操作"""
- x, y = 508, 252
- self.log_message(f"开始在模拟器 {emu_index} 上点击位置 ({x}, {y})")
-
- try:
- manager = MuMuEmulatorManager(self.mumu_manager_path)
-
- # 先获取adb端口
- adb_port = manager.get_adb_port(emu_index, self.log_message)
- if not adb_port:
- self.log_message(f"❌ 无法获取模拟器 {emu_index} 的ADB端口", "ERROR")
- return
-
- # 执行点击
- if manager.tap(emu_index, x, y, self.log_message):
- self.log_message(f"✅ 点击成功!模拟器 {emu_index} 位置 ({x}, {y})")
- rectX, rectY = activate_window_and_get_position(window_title)
- time.sleep(2) # 短暂等待窗口激活
- click_screen(rectX + 339, rectY + 576)
- time.sleep(1)
- showImageBox = show_image_at_position("C:\\Users\\mail\\Downloads\\chepai\\" + content["tableData"][2] + ".png", rectX + 273, rectY + 549 )
- # 开始检测是否扫描
- color1 = get_emulator_pixel_color(self.mumu_manager_path, emu_index, 523, 1114)
- while color1 != "#007AFF": # 假设白色表示未扫描
- self.log_message(f"当前颜色: {color1}, 等待扫描完成...")
- time.sleep(1)
- color1 = get_emulator_pixel_color(self.mumu_manager_path, emu_index, 523, 1114)
- # 关闭窗口
- showImageBox.destroy()
- self.log_message(f"关闭二维码窗口!")
- click_screen(rectX + 323, rectY + 718)
- time.sleep(1)
- drag_in_emulator(self.mumu_manager_path, emu_index, 430, 1046, 980, 400)
- time.sleep(0.5)
- click_screen(rectX + 650, rectY + 743)
- time.sleep(1)
- click_screen(rectX + 358, rectY + 1112)
- time.sleep(1)
- click_screen(rectX + 419, rectY + 760)
-
- else:
- self.log_message(f"❌ 点击失败", "ERROR")
-
- except Exception as e:
- self.log_message(f"❌ 执行出错: {e}", "ERROR")
- def perform_click2(self, emu_index, window_title, content):
- """执行点击操作"""
- x, y = 508, 252
- self.log_message(f"开始在模拟器 {emu_index} 上点击位置 ({x}, {y})")
-
- try:
- manager = MuMuEmulatorManager(self.mumu_manager_path)
-
- # 先获取adb端口
- adb_port = manager.get_adb_port(emu_index, self.log_message)
- if not adb_port:
- self.log_message(f"❌ 无法获取模拟器 {emu_index} 的ADB端口", "ERROR")
- return
-
- # 执行点击
- if manager.tap(emu_index, x, y, self.log_message):
- self.log_message(f"✅ 点击成功!模拟器 {emu_index} 位置 ({x}, {y})")
- rectX, rectY = activate_window_and_get_position(window_title)
- time.sleep(2) # 短暂等待窗口激活
- click_screen(rectX + 339, rectY + 576)
- time.sleep(1)
- showImageBox = show_image_at_position("C:\\Users\\mail\\Downloads\\chepai\\" + content["tableData"][2] + ".png", rectX + 273, rectY + 549 )
- # 开始检测是否扫描
- color1 = get_emulator_pixel_color(self.mumu_manager_path, emu_index, 530, 1200)
- while color1 != "#007AFF": # 假设白色表示未扫描
- self.log_message(f"当前颜色: {color1}, 等待扫描完成...")
- time.sleep(1)
- color1 = get_emulator_pixel_color(self.mumu_manager_path, emu_index, 530, 1200)
- # 关闭窗口
- showImageBox.destroy()
- self.log_message(f"关闭二维码窗口!")
- manager.tap(emu_index, 323, 718, self.log_message)
- time.sleep(2)
- manager.tap(emu_index, 652, 748, self.log_message)
- time.sleep(1)
- manager.tap(emu_index, 347, 918, self.log_message)
- time.sleep(1)
- input_text_to_emulator(self.mumu_manager_path, emu_index, random.randint(35, 36))
- time.sleep(1)
- time.sleep(1)
- manager.tap(emu_index, 530, 1200, self.log_message)
- time.sleep(1)
- manager.tap(emu_index, 419, 765, self.log_message)
- else:
- self.log_message(f"❌ 点击失败", "ERROR")
-
- except Exception as e:
- self.log_message(f"❌ 执行出错: {e}", "ERROR")
- def main():
- root = tk.Tk()
- app = SimpleMuMuManager(root)
- root.mainloop()
- if __name__ == "__main__":
- main()
|