临时提交

This commit is contained in:
2025-09-20 21:57:37 +08:00
parent bfdf684952
commit 8f290cf610
25 changed files with 343 additions and 87 deletions

147
.idea/workspace.xml generated
View File

@@ -5,11 +5,45 @@
</component>
<component name="ChangeListManager">
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成">
<change afterPath="$PROJECT_DIR$/Module/log/last_message.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Utils/IOSAIStorage.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/resources/2335890f77fb51322361bd46b85f7fd1311aed53/bgv.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/resources/3157d8bd35c230012cb3640d2f26ffd0b61a10d1/bgv.png" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/git_toolbox_blame.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/git_toolbox_blame.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/git_toolbox_prj.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/git_toolbox_prj.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/iOSAI.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/iOSAI.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/jsLibraryMappings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/jsLibraryMappings.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Entity/AnchorModel.py" beforeDir="false" afterPath="$PROJECT_DIR$/Entity/AnchorModel.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Entity/DeviceModel.py" beforeDir="false" afterPath="$PROJECT_DIR$/Entity/DeviceModel.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Entity/ResultData.py" beforeDir="false" afterPath="$PROJECT_DIR$/Entity/ResultData.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Entity/Variables.py" beforeDir="false" afterPath="$PROJECT_DIR$/Entity/Variables.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Module/DeviceInfo.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/DeviceInfo.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Module/FlaskService.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/FlaskService.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Module/FlaskSubprocessManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/FlaskSubprocessManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Module/Main.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/Main.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/AiUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/AiUtils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/ControlUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/ControlUtils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/DevDiskImageDeployer.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/DevDiskImageDeployer.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/JsonUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/JsonUtils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/LogManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/LogManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/Requester.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/Requester.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/SubprocessKit.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/SubprocessKit.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Utils/ThreadManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/ThreadManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/build-tidevice.bat" beforeDir="false" afterPath="$PROJECT_DIR$/build-tidevice.bat" afterDir="false" />
<change beforePath="$PROJECT_DIR$/build.bat" beforeDir="false" afterPath="$PROJECT_DIR$/build.bat" afterDir="false" />
<change beforePath="$PROJECT_DIR$/requirements.txt" beforeDir="false" afterPath="$PROJECT_DIR$/requirements.txt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/script/mac_wda_agent.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/mac_wda_agent.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/script/windows_run.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/windows_run.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tidevice_entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/tidevice_entry.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -60,7 +94,6 @@
"Python.123.executor": "Run",
"Python.Main.executor": "Run",
"Python.Test.executor": "Run",
"Python.test (1).executor": "Run",
"Python.test.executor": "Run",
"Python.tidevice_entry.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
@@ -70,19 +103,20 @@
"git-widget-placeholder": "main",
"javascript.nodejs.core.library.configured.version": "20.17.0",
"javascript.nodejs.core.library.typings.version": "20.17.58",
"last_opened_file_path": "C:/Users/zhangkai/Desktop/20250916ios/iOSAI/resources",
"last_opened_file_path": "C:/Users/zhangkai/Desktop/last-item/Storage",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"settings.editor.selected.configurable": "org.jetbrains.plugins.gitlab.GitLabSettingsConfigurable",
"two.files.diff.last.used.file": "E:/share/iOSAI/Module/FlaskService.py",
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\zhangkai\Desktop\last-item\iosai\Utils" />
<recent name="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\resources" />
</key>
<key name="MoveFile.RECENT_KEYS">
@@ -90,7 +124,53 @@
<recent name="E:\Code\python\iOSAI" />
</key>
</component>
<component name="RunManager">
<component name="RunManager" selected="Python.Main">
<configuration name="12" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="iOSAI" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/12.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="123" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="iOSAI" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/123.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="Main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="iOSAI" />
<option name="ENV_FILES" value="" />
@@ -100,11 +180,11 @@
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="SDK_NAME" value="Python 3.12 (iOSAI)" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Module/Main.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
@@ -114,6 +194,60 @@
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="Test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="iOSAI" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Utils" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_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="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="iOSAI" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Utils" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_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="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python.test" />
<item itemvalue="Python.123" />
<item itemvalue="Python.Test" />
<item itemvalue="Python.12" />
</list>
</recent_temporary>
</component>
<component name="SharedIndexes">
<attachedChunks>
@@ -184,6 +318,8 @@
<workItem from="1757506636968" duration="5910000" />
<workItem from="1757567423145" duration="16668000" />
<workItem from="1757998910052" duration="3676000" />
<workItem from="1758354349526" duration="2512000" />
<workItem from="1758358259572" duration="8994000" />
</task>
<task id="LOCAL-00001" summary="ai 开始测试">
<option name="closed" value="true" />
@@ -263,6 +399,7 @@
<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$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1758369933536" 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$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="1758120400301" 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="1758115088356" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />

View File

@@ -9,6 +9,7 @@ from typing import Any, Dict
from Entity.AnchorModel import AnchorModel
from Utils.AiUtils import AiUtils
from Utils.IOSAIStorage import IOSAIStorage
from Utils.LogManager import LogManager
import tidevice
import wda
@@ -244,8 +245,8 @@ def growAccount():
thread = threading.Thread(target=manager.growAccount, args=(udid, event))
thread.start()
# 添加到线程管理
code , msg = ThreadManager.add(udid, thread, event)
return ResultData(data="", code=code, message= msg).toJson()
code, msg = ThreadManager.add(udid, thread, event)
return ResultData(data="", code=code, message=msg).toJson()
# 观看直播
@@ -377,6 +378,7 @@ def monitorMessages():
return ResultData(data="").toJson()
# 上传日志
@app.route("/setLoginInfo", methods=['POST'])
def upLoadLogLogs():
data = request.get_json() # 解析 JSON
@@ -418,7 +420,7 @@ def updateAnchorList():
"""
data = request.get_json(force=True, silent=True) or {}
invitationType = data.get("invitationType")
state = bool(data.get("state")) # 转成布尔
state = bool(data.get("state")) # 转成布尔
# 要更新成的值
new_status = 1 if state else 0
@@ -474,6 +476,7 @@ def deleteAnchorWithIds():
return ResultData(data={"deleted": deleted}).toJson()
# 配置ai人设
@app.route("/aiConfig", methods=['POST'])
def aiConfig():
data = request.get_json()
@@ -489,7 +492,8 @@ def aiConfig():
"contact": contact
}
JsonUtils.write_json("aiConfig", dict)
# JsonUtils.write_json("aiConfig", dict)
IOSAIStorage.overwrite(dict, "aiConfig.json")
return ResultData(data="").toJson()
@@ -500,6 +504,7 @@ def select_last_message():
return ResultData(data=data).toJson()
# 修改消息(已读改成未读)
@app.route("/update_last_message", methods=['POST'])
def update_last_message():
data = request.get_json() # 解析 JSON
@@ -518,6 +523,7 @@ def update_last_message():
return ResultData(data=updated_count, message="修改失败").toJson()
# 删除已读消息
@app.route("/delete_last_message", methods=['POST'])
def delete_last_message():
data = request.get_json() # 解析 JSON
@@ -534,13 +540,15 @@ def delete_last_message():
return ResultData(data=updated_count, message="修改成功").toJson()
return ResultData(data=updated_count, message="修改失败").toJson()
#的停止所有任务
# 的停止所有任务
@app.route("/stopAllTask", methods=['POST'])
def stopAllTask():
idList = request.get_json()
code, data , msg = ThreadManager.batch_stop(idList)
code, data, msg = ThreadManager.batch_stop(idList)
return ResultData(code, data, msg).toJson()
# @app.route("/killWda", methods=['POST'])
# def killWda():
# data = request.get_json() # 解析 JSON

View File

@@ -336,6 +336,7 @@ class AiUtils(object):
screen_h = cls.parse_float(app[0], 'height', 736.0) if app else 736.0
# ---------- 主容器探测(评分选择最像聊天区的容器) ----------
def pick_container():
cands = []
for xp, ctype in (
@@ -706,7 +707,7 @@ class AiUtils(object):
data.extend(to_add)
cls._write_json_list(file_path, data)
LogManager.method_info(f"写入的路径是:{file_path}", "写入数据")
# LogManager.method_info(f"写入的路径是:{file_path}", "写入数据")
LogManager.info(f"[acList] 已追加 {len(to_add)} 条,当前总数={len(data)} -> {file_path}")
@@ -842,24 +843,25 @@ class AiUtils(object):
ids.append(str(it["anchorId"]))
return ids
@classmethod
def delete_anchors_by_ids(cls, ids: list[str], filename="log/acList.json") -> int:
def delete_anchors_by_ids(cls, ids: list[str], filename: str = "acList.json") -> int:
"""
根据 anchorId 列表根目录/log/acList.json 中删除匹配的 anchor。
返回删除数量。
根据 anchorId 列表,从项目根目录/log/acList.json 中删除匹配的 anchor。
- ids: 要删除的 anchorId 列表
- filename: 默认为 acList.json可以传文件名或绝对路径
返回:删除数量
"""
# 确保路径固定在根目录下的 log 文件夹
# 计算文件路径
root_dir = Path(__file__).resolve().parent.parent
file_path = root_dir / "log" / filename
if Path(filename).is_absolute():
file_path = Path(filename)
else:
file_path = root_dir / "log" / filename
if not file_path.exists():
return 0
# 读取 JSON 文件
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
@@ -870,10 +872,12 @@ class AiUtils(object):
return 0
before = len(data)
# 保留不在 ids 里的对象
data = [d for d in data if isinstance(d, dict) and d.get("anchorId") not in ids]
deleted = before - len(data)
# 写回 JSON 文件
try:
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "w", encoding="utf-8") as f:

66
Utils/IOSAIStorage.py Normal file
View File

@@ -0,0 +1,66 @@
import json
from pathlib import Path
class IOSAIStorage:
@staticmethod
def _get_iosai_dir() -> Path:
"""获取 C:/Users/<用户名>/IOSAI/ 目录"""
user_dir = Path.home()
iosai_dir = user_dir / "IOSAI"
iosai_dir.mkdir(parents=True, exist_ok=True)
return iosai_dir
@classmethod
def save(cls, data: dict | list, filename: str = "data.json") -> Path:
"""
存储数据到 C:/Users/<用户名>/IOSAI/filename
"""
file_path = cls._get_iosai_dir() / filename
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"[IOSAIStorage] 已保存到: {file_path}")
except Exception as e:
print(f"[IOSAIStorage] 写入失败: {e}")
return file_path
@classmethod
def load(cls, filename: str = "data.json") -> dict | list | None:
"""
从 C:/Users/<用户名>/IOSAI/filename 读取数据
"""
file_path = cls._get_iosai_dir() / filename
if not file_path.exists():
print(f"[IOSAIStorage] 文件不存在: {file_path}")
return {}
try:
with open(file_path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
print(f"[IOSAIStorage] 读取失败: {e}")
return {}
@classmethod
def overwrite(cls, data: dict | list, filename: str = "data.json") -> Path:
"""
强制覆盖写入数据到 C:/Users/<用户名>/IOSAI/filename
(无论是否存在,都会写入)
"""
file_path = cls._get_iosai_dir() / filename
try:
# "w" 模式本身就是覆盖,但这里单独做一个方法
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"[IOSAIStorage] 已覆盖写入: {file_path}")
except Exception as e:
print(f"[IOSAIStorage] 覆盖失败: {e}")
return file_path

View File

@@ -142,7 +142,7 @@ class JsonUtils:
data.extend(items)
LogManager.method_info(filename,"路径")
# LogManager.method_info(filename,"路径")
cls._write_json_list(file_path, data)

View File

@@ -1,5 +1,6 @@
import requests
from Entity.Variables import prologueList
from Utils.IOSAIStorage import IOSAIStorage
from Utils.JsonUtils import JsonUtils
from Utils.LogManager import LogManager
@@ -56,6 +57,7 @@ class Requester():
@classmethod
def chatToAi(cls, param):
aiConfig = JsonUtils.read_json("aiConfig")
aiConfig = IOSAIStorage.load("aiConfig.json")
agentName = aiConfig.get("agentName")
guildName = aiConfig.get("guildName")
contactTool = aiConfig.get("contactTool", "")

View File

@@ -67,13 +67,6 @@ class ThreadManager:
@classmethod
def batch_stop(cls, udids: List[str]) -> Tuple[int, List[str], str]:
"""
批量停止任务——瞬间下发停止信号,仍统计失败结果。
返回格式与原接口 100% 兼容:
(code, fail_list, msg)
code=200 全部成功
code=1001 部分/全部失败
"""
if not udids:
return 200, [], "无设备需要停止"
@@ -83,48 +76,53 @@ class ThreadManager:
with cls._lock:
for udid in udids:
task = cls._tasks.get(udid)
if not task or not task.get("running"):
# fail_list.append(f"设备{udid}停止失败:当前设备没有执行相关任务")
continue
task["event"].set() # 下发停止信号
if task and task.get("running"):
task["event"].set()
# ---------- 2. 并发等 0.2 s 收尾 ----------
def _wait_and_clean(udid: str) -> Tuple[int, str]:
with cls._lock:
task = cls._tasks.get(udid)
if not task:
return 400, "任务记录已丢失"
thread = task["thread"]
# 第一次等 3 秒,让“分片睡眠”有机会退出
thread.join(timeout=3)
# 如果还活,再补 2 秒
if thread.is_alive():
thread.join(timeout=2)
# 最终仍活,记录日志但不硬杀,避免僵尸
with cls._lock:
cls._tasks.pop(udid, None)
if thread.is_alive():
LogManager.warning(f"[batch_stop] 线程 5s 未退出,已清理记录但线程仍跑 {udid}")
return 201, "已下发停止,线程超长任务未立即结束"
return 200, "已停止"
# ---------- 2. 单设备重试 5 次 ----------
def _wait_and_clean_with_retry(udid: str) -> Tuple[int, List[str], str]:
"""
内部重试 5 次,最终返回格式与 batch_stop 完全一致:
(code, fail_list, msg) fail_list 空表示成功
"""
for attempt in range(1, 6):
with cls._lock:
task = cls._tasks.get(udid)
if not task:
return 200, [], "任务已不存在" # 成功
thread = task["thread"]
thread.join(timeout=5)
still_alive = thread.is_alive()
# 最后一次重试才清理记录
if attempt == 5:
with cls._lock:
cls._tasks.pop(udid, None)
if not still_alive:
return 200, [], "已停止"
LogManager.warning(f"[batch_stop] {udid}{attempt}/5 次停止未立即结束,重试", udid)
# 5 次都失败
LogManager.error(f"[batch_stop] {udid} 停止失败5 次重试后线程仍活)", udid)
return 1001, [udid], "部分设备停止失败"
# ---------- 3. 并发执行 ----------
with ThreadPoolExecutor(max_workers=min(32, len(udids))) as executor:
future_map = {executor.submit(_wait_and_clean, udid): udid for udid in udids}
future_map = {executor.submit(_wait_and_clean_with_retry, udid): udid for udid in udids}
for future in as_completed(future_map):
udid = future_map[future]
try:
code, reason = future.result()
if code != 200:
fail_list.append(f"设备{udid}停止失败:{reason}")
except Exception as exc:
fail_list.append(f"设备{udid}停止异常:{exc}")
code, sub_fail_list, sub_msg = future.result() # ← 现在解包正确
if code != 200:
fail_list.extend(sub_fail_list) # 收集失败 udid
# ---------- 3. 返回兼容格式 ----------
# ---------- 4. 返回兼容格式 ----------
if fail_list:
return 1001, fail_list, "部分设备停止失败"
return 200, [], "全部设备停止成功"
@classmethod
def is_task_running(cls, udid: str) -> bool:
"""

View File

@@ -7,6 +7,7 @@ import wda
import os
from Utils.AiUtils import AiUtils
from Utils.ControlUtils import ControlUtils
from Utils.IOSAIStorage import IOSAIStorage
from Utils.JsonUtils import JsonUtils
from Utils.LogManager import LogManager
from Entity.Variables import anchorList, removeModelFromAnchorList, anchorWithSession
@@ -105,7 +106,13 @@ class ScriptManager():
if homeButton:
LogManager.method_info("有首页按钮,查看视频", "养号", udid)
videoTime = random.randint(5, 15)
event.wait(timeout=videoTime)
LogManager.method_info("准备停止脚本", method="task")
for _ in range(videoTime): # 0.2 秒一片
if event.is_set():
LogManager.method_info("停止脚本中", method="task")
break
event.wait(timeout=1)
LogManager.method_info("停止脚本成功", method="task")
# 重置 session
client = wda.USBClient(udid)
@@ -118,7 +125,10 @@ class ScriptManager():
LogManager.method_info("继续观看视频", "养号", udid)
videoTime = random.randint(10, 30)
event.wait(timeout=videoTime)
for _ in range(videoTime):
if event.is_set():
break
event.wait(timeout=1)
LogManager.method_info("准备划到下一个视频", "养号", udid)
client.swipe_up()
@@ -126,7 +136,10 @@ class ScriptManager():
LogManager.method_error("找不到首页按钮。出错了", "养号", udid)
else:
nextTime = random.randint(1, 5)
event.wait(timeout=nextTime)
for _ in range(nextTime):
if event.is_set():
break
event.wait(timeout=1)
client.swipe_up()
except Exception as e:
@@ -168,7 +181,10 @@ class ScriptManager():
# 抛出异常
raise Exception(f"找不到直播按钮,抛出异常 重新启动")
waitTime = random.randint(15, 20)
event.wait(timeout=waitTime)
for _ in range(waitTime): # 0.2 秒一片
if event.is_set():
break
event.wait(timeout=1)
live_button = session(xpath='//XCUIElementTypeButton[@name="直播"]')
if live_button.exists:
@@ -239,8 +255,12 @@ class ScriptManager():
print("--------------------------------------------")
# 换成
if not self.interruptible_sleep(event, random.randint(300, 600)):
break
total_seconds = random.randint(300, 600)
for _ in range(total_seconds): # 0.2 秒一片
if event.is_set():
break
event.wait(timeout=1)
session.swipe_up()
# 正常退出(外部 event 触发)
@@ -256,7 +276,10 @@ class ScriptManager():
except Exception:
pass
# 冷却后整段流程重来
event.wait(timeout=backoff_sec)
for _ in range(backoff_sec): # 0.2 秒一片
if event.is_set():
break
event.wait(timeout=1)
continue
"""
@@ -275,6 +298,10 @@ class ScriptManager():
retries += 1
LogManager.method_error(f"greetNewFollowers 出现异常: {e},准备第 {retries} 次重试", "关注打招呼", udid)
event.wait(timeout=3)
if event.is_set():
LogManager.method_info("外层 while 检测到停止,即将 break", "关注打招呼", udid)
break
LogManager.method_error("greetNewFollowers 重试次数耗尽,任务终止", "关注打招呼", udid)
# 关注打招呼以及回复主播消息
@@ -317,6 +344,10 @@ class ScriptManager():
# 循环条件。1、 循环关闭 2、 数据处理完毕
while not event.is_set():
LogManager.method_info("=== 外层 while 新一轮 ===", "关注打招呼", udid)
if event.is_set():
break
# 获取一个主播,
LogManager.method_info(f"开始获取数据", "关注打招呼", udid)
# 获取一个主播,
@@ -404,7 +435,13 @@ class ScriptManager():
count = workCount
while count != 0:
event.wait(timeout=5)
LogManager.method_info("准备停止脚本", method="task")
for _ in range(5):
LogManager.method_info("停止脚本中", method="task")
if event.is_set():
break
event.wait(timeout=1)
LogManager.method_info("停止脚本成功", method="task")
img = client.screenshot()
event.wait(timeout=1)
@@ -427,14 +464,22 @@ class ScriptManager():
# if r == True:
count -= 1
LogManager.method_info("准备停止脚本", method="task")
# 随机看视频 15~30秒
event.wait(timeout=random.randint(15, 30))
for _ in range(random.randint(15, 30)):
LogManager.method_info("停止脚本中", method="task")
if event.is_set():
break
event.wait(timeout=1)
LogManager.method_info("停止脚本成功", method="task")
if count != 0:
client.swipe_up()
# 右滑返回
client.swipe_right()
if event.is_set():
LogManager.method_info("viewAnchorVideo 检测到停止,提前退出", "关注打招呼", udid)
return
# 如果打开视频失败。说明该主播没有视频
if cellClickResult == True:
@@ -510,7 +555,6 @@ class ScriptManager():
LogManager.method_info(f"给主播{aid} 发送消息失败", "关注打招呼", udid)
# 接着下一个主播
# removeModelFromAnchorList(anchor)
goBack(1)
# 点击关注按钮
@@ -687,13 +731,6 @@ class ScriptManager():
)
# 用户消息
# xp_badge_numeric = (
# '//XCUIElementTypeOther['
# ' @name="AWEIMChatListCellUnreadCountViewComponent"'
# ' or @name="TikTokIMImpl.InboxCellUnreadCountViewBuilder"'
# ']//XCUIElementTypeStaticText[@value and translate(@value,"0123456789","")=""]'
# )
xp_badge_numeric = (
"("
# 你的两类未读容器组件 + 数字徽标value 纯数字)
@@ -725,15 +762,14 @@ class ScriptManager():
if user_text:
user_text.tap()
event.wait(timeout=3)
xml = session.source()
msgs = AiUtils.extract_messages_from_xml(xml)
# 检测出对方发的最后一条信息
# last_msg_text = next(item['text'] for item in reversed(msgs) if item['type'] == 'msg')
text_list = ['What do you think of my live stream?',
'What do you think makes my streams special?',
'Do you think Im one of the most engaging streamers youve seen?']
# 检测出对方发的最后一条信息
last_msg = next((item['text'] for item in reversed(msgs) if item['type'] == 'msg'),
random.choice(text_list))
@@ -775,13 +811,17 @@ class ScriptManager():
LogManager.method_info(f"主播最后发送的数据,传递给前端进行记录:{last_data}", "检测消息", udid)
JsonUtils.append_json_items(last_data, "log/last_message.json")
# 从C盘中读取数据
anchorWithSession = IOSAIStorage.load()
sel = session.xpath("//TextView")
if anchor_name not in anchorWithSession:
# 如果是第一次发消息(没有sessionId的情况)
LogManager.method_info(f"第一次发消息:{anchor_name},没有记忆", "检测消息", udid)
LogManager.method_info(f"第一次发消息:{anchor_name},没有记忆 开始请求ai", "检测消息", udid)
LogManager.method_info(f"向ai发送的参数", "检测消息", udid)
aiResult, sessionId = Requester.chatToAi({"query": last_msg_text, "user": "1"})
anchorWithSession[anchor_name] = sessionId
IOSAIStorage.save({anchor_name: sessionId})
# 找到输入框输入ai返回出来的消息
if sel.exists:
@@ -913,4 +953,5 @@ class ScriptManager():
timeout = min(slice_, left)
event.wait(timeout=timeout)
left -= timeout
return not event.is_set() # 返回 True 表示正常睡完False 被中断
return not event.is_set() # 返回 True 表示正常睡完False 被中断