butubb
|
b98e6deac1
|
feat(发布计划): 视频发布计划(批量上传配对 → 时间线 → 推送到手机 → 发布任务 → 分享链接)
一、平台侧(账号 → 发布计划页)
- 新表 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
|
2026-09-28 15:59:09 +08:00 |
|
butubb
|
7ce4ae9016
|
feat(账号): 账号台账(web 页 + 任务取号 + 设备端身份页);fix(剪贴板): 注入通道改走设备端 Agent
一、账号台账(新表 device_account,schema v8)
- core/ledger.py:CRUD、Excel 粘贴解析(Tab 分隔 / 表头乱序缺列 / 是·否布尔 / 逐行留痕)、
按范围取号(本机台账 / 全部 / 按设备分组)、设备端账号块、dry_run 预览
- 新「账号」顶级 Tab(权限 devices):列表 + 搜索 + 设备筛选 + 增删改 +
粘贴导入(默认先预览再写,逐行显示 新增/覆盖/跳过/失败 + 留痕)
- 设备池加「账号 N」列(0 个显示"未登记"),点开看该设备账号明细
- 任务「条件判断」新增 cmp_source / cmp_group:候选值可直接来自台账,
与手填值**合并(OR)**;取不到号时回落手填值,并把原因写进步骤明细与日志
(否则"永远走 else 分支"而任务照样显示成功,最难查)
- 设备端契约:身份页多推 accounts_b64(base64 JSON;默认不含手机号;
超预算按整条丢且绝不算字节切),见 doc/DEVICE_AGENT.md §5.2.1
- 备份/文档红线:TABLE_LABELS 加「账号台账」;DATA_MODEL/API/ARCHITECTURE/DEPLOY/
TASK_DEV/DEVICE_AGENT/DEVELOPMENT/README 同步;顺手补上 DATA_MODEL 漏列的 done_mark
⚠ 台账里的抖音号是**纯号**,只当"比对用的候选值":绝不能拿去填「去重」的身份元素
(身份是元素原文逐字算 key,格式不同会让去重静默失效)。代码与文档都写明了。
二、剪贴板注入通道(修:平台还在调早期的独立 APK)
- 改为按顺序尝试:设备端 Agent(com.example.deviceagent/.ClipActivity)→
旧版独立 ClipInject 兜底;两个都没有时报"需要设备端 Agent"(不再只说 ClipInject)
- 读回验证改成**轮询到 3 秒**:透明 Activity 要等窗口拿到焦点才写,
原来只睡 0.5s 会读到上一次的内容 → 误报"写入可能被拒"(实测内容已写入却回失败)
|
2026-09-24 16:12:58 +08:00 |
|
butubb
|
876224f876
|
feat(去重): 跨设备「已做过」账本 —— 同一个号不会做两次 + 「谁做过了」看得见
用户场景(他原话):一台手机登录 5 个抖音号、一共 5 台手机,每个任务只让其中一个
目标号评论;每天跑一次但不知道什么时候跑完,于是"一直重复跑" → 结果
"一个手机还没评论到,一个手机都评论两次了"。
**根因不是"单设备重复",是跨设备没有共享的判断 + 进度不可见。** 所以做两件事:
① 幂等;② 把"谁做过了、还差谁"摆到台面上(不然只能靠重跑确认,而重跑又在制造重复)。
- `core/models.py`:新表 `done_mark`(迁移账本补 v7)。**判据只有 `scope_key` 的
唯一索引**——多台设备会同时判断"没做过","先查后插"有竞态(两台都插),
唯一索引 + `INSERT ... ON DUPLICATE KEY`/`INSERT OR IGNORE` 的**受影响行数**才原子。
- `core/dedup.py`(新):`build_key`(`任务|身份|时间桶`)/ `check` / `mark` /
`list_marks`(带"今天做了几台/几个号"统计)/ `delete_mark` / `clear_job` / `purge_old`。
自建 app context(照 device_pool 的 `_ctx()`),任务线程/Web/清理都不用关心。
- 任务侧两个部件(**检查在前、记账在后**):
· `if_el` 新增条件类型 `selector_type="dedup"`:命中=这个身份做过了 → 走 then 分支。
身份元素在 `ident_type`/`ident_value`(留空 = 用设备 serial,一号一机场景)。
· 新步骤 `mark_done`「记为已做」(22 种步骤):放动作**成功之后**。
拆两步的用意:动作失败就不记账,下次重跑还会重试该设备 —— 失败不丢。
- 有效期(`dedup_reset` = day/all/hours)放**任务级**:检查与记账两处各填一份的话,
填不一致就算出两个 key、去重会**静默失效**,所以强制只配一处(编辑器顶部下拉)。
- 三条防误伤规则(都有测试兜着):
· 身份读不到 / 身份值过长 → **不去重、当没做过照常执行**。绝不能把"读不到"
当成空身份——那会让所有设备共用一个 key、第一台记账后其余全被误判成"做过"。
· `kind='all'`(只做一次)的记录**永不清理**(清了等于语义失效);清理只删 day/hours。
· 去重的两个易错点在保存时直接告警:身份元素两边不一致、有检查没记账/有记账没检查。
- 「任务 → 去重记录」新子分栏(`static/admin/dedup.js`):统计行 + 明细表 +
删单条(那个号重跑)/ 清空任务(整批重跑)。接口 3 个(GET/delete/clear,PERM_TASKS)。
- 每日 04:23 清理(挂现有 APScheduler),`TABLE_LABELS` 补中文名(备份覆盖自动派生)。
- AI 建任务草稿校验同步:`dedup` 走自己的规则(要 ident_value、xpath 前缀校验),
没填身份元素只警告不拦(用设备当身份是合法用法);普通条件空选择器仍然拦。
- 文档:TASK_DEV §4.6(去重专章 + App 内检测的兜底配方与它的三个局限)、
DATA_MODEL §2.9、API 三个接口、ARCHITECTURE(分层/装配/子分栏/JS 分工/清理)、
DEPLOY §5.2(15 张表)、步骤数 21→22 全库同步。
自测:单元 + 集成 33 项(**含 8 线程抢同一个身份、恰好一个成功**的原子性断言,
以及"all 记录不被清理""身份读不到不去重""清了能重跑")、
**真机端到端**(cs1 上"检查→动作→记账"跑两遍:第二遍被拦、换 serial 的"另一台设备"
同样被拦、删记录后能重跑)、草稿校验 5 项、GET 冒烟 56 路由 0 个 500。
(注:本分支基于 feat/if-el-multi-value,因为它俩都要改 task.py 的 STEP_TYPES 与
editor.js 的 STEP_LIB 同一区域,分开从 dev 拉必然冲突——这份是超集,合一次两份都进。)
|
2026-09-24 10:55:29 +08:00 |
|
butubb
|
beb51637dd
|
feat(设备): 电量显示(大屏 + 监控页)+ 低电量 webhook 告警
需求:大屏和监控页都要能看到每台设备的电量,并且"低于多少电量就发通知"。
数据从哪来:`adb -s <serial> shell dumpsys battery`(**只读**,实测 0.2~0.35s/台,
11 台并发一轮 0.44s)。设备只要在 `adb devices` 里就说明 transport 现成,
**不需要 connect**——所以连空闲设备也能查。这是本模块敢每轮扫全量的前提,
也是这条只读路径与"空闲设备不主动 connect"红线(DEVELOPMENT.md §2 #3)的边界,
已写进文档免得后人误加 connect。
- core/device_battery.py(新):后台 daemon 线程 `device-battery`(照 device_discovery
的 init_app/shutdown 模式),默认 60s 一轮,采「设备池 ∩ 在线」(与 list_ready 同口径,
adb 可见但不归平台管的设备不上屏也不告警)。
· 结果**只放内存**(不落库 → 不建表、不动备份覆盖清单),离线设备保留最后读数;
· 读失败不清缓存:list_devices 失败时若照清会把整份缓存抹掉、大屏全体变「—」;
· 配置存 `app_meta.device_battery`(照 step_defaults:出厂值 + 白名单 + 范围钳制 +
只落与出厂值不同的字段),改阈值不用重启(线程每轮重读配置)。
- 告警:事件 `device.battery.low` / `device.battery.recovered`(默认关,去通知页勾订阅)。
**按档位做状态沿**(0 正常 / 1 低 / 2 严重):掉档才报、严重再报一次、恢复报一次;
迟滞 +5% 避免在阈值上下反复告警;同档位每 6h 重复提醒一次。
"充电中"看的是 `status:` 行(2/5),**不是"插着电"**——所以"插着却没充电"
(劣质线/温控/满电停充 status:4)仍会告警,那正是最该知道的情况(实测 fleet 里
就有一台 8% 插着 AC 但 `Max charging current: 0`)。
- GET /api/status 每台设备多一个 `battery`:`{level,charging,at,tier}`。
**tier 由后端算好**,前端只按它上色——阈值只在「工具 → 设备发现 → 电量监控」一处定义,
免得 JS 里再判一遍、两边不一致。
- 前端:大屏卡片型号行右侧加电量徽标(绿/黄/红 + ⚡ 充电中,离线置灰),
`cardHtml`/`renderGrid` 增量更新/脏检查白名单三处都改了(漏一处就不刷新);
监控页设备表新增可排序的「电量」列(空表 colspan 11 顺带对齐了)。
- 接口:GET/POST /api/devices/battery[/settings] + POST /scan(需设备权限,
与 /api/devices/discovery/* 同权限档)。
自测:单元 30 项(解析真机输出/档位/状态机/钳制,含 scale=255、无 status 行、
垃圾输出等边界)、集成 27 项(临时库自建设备池:缓存/通知/离线保留/删设备清理/
配置往返/阈值改动即时生效)、真实 11 台设备一轮 0.44s 全读到、API 端到端
(登录/权限/钳制/越界/REST 往返/页面 DOM)、两份前端各自语法检查 + 渲染用例。
|
2026-09-24 08:37:42 +08:00 |
|
butubb
|
73895f7233
|
feat(任务): 录制手势改成**纯录制回放**(真手指轨迹)+ 手机端 getevent 录制
用户反馈:录制出来的滑动又被套上滑动那套逻辑(方向/幅度/拟人重新生成),
"导致滑动还是不顺畅"。录制就该是录制——按录下的路径与时间原样重放。
- 新步骤 `gesture`「录制手势」:存完整轨迹点列 `[[x,y,t_ms],…]`,
回放时原样交给设备(`d.swipe_points(points, duration)`),不做任何加工。
与 `swipe` 是两套东西(滑动是参数化的,录制是点列)。
- core/gesture.py(新):
· 手机端录制 `PhoneRecorder`:`getevent` 读**真触屏**设备(自动挑 fts_ts 这类、
排除 uinput/vitural-sar 等合成节点),解析两套协议(BTN_TOUCH / ABS_MT_TRACKING_ID);
**合成的注入事件不会出现在真触屏节点上**(实测),所以录到的只有人手的动作;
· 点列清洗:按 ≥16ms 抽稀但**末点必留**(快划时不能把收尾丢了);
· 回放**一次调用**而不是逐点注入:实测这台设备单次触摸 RPC ≈190ms,
逐点回放 20 点要 3.8 秒——只能让设备自己插值,"时间"由点密度还原。
- 接口:POST /api/gesture/record/{start,stop}(需设备权限;重复开始返回 409)。
- 「录制手势」步骤卡片:录到就回填(手机上录 / 网页上录),显示点数/时长;
**滑动步骤上的「录制手势」按钮已移除**(按用户要求,录制不再走滑动逻辑)。
- 录制前自动唤醒设备:**息屏时 u2 抓 UI 树会从 3 秒退化到 65 秒**(实测),
截图也是黑的——这是本次排查最耗时的坑,已写进文档。
自测:假设备单测(点列/时长/抽稀/失败退回直线,10 项);
无头浏览器 + CDP 真模拟拖动 → 录到 14 点/605ms 并按实际轨迹回填;
手机录制接口起停/重复拦截;
真机回放:用录下的轨迹跑任务 → 步骤 `gesture ok`。
⚠️ 手机端"真手指轨迹"这一段需要人真的去划才能端到端验证(我没有手指)。
文档:TASK_DEV §3.2(两套东西对比 + 三个坑)、步骤表 21 种、API、README。
|
2026-09-20 15:04:21 +08:00 |
|
butubb
|
bd51306a2a
|
feat(任务): 「动作配置」分栏——步骤默认值 + 动作录制器
需求:滑动要能"我自己录制";在自定义动作旁边加一个分栏,能配动作默认值、
能录制操作当动作、能自己建立动作。
**步骤默认值**(core/step_defaults.py,存 app_meta.step_defaults 单键,不建表):
- 覆盖 滑动(方向/时长区间/幅度/抖动/拟人)、点击(等待超时)、长按(时长/超时)、
等待(区间)、按键、输入文字(模式/候选文案/清空);
- **只影响之后新建的步骤**,已有步骤不动;新建时 `_makeStep` 用出厂值打底 + 默认值覆盖;
- 只存与出厂值不同的字段(以后调出厂默认,没配过的能跟着走);
- 字段白名单 + 范围夹取(SPEC):越界夹边界、非法值丢弃 → 手改 JSON 塞脏值也进不来。
**动作录制器**(static/admin/recorder.js):
- 在设备画面上点/划/按键/输入 → 自动翻译成步骤:
点→命中元素记「点击元素」(选择器)、没命中退化成「点击坐标」(百分比);
划→「滑动」方向/幅度/时长全按你实际操作算;停顿≥1s 可选记成「等待」(±15%);
- 两个入口:动作配置页「录制动作」(存成自定义动作,进动作库复用)、
步骤编辑器滑动那步的「录制手势」(只取手势回填该步骤);
- 画面+元素树走 /api/uiauto/snapshot 一次取齐,**每次操作后自动刷新**,
所以下一次点击用的是最新的树;执行仍走 /api/screen/{tap,swipe,key,text}(与大屏同一套)。
接口:GET/POST /api/step_defaults(读登录、写需 tasks 权限)。
文档:TASK_DEV §3.1(含翻译规则表与三条实现口径)、API §2.3、DATA_MODEL §5、README。
自测(无头浏览器 + CDP 真模拟拖动):默认值保存→回读→新建滑动步骤确实预填 0.42;
录制器取到画面与元素树;模拟"按住往上划"→ 录出
{direction:up, distance_ratio:0.5, duration_min:0.47, humanize:true}(幅度/时长按实际操作算);
恢复出厂链路 0.6 通过;页面无 JS 报错。
|
2026-09-20 14:32:35 +08:00 |
|
butubb
|
9a44eeba95
|
Merge branch 'feat/task-patrol'——任务级公共巡检(含与拟人滑动的合并:import、能力表两处冲突已解)
|
2026-09-16 13:10:05 +08:00 |
|
butubb
|
c29516cff5
|
feat(任务): 任务级「公共巡检」——独立于步骤画布的守护条件(含 webhook 通知)
需求:任务编辑器里能单独配"这个任务每隔 N 秒检查一次"——熄屏就点亮、某个元素
出现就通知、掉出 App 就停本设备;通知标题正文要能自己写。
配置与执行分离(这是本次的关键设计):
- **配置是任务级的**(`params.watchers`),在任务编辑器单独一块,不进步骤画布;
- **执行是穿插的**:worker 每执行完一步、以及长等待的每个分片,看一眼哪个巡检
到点了。不起线程 → 不需要并发模型,也不会和主流程抢屏幕(两边同时点屏幕会
互相打断)。代价是精度受步长影响(某步卡 30s,巡检最多晚 30s),已在文档写明。
- core/patrol.py(新):检查项/动作注册表(CHECKS/ACTIONS)+ evaluate/act。
检查:屏幕熄灭/亮着、元素存在/不存在、前台是/不是某 App;
动作:只通知、点亮、息屏、停止本设备。屏幕走 `dumpsys power`(0.3s,
不用 d.info——那玩意在部分设备要 14s),前台走 d.app_current()(0.7s)。
- tasks/generic/task.py:`_maybe_patrol` / `_run_patrol`(冷却、命中记一条
步骤明细、发通知);**文案在动作之前渲染**——点亮后 {screen} 就成了"亮屏",
用户要看的是"发现熄屏,已点亮"。
- 任务编辑器新增「公共巡检」块(static/admin/tasks.js)+ 样式;保存进 params.watchers。
- 通知:新增事件 `task.patrol.hit`(巡检命中)与 `task.notify.custom`(步骤发通知);
给了 title 就用它当标题(不再拼前缀),level 字段可点名级别。
顺带(巡检需要的原语,也可单独用):
- if_el 条件判断支持 `selector_type=screen`(亮/熄)与 `foreground`(前台包名);
非元素条件不参与「选择器健康」统计(否则会攒出假的"选择器失效"告警)。
- 新增两个步骤:`notify`(发自定义通知)、`stop_self`(停本设备,记"被停止"
而不是失败,不触发重试)。步骤类型 18 → 20,相关文档计数一并更新。
修 bug:`wait` 步骤在巡检耗时超过剩余时间后 `sleep(负数)` 抛
"sleep length must be non-negative"(真机联调抓到,已 clamp 到 0)。
自测:假设备单测 12 组(命中/冷却/间隔/元素/前台/停止/静默/异常不炸);
真机联调:熄屏→点亮(False→True)+ 通知文案正确、掉出抖音按间隔命中 5 次、
长等待里穿插生效且步骤回到 ok、清理后用户通知配置原样恢复。
文档:TASK_DEV §4.5(含两个可抄的例子)与步骤表/条件类型、NOTIFY §3、README。
|
2026-09-16 13:02:40 +08:00 |
|
butubb
|
cf075ba4e1
|
feat(任务): 滑动拟人化 + 每台设备有自己的手感(core/humanize.py)
用户反馈"滑动太像机器人""批量执行时每台设备都一样"。
core/humanize.py(新)分两个层次:
- **每次不同**:起止点/幅度/时长抖动、弧线方向随机——最容易被识别的不是"慢",
是"每次都一模一样";
- **每台设备不同**:由 crc32(serial) 派生稳定的"性格"(手速 0.82~1.32、
幅度 0.86~1.16、弧度、常用横坐标 ±9% 屏宽、停顿 0.80~1.35)。同设备多次运行
风格一致,不同设备明显不同——13 台批量跑看着像 13 个人各刷各的。
用独立 Random 实例播种,不碰全局 random(多线程 worker 会打乱取值顺序)。
轨迹用二次贝塞尔走 d.swipe_points(曲线),异常时自动退回直线 d.swipe。
**点数固定 4 个**:swipe_points 在慢设备上每多一个点约多 1 秒(实测 2 点
1.4s / 6 点 6.0s / 10 点 10.6s),设备自己会插值几十步,4 点已足够弯。
顺带修一个真机上的老毛病:滑动原本每次都要读 d.info,而它在部分设备上要
**14 秒**。改用 humanize.screen_size()(走 window_size,同设备 0.6s,缓存
120s)——所以哪怕多了弧线,真机单次滑动反而从 ~15s 降到 ~5s。
- tasks/generic/task.py:swipe / swipe_until 走拟人(新增 distance_ratio、
jitter、humanize 参数,默认开);swipe_until 每轮停顿也抖动;wait 步骤新增
可选 vary_pace(默认关,按设备节奏缩放 0.8~1.35 倍);
- static/admin/editor.js:滑动类步骤参数面板加"幅度/抖动/拟人轨迹"+ 说明;
- 文档:TASK_DEV.md §8.4(含"点数别调大""别用 d.info"两个坑)、步骤表、
README 能力表与结构。
自测:假设备单测(曲线/抖动/设备间差异/退化路径/不越界/尺寸缓存)+ 真机联调
(4 次滑动全部 ok,标注"弧线"、坐标每次不同、对照的 humanize=false 仍是直线)。
|
2026-09-16 12:39:47 +08:00 |
|
butubb
|
621d48c800
|
feat(日志): 任务步骤明细表 + 「日志 → 步骤明细」面板(按设备/任务/时间过滤、按运行归组、导出 CSV)
文本日志只能 grep,"这台设备这次运行为什么失败"翻起来很费劲。新增一张
**结构化**的步骤明细表,把"哪一步、什么类型、哪个选择器、结果、耗时"落库。
- core/models.py:新增 task_step_log(run_id/job/设备/step_path/selector/
result/detail/duration_ms),索引 run_id、created_at、(serial,created_at)、
(job_id,created_at);
- core/step_log.py(新):**专用写线程 + 有界队列**批量落库——步骤执行是热路径,
任务线程只 put_nowait(实测 9000 次入队 31ms),队列满丢弃并计数,绝不阻塞;
另有保留期清理(默认 14 天,每日 04:13 + 每次启动);
- tasks/generic/task.py:_exec_one 记一条(异常=error / handler 返回 False=miss /
未知类型=unknown / 概率未触发=skip);_exec_steps 维护路径栈得到 "2.1.3"
这样的嵌套位置;**单次运行封顶 2000 条**——forever 循环任务否则会写爆表;
- 运行上下文 ctx(run_id/job_id/job_name/device_name)由 TaskManager 生成,
经 create_worker(serial, params, ctx=None) 传入 worker(扩展点向后兼容);
- 接口:/api/step_logs(过滤+分页)、/runs(按运行归组)、/filters(下拉选项)、
/download(CSV,带 BOM);
- 前端:日志页拆成「文件日志 / 步骤明细」子分栏 + static/admin/steplog.js。
红线:新表自动进备份覆盖清单(SUMMARY_TABLES 由元数据派生),已补
TABLE_LABELS 中文标签,导出实测 14 张表、coverage_missing 为空。
文档:DATA_MODEL §2.8/§1、API §10.2、ARCHITECTURE §1.1/§2.2/§3.1、
DEVELOPMENT §5.2 与红线表、DEPLOY §5.2、TASK_DEV §8.3、README。
|
2026-09-16 08:58:43 +08:00 |
|
butubb
|
2c32c397c8
|
feat(日志): 日志页支持关键字/级别/时间过滤 + 下载(含滚动历史)
「日志」页原来只能 tail 固定行数、且文件下拉写死了 4 项(新增的 notify.log
根本选不到)。本次:
- core/logger.py 新增读取接口:list_log_files()(模块文件 + .1/.2 滚动历史,
白名单)、query_log()(关键字/最低级别/时间过滤,返回结构化行)、
read_log_text()(导出)。
· 时间/级别过滤对**续行**用继承值(traceback 缩进行本身没有前缀),
按 ERROR 筛不会把堆栈拆散;
· 过滤按"最近 N 条命中"返回,保持文件原顺序;
- GET /api/logs 换新协议(rows/files/matched/scanned/truncated),
新增 GET /api/logs/download(附件下载,支持同样过滤;无一条件时整文件);
- 顺带修 **目录穿越**:原实现 os.path.join(_LOG_DIR, file) 可用 ../../ 逃逸,
现在 file 必须命中白名单;
- 顺带修 web/admin_api.py 里 _log 未定义(用户增删改的日志行会 NameError);
- 前端:文件下拉改为按接口渲染、加关键字(命中高亮)/级别/时间范围/下载/重置,
自动刷新时不再把正在上翻的用户拽回底部。
文档:doc/API.md §10.1(参数与返回)、README 日志节。
|
2026-09-16 08:44:19 +08:00 |
|
butubb
|
3b6ec8c98f
|
feat(通知): 加钉钉 / 飞书两种格式(含各自的加签算法)
## 两家签名算法不一样,照抄必错
| | 钉钉 | 飞书 |
|---|---|---|
| HMAC key | `secret` | `"{timestamp}\n{secret}"` |
| 被签内容 | `"{timestamp}\n{secret}"` | 空 |
| timestamp | 毫秒 | 秒 |
| 拼在哪 | URL query | JSON body |
| 结果 | Base64 再 urlencode | Base64 |
| 出错码 | `errcode 310000` | `code 19021` |
实现为 `BaseAdapter.sign_request()` 钩子(默认不动),两家各写各的;
`core/notifier.py` 的 `_post_once` 在发请求前调用它。
## 另外
- 钉钉:markdown 消息(title+text);成功码 0;默认限流 15/分(官方 20/分,**超限会被限 10 分钟**,留余量)
- 飞书:交互式卡片(header 颜色按事件级别 + markdown 元素);成功码 0;默认 60/分
- `PLANNED_FORMATS` 清空(下拉里不再有置灰项)
- **修一个泄漏**:`mask_url` 原来只打码 query 参数——**飞书的 token 在 URL 路径里**(`/hook/<token>`)
→ 接口回显会漏出去。现在同时处理路径 token(≥20 位随机串)与 Slack 的 `/services/T…/B…/X…` 三段。
## 验证(都跑过)
- **签名对拍官方示例**:钉钉逐字节一致(含 urlencode,`%2B` 级别)、飞书的 key/message 口径一致;
时间戳钉住后比对,不靠"自己跟自己一致"
- 成功码按格式:wecom/dingtalk/feishu 的 0 与 bark 的 200 分别判成功,各自的错误码判失败
- 端到端 15 项:钉钉/飞书在假接收端上真发(URL 带签名、body 带 timestamp/sign、卡片是 markdown 元素、
毫秒 vs 秒的时间戳),签名错时正确判失败(310000 / 19021),token 在回显里被打码
|
2026-09-15 14:29:11 +08:00 |
|
butubb
|
515cb5d580
|
docs: 根 README 结构表/前端 JS 加载清单补上 notify(含 notify_api/notifier/taskgen/notify.js,并修正文件数)
|
2026-09-15 14:05:44 +08:00 |
|
butubb
|
24ea289c15
|
feat(通知): 加 Bark 格式 + 格式切换时界面提示全部跟着变
## 用户报的第二件事:格式改了,提示没跟着改
弹窗里的 URL 示例/说明、密钥标签、限流说明都写死成企业微信了——选「通用 JSON」时
占位还是 `qyapi.weixin.qq.com`,限流还写着「企微硬限 20」。现在这些文案挂在**适配器**上
(`url_hint/url_help/secret_label/secret_help/limit_help`),由接口随格式下发,切格式即时更新;
限流值在用户没手动改过时也跟着格式的推荐值走(企微 20 / 通用 JSON 60 / Bark 60)。
## 新格式:Bark(iOS 推送)
- `POST {url}`,body `{title, body, markdown, group, level[, device_key]}`
—— `markdown` 传富文本、`body` 传纯文本兜底(老版本 App 不认 markdown 字段时也能看清)
- URL 两种填法都支持:直接粘 Bark 复制的那串(`https://api.day.app/<key>`,key 在路径里),
或填 `https://api.day.app/push` + 把 key 填到「设备 Key」(作为 device_key 发送)
- **成功判定按格式**:Bark 是 `code==200`(企业微信是 `errcode==0`)→ 新增
`BaseAdapter.ok_codes`,`_post_once` 用它判定。少了这一步 Bark 的每次成功都会被误判成失败
- 正文按 2048 字节截断(走 APNs,体量有限)
## 顺带
- 通用 JSON 的 `secret` 现在会作为 `X-Webhook-Secret` 请求头发出(原先填了没用)
- `_formats()` 改为序列化适配器元信息,新增格式只改一处
## 验证
- 格式切换 14 项断言全绿:三种格式的 URL 示例/密钥标签/密钥说明/限流说明/模板区/限流默认值
全部跟着切换;下拉里 Bark 出现在已实现区
- Bark 端到端 7 项:`code=200` 判成功、`code=400` 判失败(含重试)、请求体带
device_key/title/body/markdown/group、URL 里的 key 在接口回显里被打码
- 文档:NOTIFY.md §5 的格式表补 Bark 列(URL 怎么填 / 成功码 / 约束),并说明
"提示文案挂在适配器上,别写死在页面里"
|
2026-09-15 14:05:33 +08:00 |
|
butubb
|
97cec6e211
|
feat(通知): 系统级 Webhook 通知子系统(事件目录 + 可插拔适配器 + 防刷屏)
平台此前出了问题只能靠人盯页面。现在各组件统一走 `notifier.notify(事件, **字段)`,
推到企业微信 / 自建服务;**所有可通知点都登记进事件目录,默认全关,用户按 webhook 勾选**。
## 架构(`core/notifier.py` + `core/notify_events.py`)
业务线程调 `notify()` → 只做内存操作(读配置快照/匹配订阅/入队)→ 返回;
后台 1 个 dispatcher(聚合 + 每 hook 限流 + 折叠摘要)+ 3 个 sender(真实 HTTP、退避重试)
负责真正发出去。硬约束:**notify 零 DB、零 HTTP、零阻塞、异常不冒泡**——所以任务线程里
可以直接调(不用 app_context、不用 try/except),但**必须放在所有 `with self._lock` 之外**。
- **事件目录 36 条**(任务批次/单设备/设备/Worker/业务/安装/系统/AI),支持 `task.*` 通配订阅;
语义分工避免重复告警:`worker.*` 是单次尝试级,`task.device.success/failed` 是唯一权威结论。
- **适配器可插拔**:`wecom`(markdown,按 4096 **字节**截断、超限不截半个汉字)+
`json`(模板占位符,替换值按 JSON 转义,保存前干跑校验);钉钉/飞书留了插槽(前端置灰)。
- **防打爆四层**:聚合窗口(默认 30s,同批次合并成一条并带样本)→ 令牌桶限流(默认 18/分,
对齐企微硬限 20)→ 被限流的**折叠成摘要不丢弃** → 有界队列背压。取舍:失败通知最多延迟
一个窗口,换来群不被刷屏。
## 安全与存储
- 配置只落 `app_meta.notify_webhooks` 一个键(**不建表** → 不涉及备份覆盖红线)。
- URL 本身就是凭据(企微 `?key=`)→ 接口回显/发送记录/日志一律 `mask_url()/scrub()`;
编辑时留空即不修改;secret 永不回显。DATA_MODEL 的明文凭据告警补上了这一条。
- 发送记录:内存环形缓冲 200 条(重启清空)+ 独立 `logs/notify.log`。
## 接入点(每个都放在锁外、不改 return 顺序)
task_manager(批次开始/结束用新增的 `_BatchTracker` 统一在 finally 计数、单设备成功/失败/
离线/重试/停止/抢占/归还/cron 停止)、device_worker 心跳看门狗、generic 任务选择器连续失效、
apk 安装开始/完成、设备上下线(**状态沿检测**,只报新变化)、备份导出/恢复、经验巡检、
用户登录、服务启停。
## 前端
系统 Tab 新增「通知 / Webhook」子分栏:多条 webhook 列表(URL 打码)+ 编辑弹窗(格式/URL/
密钥/事件勾选树带 ★建议/聚合/限流/自定义模板/预览)+ 发送测试 + 发送记录。
## 自测
- 进程内逻辑 10 组断言全绿:聚合合并、限流+折叠、无配置/全局关静默丢弃、未知事件、
内部异常不外泄、JSON 转义(标题含引号换行仍合法)、URL/异常消息脱敏、配置校验。
- 端到端(假 webhook 接收端)17 项断言全绿:真实事件投递(user.login / task.batch.no_device)、
企微请求体形状、**HTTP 200 + errcode 93000 判为失败**、500 重试 3 次、记录里 URL 打码。
- 韧性:webhook 指向黑洞地址时登录耗时 100~114ms(基线 107~133ms,**异步隔离生效**);
配置写成坏 JSON 服务照常启动、通知静默不发、日志有 error(服务端实测后已复原)。
- 页面:系统 → 通知 面板/弹窗/36 个事件复选框/预览全部正常,无 JS 报错。
- 自测数据已清理(webhook、自建任务、写坏又复原的配置键)。
文档:新增 doc/NOTIFY.md(事件表/配置/格式约束/防刷屏/加事件三步骤/排障)并登记进 doc/README;
API.md §2.11;DATA_MODEL 的 app_meta 键表与明文凭据告警;ARCHITECTURE 线程表/分层/扩展点;
根 README 功能索引与日志表。
|
2026-09-15 13:55:14 +08:00 |
|
butubb
|
ed9e8bacb1
|
feat(AI 建任务): 直接创建 + 草稿沉淀 + MCP de_snapshot;修「建任务页收不到 done」
用户报的"探索完无法点击创建任务"真因:一个 run 的事件原先只有**一条** queue.Queue,
聊天页与建任务页同时开着时两个 EventSource 会**瓜分**它——建任务页的回放卡在中间、
`done` 被聊天页取走 → 永远等不到草稿,页面上自然没有可点的"创建"。
一、修(根因 + 表现)
- `web/agent_api.py` 新增 `_Fanout`:**每个订阅者一个专属队列**,多开页面各看各的,
还带单轮事件缓冲(晚订阅/刷新重连也能补齐回放,终止事件一定送达)。
实测两路订阅者收到完全一致的 1039 条事件(含 done)。
- `static/admin/agent.js`:断线重连的兜底订阅也按 `mode` 让开(此前漏了这一处)。
二、补齐上一批的三项
- **「直接创建」**:`POST /api/agent/task_draft/create`(草稿体只在服务端、创建前再校验一次、
成功后清草稿避免重复建)+ 草稿预览里的「✓ 直接创建任务」按钮 + 「探索完直接创建任务」勾选框。
- **草稿沉淀经验/动作**:designer 轮次也走 `_distill_experience/_distill_actions`,
但**只在草稿通过校验时**(没走通的试错不入库,免得把误点当经验)。
- **MCP `de_snapshot`**(第 20 个工具):截图+元素树一次取齐(省一次来回、不会因界面在动而错位),
附带 `screen_state`/`unstable`;两套提示词都改为优先用它。
平台侧 `/api/uiauto/snapshot` 随之多返回 `screen_state`。
三、文档
- AI_TASK_GEN §10:§10.3 记两个 bug 的真因与修法、§10.4 三项标完成、§10.5 剩余项。
- AI_CONSOLE(扇出语义、多页面同时看一轮)、API(task_draft/create、snapshot 字段)、
MCP/MCP_DESIGN/staffdeck/README/ARCHITECTURE:工具数 19→20 + de_snapshot 条目。
- backlog:记一条新发现的缺陷——`mcp_server/platform_client._login()` 会把"登录页 200"
当成登录成功(现场进程缺 `MCP_PLATFORM_PASS` 时表现为含糊的 platform_unavailable)。
自测:真机浏览器端到端(勾上"探索完直接创建")→ 探索 12 步 → 草稿 → 自动建任务成功;
`de_snapshot` 直连真机校验;校验器 21 条用例、扇出单元用例、本地工具契约用例全绿。
自测产生的任务/草稿已全部清理(未碰用户既有数据)。
|
2026-09-14 08:14:11 +08:00 |
|
butubb
|
24d57d3b96
|
docs: doc/ 全量重整——按现状重写并建立文档索引;项目统一更名 auto_control
背景:文档长期落后于代码(Tab 数、任务类型、接口示例等多处与现状不符),
且信息分散重复。这次按当前代码状态逐篇重写,并建立统一的文档体系。
新增
- doc/README.md:文档总索引(文档地图 / 推荐阅读路径 / **文档维护约定**)
- doc/DATA_MODEL.md:数据模型(7 张模型表 + 5 张非模型表、迁移机制、app_meta 键、
数据目录、备份覆盖清单与双向自检)
- doc/AI_CONSOLE.md:AI 控制台机制(会话与 SSE、经验库/动作库蒸馏与召回、巡检、
Markdown 渲染、推理链、token 统计、故障排查)
重写(按现状,去掉过时与重复)
- README.md:7 个 Tab、18 种步骤、设备生命周期、调度/窗口语义、常见问题;修掉
「6 个 Tab / 分组为顶级 Tab」等过时内容与损坏的目录树
- doc/ARCHITECTURE.md:补启动装配顺序(import 期副作用、A~G 七阶段)、线程与锁清单、
设备状态机、调度全链路、前端结构与实时通道、设计决策、**已知缺陷与踩坑清单**、扩展点
- doc/API.md:按蓝图重建「接口总索引」(107 条路由含鉴权)+ 分域详细说明 +
非 JSON 响应汇总 + 错误分支速查
- doc/TASK_DEV.md:18 种步骤全表(参数/默认值/语义)、容器与公共参数、
选择器与 XPath 序号语义、抓取器建议规则、新增任务类型骨架
- doc/DEPLOY.md:容器入口 start.sh 三件事、发布流程与检查清单、备份覆盖红线、
按现象分类的故障排查
- doc/DEVELOPMENT.md:流程/红线/本地开发/**测试与写测试的约定**/配置速查/文档同步
- doc/MCP.md:19 个工具的参数级清单、坐标空间、写门控三连、安全与审计
- doc/MCP_DESIGN.md、doc/AI_TASK_GEN.md:标注设计 vs 实现现状,补交叉链接
- doc/backlog/TODO.md:新增「已知缺陷」小节(含复现与影响)+ 已完成留档
- .env.example:按代码实际读取的键重写(补 USB/DISCOVERY/MCP/AGENT,删死配置)
其它
- 项目名统一 auto_control:README/文档/scripts/pack.py 产物名;代码内的
doc 章节引用(templates/admin/monitor.html)同步更新
- 校验:16 篇文档 156 条相对链接全部可解析;文档中的关键数字与代码核对一致
(19 个 MCP 工具 / 18 种步骤 / 12 张备份表 / 1 种任务类型)
|
2026-09-10 22:19:18 +08:00 |
|
butubb
|
98cdc39224
|
chore: 删除抖音养号任务类型,平台只保留 generic_steps
tasks/douyin/(task_type=douyin_nurture)整体删除,唯一任务类型是 generic_steps。
所有默认值/文档/接口示例同步改成 generic_steps:
- tasks/:删 douyin 包;__init__ 只注册 generic;base.py/generic 注释改为照 generic 抄
- 默认值:core/models.py(列默认 + 旧 JSON 迁移默认)、core/task_manager.py TaskJob、
web/tasks_api.py 建任务默认、static/admin/tasks.js 新建任务默认
- core/task_manager.py:去掉 douyin 专属的"清理废弃 comment 参数"迁移块,改为**启动时告警**
仍残留已删类型的任务(只告警不改数据);run_job_now 对已删类型直接返回明确错误,
不再"报已触发、线程里静默失败"
- 清理残留:douyin_running 状态位(无任何读取方)、core/__init__、core/actions/*、
core/logger.py 注释里的抖音示例
- 文档:README(特性/目录树/类型表/参数表/示例)、TASK_DEV(目录树/注册说明/模板引用)、
ARCHITECTURE(注册示例/action 注册表示例)、API.md(task_types 与任务 JSON 示例)、
AI_TASK_GEN(P1 去掉 douyin 预设)、DEVELOPMENT
- 注:示例里"抖音"作为**App 名**(MCP 列应用、AI 建任务的需求举例)保留,与任务类型无关
自测(全部通过):类型列表只剩 generic_steps;建任务不传类型默认 generic_steps;传
douyin_nurture 被 400 拒;库里塞残留旧类型任务 → 启动日志告警 + 执行返回明确错误 +
不自动删用户数据;前端新建任务下拉 1 项且默认选中、界面建任务成功;监控页卡片两个按钮 +
覆盖设备正常。临时任务/数据验完已清理,任务集合复原。
|
2026-09-10 21:43:08 +08:00 |
|
butubb
|
ce47a5b83d
|
refactor: web_server 模块化拆分(web/ 蓝图包 auth/monitor/tasks/admin/tools/devices/apks/tailscale + context/common,web_server 1796→193 行仅留装配与启动)+ 大屏优化(网格 5s 轮询、弹窗独占缩略图、流支持 q/fps)+ 三修复(MIUI 屏幕状态检测改 dumpsys power、补 ADB_PATH 导入修复一键亮屏、renderGrid 增量渲染不重载缩略图)
|
2026-08-19 14:55:29 +08:00 |
|
butubb
|
c78f17d72f
|
feat: 监控大屏(/wall 全屏深色控制室风格:设备卡片网格+缩略图轮询+统计+时钟,缩略图 360px/q55 每 2.5s 一帧 20 台仅 ~0.2 核 CPU)+ /api/screen/thumb 接口 + 导航栏大屏入口
|
2026-08-19 14:13:15 +08:00 |
|
butubb
|
5543bad0e6
|
docs: 全面更新——README/API/DEVELOPMENT 摘除 STF 章节(设备池/型号/看屏/新端点文档,技术红线改为共享 transport 表述)
|
2026-08-19 14:07:53 +08:00 |
|
butubb
|
21d3aa8268
|
feat: 任务离线设备自动跳过(serial/分组模式触发时过滤 STF 池离线设备,默认开启,任务编辑器可勾选关闭)
|
2026-08-14 09:16:54 +08:00 |
|
butubb
|
060157ea1a
|
refactor: 前端JS拆分为8个模块(base/list/monitor/editor/tasks/tools/apps/admin,按依赖顺序加载)+ 删除废弃文件(start_web.bat/main.py/fix_web.bat/cleanup.py,web已全覆盖)+ 文档同步 + 修复/api/summary缺导入
|
2026-08-11 14:53:04 +08:00 |
|
butubb
|
3bd56b4f59
|
feat: 工具Tab新增应用版本管理(按包名并发查询所有设备安装版本)+ 文档同步更新(README/API/ARCHITECTURE/DEPLOY:工具Tab各工具、条件判断if_el+OCR、STF设备管理、.env密钥配置)
|
2026-08-11 10:57:14 +08:00 |
|
butubb
|
de66c6194b
|
feat: 维护页 Tailscale 管理(设备列表/改名/授权/密钥不过期/设置IP/auth key,独立模块 core/tailscale_client.py)+ 密钥迁移 .env(STF_TOKEN/WEB_SECRET_KEY/TAILSCALE_API_KEY 不再入库)+ 修复设备更新端点(/device/{id} 405,改用 /name /authorized /key 专属端点)+ auth key description 仅 ASCII
|
2026-08-11 09:06:59 +08:00 |
|
butubb
|
a48032da23
|
chore: 移除已废弃的一次性脚本 create_task.py(任务创建已由 Web 界面覆盖),README 同步清理引用
|
2026-08-11 08:34:38 +08:00 |
|
butubb
|
a0d1a533c6
|
docs: 文档同步(README/API/架构/部署)
|
2026-08-11 08:27:33 +08:00 |
|
butubb
|
e3afa5d36f
|
docs: 新增开发手册(流程/准则/技术红线/手册),修正 README 文档索引
- doc/DEVELOPMENT.md:开发流程与 git 工作流(所有 git 操作需确认)、
环境说明、技术红线(绝不 kill-server/断开IP:5555 等)、本地开发手册、常见开发任务
- README 修正错误的 Windows 绝对路径为相对路径,目录结构补充 monitor.js
|
2026-08-08 21:17:25 +08:00 |
|
butubb
|
468e86f0a9
|
feat: 新增通用步骤任务+元素抓取,完善全部项目文档
- 新增 generic_steps 通用步骤任务(可视化步骤编辑器编排流程,支持 open_app/click/swipe/input_text/wait/loop/group)
- 新增 core/uiauto_helper.py 封装 uiautodev 元素抓取客户端
- web_server 新增元素抓取/截图/设备列表等 API
- monitor.html 新增步骤编辑器、元素抓取模态框、独立关闭逻辑
- 新增 README.md 项目总览(快速上手/架构/配置/FAQ)
- 新增 doc/ARCHITECTURE.md 架构详解、doc/DEPLOY.md 部署指南、doc/API.md 接口文档
- 修复 doc/TASK_DEV.md:移除已删除的 comment 引用,补充 generic 包,更新注册示例
- .gitignore 忽略 .claude/ 工具产物
|
2026-08-08 10:40:06 +08:00 |
|