2025-10-27 16:25:47 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2025-08-01 13:43:51 +08:00
|
|
|
|
import json
|
2025-11-19 17:23:41 +08:00
|
|
|
|
import logging
|
2025-08-01 13:43:51 +08:00
|
|
|
|
import os
|
|
|
|
|
|
import socket
|
|
|
|
|
|
import threading
|
2025-10-27 16:25:47 +08:00
|
|
|
|
import time
|
2025-09-08 21:42:09 +08:00
|
|
|
|
from pathlib import Path
|
2025-08-01 13:43:51 +08:00
|
|
|
|
from queue import Queue
|
2025-11-14 16:58:55 +08:00
|
|
|
|
from typing import Any, Dict, List
|
2025-11-18 22:09:19 +08:00
|
|
|
|
import anyio
|
2025-11-19 17:23:41 +08:00
|
|
|
|
import wda
|
|
|
|
|
|
from quart import Quart, request, g
|
2025-11-18 22:09:19 +08:00
|
|
|
|
from quart_cors import cors
|
2025-11-19 17:23:41 +08:00
|
|
|
|
import Entity.Variables as ev
|
2025-10-22 18:24:43 +08:00
|
|
|
|
from Entity import Variables
|
2025-11-19 17:23:41 +08:00
|
|
|
|
from Entity.ResultData import ResultData
|
|
|
|
|
|
from Entity.Variables import addModelToAnchorList, wdaFunctionPort
|
2025-08-14 14:30:36 +08:00
|
|
|
|
from Utils.AiUtils import AiUtils
|
2025-11-19 17:23:41 +08:00
|
|
|
|
from Utils.ControlUtils import ControlUtils
|
2025-09-20 21:57:37 +08:00
|
|
|
|
from Utils.IOSAIStorage import IOSAIStorage
|
2025-11-19 17:23:41 +08:00
|
|
|
|
from Utils.JsonUtils import JsonUtils
|
2025-08-20 13:48:32 +08:00
|
|
|
|
from Utils.LogManager import LogManager
|
2025-08-06 22:11:33 +08:00
|
|
|
|
from Utils.ThreadManager import ThreadManager
|
2025-08-01 13:43:51 +08:00
|
|
|
|
from script.ScriptManager import ScriptManager
|
2025-11-07 14:31:07 +08:00
|
|
|
|
|
|
|
|
|
|
for name in ('werkzeug', 'werkzeug.serving'):
|
|
|
|
|
|
log = logging.getLogger(name)
|
|
|
|
|
|
log.disabled = True
|
|
|
|
|
|
log.propagate = False
|
|
|
|
|
|
log.handlers.clear()
|
2025-09-08 21:42:09 +08:00
|
|
|
|
|
2025-11-18 22:09:19 +08:00
|
|
|
|
app = Quart(__name__) # ⭐ 这里用 Quart,而不是 Flask
|
|
|
|
|
|
app = cors(app, allow_origin="*") # 允许所有来源跨域
|
|
|
|
|
|
|
2025-09-08 19:16:05 +08:00
|
|
|
|
app.config['JSON_AS_ASCII'] = False # Flask jsonify 不转义中文/emoji
|
|
|
|
|
|
app.config['JSONIFY_MIMETYPE'] = "application/json; charset=utf-8"
|
2025-09-08 21:42:09 +08:00
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# ============ 设备状态内存表 ============
|
2025-08-01 13:43:51 +08:00
|
|
|
|
listData = []
|
2025-09-10 16:15:47 +08:00
|
|
|
|
listLock = threading.Lock()
|
|
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# 历史遗留:不再消费队列,改为socket线程直接落地
|
2025-08-01 13:43:51 +08:00
|
|
|
|
dataQueue = Queue()
|
|
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# ---- 黏性快照(避免瞬时空) ----
|
|
|
|
|
|
_last_nonempty_snapshot = []
|
|
|
|
|
|
_last_snapshot_ts = 0.0
|
|
|
|
|
|
_STICKY_TTL_SEC = 10.0 # 在瞬时空时,回退到上一份非空快照10秒
|
|
|
|
|
|
_empty_logged = False
|
|
|
|
|
|
_recovered_logged = False
|
|
|
|
|
|
|
|
|
|
|
|
# ===== 设备集合变化跟踪 =====
|
|
|
|
|
|
change_version = 0
|
|
|
|
|
|
_device_ids_snapshot = set()
|
|
|
|
|
|
_last_device_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
def _log_device_changes(action: str):
|
|
|
|
|
|
"""记录设备集合增删变化"""
|
|
|
|
|
|
global _device_ids_snapshot, change_version
|
|
|
|
|
|
curr_ids = {d.get("deviceId") for d in listData if _is_online(d)}
|
|
|
|
|
|
added = curr_ids - _device_ids_snapshot
|
|
|
|
|
|
removed = _device_ids_snapshot - curr_ids
|
|
|
|
|
|
if added or removed:
|
|
|
|
|
|
change_version += 1
|
|
|
|
|
|
try:
|
|
|
|
|
|
LogManager.info(f"[DEVICE][CHANGED][{action}] rev={change_version} count={len(curr_ids)} added={list(added)} removed={list(removed)}")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
print(f"[DEVICE][CHANGED][{action}] rev={change_version} count={len(curr_ids)} added={list(added)} removed={list(removed)}")
|
|
|
|
|
|
_device_ids_snapshot = curr_ids
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_type(v) -> int:
|
|
|
|
|
|
"""把各种表示在线/离线的值,规范成 1/0"""
|
|
|
|
|
|
if isinstance(v, bool):
|
|
|
|
|
|
return 1 if v else 0
|
|
|
|
|
|
if isinstance(v, (int, float)):
|
|
|
|
|
|
return 1 if int(v) == 1 else 0
|
|
|
|
|
|
if isinstance(v, str):
|
|
|
|
|
|
s = v.strip().lower()
|
|
|
|
|
|
if s.isdigit():
|
|
|
|
|
|
return 1 if int(s) == 1 else 0
|
|
|
|
|
|
if s in ("true", "online", "on", "yes"):
|
|
|
|
|
|
return 1
|
|
|
|
|
|
return 0
|
|
|
|
|
|
return 1 if v else 0
|
|
|
|
|
|
|
|
|
|
|
|
def _is_online(d: Dict[str, Any]) -> bool:
|
|
|
|
|
|
return _normalize_type(d.get("type", 1)) == 1
|
|
|
|
|
|
|
2025-11-14 16:58:55 +08:00
|
|
|
|
def _apply_device_snapshot(devices: List[Dict[str, Any]]):
|
|
|
|
|
|
"""接收 DeviceInfo 送来的全量设备列表,直接覆盖 listData"""
|
|
|
|
|
|
global listData
|
|
|
|
|
|
try:
|
|
|
|
|
|
normed = []
|
|
|
|
|
|
for d in devices:
|
|
|
|
|
|
# 拷贝一份,避免引用共享
|
|
|
|
|
|
d = dict(d)
|
|
|
|
|
|
d["type"] = _normalize_type(d.get("type", 1)) # 规范成 0/1
|
|
|
|
|
|
normed.append(d)
|
|
|
|
|
|
|
|
|
|
|
|
with listLock:
|
|
|
|
|
|
before = len(listData)
|
|
|
|
|
|
listData[:] = normed # 全量覆盖
|
|
|
|
|
|
|
|
|
|
|
|
_log_device_changes("SNAPSHOT")
|
|
|
|
|
|
try:
|
|
|
|
|
|
LogManager.info(f"[DEVICE][SNAPSHOT] size={len(normed)} (was={before})")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
print(f"[DEVICE][SNAPSHOT] size={len(normed)} (was={before})")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.error(f"[DEVICE][SNAPSHOT][ERROR] {e}")
|
|
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
def _apply_device_event(obj: Dict[str, Any]):
|
|
|
|
|
|
"""把单条设备上线/下线事件落到 listData,并打印关键日志"""
|
2025-08-01 13:43:51 +08:00
|
|
|
|
try:
|
2025-10-27 16:25:47 +08:00
|
|
|
|
dev_id = obj.get("deviceId")
|
|
|
|
|
|
typ = _normalize_type(obj.get("type", 1))
|
|
|
|
|
|
obj["type"] = typ # 写回规范后的值,避免后续被误判
|
|
|
|
|
|
if dev_id is None:
|
|
|
|
|
|
LogManager.warning(f"[DEVICE][WARN] missing deviceId in obj={obj}")
|
|
|
|
|
|
return
|
|
|
|
|
|
with listLock:
|
|
|
|
|
|
before = len(listData)
|
|
|
|
|
|
# 删除同 udid 旧记录
|
|
|
|
|
|
listData[:] = [d for d in listData if d.get("deviceId") != dev_id]
|
|
|
|
|
|
if typ == 1:
|
|
|
|
|
|
listData.append(obj) # 上线
|
|
|
|
|
|
LogManager.info(f"[DEVICE][UPSERT] id={dev_id} type={typ} size={len(listData)} (replaced={before - (len(listData)-1)})")
|
|
|
|
|
|
_log_device_changes("UPSERT")
|
|
|
|
|
|
else:
|
|
|
|
|
|
LogManager.warning(f"[DEVICE][REMOVE] id={dev_id} type={typ} size={len(listData)} (removed_prev={before - len(listData)})")
|
|
|
|
|
|
_log_device_changes("REMOVE")
|
2025-08-01 13:43:51 +08:00
|
|
|
|
except Exception as e:
|
2025-10-27 16:25:47 +08:00
|
|
|
|
LogManager.error(f"[DEVICE][APPLY_EVT][ERROR] {e}")
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# ============ 设备事件 socket 监听 ============
|
2025-09-15 21:58:47 +08:00
|
|
|
|
def _handle_conn(conn: socket.socket, addr):
|
2025-11-05 17:07:51 +08:00
|
|
|
|
"""统一的连接处理函数(拆 JSON 行 → 正常化 type → 应用到 listData)"""
|
2025-09-15 21:58:47 +08:00
|
|
|
|
try:
|
|
|
|
|
|
with conn:
|
2025-11-05 17:07:51 +08:00
|
|
|
|
try:
|
|
|
|
|
|
conn.settimeout(3.0) # 避免永久阻塞
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2025-09-15 21:58:47 +08:00
|
|
|
|
buffer = ""
|
|
|
|
|
|
while True:
|
2025-11-05 17:07:51 +08:00
|
|
|
|
try:
|
|
|
|
|
|
data = conn.recv(1024)
|
|
|
|
|
|
if not data: # 对端关闭
|
2025-09-15 21:58:47 +08:00
|
|
|
|
break
|
2025-11-05 17:07:51 +08:00
|
|
|
|
buffer += data.decode('utf-8', errors='ignore')
|
|
|
|
|
|
|
|
|
|
|
|
# 按行切 JSON;发送端每条以 '\n' 结尾
|
|
|
|
|
|
while True:
|
|
|
|
|
|
line, sep, buffer = buffer.partition('\n')
|
|
|
|
|
|
if not sep:
|
|
|
|
|
|
break
|
|
|
|
|
|
line = line.strip()
|
|
|
|
|
|
if not line:
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
obj = json.loads(line)
|
|
|
|
|
|
except json.JSONDecodeError as e:
|
|
|
|
|
|
LogManager.warning(f"[SOCKET][WARN] 非法 JSON 丢弃: {line[:120]} err={e}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2025-11-14 16:58:55 +08:00
|
|
|
|
# === 新增:如果是全量快照(包含 devices 字段) ===
|
|
|
|
|
|
if "devices" in obj:
|
|
|
|
|
|
devs = obj.get("devices") or []
|
|
|
|
|
|
LogManager.info(f"[SOCKET][RECV][SNAPSHOT] size={len(devs)} keys={list(obj.keys())}")
|
|
|
|
|
|
try:
|
|
|
|
|
|
_apply_device_snapshot(devs)
|
|
|
|
|
|
LogManager.info(f"[SOCKET][APPLY][SNAPSHOT] size={len(devs)}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.error(f"[DEVICE][APPLY_SNAPSHOT][ERROR] {e}")
|
|
|
|
|
|
continue # 处理完这一条,继续下一条 JSON
|
|
|
|
|
|
|
|
|
|
|
|
# === 否则按原来的单条设备事件处理(兼容旧逻辑) ===
|
2025-11-05 17:07:51 +08:00
|
|
|
|
dev_id = obj.get("deviceId")
|
|
|
|
|
|
typ = _normalize_type(obj.get("type", 1))
|
|
|
|
|
|
obj["type"] = typ # 规范 1/0
|
|
|
|
|
|
LogManager.info(f"[SOCKET][RECV] deviceId={dev_id} type={typ} keys={list(obj.keys())}")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2025-11-14 16:58:55 +08:00
|
|
|
|
_apply_device_event(obj) # 保持你原来的增删逻辑
|
2025-11-05 17:07:51 +08:00
|
|
|
|
LogManager.info(f"[SOCKET][APPLY] deviceId={dev_id} type={typ}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 单条业务异常不让线程死
|
|
|
|
|
|
LogManager.error(f"[DEVICE][APPLY_EVT][ERROR] {e}")
|
|
|
|
|
|
|
|
|
|
|
|
except (socket.timeout, ConnectionResetError, BrokenPipeError):
|
|
|
|
|
|
# 连接级异常:关闭该连接,回到 accept
|
|
|
|
|
|
break
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.warning(f"[SOCKET][WARN] recv error: {e}")
|
|
|
|
|
|
break
|
2025-09-15 21:58:47 +08:00
|
|
|
|
except Exception as e:
|
2025-10-27 16:25:47 +08:00
|
|
|
|
LogManager.error(f"[SOCKET][ERROR] 连接处理异常: {e}")
|
2025-09-15 21:58:47 +08:00
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
def start_socket_listener():
|
2025-11-05 17:07:51 +08:00
|
|
|
|
"""启动设备事件监听(仅走 FLASK_COMM_PORT;增强健壮性,不改业务)"""
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# 统一使用 FLASK_COMM_PORT,默认 34566
|
|
|
|
|
|
port = int(os.getenv('FLASK_COMM_PORT', 34566))
|
|
|
|
|
|
LogManager.info(f"Received port from environment: {port}")
|
|
|
|
|
|
print(f"Received port from environment: {port}")
|
|
|
|
|
|
|
|
|
|
|
|
if port <= 0:
|
|
|
|
|
|
LogManager.info("未获取到通信端口,跳过Socket监听")
|
|
|
|
|
|
print("未获取到通信端口,跳过Socket监听")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-11-05 17:07:51 +08:00
|
|
|
|
backoff = 0.5 # 自愈退避,起于 0.5s,上限 8s
|
|
|
|
|
|
while True:
|
|
|
|
|
|
s = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
|
try:
|
|
|
|
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.warning(f"[SOCKET][WARN] setsockopt SO_REUSEADDR failed: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
s.bind(('127.0.0.1', port))
|
|
|
|
|
|
print(f"[INFO] Socket successfully bound to port {port}")
|
|
|
|
|
|
LogManager.info(f"[INFO] Socket successfully bound to port {port}")
|
|
|
|
|
|
except Exception as bind_error:
|
|
|
|
|
|
print(f"[ERROR]端口绑定失败: {bind_error}")
|
|
|
|
|
|
LogManager.info(f"[ERROR]端口绑定失败: {bind_error}")
|
|
|
|
|
|
# 绑定失败通常是端口未释放/竞争,退避后重试
|
|
|
|
|
|
time.sleep(backoff)
|
|
|
|
|
|
backoff = min(backoff * 2, 8.0)
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2025-11-05 21:04:04 +08:00
|
|
|
|
s.listen(256)
|
2025-11-05 17:07:51 +08:00
|
|
|
|
try:
|
|
|
|
|
|
s.settimeout(1.5) # accept 超时,便于检查自愈循环
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
LogManager.info(f"[INFO] Socket listener started on port {port}, waiting for connections...")
|
|
|
|
|
|
print(f"[INFO] Socket listener started on port {port}, waiting for connections...")
|
|
|
|
|
|
# 监听成功 → 退避复位
|
|
|
|
|
|
backoff = 0.5
|
2025-10-27 16:25:47 +08:00
|
|
|
|
|
2025-11-05 17:07:51 +08:00
|
|
|
|
while True:
|
|
|
|
|
|
try:
|
|
|
|
|
|
conn, addr = s.accept()
|
|
|
|
|
|
except socket.timeout:
|
|
|
|
|
|
# 定期“透气”,避免永久卡死;继续等待
|
|
|
|
|
|
continue
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 发生 accept 级错误:重建 socket(进入外层 while 自愈)
|
|
|
|
|
|
LogManager.error(f"[ERROR] accept 失败: {e}")
|
|
|
|
|
|
break
|
2025-10-27 16:25:47 +08:00
|
|
|
|
|
2025-11-05 17:07:51 +08:00
|
|
|
|
# 每个连接独立线程处理,保持你原来的做法
|
|
|
|
|
|
threading.Thread(target=_handle_conn, args=(conn, addr), daemon=True).start()
|
2025-10-27 16:25:47 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2025-11-05 17:07:51 +08:00
|
|
|
|
# 任意未兜住的异常,记录并进入退避自愈
|
|
|
|
|
|
LogManager.error(f"[SOCKET][ERROR] 监听主循环异常: {e}")
|
|
|
|
|
|
time.sleep(backoff)
|
|
|
|
|
|
backoff = min(backoff * 2, 8.0)
|
|
|
|
|
|
finally:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if s:
|
|
|
|
|
|
s.close()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2025-10-27 16:25:47 +08:00
|
|
|
|
|
|
|
|
|
|
# 独立线程启动 Socket 服务 + 看门狗
|
2025-11-07 16:31:18 +08:00
|
|
|
|
def bootstrap_server_side_effects():
|
|
|
|
|
|
# 仅在真正的 Flask 进程里启动副作用(监听、定时器、MQ 等)
|
|
|
|
|
|
listener_thread = threading.Thread(target=start_socket_listener, daemon=True)
|
|
|
|
|
|
listener_thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取app
|
|
|
|
|
|
def get_app():
|
|
|
|
|
|
return app
|
2025-08-01 13:43:51 +08:00
|
|
|
|
|
2025-11-18 22:09:19 +08:00
|
|
|
|
@app.before_request
|
|
|
|
|
|
def _log_request_start():
|
|
|
|
|
|
g._start_ts = time.time()
|
|
|
|
|
|
LogManager.info(
|
|
|
|
|
|
text=f"[HTTP] START {request.method} {request.path}",
|
|
|
|
|
|
udid="flask"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@app.after_request
|
|
|
|
|
|
def _log_request_end(response):
|
|
|
|
|
|
cost = time.time() - getattr(g, "_start_ts", time.time())
|
|
|
|
|
|
LogManager.info(
|
|
|
|
|
|
text=f"[HTTP] END {request.method} {request.path} {response.status_code} in {cost:.3f}s",
|
|
|
|
|
|
udid="flask"
|
|
|
|
|
|
)
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# ============ API 路由 ============
|
2025-08-01 13:43:51 +08:00
|
|
|
|
@app.route('/deviceList', methods=['GET'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def deviceList():
|
2025-10-27 16:25:47 +08:00
|
|
|
|
global _last_device_count, change_version
|
|
|
|
|
|
global _last_nonempty_snapshot, _last_snapshot_ts, _STICKY_TTL_SEC
|
|
|
|
|
|
global _empty_logged, _recovered_logged
|
2025-08-18 22:20:23 +08:00
|
|
|
|
try:
|
2025-09-11 22:46:55 +08:00
|
|
|
|
with listLock:
|
2025-10-27 16:25:47 +08:00
|
|
|
|
# 宽容判定在线(字符串'1'/'true'/True 都算)
|
|
|
|
|
|
data = [d for d in listData if _is_online(d)]
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
# 记录最近一次非空快照
|
|
|
|
|
|
if data:
|
|
|
|
|
|
_last_nonempty_snapshot = data.copy()
|
|
|
|
|
|
_last_snapshot_ts = now
|
|
|
|
|
|
if _recovered_logged:
|
|
|
|
|
|
LogManager.info(f"[API][deviceList][RECOVERED] count={len(data)} rev={change_version}")
|
|
|
|
|
|
_recovered_logged = False
|
|
|
|
|
|
_empty_logged = False
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 瞬时空:在 TTL 内回退上一份非空快照
|
|
|
|
|
|
if _last_nonempty_snapshot and (now - _last_snapshot_ts) <= _STICKY_TTL_SEC:
|
|
|
|
|
|
LogManager.warning(f"[API][deviceList][STICKY] serving last non-empty snapshot count={len(_last_nonempty_snapshot)} age={now - _last_snapshot_ts:.1f}s rev={change_version}")
|
|
|
|
|
|
return ResultData(data=_last_nonempty_snapshot).toJson()
|
|
|
|
|
|
if not _empty_logged:
|
|
|
|
|
|
LogManager.error(f"[API][deviceList][DROP_TO_EMPTY] last_count={_last_device_count} rev={change_version}")
|
|
|
|
|
|
_empty_logged = True
|
|
|
|
|
|
_recovered_logged = True
|
|
|
|
|
|
|
|
|
|
|
|
_last_device_count = len(data)
|
|
|
|
|
|
LogManager.info(f"[API][deviceList] return_count={len(data)} rev={change_version}")
|
|
|
|
|
|
return ResultData(data=data).toJson()
|
2025-08-18 22:20:23 +08:00
|
|
|
|
except Exception as e:
|
2025-10-27 16:25:47 +08:00
|
|
|
|
LogManager.error(f"[API][deviceList] error={e}")
|
2025-08-18 22:20:23 +08:00
|
|
|
|
return ResultData(data=[]).toJson()
|
2025-08-14 15:40:17 +08:00
|
|
|
|
|
2025-09-24 16:32:05 +08:00
|
|
|
|
@app.route('/passToken', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def passToken():
|
|
|
|
|
|
data = await request.get_json()
|
2025-11-17 17:13:22 +08:00
|
|
|
|
print(json.dumps(data))
|
2025-09-24 16:32:05 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
2025-09-10 22:17:27 +08:00
|
|
|
|
|
2025-08-01 13:43:51 +08:00
|
|
|
|
# 获取设备应用列表
|
|
|
|
|
|
@app.route('/deviceAppList', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def deviceAppList():
|
|
|
|
|
|
param = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = param["udid"]
|
2025-08-08 22:08:10 +08:00
|
|
|
|
apps = ControlUtils.getDeviceAppList(udid)
|
|
|
|
|
|
return ResultData(data=apps).toJson()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
|
2025-08-05 15:41:20 +08:00
|
|
|
|
# 打开指定app
|
2025-08-01 13:43:51 +08:00
|
|
|
|
@app.route('/launchApp', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def launchApp():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
|
|
|
|
|
bundleId = body.get("bundleId")
|
2025-10-23 22:06:31 +08:00
|
|
|
|
t = wda.USBClient(udid, wdaFunctionPort)
|
|
|
|
|
|
t.session().app_start(bundleId)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 回到首页
|
|
|
|
|
|
@app.route('/toHome', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def toHome():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-21 18:19:19 +08:00
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
client.home()
|
|
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 点击事件
|
|
|
|
|
|
@app.route('/tapAction', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def tapAction():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-21 18:19:19 +08:00
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
2025-10-23 18:53:22 +08:00
|
|
|
|
print("-----------------------")
|
|
|
|
|
|
print(client)
|
|
|
|
|
|
print("-----------------------")
|
2025-08-01 13:43:51 +08:00
|
|
|
|
session = client.session()
|
|
|
|
|
|
session.appium_settings({"snapshotMaxDepth": 0})
|
2025-08-14 13:49:28 +08:00
|
|
|
|
x = body.get("x")
|
|
|
|
|
|
y = body.get("y")
|
|
|
|
|
|
session.tap(x, y)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 拖拽事件
|
|
|
|
|
|
@app.route('/swipeAction', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def swipeAction():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-22 19:17:52 +08:00
|
|
|
|
duration = body.get("duration") # 时长
|
|
|
|
|
|
sx = body.get("sx") # 起始X点
|
|
|
|
|
|
sy = body.get("sy") # 起始Y点
|
|
|
|
|
|
ex = body.get("ex") # 结束X点
|
|
|
|
|
|
ey = body.get("ey") # 结束Y点
|
|
|
|
|
|
|
2025-10-21 18:19:19 +08:00
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
session = client.session()
|
|
|
|
|
|
session.appium_settings({"snapshotMaxDepth": 0})
|
|
|
|
|
|
|
2025-10-22 19:17:52 +08:00
|
|
|
|
session.swipe(sx, sy, ex, ey, duration)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 长按事件
|
|
|
|
|
|
@app.route('/longPressAction', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def longPressAction():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
|
|
|
|
|
x = body.get("x")
|
|
|
|
|
|
y = body.get("y")
|
2025-10-21 18:19:19 +08:00
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
session = client.session()
|
|
|
|
|
|
session.appium_settings({"snapshotMaxDepth": 5})
|
2025-08-14 15:40:17 +08:00
|
|
|
|
session.tap_hold(x, y, 1.0)
|
2025-08-01 13:43:51 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 养号
|
|
|
|
|
|
@app.route('/growAccount', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def growAccount():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-23 19:55:58 +08:00
|
|
|
|
Variables.commentList = body.get("comment")
|
2025-10-27 16:54:45 +08:00
|
|
|
|
isComment = body.get("isComment")
|
2025-08-01 13:43:51 +08:00
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
manager = ScriptManager()
|
|
|
|
|
|
event = threading.Event()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
# 启动脚本
|
2025-10-27 16:54:45 +08:00
|
|
|
|
thread = threading.Thread(target=manager.growAccount, args=(udid, isComment, event,))
|
2025-08-06 22:11:33 +08:00
|
|
|
|
# 添加到线程管理
|
2025-09-20 21:57:37 +08:00
|
|
|
|
code, msg = ThreadManager.add(udid, thread, event)
|
|
|
|
|
|
return ResultData(data="", code=code, message=msg).toJson()
|
2025-08-01 13:43:51 +08:00
|
|
|
|
|
2025-08-11 22:06:48 +08:00
|
|
|
|
# 观看直播
|
|
|
|
|
|
@app.route("/watchLiveForGrowth", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def watchLiveForGrowth():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-11 22:06:48 +08:00
|
|
|
|
udid = body.get("udid")
|
|
|
|
|
|
manager = ScriptManager()
|
|
|
|
|
|
event = threading.Event()
|
|
|
|
|
|
thread = threading.Thread(target=manager.watchLiveForGrowth, args=(udid, event))
|
|
|
|
|
|
# 添加到线程管理
|
|
|
|
|
|
ThreadManager.add(udid, thread, event)
|
|
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
# 停止脚本
|
|
|
|
|
|
@app.route("/stopScript", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def stopScript():
|
|
|
|
|
|
body = await request.get_json()
|
2025-08-06 22:11:33 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-09-17 22:23:57 +08:00
|
|
|
|
LogManager.method_info(f"接口收到 /stopScript udid={udid}", method="task")
|
|
|
|
|
|
code, msg = ThreadManager.stop(udid)
|
2025-09-23 20:17:33 +08:00
|
|
|
|
return ResultData(code=code, data=[], message=msg).toJson()
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 关注打招呼
|
2025-08-08 22:08:10 +08:00
|
|
|
|
@app.route('/passAnchorData', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def passAnchorData():
|
2025-09-08 21:42:09 +08:00
|
|
|
|
try:
|
2025-09-18 20:15:07 +08:00
|
|
|
|
LogManager.method_info("关注打招呼", "关注打招呼")
|
2025-11-18 22:09:19 +08:00
|
|
|
|
data: Dict[str, Any] = await request.get_json()
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 设备列表
|
|
|
|
|
|
idList = data.get("deviceList", [])
|
|
|
|
|
|
# 主播列表
|
|
|
|
|
|
acList = data.get("anchorList", [])
|
2025-10-23 19:55:58 +08:00
|
|
|
|
Variables.commentList = data.get("comment")
|
2025-10-27 16:54:45 +08:00
|
|
|
|
isComment = data.get("isComment")
|
2025-09-18 20:15:07 +08:00
|
|
|
|
LogManager.info(f"[INFO] 获取数据: {idList} {acList}")
|
2025-09-08 21:42:09 +08:00
|
|
|
|
AiUtils.save_aclist_flat_append(acList)
|
|
|
|
|
|
# 是否需要回复
|
2025-10-22 18:24:43 +08:00
|
|
|
|
needReply = data.get("needReply", False)
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 获取打招呼数据
|
|
|
|
|
|
ev.prologueList = data.get("prologueList", [])
|
2025-11-04 14:07:46 +08:00
|
|
|
|
needTranslate = data.get("needTranslate", False)
|
|
|
|
|
|
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 添加主播数据
|
|
|
|
|
|
addModelToAnchorList(acList)
|
2025-11-26 16:18:02 +08:00
|
|
|
|
|
|
|
|
|
|
failed_ids = []
|
|
|
|
|
|
# 启动线程,执行脚本(单个设备异常不影响其它设备)
|
2025-09-08 21:42:09 +08:00
|
|
|
|
for udid in idList:
|
2025-11-26 16:18:02 +08:00
|
|
|
|
try:
|
|
|
|
|
|
manager = ScriptManager()
|
|
|
|
|
|
event = threading.Event()
|
|
|
|
|
|
thread = threading.Thread(
|
|
|
|
|
|
target=manager.safe_greetNewFollowers,
|
|
|
|
|
|
args=(udid, needReply, isComment, needTranslate, event,),
|
|
|
|
|
|
)
|
|
|
|
|
|
ThreadManager.add(udid, thread, event)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
failed_ids.append(udid)
|
|
|
|
|
|
LogManager.error(f"[passAnchorData] 设备 {udid} 启动脚本失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# 如果所有设备都失败,可以考虑返回错误码
|
|
|
|
|
|
if failed_ids and len(failed_ids) == len(idList):
|
|
|
|
|
|
return ResultData(
|
|
|
|
|
|
data="",
|
|
|
|
|
|
code=1001,
|
|
|
|
|
|
message=f"所有设备启动失败: {failed_ids}"
|
|
|
|
|
|
).toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 部分失败也算整体成功,只是记录一下
|
|
|
|
|
|
if failed_ids:
|
|
|
|
|
|
LogManager.warning(f"[passAnchorData] 部分设备启动失败: {failed_ids}")
|
|
|
|
|
|
|
2025-09-08 21:42:09 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.error(e)
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data="", code=1001).toJson()
|
2025-08-08 22:08:10 +08:00
|
|
|
|
|
2025-09-22 19:52:45 +08:00
|
|
|
|
@app.route('/followAndGreetUnion', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def followAndGreetUnion():
|
2025-09-22 19:52:45 +08:00
|
|
|
|
try:
|
|
|
|
|
|
LogManager.method_info("关注打招呼", "关注打招呼(联盟号)")
|
2025-11-18 22:09:19 +08:00
|
|
|
|
data: Dict[str, Any] = await request.get_json()
|
2025-11-26 16:18:02 +08:00
|
|
|
|
|
2025-09-22 19:52:45 +08:00
|
|
|
|
# 设备列表
|
|
|
|
|
|
idList = data.get("deviceList", [])
|
|
|
|
|
|
# 主播列表
|
|
|
|
|
|
acList = data.get("anchorList", [])
|
|
|
|
|
|
LogManager.info(f"[INFO] 获取数据: {idList} {acList}")
|
|
|
|
|
|
|
|
|
|
|
|
AiUtils.save_aclist_flat_append(acList)
|
|
|
|
|
|
|
|
|
|
|
|
# 是否需要回复
|
|
|
|
|
|
needReply = data.get("needReply", True)
|
2025-11-04 14:07:46 +08:00
|
|
|
|
needTranslate = data.get("needTranslate", False)
|
2025-09-22 19:52:45 +08:00
|
|
|
|
# 获取打招呼数据
|
|
|
|
|
|
ev.prologueList = data.get("prologueList", [])
|
|
|
|
|
|
|
|
|
|
|
|
# 添加主播数据
|
|
|
|
|
|
addModelToAnchorList(acList)
|
2025-11-26 16:18:02 +08:00
|
|
|
|
|
|
|
|
|
|
failed_ids = []
|
|
|
|
|
|
|
|
|
|
|
|
# 启动线程,执行脚本(单个设备异常不影响其它设备)
|
2025-09-22 19:52:45 +08:00
|
|
|
|
for udid in idList:
|
2025-11-26 16:18:02 +08:00
|
|
|
|
try:
|
|
|
|
|
|
manager = ScriptManager()
|
|
|
|
|
|
event = threading.Event()
|
|
|
|
|
|
thread = threading.Thread(
|
|
|
|
|
|
target=manager.safe_followAndGreetUnion,
|
|
|
|
|
|
args=(udid, needReply, needTranslate, event),
|
|
|
|
|
|
)
|
|
|
|
|
|
ThreadManager.add(udid, thread, event)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
failed_ids.append(udid)
|
|
|
|
|
|
LogManager.error(f"[followAndGreetUnion] 设备 {udid} 启动脚本失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# 如果所有设备都失败,可以返回错误码
|
|
|
|
|
|
if failed_ids and len(failed_ids) == len(idList):
|
|
|
|
|
|
return ResultData(
|
|
|
|
|
|
data="",
|
|
|
|
|
|
code=1001,
|
|
|
|
|
|
message=f"所有设备启动失败: {failed_ids}",
|
|
|
|
|
|
).toJson()
|
|
|
|
|
|
|
|
|
|
|
|
# 部分失败也算整体成功,只是记录一下
|
|
|
|
|
|
if failed_ids:
|
|
|
|
|
|
LogManager.warning(f"[followAndGreetUnion] 部分设备启动失败: {failed_ids}")
|
|
|
|
|
|
|
2025-09-22 19:52:45 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
2025-11-26 16:18:02 +08:00
|
|
|
|
|
2025-09-22 19:52:45 +08:00
|
|
|
|
except Exception as e:
|
2025-11-26 16:18:02 +08:00
|
|
|
|
LogManager.error(f"[followAndGreetUnion] 接口级异常: {e}")
|
2025-09-22 19:52:45 +08:00
|
|
|
|
return ResultData(data="", code=1001).toJson()
|
|
|
|
|
|
|
2025-09-05 16:32:27 +08:00
|
|
|
|
# 获取私信数据
|
|
|
|
|
|
@app.route("/getPrologueList", methods=['GET'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def getPrologueList():
|
2025-09-08 21:42:09 +08:00
|
|
|
|
import Entity.Variables as Variables
|
|
|
|
|
|
return ResultData(data=Variables.prologueList).toJson()
|
2025-08-14 15:40:17 +08:00
|
|
|
|
|
2025-08-08 22:08:10 +08:00
|
|
|
|
# 添加临时数据
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 批量追加主播到 JSON 文件
|
2025-08-08 22:08:10 +08:00
|
|
|
|
@app.route("/addTempAnchorData", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def addTempAnchorData():
|
2025-09-08 21:42:09 +08:00
|
|
|
|
"""
|
|
|
|
|
|
请求体支持:
|
|
|
|
|
|
- 单个对象:{"anchorId": "xxx", "country": "CN"}
|
|
|
|
|
|
- 对象数组:[{"anchorId": "xxx", "country": "CN"}, {"anchorId": "yyy", "country": "US"}]
|
|
|
|
|
|
"""
|
2025-11-18 22:09:19 +08:00
|
|
|
|
data = await request.get_json()
|
2025-09-08 21:42:09 +08:00
|
|
|
|
if not data:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(code=400, message="请求数据为空").toJson()
|
2025-09-08 21:42:09 +08:00
|
|
|
|
# 追加到 JSON 文件
|
2025-10-29 16:54:18 +08:00
|
|
|
|
AiUtils.save_aclist_flat_append(data, "data/acList.json")
|
2025-09-08 21:42:09 +08:00
|
|
|
|
return ResultData(data="ok").toJson()
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
2025-08-14 14:30:36 +08:00
|
|
|
|
# 获取当前屏幕上的聊天信息
|
|
|
|
|
|
@app.route("/getChatTextInfo", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def getChatTextInfo():
|
|
|
|
|
|
data = await request.get_json()
|
2025-08-14 14:30:36 +08:00
|
|
|
|
udid = data.get("udid")
|
2025-10-22 18:24:43 +08:00
|
|
|
|
client = wda.USBClient(udid,wdaFunctionPort)
|
2025-08-14 14:30:36 +08:00
|
|
|
|
session = client.session()
|
|
|
|
|
|
xml = session.source()
|
2025-08-27 21:58:55 +08:00
|
|
|
|
try:
|
|
|
|
|
|
result = AiUtils.extract_messages_from_xml(xml)
|
2025-10-22 18:24:43 +08:00
|
|
|
|
|
|
|
|
|
|
last_in = None
|
|
|
|
|
|
last_out = None
|
|
|
|
|
|
|
|
|
|
|
|
for item in reversed(result): # 从后往前找
|
|
|
|
|
|
if item.get('type') != 'msg':
|
|
|
|
|
|
continue
|
|
|
|
|
|
if last_in is None and item['dir'] == 'in':
|
|
|
|
|
|
last_in = item['text']
|
|
|
|
|
|
if last_out is None and item['dir'] == 'out':
|
|
|
|
|
|
last_out = item['text']
|
|
|
|
|
|
if last_in is not None and last_out is not None:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
print(f"检测出对方的最后一条数据:{last_in},{type(last_in)}")
|
|
|
|
|
|
print(f"检测出我的最后一条数据:{last_out},{type(last_out)}")
|
|
|
|
|
|
|
2025-08-27 21:58:55 +08:00
|
|
|
|
return ResultData(data=result).toJson()
|
|
|
|
|
|
except Exception as e:
|
2025-09-10 22:17:27 +08:00
|
|
|
|
|
|
|
|
|
|
LogManager.error(f"获取屏幕翻译出现错误:{e}", "获取屏幕翻译")
|
|
|
|
|
|
|
2025-08-27 21:58:55 +08:00
|
|
|
|
data = [
|
|
|
|
|
|
{
|
2025-09-16 21:34:15 +08:00
|
|
|
|
'type': 'massage',
|
2025-08-27 21:58:55 +08:00
|
|
|
|
'dir': 'in',
|
|
|
|
|
|
'text': '当前页面无法获取聊天记录,请在tiktok聊天页面进行获取!!!'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-09-16 21:34:15 +08:00
|
|
|
|
'type': 'massage',
|
2025-08-27 21:58:55 +08:00
|
|
|
|
'dir': 'in',
|
|
|
|
|
|
'text': 'Unable to retrieve chat messages on the current screen. Please navigate to the TikTok chat page and try again!!!'
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data=data, message="解析失败").toJson()
|
2025-08-14 14:30:36 +08:00
|
|
|
|
|
2025-08-14 15:40:17 +08:00
|
|
|
|
# 监控消息
|
|
|
|
|
|
@app.route("/replyMessages", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def monitorMessages():
|
2025-09-28 20:42:01 +08:00
|
|
|
|
LogManager.method_info("开始监控消息,监控消息脚本启动", "监控消息")
|
2025-11-18 22:09:19 +08:00
|
|
|
|
body = await request.get_json()
|
2025-08-14 15:40:17 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-22 18:24:43 +08:00
|
|
|
|
# Variables.commentList = body.get("comment")
|
|
|
|
|
|
|
2025-08-14 15:40:17 +08:00
|
|
|
|
manager = ScriptManager()
|
|
|
|
|
|
event = threading.Event()
|
|
|
|
|
|
thread = threading.Thread(target=manager.replyMessages, args=(udid, event))
|
2025-09-28 20:42:01 +08:00
|
|
|
|
LogManager.method_info("创建监控消息脚本线程成功", "监控消息")
|
2025-08-14 15:40:17 +08:00
|
|
|
|
# 添加到线程管理
|
|
|
|
|
|
ThreadManager.add(udid, thread, event)
|
|
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
2025-09-20 21:57:37 +08:00
|
|
|
|
# 上传日志
|
2025-09-04 19:35:36 +08:00
|
|
|
|
@app.route("/setLoginInfo", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def upLoadLogLogs():
|
|
|
|
|
|
data = await request.get_json() # 解析 JSON
|
2025-09-04 19:35:36 +08:00
|
|
|
|
token = data.get("token")
|
|
|
|
|
|
userId = data.get("userId")
|
|
|
|
|
|
tenantId = data.get("tenantId")
|
|
|
|
|
|
ok = LogManager.upload_all_logs("http://47.79.98.113:8101/api/log/upload", token, userId, tenantId)
|
2025-09-01 21:51:36 +08:00
|
|
|
|
if ok:
|
|
|
|
|
|
return ResultData(data="日志上传成功").toJson()
|
|
|
|
|
|
else:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data="", message="日志上传失败").toJson()
|
2025-09-01 21:51:36 +08:00
|
|
|
|
|
2025-09-05 16:32:27 +08:00
|
|
|
|
# 获取当前的主播列表数据
|
|
|
|
|
|
@app.route("/anchorList", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def queryAnchorList():
|
2025-09-19 19:39:32 +08:00
|
|
|
|
# 项目根目录(当前文件在 infos 下,回退两层到根目录)
|
|
|
|
|
|
root_dir = Path(__file__).resolve().parent.parent
|
2025-10-29 16:54:18 +08:00
|
|
|
|
file_path = root_dir / "data" / "acList.json"
|
2025-09-19 19:39:32 +08:00
|
|
|
|
|
2025-09-05 16:32:27 +08:00
|
|
|
|
data = []
|
2025-09-19 19:39:32 +08:00
|
|
|
|
if file_path.exists():
|
2025-09-08 21:42:09 +08:00
|
|
|
|
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 = []
|
2025-09-05 16:32:27 +08:00
|
|
|
|
return ResultData(data=data).toJson()
|
|
|
|
|
|
|
2025-09-18 20:15:07 +08:00
|
|
|
|
# 修改当前的主播列表数据
|
|
|
|
|
|
@app.route("/updateAnchorList", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def updateAnchorList():
|
2025-09-18 20:15:07 +08:00
|
|
|
|
"""
|
|
|
|
|
|
invitationType: 1 普票 2 金票
|
|
|
|
|
|
state: 1 通行(True) / 0 不通行(False)
|
|
|
|
|
|
"""
|
2025-11-18 22:09:19 +08:00
|
|
|
|
data = await request.get_json(force=True, silent=True) or {}
|
2025-09-18 20:15:07 +08:00
|
|
|
|
invitationType = data.get("invitationType")
|
2025-09-20 21:57:37 +08:00
|
|
|
|
state = bool(data.get("state")) # 转成布尔
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
|
|
|
|
|
# 要更新成的值
|
|
|
|
|
|
new_status = 1 if state else 0
|
|
|
|
|
|
|
|
|
|
|
|
# 用工具类解析路径,避免 cwd 影响
|
2025-10-29 16:54:18 +08:00
|
|
|
|
file_path = AiUtils._resolve_path("data/acList.json")
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
|
|
|
|
|
# 加载 JSON
|
|
|
|
|
|
try:
|
|
|
|
|
|
doc = json.loads(file_path.read_text(encoding="utf-8-sig"))
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.error(f"[updateAnchorList] 读取失败: {e}")
|
2025-09-19 19:39:32 +08:00
|
|
|
|
return ResultData(code=1001, message=f"暂无数据").toJson()
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
|
|
|
|
|
# 定位 anchorList
|
|
|
|
|
|
if isinstance(doc, list):
|
|
|
|
|
|
acList = doc
|
|
|
|
|
|
wrapper = None
|
|
|
|
|
|
elif isinstance(doc, dict) and isinstance(doc.get("anchorList"), list):
|
|
|
|
|
|
acList = doc["anchorList"]
|
|
|
|
|
|
wrapper = doc
|
|
|
|
|
|
else:
|
2025-09-19 19:39:32 +08:00
|
|
|
|
return ResultData(code=500, message="文件格式不合法").toJson()
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
|
|
|
|
|
# 遍历并更新
|
|
|
|
|
|
updated = 0
|
|
|
|
|
|
for item in acList:
|
|
|
|
|
|
if isinstance(item, dict) and item.get("invitationType") == invitationType:
|
2025-09-18 21:31:56 +08:00
|
|
|
|
item["state"] = new_status
|
2025-09-18 20:15:07 +08:00
|
|
|
|
updated += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 写回(保持原始结构)
|
|
|
|
|
|
try:
|
|
|
|
|
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
to_write = wrapper if wrapper is not None else acList
|
|
|
|
|
|
file_path.write_text(json.dumps(to_write, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.error(f"[updateAnchorList] 写入失败: {e}")
|
2025-09-19 19:39:32 +08:00
|
|
|
|
return ResultData(code=500, message=f"写入失败: {e}").toJson()
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
|
|
|
|
|
if updated:
|
2025-09-19 19:39:32 +08:00
|
|
|
|
return ResultData(data=updated, message=f"已更新 {updated} 条记录").toJson()
|
2025-09-18 20:15:07 +08:00
|
|
|
|
else:
|
2025-09-19 19:39:32 +08:00
|
|
|
|
return ResultData(data=0, message="未找到符合条件的记录").toJson()
|
2025-09-18 20:15:07 +08:00
|
|
|
|
|
2025-09-05 16:32:27 +08:00
|
|
|
|
# 删除主播
|
|
|
|
|
|
@app.route("/deleteAnchorWithIds", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def deleteAnchorWithIds():
|
|
|
|
|
|
ls: list[dict] = await request.get_json() # [{"anchorId": "xxx"}, ...]
|
2025-09-08 21:42:09 +08:00
|
|
|
|
ids = [d.get("anchorId") for d in ls if d.get("anchorId")]
|
|
|
|
|
|
deleted = AiUtils.delete_anchors_by_ids(ids)
|
|
|
|
|
|
return ResultData(data={"deleted": deleted}).toJson()
|
|
|
|
|
|
|
2025-09-20 21:57:37 +08:00
|
|
|
|
# 配置ai人设
|
2025-09-11 19:02:00 +08:00
|
|
|
|
@app.route("/aiConfig", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def aiConfig():
|
|
|
|
|
|
data = await request.get_json()
|
2025-09-11 19:02:00 +08:00
|
|
|
|
agentName = data.get("agentName")
|
|
|
|
|
|
guildName = data.get("guildName")
|
|
|
|
|
|
contactTool = data.get("contactTool")
|
|
|
|
|
|
contact = data.get("contact")
|
|
|
|
|
|
|
2025-10-22 18:24:43 +08:00
|
|
|
|
age = data.get("age")
|
|
|
|
|
|
sex = data.get("sex")
|
|
|
|
|
|
height = data.get("height")
|
|
|
|
|
|
weight = data.get("weight")
|
|
|
|
|
|
body_features = data.get("body_features")
|
|
|
|
|
|
nationality = data.get("nationality")
|
|
|
|
|
|
personality = data.get("personality")
|
|
|
|
|
|
strengths = data.get("strengths")
|
|
|
|
|
|
|
2025-09-11 19:02:00 +08:00
|
|
|
|
dict = {
|
|
|
|
|
|
"agentName": agentName,
|
|
|
|
|
|
"guildName": guildName,
|
|
|
|
|
|
"contactTool": contactTool,
|
2025-10-22 18:24:43 +08:00
|
|
|
|
"contact": contact,
|
|
|
|
|
|
"age": age,
|
|
|
|
|
|
"sex": sex,
|
|
|
|
|
|
"height": height,
|
|
|
|
|
|
"weight": weight,
|
|
|
|
|
|
"body_features": body_features,
|
|
|
|
|
|
"nationality": nationality,
|
|
|
|
|
|
"personality": personality,
|
|
|
|
|
|
"strengths": strengths,
|
|
|
|
|
|
"api-key": "app-sdRfZy2by9Kq7uJg7JdOSVr8"
|
2025-09-11 19:02:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 21:57:37 +08:00
|
|
|
|
# JsonUtils.write_json("aiConfig", dict)
|
|
|
|
|
|
IOSAIStorage.overwrite(dict, "aiConfig.json")
|
2025-09-11 19:02:00 +08:00
|
|
|
|
return ResultData(data="").toJson()
|
2025-09-08 21:42:09 +08:00
|
|
|
|
|
2025-09-11 21:14:57 +08:00
|
|
|
|
# 查询主播聊天发送的最后一条信息
|
|
|
|
|
|
@app.route("/select_last_message", methods=['GET'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def select_last_message():
|
2025-09-11 21:14:57 +08:00
|
|
|
|
data = JsonUtils.query_all_json_items()
|
|
|
|
|
|
return ResultData(data=data).toJson()
|
|
|
|
|
|
|
2025-09-20 21:57:37 +08:00
|
|
|
|
# 修改消息(已读改成未读)
|
2025-09-11 21:14:57 +08:00
|
|
|
|
@app.route("/update_last_message", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def update_last_message():
|
|
|
|
|
|
data = await request.get_json() # 解析 JSON
|
2025-09-11 21:14:57 +08:00
|
|
|
|
sender = data.get("sender")
|
|
|
|
|
|
udid = data.get("device")
|
|
|
|
|
|
text = data.get("text")
|
|
|
|
|
|
|
|
|
|
|
|
updated_count = JsonUtils.update_json_items(
|
|
|
|
|
|
match={"sender": sender, "text": text}, # 匹配条件
|
2025-10-22 18:24:43 +08:00
|
|
|
|
patch={"status": 1}, # 修改内容
|
|
|
|
|
|
filename="log/last_message.json", # 要修改的文件
|
|
|
|
|
|
multi=True # 只改第一条匹配的
|
2025-09-11 21:14:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
if updated_count > 0:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data=updated_count, message="修改成功").toJson()
|
|
|
|
|
|
return ResultData(data=updated_count, message="修改失败").toJson()
|
2025-09-11 21:14:57 +08:00
|
|
|
|
|
2025-09-20 21:57:37 +08:00
|
|
|
|
# 删除已读消息
|
2025-09-11 21:14:57 +08:00
|
|
|
|
@app.route("/delete_last_message", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def delete_last_message():
|
|
|
|
|
|
data = await request.get_json() # 解析 JSON
|
2025-09-11 21:14:57 +08:00
|
|
|
|
sender = data.get("sender")
|
|
|
|
|
|
udid = data.get("device")
|
|
|
|
|
|
text = data.get("text")
|
|
|
|
|
|
|
|
|
|
|
|
updated_count = JsonUtils.delete_json_items(
|
|
|
|
|
|
match={"sender": sender, "text": text}, # 匹配条件
|
2025-10-22 18:24:43 +08:00
|
|
|
|
filename="log/last_message.json", # 要修改的文件
|
|
|
|
|
|
multi=True # 只改第一条匹配的
|
2025-09-11 21:14:57 +08:00
|
|
|
|
)
|
|
|
|
|
|
if updated_count > 0:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data=updated_count, message="修改成功").toJson()
|
2025-10-22 18:24:43 +08:00
|
|
|
|
|
2025-09-18 20:09:52 +08:00
|
|
|
|
return ResultData(data=updated_count, message="修改失败").toJson()
|
2025-09-11 21:14:57 +08:00
|
|
|
|
|
2025-10-22 18:24:43 +08:00
|
|
|
|
# 停止所有任务
|
2025-09-19 19:39:32 +08:00
|
|
|
|
@app.route("/stopAllTask", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def stopAllTask():
|
|
|
|
|
|
idList = await request.get_json()
|
2025-11-07 21:58:02 +08:00
|
|
|
|
code, msg, data = ThreadManager.batch_stop(idList)
|
|
|
|
|
|
return ResultData(code, data, msg).toJson()
|
2025-09-11 21:14:57 +08:00
|
|
|
|
|
2025-09-28 20:42:01 +08:00
|
|
|
|
# 切换账号
|
|
|
|
|
|
@app.route('/changeAccount', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def changeAccount():
|
|
|
|
|
|
body = await request.get_json()
|
2025-09-28 20:42:01 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-10-22 18:24:43 +08:00
|
|
|
|
if not udid:
|
|
|
|
|
|
return ResultData(data="", code=400, message="缺少 udid").toJson()
|
2025-09-28 20:42:01 +08:00
|
|
|
|
|
|
|
|
|
|
manager = ScriptManager()
|
2025-10-22 18:24:43 +08:00
|
|
|
|
threading.Event()
|
2025-09-28 20:42:01 +08:00
|
|
|
|
|
|
|
|
|
|
# 启动脚本
|
2025-10-22 18:24:43 +08:00
|
|
|
|
code, msg = manager.changeAccount(udid)
|
|
|
|
|
|
# thread = threading.Thread(target=, args=(udid,))
|
|
|
|
|
|
# # 添加到线程管理
|
|
|
|
|
|
# thread.start()
|
2025-09-28 20:42:01 +08:00
|
|
|
|
return ResultData(data="", code=code, message=msg).toJson()
|
2025-09-12 21:36:47 +08:00
|
|
|
|
|
2025-10-31 15:51:09 +08:00
|
|
|
|
# 查看设备网络状态
|
|
|
|
|
|
@app.route('/getDeviceNetStatus', methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def getDeviceNetStatus():
|
|
|
|
|
|
body = await request.get_json()
|
2025-10-31 15:51:09 +08:00
|
|
|
|
udid = body.get("udid")
|
2025-11-18 22:09:19 +08:00
|
|
|
|
# 同步且超级慢的逻辑 → 丢到线程池,不阻塞事件循环
|
|
|
|
|
|
def _work():
|
|
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
|
|
|
|
|
r = client.getNetWorkStatus()
|
|
|
|
|
|
return r.get("value")
|
|
|
|
|
|
value = await anyio.to_thread.run_sync(_work)
|
|
|
|
|
|
|
2025-10-31 15:51:09 +08:00
|
|
|
|
return ResultData(data=value, code=200).toJson()
|
|
|
|
|
|
|
2025-10-24 22:04:28 +08:00
|
|
|
|
# 获取ai配置
|
|
|
|
|
|
@app.route("/getAiConfig", methods=['GET'])
|
|
|
|
|
|
def getAiConfig():
|
|
|
|
|
|
data = IOSAIStorage.load("aiConfig.json")
|
|
|
|
|
|
return ResultData(data=data).toJson()
|
2025-10-22 18:24:43 +08:00
|
|
|
|
|
2025-10-28 15:37:20 +08:00
|
|
|
|
# 重新开启tiktok
|
|
|
|
|
|
@app.route("/restartTikTok", methods=['POST'])
|
2025-11-18 22:09:19 +08:00
|
|
|
|
async def restartTikTok():
|
|
|
|
|
|
json = await request.get_json()
|
2025-10-28 15:37:20 +08:00
|
|
|
|
udid = json.get("udid")
|
|
|
|
|
|
client = wda.USBClient(udid, wdaFunctionPort)
|
|
|
|
|
|
session = client.session()
|
|
|
|
|
|
ControlUtils.closeTikTok(session, udid)
|
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
ControlUtils.openTikTok(session, udid)
|
|
|
|
|
|
return ResultData(data="").toJson()
|
|
|
|
|
|
|
2025-11-25 18:13:02 +08:00
|
|
|
|
# 健康检查
|
|
|
|
|
|
@app.get("/health")
|
|
|
|
|
|
async def health():
|
|
|
|
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
2025-08-01 13:43:51 +08:00
|
|
|
|
if __name__ == '__main__':
|
2025-11-07 16:31:18 +08:00
|
|
|
|
# 只有“明确是 Flask 进程”才跑副作用(通过 APP_ROLE 控制)
|
|
|
|
|
|
app.run("0.0.0.0", port=5000, debug=False, use_reloader=False, threaded=True)
|