20250904-初步功能已完成

This commit is contained in:
2025-09-08 21:42:09 +08:00
parent 31f0e19b13
commit 4b3247d0bf
8 changed files with 692 additions and 177 deletions

98
.idea/workspace.xml generated
View File

@@ -4,9 +4,18 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="ai 开始测试"> <list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成">
<change afterPath="$PROJECT_DIR$/2111.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Module/log/acList.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/resources/e5ab9d3c548302dca3b1383589ac43eedd41f24e/bgv.png" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tidevice_entry.py" 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/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/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$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tidevice_entry.py" beforeDir="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" />
@@ -34,6 +43,9 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="PerforceDirect.Settings">
<option name="CHARSET" value="无" />
</component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;, &quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 5 &quot;associatedIndex&quot;: 5
@@ -46,27 +58,28 @@
<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">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;Python.123.executor&quot;: &quot;Run&quot;, "Python.123.executor": "Run",
&quot;Python.Main.executor&quot;: &quot;Run&quot;, "Python.2111.executor": "Run",
&quot;Python.tidevice_entry.executor&quot;: &quot;Run&quot;, "Python.Main.executor": "Run",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "Python.tidevice_entry.executor": "Run",
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;git-widget-placeholder&quot;: &quot;main&quot;, "SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;javascript.nodejs.core.library.configured.version&quot;: &quot;22.18.0&quot;, "git-widget-placeholder": "main",
&quot;javascript.nodejs.core.library.typings.version&quot;: &quot;22.18.1&quot;, "javascript.nodejs.core.library.configured.version": "20.17.0",
&quot;last_opened_file_path&quot;: &quot;F:/company code/AI item/20250820/iOSAI&quot;, "javascript.nodejs.core.library.typings.version": "20.17.58",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "last_opened_file_path": "F:/company code/AI item/20250820/iOSAI",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;settings.editor.selected.configurable&quot;: &quot;com.gitee.ui.GiteeSettingsConfigurable&quot;, "nodejs_package_manager_path": "npm",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "settings.editor.selected.configurable": "com.gitee.ui.GiteeSettingsConfigurable",
"vue.rearranger.settings.migration": "true"
} }
}</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" />
@@ -89,6 +102,29 @@
</myKeys> </myKeys>
</component> </component>
<component name="RunManager" selected="Python.Main"> <component name="RunManager" selected="Python.Main">
<configuration name="2111" 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$/2111.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"> <configuration name="Main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
<module name="iOSAI" /> <module name="iOSAI" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
@@ -137,6 +173,7 @@
</configuration> </configuration>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Python.2111" />
<item itemvalue="Python.tidevice_entry" /> <item itemvalue="Python.tidevice_entry" />
</list> </list>
</recent_temporary> </recent_temporary>
@@ -195,6 +232,7 @@
<workItem from="1756962238298" duration="14230000" /> <workItem from="1756962238298" duration="14230000" />
<workItem from="1756979981948" duration="4536000" /> <workItem from="1756979981948" duration="4536000" />
<workItem from="1757053266703" duration="6442000" /> <workItem from="1757053266703" duration="6442000" />
<workItem from="1757308014161" duration="25659000" />
</task> </task>
<task id="LOCAL-00001" summary="ai 开始测试"> <task id="LOCAL-00001" summary="ai 开始测试">
<option name="closed" value="true" /> <option name="closed" value="true" />
@@ -204,7 +242,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1756303135240</updated> <updated>1756303135240</updated>
</task> </task>
<option name="localTasksCounter" value="2" /> <task id="LOCAL-00002" summary="20250904-初步功能已完成">
<option name="closed" value="true" />
<created>1757070975086</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1757070975086</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -227,7 +273,8 @@
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" /> <option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="ai 开始测试" /> <MESSAGE value="ai 开始测试" />
<option name="LAST_COMMIT_MESSAGE" value="ai 开始测试" /> <MESSAGE value="20250904-初步功能已完成" />
<option name="LAST_COMMIT_MESSAGE" value="20250904-初步功能已完成" />
</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" />
@@ -238,7 +285,8 @@
<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$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$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$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="1757070612000" 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$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1757338477053" 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="1756712884433" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> <SUITE FILE_PATH="coverage/iOSAI$123.coverage" NAME="123 覆盖结果" MODIFIED="1756712884433" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" />
<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$" />
</component> </component>
</project> </project>

View File

@@ -2,6 +2,7 @@ import json
import os import os
import socket import socket
import threading import threading
from pathlib import Path
from queue import Queue from queue import Queue
from typing import Any, Dict from typing import Any, Dict
@@ -16,15 +17,18 @@ from Entity.ResultData import ResultData
from Utils.ControlUtils import ControlUtils from Utils.ControlUtils import ControlUtils
from Utils.ThreadManager import ThreadManager from Utils.ThreadManager import ThreadManager
from script.ScriptManager import ScriptManager from script.ScriptManager import ScriptManager
from Entity.Variables import anchorList, addModelToAnchorList, prologueList, removeModelFromAnchorList from Entity.Variables import anchorList, prologueList, addModelToAnchorList, removeModelFromAnchorList
import Entity.Variables as ev import Entity.Variables as ev
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
listData = [] listData = []
dataQueue = Queue() dataQueue = Queue()
def start_socket_listener(): def start_socket_listener():
port = int(os.getenv('FLASK_COMM_PORT', 0)) port = int(os.getenv('FLASK_COMM_PORT', 0))
LogManager.info(f"Received port from environment: {port}") LogManager.info(f"Received port from environment: {port}")
@@ -231,49 +235,61 @@ def stopScript():
return ResultData(code=code, data="", msg=msg).toJson() return ResultData(code=code, data="", msg=msg).toJson()
# 传递主播数据(关注主播打招呼)
# 关注打招呼
@app.route('/passAnchorData', methods=['POST']) @app.route('/passAnchorData', methods=['POST'])
def passAnchorData(): def passAnchorData():
data: Dict[str, Any] = request.get_json() try:
# 设备列表 data: Dict[str, Any] = request.get_json()
# 设备列表
idList = data.get("deviceList", [])
# 主播列表
acList = data.get("anchorList", [])
AiUtils.save_aclist_flat_append(acList)
print("接收的数据", data) # 是否需要回复
needReply = data.get("needReply", True)
# 获取打招呼数据
ev.prologueList = data.get("prologueList", [])
idList = data.get("deviceList", []) # 添加主播数据
# 主播列表 addModelToAnchorList(acList)
acList = data.get("anchorList", []) # 启动线程,执行脚本
# 是否需要回复 for udid in idList:
needReply = data.get("needReply", True) manager = ScriptManager()
event = threading.Event()
# 获取打招呼数据 # 启动脚本
ev.prologueList = data.get("prologueList", []) thread = threading.Thread(target=manager.safe_greetNewFollowers, args=(udid, needReply, event))
thread.start()
# 添加到线程管理
# 添加主播数据 ThreadManager.add(udid, thread, event)
addModelToAnchorList(acList) return ResultData(data="").toJson()
# 启动线程,执行脚本 except Exception as e:
for udid in idList: LogManager.error(e)
manager = ScriptManager()
event = threading.Event()
# 启动脚本
thread = threading.Thread(target=manager.safe_greetNewFollowers, args=(udid, needReply, event))
thread.start()
# 添加到线程管理
ThreadManager.add(udid, thread, event)
return ResultData(data="").toJson()
# 获取私信数据 # 获取私信数据
@app.route("/getPrologueList", methods=['GET']) @app.route("/getPrologueList", methods=['GET'])
def getPrologueList(): def getPrologueList():
print(ev.prologueList) import Entity.Variables as Variables
return ResultData(data=ev.prologueList).toJson() return ResultData(data=Variables.prologueList).toJson()
# 添加临时数据 # 添加临时数据
# 批量追加主播到 JSON 文件
@app.route("/addTempAnchorData", methods=['POST']) @app.route("/addTempAnchorData", methods=['POST'])
def addTempAnchorData(): def addTempAnchorData():
"""
请求体支持:
- 单个对象:{"anchorId": "xxx", "country": "CN"}
- 对象数组:[{"anchorId": "xxx", "country": "CN"}, {"anchorId": "yyy", "country": "US"}]
"""
data = request.get_json() data = request.get_json()
addModelToAnchorList(data) if not data:
return ResultData(data="").toJson() return ResultData(code=400, msg="请求数据为空").toJson()
# 追加到 JSON 文件
AiUtils.save_aclist_flat_append(data, "log/acList.json")
return ResultData(data="ok").toJson()
# 获取当前屏幕上的聊天信息 # 获取当前屏幕上的聊天信息
@@ -333,20 +349,27 @@ def upLoadLogLogs():
# 获取当前的主播列表数据 # 获取当前的主播列表数据
@app.route("/anchorList", methods=['POST']) @app.route("/anchorList", methods=['POST'])
def queryAnchorList(): def queryAnchorList():
file_path = "log/acList.json"
data = [] data = []
for model in anchorList: if Path(file_path).exists():
data.append(AnchorModel.modelToDict(model)) try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
LogManager.error(f"[anchorList] 读取失败: {e}")
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():
ls: list[dict] = request.get_json() ls: list[dict] = request.get_json() # [{"anchorId": "xxx"}, ...]
for dic in ls: ids = [d.get("anchorId") for d in ls if d.get("anchorId")]
for model in anchorList: deleted = AiUtils.delete_anchors_by_ids(ids)
if dic.get("anchorId") == model.anchorId: return ResultData(data={"deleted": deleted}).toJson()
removeModelFromAnchorList(model)
return ResultData(data="").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,7 +1,11 @@
import json
import os import os
import re import re
from pathlib import Path
import cv2 import cv2
import numpy as np import numpy as np
import unicodedata
import wda import wda
from Utils.LogManager import LogManager from Utils.LogManager import LogManager
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@@ -294,7 +298,6 @@ class AiUtils(object):
print(f"btn:{btn}") print(f"btn:{btn}")
return cls.findNumber(btn.label) return cls.findNumber(btn.label)
@classmethod @classmethod
def extract_messages_from_xml(cls, xml: str): def extract_messages_from_xml(cls, xml: str):
""" """
@@ -304,7 +307,6 @@ class AiUtils(object):
root = etree.fromstring(xml.encode("utf-8")) root = etree.fromstring(xml.encode("utf-8"))
items = [] items = []
# 屏幕宽度 # 屏幕宽度
app = root.xpath('/XCUIElementTypeApplication') app = root.xpath('/XCUIElementTypeApplication')
screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0 screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0
@@ -474,3 +476,137 @@ class AiUtils(object):
# 使用正则表达式匹配中文字符 # 使用正则表达式匹配中文字符
pattern = re.compile(r'[\u4e00-\u9fff]') pattern = re.compile(r'[\u4e00-\u9fff]')
return bool(pattern.search(text)) return bool(pattern.search(text))
@classmethod
def is_language(cls, text: str) -> bool:
if not text:
return False
for ch in text:
if unicodedata.category(ch).startswith("L"):
return True
return False
@classmethod
def _read_json_list(cls, file_path: Path) -> list:
"""读取为 list读取失败或不是 list 则返回空数组"""
if not file_path.exists():
return []
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
return data if isinstance(data, list) else []
except Exception as e:
LogManager.error(f"[acList] 读取失败,将按空数组处理: {e}")
return []
@classmethod
def _write_json_list(cls, file_path: Path, data: list) -> None:
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
@staticmethod
def _normalize_anchor_items(items):
"""
规范化输入为 [{anchorId, country}] 的列表:
- 允许传入:单个对象、对象列表、字符串(当 anchorId 用)
- 过滤不合规项
"""
result = []
if items is None:
return result
if isinstance(items, dict):
# 单个对象
aid = items.get("anchorId")
if aid:
result.append({"anchorId": str(aid), "country": items.get("country", "")})
return result
if isinstance(items, list):
for it in items:
if isinstance(it, dict):
aid = it.get("anchorId")
if aid:
result.append({"anchorId": str(aid), "country": it.get("country", "")})
elif isinstance(it, str):
result.append({"anchorId": it, "country": ""})
return result
if isinstance(items, str):
result.append({"anchorId": items, "country": ""})
return result
# -------- 追加(对象数组平铺追加) --------
@classmethod
def save_aclist_flat_append(cls, acList, filename="log/acList.json"):
"""
将 anchor 对象数组平铺追加到 JSON 文件(数组)中。
期望 acList 形如:
[
{"anchorId": "ldn327_", "country": ""},
{"anchorId": "tianliang30", "country": ""}
]
"""
file_path = Path(filename)
data = cls._read_json_list(file_path)
# 规范化输入,确保都是 {anchorId, country}
to_add = cls._normalize_anchor_items(acList)
if not to_add:
LogManager.info("[acList] 传入为空或不合规,跳过写入")
return
data.extend(to_add)
cls._write_json_list(file_path, data)
LogManager.info(f"[acList] 已追加 {len(to_add)} 条,当前总数={len(data)} -> {file_path}")
# -------- 弹出(取一个删一个) --------
@classmethod
def pop_aclist_first(cls, filename="log/acList.json"):
"""
从 JSON 数组中取出第一个 anchor 对象,并删除它;为空或文件不存在返回 None。
返回形如:{"anchorId": "...", "country": "..."}
"""
file_path = Path(filename)
data = cls._read_json_list(file_path)
if not data:
return None
first = data.pop(0)
# 兜底保证结构
norm = cls._normalize_anchor_items(first)
first = norm[0] if norm else None
cls._write_json_list(file_path, data)
return first
@classmethod
def delete_anchors_by_ids(cls, ids: list[str], filename="log/acList.json") -> int:
"""
根据 anchorId 列表从 JSON 文件中删除匹配的 anchor。
返回删除数量。
"""
file_path = Path(filename)
if not file_path.exists():
return 0
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, list):
return 0
except Exception as e:
LogManager.error(f"[delete_anchors_by_ids] 读取失败: {e}")
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)
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception as e:
LogManager.error(f"[delete_anchors_by_ids] 写入失败: {e}")
return deleted

View File

@@ -172,3 +172,14 @@ class ControlUtils(object):
left_x = max(1, rect.x - 20) left_x = max(1, rect.x - 20)
center_y = rect.y + rect.height // 2 center_y = rect.y + rect.height // 2
session.tap(left_x, center_y) session.tap(left_x, center_y)
# 检测五分钟前和当前的状态是否相同
# @classmethod
# def compareCurrentWithPreviousState(cls,xml):

View File

@@ -1,4 +1,212 @@
#
# import datetime
# import io
# import logging
# import os
# import re
# import sys
# import shutil
# import zipfile
# from pathlib import Path
# import requests
#
#
# class LogManager:
# # 运行根目录:打包后取 exe 目录;源码运行取项目目录
# if getattr(sys, "frozen", False):
# projectRoot = os.path.dirname(sys.executable)
# else:
# projectRoot = os.path.dirname(os.path.dirname(__file__))
#
# logDir = os.path.join(projectRoot, "log")
# _loggers = {}
# _method_loggers = {} # 新增:缓存“设备+方法”的 logger
#
# # ---------- 工具函数 ----------
# @classmethod
# def _safe_filename(cls, name: str, max_len: int = 80) -> str:
# """
# 将方法名/udid等转成安全文件名
# - 允许字母数字、点、下划线、连字符
# - 允许常见 CJK 字符(中日韩)
# - 其他非法字符替换为下划线
# - 合并多余下划线,裁剪长度
# """
# if not name:
# return "unknown"
# name = str(name).strip()
#
# # 替换 Windows 非法字符和控制符
# name = re.sub(r'[\\/:*?"<>|\r\n\t]+', '_', name)
#
# # 只保留 ① 英数._- ② CJK 统一表意文字、日文平/片假名、韩文音节
# name = re.sub(rf'[^a-zA-Z0-9_.\-'
# r'\u4e00-\u9fff' # 中
# r'\u3040-\u30ff' # 日
# r'\uac00-\ud7a3' # 韩
# r']+', '_', name)
# # 合并多余下划线,去两端空白与下划线
# name = re.sub(r'_+', '_', name).strip(' _.')
# # 避免空
# name = name or "unknown"
# # Windows 预留名避免CON/PRN/AUX/NUL/COM1…
# if re.fullmatch(r'(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])', name):
# name = f"_{name}"
# # 限长
# return name[:max_len] or "unknown"
#
# # ---------- 旧的:按级别写固定文件 ----------
# @classmethod
# def _setupLogger(cls, udid, name, logName, level=logging.INFO):
# """创建或获取 logger并绑定到设备目录下的固定文件info.log / warning.log / error.log"""
# deviceLogDir = os.path.join(cls.logDir, cls._safe_filename(udid))
# os.makedirs(deviceLogDir, exist_ok=True)
# logFile = os.path.join(deviceLogDir, logName)
#
# logger_name = f"{udid}_{name}"
# logger = logging.getLogger(logger_name)
# logger.setLevel(level)
#
# # 避免重复添加 handler
# if not any(
# isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile)
# for h in logger.handlers
# ):
# fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
# formatter = logging.Formatter(
# "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
# datefmt="%Y-%m-%d %H:%M:%S"
# )
# fileHandler.setFormatter(formatter)
# logger.addHandler(fileHandler)
#
# return logger
#
# @classmethod
# def info(cls, text, udid="system"):
# cls._setupLogger(udid, "infoLogger", "info.log", level=logging.INFO).info(f"[{udid}] {text}")
#
# @classmethod
# def warning(cls, text, udid="system"):
# cls._setupLogger(udid, "warningLogger", "warning.log", level=logging.WARNING).warning(f"[{udid}] {text}")
#
# @classmethod
# def error(cls, text, udid="system"):
# cls._setupLogger(udid, "errorLogger", "error.log", level=logging.ERROR).error(f"[{udid}] {text}")
#
# # ---------- 新增:按“设备+方法”分别写独立日志文件 ----------
# @classmethod
# def _setupMethodLogger(cls, udid: str, method: str, level=logging.INFO):
# """
# 为某设备的某个方法单独创建 logger
# log/<udid>/<method>.log
# """
# udid_key = cls._safe_filename(udid or "system")
# method_key = cls._safe_filename(method or "general")
# cache_key = (udid_key, method_key)
#
# # 命中缓存
# if cache_key in cls._method_loggers:
# return cls._method_loggers[cache_key]
#
# deviceLogDir = os.path.join(cls.logDir, udid_key)
# os.makedirs(deviceLogDir, exist_ok=True)
# logFile = os.path.join(deviceLogDir, f"{method_key}.log")
#
# logger_name = f"{udid_key}.{method_key}"
# logger = logging.getLogger(logger_name)
# logger.setLevel(level)
# logger.propagate = False # 避免向根 logger 传播导致控制台重复打印
#
# # 避免重复添加 handler
# if not any(
# isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile)
# for h in logger.handlers
# ):
# fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
# formatter = logging.Formatter(
# "%(asctime)s - %(levelname)s - %(name)s - %(message)s",
# datefmt="%Y-%m-%d %H:%M:%S"
# )
# fileHandler.setFormatter(formatter)
# logger.addHandler(fileHandler)
#
# cls._method_loggers[cache_key] = logger
# return logger
#
# @classmethod
# def method_info(cls, text, method, udid="system"):
# """按设备+方法写 INFO 到 log/<udid>/<method>.log"""
# cls._setupMethodLogger(udid, method, level=logging.INFO).info(f"[{udid}][{method}] {text}")
#
# @classmethod
# def method_warning(cls, text, method, udid="system"):
# cls._setupMethodLogger(udid, method, level=logging.WARNING).warning(f"[{udid}][{method}] {text}")
#
# @classmethod
# def method_error(cls, text, method, udid="system"):
# cls._setupMethodLogger(udid, method, level=logging.ERROR).error(f"[{udid}][{method}] {text}")
#
# # 清空日志
# @classmethod
# def clearLogs(cls):
# """启动时清空 log 目录下所有文件"""
#
# # 关闭所有 handler
# for name, logger in logging.Logger.manager.loggerDict.items():
# if isinstance(logger, logging.Logger):
# for handler in logger.handlers[:]:
# try:
# handler.close()
# except Exception:
# pass
# logger.removeHandler(handler)
#
# # 删除 log 目录
# log_path = Path(cls.logDir)
# if log_path.exists():
# for item in log_path.iterdir():
# if item.is_file():
# item.unlink()
# elif item.is_dir():
# shutil.rmtree(item)
#
# # 清缓存
# cls._method_loggers.clear()
#
# @classmethod
# def upload_all_logs(cls, server_url, token, userId, tenantId):
# log_path = Path(cls.logDir)
# if not log_path.exists():
# return False
#
# timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# filename = f"{timestamp}_logs.zip"
# print(filename)
# zip_buf = io.BytesIO()
# with zipfile.ZipFile(zip_buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
# for p in log_path.rglob("*"):
# if p.is_file():
# arcname = str(p.relative_to(log_path))
# zf.write(p, arcname=arcname)
#
# zip_bytes = zip_buf.getvalue()
#
# headers = {"vvtoken": token}
# data = {"tenantId": tenantId, "userId": userId}
#
#
# files = {
# "file": (filename, io.BytesIO(zip_bytes), "application/zip")
# }
#
# # 3) 上传
# resp = requests.post(server_url, headers=headers, data=data, files=files)
# if resp.json()['data']:
# return True
# return False
# -*- coding: utf-8 -*-
import datetime import datetime
import io import io
import logging import logging
@@ -10,8 +218,34 @@ import zipfile
from pathlib import Path from pathlib import Path
import requests import requests
# ========= 全局:强制 UTF-8打包 EXE / 无控制台也生效) =========
def _force_utf8_everywhere():
os.environ.setdefault("PYTHONUTF8", "1")
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
# windowed 模式下 stdout/stderr 可能没有 buffer这里做保护包装
try:
if getattr(sys.stdout, "buffer", None):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
except Exception:
pass
try:
if getattr(sys.stderr, "buffer", None):
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
except Exception:
pass
_force_utf8_everywhere()
# ===========================================================
class LogManager: class LogManager:
"""
设备级与“设备+方法”级日志管理:
- log/<udid>/info.log | warning.log | error.log
- log/<udid>/<method>.log
- 文件统一 UTF-8 编码,避免 GBK/CP936 导致的 emoji 报错
- 提供 clearLogs() 与 upload_all_logs()
"""
# 运行根目录:打包后取 exe 目录;源码运行取项目目录 # 运行根目录:打包后取 exe 目录;源码运行取项目目录
if getattr(sys, "frozen", False): if getattr(sys, "frozen", False):
projectRoot = os.path.dirname(sys.executable) projectRoot = os.path.dirname(sys.executable)
@@ -19,95 +253,111 @@ class LogManager:
projectRoot = os.path.dirname(os.path.dirname(__file__)) projectRoot = os.path.dirname(os.path.dirname(__file__))
logDir = os.path.join(projectRoot, "log") logDir = os.path.join(projectRoot, "log")
_loggers = {} _method_loggers = {} # 缓存“设备+方法”的 logger
_method_loggers = {} # 新增:缓存“设备+方法”的 logger
# ---------- 工具:安全文本/文件名 ----------
@staticmethod
def _safe_text(obj) -> str:
"""把任意对象安全转为可写字符串(避免因编码问题再次抛异常)"""
try:
if isinstance(obj, bytes):
return obj.decode("utf-8", "replace")
s = str(obj)
# 确保解码上屏不再出错
_ = s.encode("utf-8", "replace")
return s
except Exception:
try:
return repr(obj)
except Exception:
return "<unprintable>"
# ---------- 工具函数 ----------
@classmethod @classmethod
def _safe_filename(cls, name: str, max_len: int = 80) -> str: def _safe_filename(cls, name: str, max_len: int = 80) -> str:
""" """
将方法名/udid等转成安全文件名 将方法名/udid等转成安全文件名
- 允许字母数字、点、下划线、连字符 - 允许字母数字、点、下划线、连字符
- 允许常见 CJK 字符(中日韩) - 保留常见 CJK 字符(中日韩)
- 其他非法字符替换为下划线 - 其替换为下划线;合并下划线;避免保留名;限长
- 合并多余下划线,裁剪长度
""" """
if not name: if not name:
return "unknown" return "unknown"
name = str(name).strip() name = str(name).strip()
name = re.sub(r'[\\/:*?"<>|\r\n\t]+', '_', name) # Windows 非法字符
# 替换 Windows 非法字符和控制符 name = re.sub(
name = re.sub(r'[\\/:*?"<>|\r\n\t]+', '_', name) r'[^a-zA-Z0-9_.\-'
r'\u4e00-\u9fff' # 中
# 只保留 ① 英数._- ② CJK 统一表意文字、日文平/片假名、韩文音节 r'\u3040-\u30ff' # 日
name = re.sub(rf'[^a-zA-Z0-9_.\-' r'\uac00-\ud7a3' # 韩
r'\u4e00-\u9fff' # 中 r']+', '_', name
r'\u3040-\u30ff' # 日 )
r'\uac00-\ud7a3' # 韩
r']+', '_', name)
# 合并多余下划线,去两端空白与下划线
name = re.sub(r'_+', '_', name).strip(' _.') name = re.sub(r'_+', '_', name).strip(' _.')
# 避免空
name = name or "unknown" name = name or "unknown"
# Windows 预留名避免CON/PRN/AUX/NUL/COM1…
if re.fullmatch(r'(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])', name): if re.fullmatch(r'(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])', name):
name = f"_{name}" name = f"_{name}"
# 限长
return name[:max_len] or "unknown" return name[:max_len] or "unknown"
# ---------- 旧的:按级别写固定文件 ---------- # ---------- 设备级固定文件info/warning/error ----------
@classmethod @classmethod
def _setupLogger(cls, udid, name, logName, level=logging.INFO): def _setupLogger(cls, udid, name, logName, level=logging.INFO):
"""创建或获取 logger并绑定到设备目录下的固定文件info.log / warning.log / error.log""" """创建或获取 logger并绑定到设备目录下的固定文件info.log / warning.log / error.log"""
deviceLogDir = os.path.join(cls.logDir, cls._safe_filename(udid)) udid_key = cls._safe_filename(udid or "system")
deviceLogDir = os.path.join(cls.logDir, udid_key)
os.makedirs(deviceLogDir, exist_ok=True) os.makedirs(deviceLogDir, exist_ok=True)
logFile = os.path.join(deviceLogDir, logName) logFile = os.path.join(deviceLogDir, logName)
logger_name = f"{udid}_{name}" logger_name = f"{udid_key}_{name}"
logger = logging.getLogger(logger_name) logger = logging.getLogger(logger_name)
logger.setLevel(level) logger.setLevel(level)
logger.propagate = False # 不向根 logger 传播,避免重复
# 避免重复添加 handler # 避免重复添加同一路径的 file handler
if not any( abs_target = os.path.abspath(logFile)
isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile) for h in logger.handlers:
for h in logger.handlers if isinstance(h, logging.FileHandler) and getattr(h, "baseFilename", "") == abs_target:
): return logger
fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
return logger return logger
@classmethod @classmethod
def info(cls, text, udid="system"): def info(cls, text, udid="system"):
cls._setupLogger(udid, "infoLogger", "info.log", level=logging.INFO).info(f"[{udid}] {text}") msg = cls._safe_text(f"[{udid}] {text}")
cls._setupLogger(udid, "infoLogger", "info.log", level=logging.INFO).info(msg)
@classmethod @classmethod
def warning(cls, text, udid="system"): def warning(cls, text, udid="system"):
cls._setupLogger(udid, "warningLogger", "warning.log", level=logging.WARNING).warning(f"[{udid}] {text}") msg = cls._safe_text(f"[{udid}] {text}")
cls._setupLogger(udid, "warningLogger", "warning.log", level=logging.WARNING).warning(msg)
@classmethod @classmethod
def error(cls, text, udid="system"): def error(cls, text, udid="system"):
cls._setupLogger(udid, "errorLogger", "error.log", level=logging.ERROR).error(f"[{udid}] {text}") msg = cls._safe_text(f"[{udid}] {text}")
cls._setupLogger(udid, "errorLogger", "error.log", level=logging.ERROR).error(msg)
# ---------- “设备+方法”独立文件:<udid>/<method>.log ----------
# ---------- 新增:按“设备+方法”分别写独立日志文件 ----------
@classmethod @classmethod
def _setupMethodLogger(cls, udid: str, method: str, level=logging.INFO): def _setupMethodLogger(cls, udid: str, method: str, level=logging.INFO):
""" """
为某设备的某个方法单独创建 logger 为某设备的某个方法单独创建 loggerlog/<udid>/<method>.log
log/<udid>/<method>.log
""" """
udid_key = cls._safe_filename(udid or "system") udid_key = cls._safe_filename(udid or "system")
method_key = cls._safe_filename(method or "general") method_key = cls._safe_filename(method or "general")
cache_key = (udid_key, method_key) cache_key = (udid_key, method_key)
# 命中缓存 # 命中缓存
if cache_key in cls._method_loggers: logger = cls._method_loggers.get(cache_key)
return cls._method_loggers[cache_key] if logger:
return logger
deviceLogDir = os.path.join(cls.logDir, udid_key) deviceLogDir = os.path.join(cls.logDir, udid_key)
os.makedirs(deviceLogDir, exist_ok=True) os.makedirs(deviceLogDir, exist_ok=True)
@@ -116,92 +366,121 @@ class LogManager:
logger_name = f"{udid_key}.{method_key}" logger_name = f"{udid_key}.{method_key}"
logger = logging.getLogger(logger_name) logger = logging.getLogger(logger_name)
logger.setLevel(level) logger.setLevel(level)
logger.propagate = False # 避免向根 logger 传播导致控制台重复打印 logger.propagate = False
# 避免重复添加 handler abs_target = os.path.abspath(logFile)
if not any( for h in logger.handlers:
isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile) if isinstance(h, logging.FileHandler) and getattr(h, "baseFilename", "") == abs_target:
for h in logger.handlers cls._method_loggers[cache_key] = logger
): return logger
fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
formatter = logging.Formatter( fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
"%(asctime)s - %(levelname)s - %(name)s - %(message)s", formatter = logging.Formatter(
datefmt="%Y-%m-%d %H:%M:%S" "%(asctime)s - %(levelname)s - %(name)s - %(message)s",
) datefmt="%Y-%m-%d %H:%M:%S"
fileHandler.setFormatter(formatter) )
logger.addHandler(fileHandler) fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
cls._method_loggers[cache_key] = logger cls._method_loggers[cache_key] = logger
return logger return logger
@classmethod @classmethod
def method_info(cls, text, method, udid="system"): def method_info(cls, text, method, udid="system"):
"""按设备+方法写 INFO 到 log/<udid>/<method>.log""" msg = cls._safe_text(f"[{udid}][{method}] {text}")
cls._setupMethodLogger(udid, method, level=logging.INFO).info(f"[{udid}][{method}] {text}") cls._setupMethodLogger(udid, method, level=logging.INFO).info(msg)
@classmethod @classmethod
def method_warning(cls, text, method, udid="system"): def method_warning(cls, text, method, udid="system"):
cls._setupMethodLogger(udid, method, level=logging.WARNING).warning(f"[{udid}][{method}] {text}") msg = cls._safe_text(f"[{udid}][{method}] {text}")
cls._setupMethodLogger(udid, method, level=logging.WARNING).warning(msg)
@classmethod @classmethod
def method_error(cls, text, method, udid="system"): def method_error(cls, text, method, udid="system"):
cls._setupMethodLogger(udid, method, level=logging.ERROR).error(f"[{udid}][{method}] {text}") msg = cls._safe_text(f"[{udid}][{method}] {text}")
cls._setupMethodLogger(udid, method, level=logging.ERROR).error(msg)
# ---------- 清空日志 ----------
# 清空日志
@classmethod @classmethod
def clearLogs(cls): def clearLogs(cls):
"""启动时清空 log 目录下所有文件""" """启动时清空 log 目录下所有文件"""
# 先关闭所有 logger 的文件句柄
# 关闭所有 handler for _, logger in logging.Logger.manager.loggerDict.items():
for name, logger in logging.Logger.manager.loggerDict.items():
if isinstance(logger, logging.Logger): if isinstance(logger, logging.Logger):
for handler in logger.handlers[:]: for handler in list(logger.handlers):
try: try:
handler.close() handler.close()
except Exception: except Exception:
pass pass
logger.removeHandler(handler) logger.removeHandler(handler)
# 删除 log 目录
log_path = Path(cls.logDir) log_path = Path(cls.logDir)
if log_path.exists(): if log_path.exists():
for item in log_path.iterdir(): for item in log_path.iterdir():
if item.is_file(): try:
item.unlink() if item.is_file():
elif item.is_dir(): item.unlink(missing_ok=True) # py>=3.8
shutil.rmtree(item) elif item.is_dir():
shutil.rmtree(item, ignore_errors=True)
except Exception:
# 不阻塞清理
pass
# 清缓存
cls._method_loggers.clear() cls._method_loggers.clear()
# ---------- 上传所有日志(内存打包 zip ----------
@classmethod @classmethod
def upload_all_logs(cls, server_url, token, userId, tenantId): def upload_all_logs(cls, server_url, token, userId, tenantId):
log_path = Path(cls.logDir) """
if not log_path.exists(): 将 log/ 目录下所有日志打包为 zip内存上传至服务器
- headers: {"vvtoken": <token>}
- form: {"tenantId": <tenantId>, "userId": <userId>, "file": <zip>}
返回 True/False
"""
try:
log_path = Path(cls.logDir)
if not log_path.exists():
logging.info("[upload_all_logs] 日志目录不存在:%s", log_path)
return False
# 文件名仅用于表单,不落盘,可包含安全字符
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{timestamp}_logs.zip"
logging.info("[upload_all_logs] 打包文件名:%s", filename)
# 1) 内存中打包 zip
zip_buf = io.BytesIO()
with zipfile.ZipFile(zip_buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in log_path.rglob("*"):
if p.is_file():
arcname = str(p.relative_to(log_path))
zf.write(p, arcname=arcname)
zip_bytes = zip_buf.getvalue()
# 2) 组织请求
headers = {"vvtoken": token} if token else {}
data = {"tenantId": tenantId, "userId": userId}
files = {"file": (filename, io.BytesIO(zip_bytes), "application/zip")}
# 3) 上传
resp = requests.post(server_url, headers=headers, data=data, files=files, timeout=120)
ok = False
try:
js = resp.json()
ok = bool(js.get("data"))
except Exception:
# 响应不是 JSON也许是纯文本降级按状态码判断
ok = resp.ok
if ok:
logging.info("[upload_all_logs] 上传成功:%s", server_url)
return True
else:
logging.error("[upload_all_logs] 上传失败status=%s, text=%s", resp.status_code, LogManager._safe_text(resp.text))
return False
except Exception as e:
logging.error("[upload_all_logs] 异常:%s", LogManager._safe_text(e))
return False return False
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
filename = f"{timestamp}_logs.zip"
print(filename)
zip_buf = io.BytesIO()
with zipfile.ZipFile(zip_buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in log_path.rglob("*"):
if p.is_file():
arcname = str(p.relative_to(log_path))
zf.write(p, arcname=arcname)
zip_bytes = zip_buf.getvalue()
headers = {"vvtoken": token}
data = {"tenantId": tenantId, "userId": userId}
files = {
"file": (filename, io.BytesIO(zip_bytes), "application/zip")
}
# 3) 上传
resp = requests.post(server_url, headers=headers, data=data, files=files)
if resp.json()['data']:
return True
return False

View File

@@ -2,7 +2,6 @@ import requests
from Entity.Variables import prologueList from Entity.Variables import prologueList
BaseUrl = "https://crawlclient.api.yolozs.com/api/common/" BaseUrl = "https://crawlclient.api.yolozs.com/api/common/"
# BaseUrl = "http://192.168.1.174:8101/api/common/"
class Requester(): class Requester():
@@ -21,13 +20,12 @@ class Requester():
for i in data: for i in data:
prologueList.append(i) prologueList.append(i)
# 翻译 # 翻译
@classmethod @classmethod
def translation(cla, msg, country="英国"): def translation(cls, msg, country="英国"):
parame = { parame = {
"msg":msg, "msg": msg,
"country":country, "country": country,
} }
url = "http://ai.zhukeping.com/translation" url = "http://ai.zhukeping.com/translation"
result = requests.request(url=url, json=parame, method="POST") result = requests.request(url=url, json=parame, method="POST")
@@ -43,4 +41,4 @@ class Requester():
result = requests.request(url=url, json=param, method="POST") result = requests.request(url=url, json=param, method="POST")
json = result.json() json = result.json()
data = json.get("data", {}) data = json.get("data", {})
return data return data

View File

@@ -284,6 +284,8 @@ class ScriptManager():
time.sleep(3) time.sleep(3)
LogManager.method_error("greetNewFollowers 重试次数耗尽,任务终止", "关注打招呼", udid) LogManager.method_error("greetNewFollowers 重试次数耗尽,任务终止", "关注打招呼", udid)
# 关注打招呼以及回复主播消息 # 关注打招呼以及回复主播消息
def greetNewFollowers(self, udid, needReply, event): def greetNewFollowers(self, udid, needReply, event):
@@ -319,12 +321,15 @@ class ScriptManager():
print("总循环条件", not event.is_set() and len(anchorList) > 0) print("总循环条件", not event.is_set() and len(anchorList) > 0)
# 循环条件。1、 循环关闭 2、 数据处理完毕 # 循环条件。1、 循环关闭 2、 数据处理完毕
while not event.is_set() and len(anchorList) > 0: while not event.is_set():
# 获取一个主播,并删除 # 获取一个主播,并删除
anchor = anchorList.pop(0) anchor = AiUtils.pop_aclist_first()
aid = anchor.anchorId if not anchor:
anchorCountry = anchor.country break
aid = anchor["anchorId"]
anchorCountry = anchor.get("country", "")
# 点击搜索按钮 # 点击搜索按钮
ControlUtils.clickSearch(session) ControlUtils.clickSearch(session)
@@ -462,10 +467,13 @@ class ScriptManager():
LogManager.method_info("找到输入框了, 准备发送一条打招呼消息", "关注打招呼", udid) LogManager.method_info("找到输入框了, 准备发送一条打招呼消息", "关注打招呼", udid)
print("打招呼的数据", ev.prologueList) print("打招呼的数据", ev.prologueList)
LogManager.method_info(f"打招呼的数据:{ev.prologueList}", "关注打招呼", udid)
# 准备打招呼的文案 # 准备打招呼的文案
text = random.choice(ev.prologueList) text = random.choice(ev.prologueList)
LogManager.method_info(f"打招呼完成", "关注打招呼", udid)
isContainChniese = AiUtils.contains_chinese(text) isContainChniese = AiUtils.contains_chinese(text)
if isContainChniese: if isContainChniese:
@@ -473,6 +481,8 @@ class ScriptManager():
msg = Requester.translation(text, anchorCountry) msg = Requester.translation(text, anchorCountry)
else: else:
msg = text msg = text
LogManager.method_info(f"翻译后的私信数据:{msg}", "关注打招呼", udid)
# 准备发送一条信息 # 准备发送一条信息
chatInput.click() chatInput.click()
@@ -660,9 +670,15 @@ class ScriptManager():
'What do you think makes my streams special?', 'What do you think makes my streams special?',
'Do you think Im one of the most engaging streamers youve seen?'] 'Do you think Im one of the most engaging streamers youve seen?']
last_msg_text = 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))
isLanguage = AiUtils.is_language(last_msg)
if isLanguage:
last_msg_text = last_msg
else:
last_msg_text = random.choice(text_list)
# 向ai发送信息 # 向ai发送信息
# 获取主播的名称 # 获取主播的名称

4
tidevice_entry.py Normal file
View File

@@ -0,0 +1,4 @@
# tidevice_entry.py
from tidevice.__main__ import main
if __name__ == '__main__':
main()