Files
auto_control/config.py
T
butubb beda0f3779 feat(发布计划): 改按名字发(文件名 名字_日期_编号)+ 账号管理整页下线 + 自动切号一轮发完
发布计划不再绑设备、不再依赖账号台账:上传文件名 `名字_日期_编号`(标题行
`标题 || 正文_名字_日期_编号`),运行时读本机当前抖音昵称取计划;靠循环块
「一轮把本机待发的号跑完」自动切号,一直发到发完。

· 解析/配对换键:右锚定认名字(不许含下划线、不许纯数字 —— 不合法直接拒收并说清原因),
  删掉三处"手机号必须命中台账"的校验,加索引 ix_video_plan_name_date(启动时补建)
· 新步骤三个:读本机账号名字 / 切换账号(**按名字点控件**、切完回读校验、遇登录页停本设备)/
  循环本机待发的号(试过不回头、跑一轮没进展就换号、停止·超时·上限三个干净出口)
· 发布骨架改 18 步:读名字 → 校验账号(cmp_source=account_target)→ 推送(**推送前重读昵称核对**)
  → 抖音流程 → 标记结果;push_release 的 account 默认 name
· 账号管理整页下线:删 core/ledger.py(serial_of 迁到 core/device_pool.py)、6 个 /api/ledger*、
  「账号」页与 ledger.js、设备池「账号」列、设备端账号块;发布计划升为一级页签;
  device_account 表与 42 行历史数据保留、仍在备份清单内(**停用不等于删数据**)
· 人工入口「按名字找设备」:新增 core/account_probe.py(只读探测、跳过在跑任务的设备)
  + POST /api/video_plan/push_by_account,没设备登着的号逐条报出来;
  推送时把"实际是谁发的"写进 posted_serial / posted_device_name
· 通知:新增 task.account.switch_failed / task.account.need_login
· schema v12(posted_*)/ v13(device.cur_account_name / cur_name_at)
· 文档同步:TASK_DEV / API / DATA_MODEL / ARCHITECTURE / NOTIFY / DEVICE_AGENT / DEPLOY /
  DEVELOPMENT / README / .env.example(两个抖音控件选择器抓一次填 .env)
· 新增 scripts/tests/:6 套用例 + run_all.py(临时库 + 假设备,不碰真实数据、不连真机)
2026-10-12 09:02:40 +08:00

151 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""项目根配置:只放核心程序级配置。
任务自身的参数(观看时长、点赞开关、滑动速度等)不在这里,
放各自 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")
# ================== 抖音「我」页面的昵称控件(发布计划按名字发) ==================
# **抓一次填这里**(用「抓取元素」抓抖音「我」页面上的昵称那个 TextView)。
# 一处配置、三处一致:发布任务骨架的「读取本机账号名字」步骤默认值、
# 「切换账号」的切后校验、以及「发布计划 → 一键推送(按名字找设备)」的实时探测。
# 任务步骤里仍可单独改(存进那一步的参数),这里只是**默认值**。
# 留空 = 还没抓:相关步骤会明确报"请抓取元素",绝不会猜一个名字去发。
DOUYIN_NAME_SELECTOR_TYPE = _env("DOUYIN_NAME_SELECTOR_TYPE", "xpath")
DOUYIN_NAME_SELECTOR_VALUE = _env("DOUYIN_NAME_SELECTOR_VALUE", "")
# 「我」页面 →「切换账号」列表那个入口(同样是**抓一次填这里**):自动切号要用。
# 留空 = 「切换账号」步骤会明确报"请抓取元素"并跳过(不会瞎点)。
DOUYIN_SWITCH_SELECTOR_TYPE = _env("DOUYIN_SWITCH_SELECTOR_TYPE", "xpath")
DOUYIN_SWITCH_SELECTOR_VALUE = _env("DOUYIN_SWITCH_SELECTOR_VALUE", "")
# 单次请求体上限(字节)。**必须大于现有 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"))