创建仓库
This commit is contained in:
124
.gitignore
vendored
Normal file
124
.gitignore
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
docs/.doctrees/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type checker
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
10
.idea/iOSAI.iml
generated
Normal file
10
.idea/iOSAI.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
21
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
21
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="PySide6" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<option value="N806" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (iOSAI)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/iOSAI.iml" filepath="$PROJECT_DIR$/.idea/iOSAI.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
18
Entity/DeviceModel.py
Normal file
18
Entity/DeviceModel.py
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
# 设备模型
|
||||
class DeviceModel(object):
|
||||
def __init__(self, deviceId, screenPort, type):
|
||||
super(DeviceModel, self).__init__()
|
||||
self.deviceId = deviceId
|
||||
self.screenPort = screenPort
|
||||
# 1 添加 2删除
|
||||
self.type = type
|
||||
|
||||
|
||||
# 转字典
|
||||
def toDict(self):
|
||||
return {
|
||||
'deviceId': self.deviceId,
|
||||
'screenPort': self.screenPort,
|
||||
'type': self.type
|
||||
}
|
||||
16
Entity/ResultData.py
Normal file
16
Entity/ResultData.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import json
|
||||
|
||||
# 返回数据模型
|
||||
class ResultData(object):
|
||||
def __init__(self, code=200, data=None, msg="获取成功"):
|
||||
super(ResultData, self).__init__()
|
||||
self.code = code
|
||||
self.data = data
|
||||
self.msg = msg
|
||||
|
||||
def toJson(self):
|
||||
return json.dumps({
|
||||
"code": self.code,
|
||||
"data": self.data,
|
||||
"msg": self.msg
|
||||
}, ensure_ascii=False) # ensure_ascii=False 确保中文不会被转义
|
||||
4
Entity/Variables.py
Normal file
4
Entity/Variables.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Tik Tok app bundle id
|
||||
tikTokApp = "com.zhiliaoapp.musically"
|
||||
# wda apple bundle id
|
||||
WdaAppBundleId = "com.vv.wda.xctrunner"
|
||||
175
Flask/FlaskService.py
Normal file
175
Flask/FlaskService.py
Normal file
@@ -0,0 +1,175 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
from queue import Queue
|
||||
import tidevice
|
||||
import wda
|
||||
from flask import Flask, request
|
||||
from flask_cors import CORS
|
||||
from Entity.ResultData import ResultData
|
||||
from script.ScriptManager import ScriptManager
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
listData = []
|
||||
dataQueue = Queue()
|
||||
|
||||
def start_socket_listener():
|
||||
port = int(os.getenv('FLASK_COMM_PORT', 0))
|
||||
print(f"Received port from environment: {port}")
|
||||
if port <= 0:
|
||||
print("⚠️ 未获取到通信端口,跳过Socket监听")
|
||||
return
|
||||
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# 设置端口复用,避免端口被占用时无法绑定
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
# 尝试绑定端口
|
||||
try:
|
||||
s.bind(('127.0.0.1', port))
|
||||
print(f"[INFO] Socket successfully bound to port {port}")
|
||||
except Exception as bind_error:
|
||||
print(f"[ERROR] ❌ 端口绑定失败: {bind_error}")
|
||||
return
|
||||
|
||||
# 开始监听
|
||||
s.listen()
|
||||
print(f"[INFO] Socket listener started on port {port}, waiting for connections...")
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
conn, addr = s.accept()
|
||||
print(f"[INFO] Connection accepted from: {addr}")
|
||||
|
||||
raw_data = conn.recv(1024).decode('utf-8').strip()
|
||||
print(f"[INFO] Raw data received: {raw_data}")
|
||||
|
||||
data = json.loads(raw_data)
|
||||
print(f"[INFO] Parsed data: {data}")
|
||||
dataQueue.put(data)
|
||||
except Exception as conn_error:
|
||||
print(f"[ERROR] ❌ 连接处理失败: {conn_error}")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] ❌ Socket服务启动失败: {e}")
|
||||
|
||||
# 在独立线程中启动Socket服务
|
||||
listener_thread = threading.Thread(target=start_socket_listener, daemon=True)
|
||||
listener_thread.start()
|
||||
|
||||
# 获取设备列表
|
||||
@app.route('/deviceList', methods=['GET'])
|
||||
def deviceList():
|
||||
while not dataQueue.empty():
|
||||
obj = dataQueue.get()
|
||||
type = obj["type"]
|
||||
if type == 1:
|
||||
listData.append(obj)
|
||||
else:
|
||||
for data in listData:
|
||||
if data.get("deviceId") == obj.get("deviceId") and data.get("screenPort") == obj.get("screenPort"):
|
||||
listData.remove(data)
|
||||
return ResultData(data=listData).toJson()
|
||||
|
||||
# 获取设备应用列表
|
||||
@app.route('/deviceAppList', methods=['POST'])
|
||||
def deviceAppList():
|
||||
param = request.get_json()
|
||||
udid = param["udid"]
|
||||
t = tidevice.Device(udid)
|
||||
|
||||
# 获取已安装的应用列表
|
||||
apps = []
|
||||
for app in t.installation.iter_installed():
|
||||
apps.append({
|
||||
"name": app.get("CFBundleDisplayName", "Unknown"),
|
||||
"bundleId": app.get("CFBundleIdentifier", "Unknown"),
|
||||
"version": app.get("CFBundleShortVersionString", "Unknown"),
|
||||
"path": app.get("Path", "Unknown")
|
||||
})
|
||||
|
||||
# 筛选非系统级应用(过滤掉以 com.apple 开头的系统应用)
|
||||
non_system_apps = [app for app in apps if not app["bundleId"].startswith("com.apple")]
|
||||
return ResultData(data=non_system_apps).toJson()
|
||||
|
||||
# 打开置顶app
|
||||
@app.route('/launchApp', methods=['POST'])
|
||||
def launchApp():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
bundleId = body.get("bundleId")
|
||||
t = tidevice.Device(udid)
|
||||
t.app_start(bundleId)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
# 回到首页
|
||||
@app.route('/toHome', methods=['POST'])
|
||||
def toHome():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
client = wda.USBClient(udid)
|
||||
client.home()
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
# 点击事件
|
||||
@app.route('/tapAction', methods=['POST'])
|
||||
def tapAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
x = body.get("x")
|
||||
y = body.get("y")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 0})
|
||||
session.tap(x, y)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
# 拖拽事件
|
||||
@app.route('/swipeAction', methods=['POST'])
|
||||
def swipeAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
direction = body.get("direction")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 0})
|
||||
|
||||
if direction == 1:
|
||||
session.swipe_up()
|
||||
elif direction == 2:
|
||||
session.swipe_left()
|
||||
elif direction == 3:
|
||||
session.swipe_down()
|
||||
else:
|
||||
session.swipe_right()
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
# 长按事件
|
||||
@app.route('/longPressAction', methods=['POST'])
|
||||
def longPressAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
x = body.get("x")
|
||||
y = body.get("y")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 5})
|
||||
session.tap_hold(x,y,1.0)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
# 养号
|
||||
@app.route('/growAccount', methods=['POST'])
|
||||
def growAccount():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
|
||||
# 启动脚本
|
||||
threading.Thread(target=ScriptManager.growAccount, args=(udid,)).start()
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run("0.0.0.0", port=5000, debug=True, use_reloader=False)
|
||||
96
Module/DeviceInfo.py
Normal file
96
Module/DeviceInfo.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import wda
|
||||
from tidevice import Usbmux
|
||||
from Entity.DeviceModel import DeviceModel
|
||||
from Entity.Variables import tikTokApp, WdaAppBundleId
|
||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||
|
||||
threadLock = threading.Lock()
|
||||
|
||||
class Deviceinfo(object):
|
||||
def __init__(self):
|
||||
self.deviceIndex = 0
|
||||
# 投屏端口
|
||||
self.screenProxy = 9110
|
||||
# 存放pid的数组
|
||||
self.pidList = []
|
||||
# 设备列表
|
||||
self.deviceArray = []
|
||||
# 获取到县城管理类
|
||||
self.manager = FlaskSubprocessManager.get_instance()
|
||||
# 给前端的设备模型数组
|
||||
self.deviceModelList = []
|
||||
|
||||
# 监听设备连接
|
||||
def startDeviceListener(self):
|
||||
while True:
|
||||
lists = Usbmux().device_list()
|
||||
# 添加设备逻辑
|
||||
for device in lists:
|
||||
if device not in self.deviceArray:
|
||||
self.screenProxy += 1
|
||||
self.connectDevice(device.udid)
|
||||
self.deviceArray.append(device)
|
||||
# 创建模型
|
||||
model = DeviceModel(device.udid,self.screenProxy,type=1)
|
||||
self.deviceModelList.append(model)
|
||||
# 发送数据
|
||||
self.manager.send(model.toDict())
|
||||
|
||||
# 处理拔出设备的逻辑
|
||||
def removeDevice():
|
||||
set1 = set(self.deviceArray)
|
||||
set2 = set(lists)
|
||||
difference = set1 - set2
|
||||
differenceList = list(difference)
|
||||
for i in differenceList:
|
||||
for j in self.deviceArray:
|
||||
# 判断是否为差异设备
|
||||
if i.udid == j.udid:
|
||||
# 从设备模型中删除数据
|
||||
for a in self.deviceModelList:
|
||||
if i.udid == a.deviceId:
|
||||
a.type = 2
|
||||
# 发送数据
|
||||
self.manager.send(a.toDict())
|
||||
self.deviceModelList.remove(a)
|
||||
|
||||
for k in self.pidList:
|
||||
# 干掉端口短发进程
|
||||
if j.udid == k["id"]:
|
||||
target = k["target"]
|
||||
target.kill()
|
||||
self.pidList.remove(k)
|
||||
# 删除已经拔出的设备
|
||||
self.deviceArray.remove(j)
|
||||
|
||||
removeDevice()
|
||||
time.sleep(1)
|
||||
|
||||
# 连接设备
|
||||
def connectDevice(self, identifier):
|
||||
d = wda.USBClient(identifier, 8100)
|
||||
d.app_start(WdaAppBundleId)
|
||||
time.sleep(2)
|
||||
d.app_start(tikTokApp)
|
||||
target = self.relayDeviceScreenPort()
|
||||
self.pidList.append({
|
||||
"target": target,
|
||||
"id": identifier
|
||||
})
|
||||
|
||||
# 转发设备端口
|
||||
def relayDeviceScreenPort(self):
|
||||
try:
|
||||
command = f"iproxy.exe {self.screenProxy} 9100"
|
||||
# 创建一个没有窗口的进程
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = 0
|
||||
r = subprocess.Popen(command, shell=True, startupinfo=startupinfo)
|
||||
return r
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return 0
|
||||
101
Module/FlaskSubprocessManager.py
Normal file
101
Module/FlaskSubprocessManager.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import subprocess
|
||||
import threading
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from typing import Optional, Union, Dict, List
|
||||
|
||||
class FlaskSubprocessManager:
|
||||
_instance: Optional['FlaskSubprocessManager'] = None
|
||||
_lock: threading.Lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance._init_manager()
|
||||
return cls._instance
|
||||
|
||||
def _init_manager(self):
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.comm_port = self._find_available_port()
|
||||
self._stop_event = threading.Event()
|
||||
atexit.register(self.stop)
|
||||
|
||||
def _find_available_port(self):
|
||||
"""动态获取可用端口"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('0.0.0.0', 0))
|
||||
return s.getsockname()[1]
|
||||
|
||||
def start(self):
|
||||
"""启动子进程(Windows兼容方案)"""
|
||||
with self._lock:
|
||||
if self.process is not None:
|
||||
raise RuntimeError("子进程已在运行中!")
|
||||
# 通过环境变量传递通信端口
|
||||
env = os.environ.copy()
|
||||
env['FLASK_COMM_PORT'] = str(self.comm_port)
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
['python', 'Flask/FlaskService.py'], # 启动一个子进程 FlaskService.py
|
||||
stdin=subprocess.PIPE, # 标准输入流,用于向子进程发送数据
|
||||
stdout=subprocess.PIPE, # 标准输出流,用于接收子进程的输出
|
||||
stderr=subprocess.PIPE, # 标准错误流,用于接收子进程的错误信息
|
||||
text=True, # 以文本模式打开流,否则以二进制模式打开
|
||||
bufsize=1, # 缓冲区大小设置为 1,表示行缓冲
|
||||
encoding='utf-8', # 指定编码为 UTF-8,确保控制台输出不会报错
|
||||
env=env # 指定子进程的环境变量
|
||||
)
|
||||
print(f"Flask子进程启动 (PID: {self.process.pid}, 通信端口: {self.comm_port})")
|
||||
|
||||
# 将日志通过主进程输出
|
||||
def print_output():
|
||||
while True:
|
||||
output = self.process.stdout.readline()
|
||||
if not output:
|
||||
break
|
||||
print(output.strip())
|
||||
|
||||
while True:
|
||||
error = self.process.stderr.readline()
|
||||
if not error:
|
||||
break
|
||||
print(f"Error: {error.strip()}")
|
||||
|
||||
threading.Thread(target=print_output, daemon=True).start()
|
||||
|
||||
def send(self, data: Union[str, Dict, List]) -> bool:
|
||||
"""通过Socket发送数据"""
|
||||
try:
|
||||
if not isinstance(data, str):
|
||||
data = json.dumps(data)
|
||||
# 等待子进程启动并准备好
|
||||
time.sleep(1) # 延时1秒,根据实际情况调整
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect(('127.0.0.1', self.comm_port))
|
||||
s.sendall((data + "\n").encode('utf-8'))
|
||||
return True
|
||||
except ConnectionRefusedError:
|
||||
print(f"连接被拒绝,确保子进程在端口 {self.comm_port} 上监听")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"发送失败: {e}")
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
with self._lock:
|
||||
if self.process and self.process.poll() is None:
|
||||
print(f"[INFO] Stopping Flask child process (PID: {self.process.pid})...")
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
print("[INFO] Flask child process stopped.")
|
||||
self._stop_event.set()
|
||||
else:
|
||||
print("[INFO] No Flask child process to stop.")
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls) -> 'FlaskSubprocessManager':
|
||||
return cls()
|
||||
11
Module/Main.py
Normal file
11
Module/Main.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from Module.DeviceInfo import Deviceinfo
|
||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("启动flask")
|
||||
manager = FlaskSubprocessManager.get_instance()
|
||||
manager.start()
|
||||
|
||||
print("启动主线程")
|
||||
info = Deviceinfo()
|
||||
info.startDeviceListener()
|
||||
BIN
Module/iproxy.exe
Normal file
BIN
Module/iproxy.exe
Normal file
Binary file not shown.
BIN
resources/bgv.png
Normal file
BIN
resources/bgv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
resources/like.png
Normal file
BIN
resources/like.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
109
script/AiTools.py
Normal file
109
script/AiTools.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import os
|
||||
import time
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# 工具类
|
||||
class AiTools(object):
|
||||
|
||||
@classmethod
|
||||
def find_image_in_image(
|
||||
cls,
|
||||
smallImageUrl,
|
||||
bigImageUrl,
|
||||
match_threshold=0.90,
|
||||
consecutive_required=3,
|
||||
scales=None
|
||||
):
|
||||
|
||||
if scales is None:
|
||||
scales = [0.5, 0.75, 1.0, 1.25, 1.5]
|
||||
|
||||
template = cv2.imread(smallImageUrl, cv2.IMREAD_COLOR)
|
||||
# if template is None:
|
||||
# raise Exception(f"❌ 无法读取模板 '{smallImageUrl}'")
|
||||
|
||||
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
cap = cv2.imread(bigImageUrl, cv2.IMREAD_COLOR)
|
||||
# if not cap.isOpened():
|
||||
# print(f"❌ 无法打开视频流: {bigImageUrl}")
|
||||
# return None
|
||||
|
||||
detected_consecutive_frames = 0
|
||||
|
||||
print("🚀 正在检测爱心图标...")
|
||||
while True:
|
||||
print("死了")
|
||||
ret, frame = cap.read()
|
||||
if not ret or frame is None:
|
||||
time.sleep(0.01)
|
||||
continue
|
||||
print("哈哈哈")
|
||||
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
current_frame_has_match = False
|
||||
best_match_val = 0
|
||||
best_match_loc = None
|
||||
best_match_w_h = None
|
||||
print("aaaaaaaaaaaa")
|
||||
for scale in scales:
|
||||
resized_template = cv2.resize(template_gray, (0, 0), fx=scale, fy=scale)
|
||||
th, tw = resized_template.shape[:2]
|
||||
|
||||
if th > frame_gray.shape[0] or tw > frame_gray.shape[1]:
|
||||
continue
|
||||
|
||||
result = cv2.matchTemplate(frame_gray, resized_template, cv2.TM_CCOEFF_NORMED)
|
||||
_, max_val, _, max_loc = cv2.minMaxLoc(result)
|
||||
|
||||
if max_val > best_match_val:
|
||||
best_match_val = max_val
|
||||
best_match_loc = max_loc
|
||||
best_match_w_h = (tw, th)
|
||||
if max_val >= match_threshold:
|
||||
current_frame_has_match = True
|
||||
print("break 了")
|
||||
break
|
||||
print("bbbbbbbbbbbbbbbbbbbbbb")
|
||||
if current_frame_has_match:
|
||||
print("111111")
|
||||
detected_consecutive_frames += 1
|
||||
last_detection_info = (best_match_loc, best_match_w_h, best_match_val)
|
||||
else:
|
||||
print("2222222")
|
||||
detected_consecutive_frames = 0
|
||||
last_detection_info = None
|
||||
|
||||
if detected_consecutive_frames >= consecutive_required and last_detection_info:
|
||||
print("333333333")
|
||||
top_left, (w, h), match_val = last_detection_info
|
||||
center_x = top_left[0] + w // 2
|
||||
center_y = top_left[1] + h // 2
|
||||
|
||||
print(f"🎯 成功识别爱心图标: 中心坐标=({center_x}, {center_y}), 匹配度={match_val:.4f}")
|
||||
return center_y, center_y
|
||||
else:
|
||||
return -1, -1
|
||||
cap.release()
|
||||
print("释放了")
|
||||
return -1, -1
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def imagePath(cls, name):
|
||||
current_file_path = os.path.abspath(__file__)
|
||||
# 获取当前文件所在的目录(即script目录)
|
||||
current_dir = os.path.dirname(current_file_path)
|
||||
# 由于script目录位于项目根目录下一级,因此需要向上一级目录移动两次
|
||||
project_root = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
# 构建资源文件的完整路径,向上两级目录,然后进入 resources 目录
|
||||
resource_path = os.path.abspath(os.path.join(project_root, 'resources', name + ".png")).replace('/', '\\\\')
|
||||
return resource_path
|
||||
|
||||
|
||||
|
||||
|
||||
46
script/ScriptManager.py
Normal file
46
script/ScriptManager.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import cv2
|
||||
import lxml
|
||||
import wda
|
||||
from lxml import etree
|
||||
|
||||
from script.AiTools import AiTools
|
||||
|
||||
|
||||
# 脚本管理类
|
||||
class ScriptManager():
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 养号
|
||||
@classmethod
|
||||
def growAccount(self, udid):
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 0})
|
||||
|
||||
deviceWidth = client.window_size().width
|
||||
deviceHeight = client.window_size().height
|
||||
|
||||
img = client.screenshot()
|
||||
tempPath = "resources/bgv.png"
|
||||
img.save(tempPath)
|
||||
|
||||
bgvPath = AiTools.imagePath("bgv")
|
||||
likePath = AiTools.imagePath("like")
|
||||
|
||||
x, y = AiTools.find_image_in_image(bgvPath, likePath)
|
||||
print(x, y)
|
||||
# client.tap(end[0] / 3 - 2, end[1] / 3 - 2)
|
||||
|
||||
|
||||
# xml = session.source()
|
||||
# print(xml)
|
||||
# root = etree.fromstring(xml.encode('utf-8'))
|
||||
# try:
|
||||
# msg = client.xpath('label="收件箱"')
|
||||
# msg.click()
|
||||
# print(msg)
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
|
||||
|
||||
BIN
script/screenshot.png
Normal file
BIN
script/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user