一、平台侧(账号 → 发布计划页) - 新表 video_plan(schema v9→v10):账号×发布日期×编号 → 素材 + 标题 + 发布状态 + 分享链接; 状态机 pending/ready/pushing/publishing/done/failed/unknown/skipped(**failed 与 unknown 必须分开**: 推送阶段的失败可安全重试;碰过抖音之后的岔子只能算"结果未知",绝不自动重发) - 素材上传:文件名 `手机号_日期_编号`(编号可省)解析配对;标题 txt `标题内容_手机号_日期_编号`; 内容寻址落盘 data/videos/YYYY-MM/(sha1 分块算,同名不存两份),**不进整库备份**但进 manifest 反查 - 新蓝图 web/video_plan_api.py:上传/时间线/统计/单条增删改/推送到手机/标记结果/裁决/链接导出 CSV/ 任务列表与一键新建、**就地编辑**(GET/PUT /tasks/<id>)、**一键推送**(POST /push_all,按设备分组、设备内串行) - 账号页拆子分栏(台账 / 发布计划)+ static/admin/release.js;清理 job(04:41 僵尸回收+过期行、04:47 素材文件) - 上传体积:MAX_CONTENT_LENGTH(默认 2GiB)+ 413 JSON + nginx client_max_body_size(修现有 APK 上传隐患) 二、任务侧(平台推素材,抖音流程你自己写) - 新步骤 push_release「推送发布视频」:原子占位 → adb push → **touch 改成"现在"** → 清旧目录同名副本 → 触发扫描并**按路径**校验相册索引 → 标题写进剪贴板;默认目录 /sdcard/DCIM/Camera - 新步骤 mark_release「标记发布结果」:回写 done/failed/unknown,成功时抓作品分享链接、删手机素材 - input_text 支持 text_source=release_title(自动取计划标题 + 回读校验); if_el 的候选值来源新增 release(**本机当前发布计划**的抖音号/昵称,发布前校验"登的是不是要发的号") - build_release_steps 骨架 15 步:⓪ 亮屏 → ① 打开抖音(等首页) → ② 点「我」→ ③ 等抖音号出现 → ④ 条件判断(账号) → then ⑤ 推送 ⑥⑦⑧⑨⑩⑪⑫ 抖音点击/填标题 → ⑬ 标记 / else 发通知跳过 三、修(推送这一路的检测机制) - **uiautomator2 3.x 的 d.shell() 返回 ShellResponse(tuple 子类)不是 str**:`'x' in resp` 恒 False、 `.strip()` 不存在 → "推上去的文件大小不对"每次都判失败(文件其实推上去了)、相册校验永远报没进、 删除确认永远判没删掉。新增 publish_flow._sh() 统一取 .output;大小改成解析 ls -l 的大小列 - **adb push 保留本地 mtime** → 推 3 天前上传的素材在按时间排序的相册里排不到最前, "点第一个 = 刚推的那个"不成立 → 推完 touch - 相册校验**按路径**比(MediaStore 的 _data 会把目录小写、/storage/emulated/0 ≡ /sdcard), 只比文件名会被老目录的同名残留骗过去 - 屏幕没亮就启动抖音会永远停在启动页(UI 树为空)→ 后面"点我/等抖音号"必然 miss, 最后报成误导人的"账号不符" → 骨架第一步固定加「亮屏」,open_app 等「首页」出现 四、其它 - core/ledger.serial_of():设备名 → 当前地址(设备换 IP 后快照是错的) - 通知事件 task.video.published / task.video.failed;备份清单加 video_plan 与素材统计 - 文档同步:DATA_MODEL §2.11 + schema v10、API(新接口与语义)、TASK_DEV §4.7 专章、 ARCHITECTURE(账号页子分栏/release.js/两个 job)、DEPLOY(表数/nginx)、NOTIFY、DEVELOPMENT、README
137 lines
7.1 KiB
Python
137 lines
7.1 KiB
Python
"""项目根配置:只放核心程序级配置。
|
||
|
||
任务自身的参数(观看时长、点赞开关、滑动速度等)不在这里,
|
||
放各自 tasks/xxx.py 顶部,保持每个任务自包含、互不干扰。
|
||
|
||
改 STF 地址/token、adb 路径、web 端口都改这里。
|
||
|
||
安全:STF_TOKEN / WEB_SECRET_KEY 建议用环境变量注入(生产不要提交到 git)。
|
||
支持 `.env` 文件(项目根目录),也可直接 `export STF_TOKEN=xxx`。
|
||
"""
|
||
import os
|
||
|
||
# 可选加载 .env 文件(不存在则忽略)
|
||
_env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
||
if os.path.exists(_env_path):
|
||
try:
|
||
with open(_env_path, encoding="utf-8") as _f:
|
||
for _line in _f:
|
||
_line = _line.strip()
|
||
if _line and not _line.startswith("#") and "=" in _line:
|
||
_k, _v = _line.split("=", 1)
|
||
os.environ.setdefault(_k.strip(), _v.strip())
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
def _env(key, default):
|
||
"""读环境变量,未设置则返回默认值。"""
|
||
return os.environ.get(key, default)
|
||
|
||
|
||
# ================== STF 配置(已废弃,阶段 3 摘除 STF 后仅保留供历史参考) ==================
|
||
STF_URL = _env("STF_URL", "")
|
||
STF_TOKEN = _env("STF_TOKEN", "") # 写入 .env,不要提交到 git
|
||
|
||
# ================== adb 路径 ==================
|
||
# adb 二进制及依赖统一放在 bin/adb/ 下
|
||
# Windows: adb.exe(依赖同目录的 dll)
|
||
# Linux: adb(需 chmod +x,Linux 版二进制)
|
||
# 代码自动识别操作系统,用户只需把对应平台的 adb 放进 bin/adb/
|
||
import sys as _sys
|
||
_ADB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin", "adb")
|
||
if _sys.platform == "win32":
|
||
ADB_PATH = os.path.join(_ADB_DIR, "adb.exe")
|
||
else:
|
||
# Linux / macOS:用 bin/adb/adb(无 .exe 后缀)
|
||
# 部署后需执行 chmod +x bin/adb/adb
|
||
ADB_PATH = os.path.join(_ADB_DIR, "adb")
|
||
|
||
# ================== 数据库 ==================
|
||
# 平台支持 MySQL(正式用法)与 SQLite(仅作回退,见 core/db_config.py)。
|
||
#
|
||
# DEPLOY_ENV 声明"这套配置连的是哪个环境的库",与库名一一绑定:
|
||
# dev → auto_control_dev(本地开发,可随意折腾)
|
||
# prod → auto_control (正式数据)
|
||
# 启动时会校验「.env 声明的环境」与「目标库登记的环境」是否一致,
|
||
# 不一致直接拒绝启动——防止把开发配置连到生产库上。
|
||
DEPLOY_ENV = _env("DEPLOY_ENV", "dev").strip().lower()
|
||
|
||
DB_HOST = _env("DB_HOST", "") # 为空则回退 SQLite(生产禁止静默回退)
|
||
DB_PORT = int(_env("DB_PORT", "3306"))
|
||
DB_USER = _env("DB_USER", "")
|
||
DB_PASSWORD = _env("DB_PASSWORD", "") # 写入 .env,不要提交到 git
|
||
DB_NAME = _env("DB_NAME", "")
|
||
DB_CHARSET = _env("DB_CHARSET", "utf8mb4")
|
||
# 排序规则用 _bin(逐码点比较,等价 SQLite 的大小写敏感语义)。
|
||
# 用默认的 utf8mb4_general_ci 会让 Admin/admin、Phone1/phone1 被判重复。
|
||
DB_COLLATION = _env("DB_COLLATION", "utf8mb4_bin")
|
||
|
||
# 完整连接串(优先级最高,迁移/回归脚本用它临时指向别的库)
|
||
DATABASE_URL = _env("DATABASE_URL", "")
|
||
|
||
# 逃生阀(默认关;仅在你明确知道自己在干什么时打开)
|
||
DB_ALLOW_ENV_MISMATCH = _env("DB_ALLOW_ENV_MISMATCH", "0") == "1"
|
||
DB_ALLOW_SQLITE_FALLBACK = _env("DB_ALLOW_SQLITE_FALLBACK", "0") == "1"
|
||
|
||
# ================== Web 后台 ==================
|
||
WEB_HOST = "0.0.0.0"
|
||
# 端口必须避开 Windows 动态端口范围(本机被改成 1024-15000,默认 49152-65535)
|
||
# 用 18050 处于动态范围之外,不会被出站连接随机占用,避免 WinError 10013
|
||
WEB_PORT = 18050
|
||
|
||
# ================== 数据目录 ==================
|
||
# 持久化数据(分组、任务计划)存放目录
|
||
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
|
||
|
||
# APK 文件存储目录(应用管理功能)
|
||
APK_DIR = os.path.join(DATA_DIR, "apks")
|
||
|
||
# 视频发布计划的素材目录(发布计划功能)。**支持 env 覆盖**,理由同下面三个:
|
||
# 自动化测试必须把素材目录指到临时位置,否则测试上传的视频会落进用户真实数据目录。
|
||
VIDEO_DIR = _env("DATA_VIDEO_DIR", "") or os.path.join(DATA_DIR, "videos")
|
||
|
||
# 单次请求体上限(字节)。**必须大于现有 APK 上传的最大包**(实测 336MB),
|
||
# 否则「应用管理」的 APK 上传会被一起卡死。默认 2GiB:一个视频 + 余量。
|
||
MAX_CONTENT_LENGTH = int(_env("MAX_UPLOAD_MB", "2048")) * 1024 * 1024
|
||
|
||
# ================== 系统数据备份(导出/导入) ==================
|
||
# 导出 zip 与恢复前快照存放;导入暂存目录;待下次启动生效的恢复目录
|
||
# (运行时产物,不入 git,见 .gitignore data/backups 等)
|
||
#
|
||
# 三个目录都支持用环境变量改到别处——自动化测试必须把恢复目录指到临时位置,
|
||
# 否则测试造出来的"待生效恢复任务"会在服务下次重启时被真当成用户的操作消费掉。
|
||
BACKUP_DIR = _env("DATA_BACKUP_DIR", "") or os.path.join(DATA_DIR, "backups")
|
||
RESTORE_STAGING_DIR = _env("DATA_RESTORE_STAGING_DIR", "") or os.path.join(DATA_DIR, "restore_staging")
|
||
RESTORE_PENDING_DIR = _env("DATA_RESTORE_PENDING_DIR", "") or os.path.join(DATA_DIR, "restore_pending")
|
||
|
||
# ================== SSH 到部署机(已废弃,STF 相关操作退役) ==================
|
||
# 阶段 3 后代码不再使用 SSH;保留配置仅供手动运维 220(如 docker stop stf)。
|
||
STF_SSH_TARGET = _env("STF_SSH_TARGET", "")
|
||
STF_SSH_PASSWORD = _env("STF_SSH_PASSWORD", "")
|
||
STF_DOCKER_CONTAINER = _env("STF_DOCKER_CONTAINER", "")
|
||
STF_ADB_CONTAINER = _env("STF_ADB_CONTAINER", "")
|
||
STF_SCRIPT_PATH = _env("STF_SCRIPT_PATH", "")
|
||
|
||
# ================== USB 设备远程 adb server(阶段 2) ==================
|
||
# USB 设备插在 220(STF 主机)上,本机 adb 看不到;220 的 adb 容器是 host 网络
|
||
# 模式,5037 已监听所有网卡(含 Tailscale),平台用 adb -H/-P 直接驱动。
|
||
# 平台自身 USB(serial 无冒号且在本机 adb 里)不受影响,仍走本地。
|
||
USB_ADB_HOST = _env("USB_ADB_HOST", "100.100.10.1") # 220 的 Tailscale IP
|
||
USB_ADB_PORT = int(_env("USB_ADB_PORT", "5037"))
|
||
|
||
# ================== Tailscale 管理(工具页) ==================
|
||
# 调用官方 API v2 管理 tailnet 设备。配置从 .env 读取:
|
||
# TAILSCALE_API_KEY:后台 → Settings → API Access Tokens 生成(或 OAuth client_id:secret)
|
||
# TAILSCALE_TAILNET:tailnet 名称/ID,个人账号一般是登录邮箱前缀
|
||
# 安全:密钥用环境变量/.env 注入,不要写死在代码或提交到 git。
|
||
TAILSCALE_API_KEY = _env("TAILSCALE_API_KEY", "") # 写入 .env,不要提交到 git
|
||
TAILSCALE_TAILNET = _env("TAILSCALE_TAILNET", "")
|
||
|
||
# ================== 设备自动发现 ==================
|
||
# 定时扫描网段中开放 adb 5555 的设备 → 待连接池(用户确认后才加入正式池)。
|
||
# 网段默认:局域网 + Tailscale;可在工具页设备池面板修改(存 app_meta)。
|
||
DISCOVERY_PORT = int(_env("DISCOVERY_PORT", "5555"))
|
||
DISCOVERY_SUBNETS = ["192.168.20.0/24", "100.100.10.0/24"]
|
||
DISCOVERY_INTERVAL = int(_env("DISCOVERY_INTERVAL", "60"))
|