创建仓库
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