Compare commits

..

11 Commits

Author SHA1 Message Date
b91aa99048 20250916-正式上线测试 2025-09-16 14:03:43 +08:00
b203579649 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.idea/workspace.xml
2025-09-15 22:41:12 +08:00
096b10f5f0 20250904-初步功能已完成 2025-09-15 22:40:45 +08:00
177757a143 Merge branch 'main' of http://49.235.115.212:3000/zw/iOSAI
# Conflicts:
#	.idea/workspace.xml
#	Module/DeviceInfo.py
2025-09-13 01:03:29 +08:00
3f5c880c0f 20250904-初步功能已完成 2025-09-13 01:03:15 +08:00
6da5926b4b Merge branch 'main' of http://49.235.115.212:3000/zw/iOSAI
# Conflicts:
#	.idea/workspace.xml
2025-09-12 21:37:01 +08:00
aa2f291f49 20250904-初步功能已完成 2025-09-12 21:36:47 +08:00
f450226d2d Merge branch 'main' of http://49.235.115.212:3000/zw/iOSAI
# Conflicts:
#	.idea/workspace.xml
2025-09-12 13:46:31 +08:00
5732db2cc2 20250904-初步功能已完成 2025-09-12 13:46:11 +08:00
1653da2f37 Merge branch 'main' of http://49.235.115.212:3000/zw/iOSAI
# Conflicts:
#	.idea/workspace.xml
2025-09-12 13:12:44 +08:00
9eb2a82880 20250904-初步功能已完成 2025-09-12 13:12:35 +08:00
11 changed files with 265 additions and 99 deletions

1
.gitignore vendored
View File

@@ -20,7 +20,6 @@ var/
.installed.cfg .installed.cfg
*.egg *.egg
out/ out/
log/
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template

2
.idea/iOSAI.iml generated
View File

@@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 (IOS-AI)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.12" /> <option name="sdkName" value="Python 3.12" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (IOS-AI)" project-jdk-type="Python SDK" />
</project> </project>

93
.idea/workspace.xml generated
View File

@@ -6,10 +6,8 @@
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成"> <list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Module/FlaskService.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/FlaskService.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Utils/Requester.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/Requester.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/build-tidevice.bat" beforeDir="false" afterPath="$PROJECT_DIR$/build-tidevice.bat" afterDir="false" /> <change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/resources/iproxy/tidevice.exe" beforeDir="false" afterPath="$PROJECT_DIR$/resources/iproxy/tidevice.exe" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tidevice_entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/tidevice_entry.py" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -52,54 +50,39 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"ASKED_ADD_EXTERNAL_FILES": "true", &quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;,
"ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true", &quot;ASKED_MARK_IGNORED_FILES_AS_EXCLUDED&quot;: &quot;true&quot;,
"Python.12.executor": "Run", &quot;Python.12.executor&quot;: &quot;Run&quot;,
"Python.123.executor": "Run", &quot;Python.123.executor&quot;: &quot;Run&quot;,
"Python.Main.executor": "Run", &quot;Python.Main.executor&quot;: &quot;Run&quot;,
"Python.Test.executor": "Run", &quot;Python.Test.executor&quot;: &quot;Run&quot;,
"Python.tidevice_entry.executor": "Run", &quot;Python.tidevice_entry.executor&quot;: &quot;Run&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
"RunOnceActivity.git.unshallow": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"SHARE_PROJECT_CONFIGURATION_FILES": "true", &quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
"git-widget-placeholder": "main", &quot;git-widget-placeholder&quot;: &quot;main&quot;,
"javascript.nodejs.core.library.configured.version": "20.17.0", &quot;javascript.nodejs.core.library.configured.version&quot;: &quot;20.17.0&quot;,
"javascript.nodejs.core.library.typings.version": "20.17.58", &quot;javascript.nodejs.core.library.typings.version&quot;: &quot;20.17.58&quot;,
"last_opened_file_path": "F:/company code/AI item/20250820/iOSAI", &quot;last_opened_file_path&quot;: &quot;F:/company code/AI item/20250820/iOSAI&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"nodejs_package_manager_path": "npm", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"settings.editor.selected.configurable": "preferences.editor.code.editing", &quot;settings.editor.selected.configurable&quot;: &quot;preferences.editor.code.editing&quot;,
"vue.rearranger.settings.migration": "true" &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}]]></component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="E:\Code\python\iOSAI\resources" /> <recent name="E:\Code\python\iOSAI\resources" />
<recent name="E:\Code\python\iOSAI" /> <recent name="E:\Code\python\iOSAI" />
</key> </key>
</component> </component>
<component name="RunAnythingCache"> <component name="RunManager" selected="Python.123">
<myKeys>
<visibility group="Grunt" flag="true" />
<visibility group="Gulp" flag="true" />
<visibility group="HTTP 请求" flag="true" />
<visibility group="Node.js" flag="true" />
<visibility group="Run Python file" flag="true" />
<visibility group="Run conda command" flag="true" />
<visibility group="Run pip command" flag="true" />
<visibility group="npm" flag="true" />
<visibility group="yarn" flag="true" />
<visibility group="最近的项目" flag="true" />
<visibility group="运行配置" flag="true" />
</myKeys>
</component>
<component name="RunManager" selected="Python.Main">
<configuration name="12" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> <configuration name="12" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="iOSAI" /> <module name="iOSAI" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@@ -182,6 +165,7 @@
<option name="IS_MODULE_SDK" value="true" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Utils/Test.py" /> <option name="SCRIPT_NAME" value="$PROJECT_DIR$/Utils/Test.py" />
<option name="PARAMETERS" value="" /> <option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" /> <option name="SHOW_COMMAND_LINE" value="false" />
@@ -193,16 +177,17 @@
</configuration> </configuration>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Python.123" />
<item itemvalue="Python.Test" /> <item itemvalue="Python.Test" />
<item itemvalue="Python.12" /> <item itemvalue="Python.12" />
<item itemvalue="Python.123" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
<component name="SharedIndexes"> <component name="SharedIndexes">
<attachedChunks> <attachedChunks>
<set> <set>
<option value="bundled-python-sdk-ce6832f46686-7b97d883f26b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.25557.178" /> <option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-PY-241.18034.82" />
<option value="bundled-python-sdk-975db3bf15a3-2767605e8bc2-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.18034.82" />
</set> </set>
</attachedChunks> </attachedChunks>
</component> </component>
@@ -267,6 +252,7 @@
<workItem from="1757498954175" duration="6736000" /> <workItem from="1757498954175" duration="6736000" />
<workItem from="1757506636968" duration="5910000" /> <workItem from="1757506636968" duration="5910000" />
<workItem from="1757567423145" duration="16668000" /> <workItem from="1757567423145" duration="16668000" />
<workItem from="1757998910052" duration="3676000" />
</task> </task>
<task id="LOCAL-00001" summary="ai 开始测试"> <task id="LOCAL-00001" summary="ai 开始测试">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -336,15 +322,18 @@
</component> </component>
<component name="com.intellij.coverage.CoverageDataManagerImpl"> <component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/iOSAI$LogManager.coverage" NAME="LogManager 覆盖结果" MODIFIED="1756711414832" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" /> <SUITE FILE_PATH="coverage/iOSAI$LogManager.coverage" NAME="LogManager 覆盖结果" MODIFIED="1756711414832" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
<SUITE FILE_PATH="coverage/iOSAI$123__1_.coverage" NAME="123 (1) 覆盖结果" MODIFIED="1756897091135" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$FlaskService.coverage" NAME="FlaskService 覆盖结果" MODIFIED="1756730187792" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Module" /> <SUITE FILE_PATH="coverage/iOSAI$FlaskService.coverage" NAME="FlaskService 覆盖结果" MODIFIED="1756730187792" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Module" />
<SUITE FILE_PATH="coverage/iOSAI$test.coverage" NAME="test 覆盖结果" MODIFIED="1756467664420" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/iOSAI$test.coverage" NAME="test 覆盖结果" MODIFIED="1756467664420" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$windows_run.coverage" NAME="windows_run Coverage Results" MODIFIED="1756473558532" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" /> <SUITE FILE_PATH="coverage/iOSAI$windows_run.coverage" NAME="windows_run Coverage Results" MODIFIED="1756473558532" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
<SUITE FILE_PATH="coverage/iOSAI$tidevice_entry.coverage" NAME="tidevice_entry 覆盖结果" MODIFIED="1757061969626" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/iOSAI$1352.coverage" NAME="1352 覆盖结果" MODIFIED="1757662777051" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$mac_wda_agent.coverage" NAME="mac_wda_agent Coverage Results" MODIFIED="1756473148639" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" /> <SUITE FILE_PATH="coverage/iOSAI$mac_wda_agent.coverage" NAME="mac_wda_agent Coverage Results" MODIFIED="1756473148639" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
<SUITE FILE_PATH="coverage/iOSAI$ScriptManager.coverage" NAME="ScriptManager 覆盖结果" MODIFIED="1756896057801" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" /> <SUITE FILE_PATH="coverage/iOSAI$123456.coverage" NAME="123456 覆盖结果" MODIFIED="1757672582575" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1757579400023" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/iOSAI$123.coverage" NAME="123 覆盖结果" MODIFIED="1757587713569" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$2111.coverage" NAME="2111 覆盖结果" MODIFIED="1757330714370" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> <SUITE FILE_PATH="coverage/iOSAI$2111.coverage" NAME="2111 覆盖结果" MODIFIED="1757330714370" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$456.coverage" NAME="456 覆盖结果" MODIFIED="1757654671631" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$123__1_.coverage" NAME="123 (1) 覆盖结果" MODIFIED="1756897091135" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$tidevice_entry.coverage" NAME="tidevice_entry 覆盖结果" MODIFIED="1757061969626" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
<SUITE FILE_PATH="coverage/iOSAI$ScriptManager.coverage" NAME="ScriptManager 覆盖结果" MODIFIED="1756896057801" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
<SUITE FILE_PATH="coverage/iOSAI$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1758002271600" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/iOSAI$123.coverage" NAME="123 覆盖结果" MODIFIED="1758002344317" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
</component> </component>
</project> </project>

View File

@@ -34,6 +34,9 @@ class Deviceinfo(object):
self._port_pool: List[int] = [] # 端口回收池 self._port_pool: List[int] = [] # 端口回收池
self._port_in_use: set[int] = set() # 正在使用的端口 self._port_in_use: set[int] = set() # 正在使用的端口
# 🔥1. 启动 WDA 健康检查线程
# threading.Thread(target=self._wda_health_checker, daemon=True).start()
# region iproxy 初始化 # region iproxy 初始化
try: try:
self.iproxy_path = self._iproxy_path() self.iproxy_path = self._iproxy_path()
@@ -123,6 +126,41 @@ class Deviceinfo(object):
time.sleep(1) time.sleep(1)
# 🔥2. WDA 健康检查
def _wda_health_checker(self):
while True:
time.sleep(1)
print(len(self.deviceModelList))
with self._lock:
online = [m for m in self.deviceModelList if m.ready] # ← 只检查就绪的
print(len(online))
for model in online:
udid = model.deviceId
if not self._wda_ok(udid):
LogManager.warning(f"WDA 异常,重启通道:{udid}", udid)
with self._lock:
self._remove_model(udid)
self.connectDevice(udid)
# 🔥3. 真正做 health-check 的地方
def _wda_ok(self, udid: str) -> bool:
"""返回 True 表示 WDA 活着False 表示已死"""
try:
# 用 2 秒超时快速探测
c = wda.USBClient(udid, 8100)
# 下面这句就是“xctest launched but check failed” 的触发点
# 如果 status 里返回了 WebDriverAgent 运行信息就认为 OK
st = c.status()
if st.get("state") != "success":
return False
# 你也可以再苛刻一点,多做一次 /wda/healthcheck
# c.http.get("/wda/healthcheck")
return True
except Exception as e:
# 任何异常连接拒绝、超时、json 解析失败)都认为已死
LogManager.error(f"WDA health-check 异常:{e}", udid)
return False
# region ===================== 增删改查唯一入口(线程安全) ===================== # region ===================== 增删改查唯一入口(线程安全) =====================
def _has_model(self, udid: str) -> bool: def _has_model(self, udid: str) -> bool:
return udid in self._model_index return udid in self._model_index

View File

@@ -2,6 +2,7 @@ import json
import os import os
import socket import socket
import threading import threading
import time
from pathlib import Path from pathlib import Path
from queue import Queue from queue import Queue
from typing import Any, Dict from typing import Any, Dict
@@ -126,6 +127,7 @@ def _handle_conn(conn: socket.socket, addr):
listener_thread = threading.Thread(target=start_socket_listener, daemon=True) listener_thread = threading.Thread(target=start_socket_listener, daemon=True)
listener_thread.start() listener_thread.start()
# 获取设备列表 # 获取设备列表
@app.route('/deviceList', methods=['GET']) @app.route('/deviceList', methods=['GET'])
def deviceList(): def deviceList():
@@ -397,6 +399,7 @@ def queryAnchorList():
data = [] data = []
return ResultData(data=data).toJson() return ResultData(data=data).toJson()
# 删除主播 # 删除主播
@app.route("/deleteAnchorWithIds", methods=['POST']) @app.route("/deleteAnchorWithIds", methods=['POST'])
def deleteAnchorWithIds(): def deleteAnchorWithIds():
@@ -405,6 +408,7 @@ def deleteAnchorWithIds():
deleted = AiUtils.delete_anchors_by_ids(ids) deleted = AiUtils.delete_anchors_by_ids(ids)
return ResultData(data={"deleted": deleted}).toJson() return ResultData(data={"deleted": deleted}).toJson()
@app.route("/aiConfig", methods=['POST']) @app.route("/aiConfig", methods=['POST'])
def aiConfig(): def aiConfig():
data = request.get_json() data = request.get_json()
@@ -423,6 +427,7 @@ def aiConfig():
JsonUtils.write_json("aiConfig", dict) JsonUtils.write_json("aiConfig", dict)
return ResultData(data="").toJson() return ResultData(data="").toJson()
# 查询主播聊天发送的最后一条信息 # 查询主播聊天发送的最后一条信息
@app.route("/select_last_message", methods=['GET']) @app.route("/select_last_message", methods=['GET'])
def select_last_message(): def select_last_message():
@@ -466,5 +471,19 @@ def delete_last_message():
return ResultData(data=updated_count, msg="修改失败").toJson() return ResultData(data=updated_count, msg="修改失败").toJson()
# @app.route("/killWda", methods=['POST'])
# def killWda():
# data = request.get_json() # 解析 JSON
# udid = data.get("device")
# print(udid)
#
#
# AiUtils.kill_wda(udid)
# time.sleep(10)
# AiUtils.launch_wda(udid)
#
# return ResultData(data="", msg="WDA重新启动").toJson()
if __name__ == '__main__': if __name__ == '__main__':
app.run("0.0.0.0", port=5000, debug=True, use_reloader=False) app.run("0.0.0.0", port=5000, debug=True, use_reloader=False)

View File

@@ -1,11 +1,16 @@
import json import json
import os import os
import shlex
import subprocess
import time
from pathlib import Path from pathlib import Path
import cv2 import cv2
import numpy as np import numpy as np
import unicodedata import unicodedata
import wda import wda
from Entity.Variables import WdaAppBundleId
from Utils.LogManager import LogManager from Utils.LogManager import LogManager
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import re, html import re, html
@@ -242,6 +247,7 @@ class AiUtils(object):
@classmethod @classmethod
def getFollowButton(cls, session: Client): def getFollowButton(cls, session: Client):
# followButton = session.xpath("//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[2]/Other[1]/Other[1]/Other[3]/Other[1]/Other[1]/Button[1]") # followButton = session.xpath("//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[2]/Other[1]/Other[1]/Other[3]/Other[1]/Other[1]/Button[1]")
session.appium_settings({"snapshotMaxDepth": 20})
followButton = session.xpath('//XCUIElementTypeButton[@name="关注" or @label="关注"]') followButton = session.xpath('//XCUIElementTypeButton[@name="关注" or @label="关注"]')
@@ -709,4 +715,72 @@ class AiUtils(object):
json.dump(data, f, ensure_ascii=False, indent=2) json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e: except Exception as e:
LogManager.error(f"[delete_anchors_by_ids] 写入失败: {e}") LogManager.error(f"[delete_anchors_by_ids] 写入失败: {e}")
return deleted return deleted
@staticmethod
def run_tidevice_command(udid, action, bundle_id, timeout=30):
"""
执行tidevice命令的辅助函数
:param udid: 设备UDID
:param action: 动作类型 ('kill''launch')
:param bundle_id: 应用的Bundle ID
:param timeout: 命令执行超时时间(秒)
:return: (bool) 成功返回True失败返回False
"""
# 构建命令列表
cmd = ["tidevice", "--udid", udid, action, bundle_id]
try:
# 执行命令并捕获输出
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout
)
# 检查命令是否成功执行返回码为0通常表示成功
if result.returncode == 0:
LogManager.info(f"Successfully {action}ed {bundle_id} on device {udid}.")
return True
else:
# 记录错误信息
LogManager.error(f"Failed to {action} {bundle_id} on device {udid}. Error: {result.stderr}")
return False
except subprocess.TimeoutExpired:
# 处理命令执行超时
LogManager.error(f"Command 'tidevice {action}' timed out after {timeout} seconds for device {udid}.")
return False
except FileNotFoundError:
# 处理tidevice命令未找到的情况通常意味着tidevice未安装或不在PATH中
LogManager.error("The 'tidevice' command was not found. Please ensure it is installed and in your system PATH.")
return False
except Exception as e:
# 捕获其他可能异常
LogManager.error(f"An unexpected error occurred while trying to {action} the app: {e}")
return False
@classmethod
def kill_wda(cls, udid, bundle_id="com.yolozsAgent.wda.xctrunner"):
"""
杀死指定设备上的WDA应用
:param udid: 设备UDID
:param bundle_id: WDA的Bundle ID默认为 com.yolozsAgent.wda.xctrunner
:return: (bool) 成功返回True失败返回False
"""
return cls.run_tidevice_command(udid, "kill", bundle_id)
@classmethod
def launch_wda(cls, udid, bundle_id="com.yolozsAgent.wda.xctrunner", timeout=60):
"""
启动指定设备上的WDA应用
:param udid: 设备UDID
:param bundle_id: WDA的Bundle ID默认为 com.yolozsAgent.wda.xctrunner
:param timeout: 启动命令超时时间默认为60秒启动可能较慢
:return: (bool) 成功返回True失败返回False
"""
return cls.run_tidevice_command(udid, "launch", bundle_id, timeout)

View File

@@ -179,17 +179,31 @@ class JsonUtils:
return updated return updated
# @classmethod
# def query_all_json_items(cls, filename="log/last_message.json") -> list:
# """
# 查询 JSON 文件(数组)中的所有项
# :param filename: JSON 文件路径
# :return: list可能为空
# """
# file_path = Path(filename)
# print(file_path)
# data = cls._read_json_list(file_path)
# return data if isinstance(data, list) else []
@classmethod @classmethod
def query_all_json_items(cls, filename="log/last_message.json") -> list: def query_all_json_items(cls, filename="log/last_message.json") -> list:
""" """
查询 JSON 文件(数组)中的所有项 查询 JSON 文件(数组)中的所有项,并剔除 sender 为空的记录
:param filename: JSON 文件路径 :param filename: JSON 文件路径
:return: list可能为空 :return: list可能为空
""" """
file_path = Path(filename) file_path = Path(filename)
print(file_path)
data = cls._read_json_list(file_path) data = cls._read_json_list(file_path)
return data if isinstance(data, list) else [] if not isinstance(data, list):
return []
# 过滤 sender 为空字符串的项
return [item for item in data if isinstance(item, dict) and item.get("sender", "").strip()]
@classmethod @classmethod
def delete_json_items(cls, def delete_json_items(cls,

View File

@@ -240,7 +240,7 @@ def _force_utf8_everywhere():
except Exception: except Exception:
pass pass
# _force_utf8_everywhere() _force_utf8_everywhere()
# ========= 全局:强制 UTF-8 + 关闭缓冲(运行期立刻生效) ========= # ========= 全局:强制 UTF-8 + 关闭缓冲(运行期立刻生效) =========

View File

@@ -29,15 +29,21 @@ class Requester():
@classmethod @classmethod
def translation(cls, msg, country="英国"): def translation(cls, msg, country="英国"):
try: try:
parame = { param = {
"msg": msg, "msg": msg,
"country": country, "country": country,
} }
url = "https://ai.yolozs.com/translation" url = "https://ai.yolozs.com/translation"
result = requests.request(url=url, json=parame, method="POST") result = requests.post(url=url, json=param)
LogManager.info(f"翻译,状态码:{result.status_code},服务器返回的内容:{result.text}")
if result.status_code != 200:
LogManager.error(f"翻译失败,状态码:{result.status_code},服务器返回的内容:{result.text}")
return None
json = result.json() json = result.json()
data = json.get("data") data = json.get("data")
print(data)
return data return data
except Exception as e: except Exception as e:
LogManager.method_error(f"翻译失败,报错的原因:{e}", "翻译失败异常") LogManager.method_error(f"翻译失败,报错的原因:{e}", "翻译失败异常")
@@ -52,19 +58,24 @@ class Requester():
contact = aiConfig.get("contact", "") contact = aiConfig.get("contact", "")
inputs = { inputs = {
"agentName":agentName, "name": agentName,
"guildName":guildName, "Trade_union": guildName,
"contactTool":contactTool, "contcat_method": contactTool,
"contact":contact "contcat_info": contact
} }
param["inputs"] = inputs param["inputs"] = inputs
print(param)
try: try:
url = "https://ai.yolozs.com/chat" url = "https://ai.yolozs.com/chat"
result = requests.request(url=url, json=param, method="POST") result = requests.post(url=url, json=param)
json = result.json() json = result.json()
data = json.get("data", {}) data = json.get("answer", {})
return data session_id = json.get("conversation_id", {})
LogManager.method_info(f"ai聊天的参数{param},ai聊天返回的内容{result.json()}", "ai聊天")
return data, session_id
except Exception as e: except Exception as e:
LogManager.method_error(f"ai聊天失败,ai聊天出现异常,报错的原因:{e}", "ai聊天接口异常") LogManager.method_error(f"ai聊天失败,ai聊天出现异常,报错的原因:{e}", "ai聊天接口异常")

View File

@@ -278,14 +278,12 @@ class ScriptManager():
retries = 0 retries = 0
while not event.is_set(): while not event.is_set():
try: try:
# AiUtils.pop_aclist_first() # AiUtils.pop_aclist_first()
# anchor = AiUtils.pop_aclist_first() # anchor = AiUtils.pop_aclist_first()
# if not anchor: # if not anchor:
# break # break
self.greetNewFollowers(udid, needReply, event) self.greetNewFollowers(udid, needReply, event)
except Exception as e: except Exception as e:
retries += 1 retries += 1
LogManager.method_error(f"greetNewFollowers 出现异常: {e},准备第 {retries} 次重试", "关注打招呼", udid) LogManager.method_error(f"greetNewFollowers 出现异常: {e},准备第 {retries} 次重试", "关注打招呼", udid)
@@ -309,7 +307,7 @@ class ScriptManager():
# 重新打开Tik Tok # 重新打开Tik Tok
ControlUtils.openTikTok(session, udid) ControlUtils.openTikTok(session, udid)
time.sleep(3) time.sleep(3)
LogManager.method_info(f"重启tiktok", "关注打招呼", udid)
# 设置查找深度 # 设置查找深度
session.appium_settings({"snapshotMaxDepth": 15}) session.appium_settings({"snapshotMaxDepth": 15})
@@ -319,6 +317,9 @@ class ScriptManager():
# 返回上一步 # 返回上一步
def goBack(count): def goBack(count):
for i in range(count): for i in range(count):
LogManager.method_info(f"返回上一步", "关注打招呼", udid)
session.appium_settings({"snapshotMaxDepth": 15})
ControlUtils.clickBack(session) ControlUtils.clickBack(session)
time.sleep(2) time.sleep(2)
@@ -339,9 +340,13 @@ class ScriptManager():
aid = anchor["anchorId"] aid = anchor["anchorId"]
anchorCountry = anchor.get("country", "") anchorCountry = anchor.get("country", "")
LogManager.method_info(f"主播的数据,用户名:{aid},国家:{anchorCountry}", "关注打招呼", udid)
# 点击搜索按钮 # 点击搜索按钮
ControlUtils.clickSearch(session) ControlUtils.clickSearch(session)
LogManager.method_info(f"点击搜索按钮", "关注打招呼", udid)
# 强制刷新session # 强制刷新session
session.appium_settings({"snapshotMaxDepth": 15}) session.appium_settings({"snapshotMaxDepth": 15})
@@ -366,21 +371,22 @@ class ScriptManager():
session.appium_settings({"snapshotMaxDepth": 23}) session.appium_settings({"snapshotMaxDepth": 23})
try: try:
# 点击关注按钮 # 点击进入首页
ControlUtils.clickFollow(session, aid) ControlUtils.clickFollow(session, aid)
LogManager.method_info("点击进入主播首页", "关注打招呼", udid)
except wda.WDAElementNotFoundError: except wda.WDAElementNotFoundError:
# 如果没有“关注”按钮,则不点击 LogManager.method_info("未找到进入主播首页的按钮,跳过点击。", "关注打招呼", udid)
print("未找到‘关注’按钮,跳过点击。")
LogManager.method_info("未找到‘关注’按钮,跳过点击。", "关注打招呼", udid)
goBack(2) goBack(2)
session.appium_settings({"snapshotMaxDepth": 15}) session.appium_settings({"snapshotMaxDepth": 15})
continue continue
time.sleep(2) time.sleep(2)
# 找到并点击第一个视频 # 找到并点击第一个视频
cellClickResult, workCount = ControlUtils.clickFirstVideoFromDetailPage(session) cellClickResult, workCount = ControlUtils.clickFirstVideoFromDetailPage(session)
LogManager.method_info(f"点击第一个视频", "关注打招呼", udid)
time.sleep(2) time.sleep(2)
# 观看主播视频 # 观看主播视频
@@ -475,9 +481,10 @@ class ScriptManager():
if isContainChniese: if isContainChniese:
# 翻译成主播国家的语言 # 翻译成主播国家的语言
LogManager.method_info(f"需要翻译:{text}, 即将进行翻译", "关注打招呼", udid) LogManager.method_info(f"需要翻译:{text},参数为:国家为{anchorCountry}, 即将进行翻译", "关注打招呼", udid)
msg = Requester.translation(text, anchorCountry) msg = Requester.translation(text, anchorCountry)
LogManager.method_info(f"翻译成功:{msg}, ", "关注打招呼", udid) LogManager.method_info(f"翻译成功:{msg}, ", "关注打招呼", udid)
else: else:
@@ -503,16 +510,16 @@ class ScriptManager():
goBack(1) goBack(1)
# 点击关注按钮 # 点击关注按钮
followButton = AiUtils.getFollowButton(session) # followButton = AiUtils.getFollowButton(session)
if followButton is not None: # if followButton is not None:
LogManager.method_info("找到关注按钮了", "关注打招呼", udid) # LogManager.method_info("找到关注按钮了", "关注打招呼", udid)
followButton.click() # followButton.click()
else: # else:
LogManager.method_info("没找到关注按钮", "关注打招呼", udid) # LogManager.method_info("没找到关注按钮", "关注打招呼", udid)
time.sleep(1) # time.sleep(1)
goBack(4) # goBack(4)
session.appium_settings({"snapshotMaxDepth": 15}) # session.appium_settings({"snapshotMaxDepth": 15})
continue # continue
session.appium_settings({"snapshotMaxDepth": 15}) session.appium_settings({"snapshotMaxDepth": 15})
goBack(3) goBack(3)
@@ -719,18 +726,30 @@ class ScriptManager():
last_msg = next((item['text'] for item in reversed(msgs) if item['type'] == 'msg'), last_msg = next((item['text'] for item in reversed(msgs) if item['type'] == 'msg'),
random.choice(text_list)) random.choice(text_list))
LogManager.method_info(f"检测到对方最后发送的消息:{last_msg}", "检测消息", udid)
isLanguage = AiUtils.is_language(last_msg) isLanguage = AiUtils.is_language(last_msg)
if isLanguage: if isLanguage:
# LogManager.method_info(f"{last_msg}", "检测消息", udid)
last_msg_text = last_msg last_msg_text = last_msg
else: else:
LogManager.method_info(f"对方发送的消息不是语言,随机挑选作为最后一条进行回复:{last_msg}", "检测消息", udid)
last_msg_text = random.choice(text_list) last_msg_text = random.choice(text_list)
# 向ai发送信息 if AiUtils.contains_chinese(last_msg_text):
LogManager.method_info(f"需要翻译:{last_msg_text}, 即将进行翻译", "检测消息", udid)
last_msg_text = Requester.translation(last_msg_text)
LogManager.method_info(f"翻译成功:{last_msg_text}, ", "检测消息", udid)
# 向ai发送信息
# 获取主播的名称 # 获取主播的名称
anchor_name = AiUtils.get_navbar_anchor_name(session) anchor_name = AiUtils.get_navbar_anchor_name(session)
LogManager.method_info(f"获取主播的名称:{anchor_name}", "检测消息", udid) LogManager.method_info(f"获取主播的名称:{anchor_name}", "检测消息", udid)
LogManager.method_info(f"获取主播最后发送的消息 进行翻译:{last_msg}", "检测消息", udid)
last_msg = Requester.translation(last_msg, "中国")
LogManager.method_info(f"翻译后的内容:{last_msg}", "检测消息", udid)
# 找到输入框 # 找到输入框
last_data = [{ last_data = [{
@@ -741,33 +760,36 @@ class ScriptManager():
}] }]
print(last_data) print(last_data)
LogManager.method_info(f"主播最后发送的数据:{last_data}", "检测消息", udid) LogManager.method_info(f"主播最后发送的数据,传递给前端进行记录:{last_data}", "检测消息", udid)
JsonUtils.append_json_items(last_data, "log/last_message.json") JsonUtils.append_json_items(last_data, "log/last_message.json")
sel = session.xpath("//TextView")
sel = session.xpath("//TextView")
if anchor_name not in anchorWithSession: if anchor_name not in anchorWithSession:
# 如果是第一次发消息(没有sessionId的情况) # 如果是第一次发消息(没有sessionId的情况)
LogManager.method_info(f"第一次发消息:{anchor_name},没有记忆", "检测消息", udid)
response = Requester.chatToAi({"query": last_msg_text}) aiResult, sessionId = Requester.chatToAi({"query": last_msg_text, "user": "1"})
aiResult = response['result']
sessionId = response['session_id']
anchorWithSession[anchor_name] = sessionId anchorWithSession[anchor_name] = sessionId
# 找到输入框输入ai返回出来的消息 # 找到输入框输入ai返回出来的消息
if sel.exists: if sel.exists:
sel.click() # 聚焦 sel.click() # 聚焦
time.sleep(1) time.sleep(1)
sel.clear_text() sel.clear_text()
sel.set_text(f"{aiResult or '暂无数据'}\n") sel.set_text(f"{aiResult or '暂无数据'}\n")
else: else:
LogManager.method_error("找不到输入框,重启", "检测消息", udid) LogManager.method_error("找不到输入框,重启", "检测消息", udid)
raise Exception("找不到输入框,重启") raise Exception("找不到输入框,重启")
else: else:
LogManager.method_info(f"不是一次发消息:{anchor_name},有记忆", "检测消息", udid)
# 如果不是第一次发消息证明存储的有sessionId # 如果不是第一次发消息证明存储的有sessionId
sessionId = anchorWithSession[anchor_name] sessionId = anchorWithSession[anchor_name]
response = Requester.chatToAi({"query": last_msg_text, "conversation_id": sessionId})
aiResult = response['result'] # TODO: user后续添加暂时写死
aiResult, sessionId = Requester.chatToAi(
{"query": last_msg_text, "conversation_id": sessionId, "user": "1"})
# aiResult = response['result']
if sel.exists: if sel.exists:
sel.click() # 聚焦 sel.click() # 聚焦
time.sleep(1) time.sleep(1)