butubb
|
51628dd8ff
|
Merge branch 'dev'——发布计划按名字发 + 账号管理整页下线 + 自动切号 + 图片
|
2026-10-12 10:16:31 +08:00 |
|
butubb
|
b8a91bb3f2
|
Merge branch 'feat/publish-by-name'——发布计划按名字发(名字_日期_编号)+ 账号管理整页下线 + 自动切号一轮发完 + 素材支持图片
|
2026-10-12 10:16:31 +08:00 |
|
butubb
|
1b65ffe089
|
feat(发布计划): 素材支持图片(图文)—— 上传视频/图片,发布流程共用一条 + 切号健壮性再修
用户实测反馈:"图片流程一样,现在也是点第一个" → 图片不做单独一套流程,只放开上传侧;
扩展名一路原样带到手机上(抖音按扩展名/MIME 判断视频还是图文,图片推成 .mp4 会变坏视频)。
· core/video_plan.py:VIDEO_EXTS + IMAGE_EXTS(jpg/jpeg/png/webp/heic) → MEDIA_EXTS;
报错文案改"不是视频/图片文件"
· tasks/generic/publish_flow.push_video:ext 默认**从本地文件名取**(不再硬编码 .mp4);
drop_old_copies 收到真实扩展名
· tasks/generic/task.py:删手机素材优先用推送时记下的 push_remote,其次按素材扩展名现算;
切号:等面板开稳再点(刚出现就点会被入场动画吞掉)、点完面板还开着(点的就是当前号)
就先关面板、一切以**回读**为准、回读窗口 12s→30s(换号会触发抖音整体重启,实测 77s 才回来);
失败时日志附带"当时屏幕上是什么"(亮屏/前台包/屏上文字)—— "点了没反应"一眼可判
· 骨架:先 stop_app 冷启动(抖音可能停在发布页/别的视频页,那种页面上没有「我」页签,
切号第一步就废);「校验账号」的选择器与「读名字」统一从 .env 取(漏了这一步会静默跳过
整个发布流程 —— 用户实际遇到的"直接完成、没发布"就是这个)
· 界面:上传视频 → 上传视频/图片,accept 加 image/*,明细表"视频文件"→"素材文件",help 文案
· 文档:TASK_DEV §4.7 / API / DATA_MODEL / ARCHITECTURE / README 同步
· 测试:解析用例补图片(jpg/png/webp/大写 JPG 认,gif 不收)+ 改报错文案期望
|
2026-10-12 10:13:49 +08:00 |
|
butubb
|
1e8b97e386
|
fix(切号): 等面板/等回读改成轮询(固定 sleep 会把"没加载出来"误判成"本机没登这个号")+ 实测选择器入档
A01(抖音 40.6.0)实测:
· 冷启动后按固定 sleep(1.2s) 找账号列表行 → 列表还没渲染 → 判成"本机没登这个号" → **漏发且不报错**;
现在两处都轮询到 deadline:等面板出目标行、等回读变成目标名。
· 实测选择器(写进 doc/research/U2_ELEMENT_SELECTORS.md §七 与 .env.example):
昵称 `resourceId=com.ss.android.ugc.aweme:id/tk+`、切换入口 `descriptionContains=切换账号`、
切号按名字点行 `textContains=<名字>`(行 TextView 不可点但 u2 按中心坐标点,行容器接住)、
「我」页签 `descriptionContains=我`(desc 是「我,按钮」)。
· 并记下"为什么不要用 (…b7w)[N]":序号型会随列表行数/顺序错位 → 点错号 = 发到别人号上。
· 冷启动切号 17.2s 成功、热态 12.9s;A01 最终已切回原账号。
|
2026-10-12 09:12:23 +08:00 |
|
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 |
|
butubb
|
31c97ea49c
|
Merge branch 'dev'——发布计划标题+正文(|| 分隔)+ 发布流程多一步填正文;账号名称全面下线
|
2026-10-11 16:37:33 +08:00 |
|
butubb
|
75bee21ffb
|
Merge branch 'feat/plan-title-body'——发布计划标题+正文(|| 分隔)+ 发布流程多一步填正文;账号名称全面下线
|
2026-10-11 16:37:32 +08:00 |
|
butubb
|
6ee089e023
|
feat(发布计划): 标题 + 正文(标题 || 正文 _手机号_日期_编号)+ 发布流程多一步填正文;账号名称全面下线
一、正文(video_plan.body,schema v10→v11)
- 标题 txt 一行里可带正文,用 `||` 分隔(标题/正文都常带下划线,不能再用下划线分列;
只切第一处,正文里再出现 `||` 原样保留):`标题 || 正文 _手机号_日期_编号`
正文可省(老文件照收);两种写法(标题在前 / 手机号在前)都能带正文
- 长度:标题 ≤100 字、正文 ≤1000 字,超了**拒收不截断**
- 「待发布」判据从"有没有标题"改成"**标题或正文有没有内容**"(`_sync_content_status` 一处定义,
改标题/改正文/批量导入三处共用),只给正文的行也照样待发
- 导入配对:同标题补一条新正文**要收下**(不能当重复跳过);已有别的标题仍然拒收(不静默覆盖)
二、发布流程多一步
- 骨架 15 → **17 步**:⑪ 输入文字(计划的标题) → ⑫ 点击正文框 → ⑬ 输入文字(计划的正文) → ⑭ 发布 → ⑮ 标记
- 「输入文字」取值来源集中到 `RELEASE_TEXT_SOURCES`:release_title(_topics) / release_body(_topics) /
release_title_body(标题+正文,只有一个输入框的抖音版本用);`build_caption(key=…)` 支持三种来源
- 任务编辑器与发布计划就地编辑器的下拉都补齐这 6 档;待抓的空选择器 5 → 6 个(多了正文框)
三、账号名称下线(用户要求:只要抖音号)
- 发布计划:卡片 / 明细表 / 搜索 / 导出 CSV 都不再有账号名称(CSV 改为
发布日期/设备号/手机号/抖音号/编号/标题/正文/分享链接/发布时间)
- 账号台账:列表列 / 编辑弹窗 / 粘贴导入预览 / 设备维度弹窗 / 导入说明与示例都去掉;
**粘贴导入忽略这一列**(不报错、不落库),库里的历史值保留(不删列)
- 设备端 Agent 身份页:平台不再下发名称(传空串,**不改 APK 也能生效**,避免老版本解析失败)
四、文档 / 测试
- DATA_MODEL:body 列 + schema 11;API:upload_titles(`||` 语义)、PUT {title,body}、CSV 列、骨架 17 步
- TASK_DEV:骨架 17 步、`input_text` 六个取值来源表、正文长度上限
- ARCHITECTURE:release.js 说明(正文、不显示账号名称)
- 测试(%TEMP% 被系统清理后重建):test_plan_body.py 33 项(解析/取内容/骨架/入库/导入配对)、
smoke_app_body.py 10 项(整机装配:页面、PUT 改正文、时间线、CSV 表头)—— 全过
|
2026-10-11 16:36:56 +08:00 |
|
butubb
|
8a6d2845e7
|
Merge branch 'dev'——标题行两种写法都认 + 行首 # 不再无条件当注释
|
2026-09-29 09:00:56 +08:00 |
|
butubb
|
a800d051ae
|
feat(发布计划): 标题行两种写法都认 + 行首 # 不再无条件当注释
一、标题行解析(core/video_plan.parse_title_line)
- 原来只认「标题内容_手机号_日期_编号」(右锚定)。现在**两种都认**:
① 标题内容_手机号_日期_编号 (推荐,右锚定 —— 标题里带下划线也不会错配)
② 手机号_日期_编号_标题 (左锚定,跟视频文件名同序 = "文件名去掉扩展名 + 标题")
两种里都**先试带编号的**,保证同一行永远只有一种解释;编号都可省略。
- 新拒收一条:`手机号_日期_1`(只有编号、没标题)**直接拒** ——
不能把它当标题"1"(静默生成一条标题是"1"的文案,比拒收危险得多);
标题真是纯数字的用写法①。
二、`#` 开头的行(parse_titles_text)
- 原来是"以 `#` 开头就整行忽略" → **`#中秋快乐_...` 这种正常标题会被静默丢掉**。
- 改成**先按标题行解析,解析得出就当标题;解析不出且以 `#` 开头才算注释**。
`# 这是注释` 照样忽略,`#话题` 开头的标题照收(`#` 保留在标题里)。
三、其它
- 文案同步:上传标题面板/帮助文案、doc/API.md 的 upload_titles 语义(两种写法、`#` 规则、拒收条件)
- 测试:新增 8 条(两种写法 × 带/不带编号 × 标题含下划线与 #话题、只有编号要拒收、整段注释与报错)
|
2026-09-29 09:00:43 +08:00 |
|
butubb
|
881bf79417
|
Merge branch 'dev'——视频发布计划(批量上传配对 → 时间线 → 推送到手机 → 发布任务 → 分享链接)+ 修推送检测机制
|
2026-09-28 15:59:39 +08:00 |
|
butubb
|
5bedab370e
|
Merge branch 'feat/video-plan'——视频发布计划(批量上传配对 → 时间线 → 推送到手机 → 发布任务 → 分享链接)+ 修推送检测机制(u2 ShellResponse 读错/touch 排序/按路径校验相册索引)
|
2026-09-28 15:59:22 +08:00 |
|
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
|
72ea3d4894
|
Merge branch 'dev'——账号台账(web 页 + 任务取号 + 设备端身份页)+ 剪贴板注入改走设备端 Agent
|
2026-09-24 16:16:09 +08:00 |
|
butubb
|
5c3dcba63e
|
Merge branch 'feat/account-ledger'——账号台账(web 页 + 任务取号 + 设备端身份页)+ 剪贴板通道改走设备端 Agent(旧 ClipInject 兜底、读回改轮询)
|
2026-09-24 16:15:18 +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
|
e82fd64695
|
feat(去重): 「记为已做」留空 = 自动跟随上面「去重」检查的身份(不再要求配两遍)
用户提的(他说得对):"这个标记已做不是应该是我上面的条件判断命中哪一个就用哪一个做吗?
这怎么还要自己选择元素啊"。
原来的设计要求检查与记账**各配一次身份元素** —— 一旦一边填了、另一边忘了,两边算出的
key 不一致 → **去重静默失效**(现象就是"去重没生效、还是重复做",他刚踩过)。
而我在"有效期"上为了避免两处配置特意强制放任务级,却在"身份"上要求填两遍,自相矛盾。
改成:
- `tasks/generic/task.py`:「去重」条件解析出的身份存进 `self._last_ident`;
`_exec_mark_done` 的身份元素**留空时复用它**(检查与记账必然是同一个字符串)。
三种情况分清(这是关键,别退化成"用设备"误标):
① 自己填了身份元素 → 用自己填的(显式优先)
② 留空 + 上面跑过去重检查 → **复用那个身份**
③ 留空 + 检查**跑了但没读到身份**(页面没到)→ **不记账**,下次重跑重试
—— 绝不能退回设备身份,那会在"什么都没做成"时把设备标成已做
④ 留空 + 任务里根本没有去重检查 → 退回设备身份(一号一机场景)
- `static/admin/editor.js`:`mark_done` 面板文案改为"留空 = 自动跟随上面检查的身份(推荐)";
校验里记账侧的**空值不再参与身份比对**(留空是合法的、且会自动对齐),
只有"填了、且和检查侧不一样"才告警,并在文案里建议留空。
- `doc/TASK_DEV.md` §4.6:身份只需在检查侧配一次(带一张四种组合的表);步骤表同步。
验证:新增 11 项(复用后账本身份 == 检查身份、换设备仍能命中=去重真生效、
显式填身份仍优先、无检查时退回设备、**读不到身份时不记账也不误标设备**);
原 dedup 套件(单元 + 真机)+ 编辑器语法回归全过;
把用户**生产任务的真实 JSON** 喂给新校验 —— **0 条警告**(旧版会报一条"身份不一致",
但现在留空=自动跟随是正确配置)。
|
2026-09-24 13:39:45 +08:00 |
|
butubb
|
4f3027b54f
|
Merge branch 'dev'——修复去重条件的校验误报 + 补身份一致性检测
|
2026-09-24 13:35:41 +08:00 |
|
butubb
|
331999d65c
|
Merge branch 'fix/dedup-validation'——去重条件不再误报文本比对警告 + 补上两处身份不一致的检测
|
2026-09-24 13:35:34 +08:00 |
|
butubb
|
6b2866692d
|
fix(编辑器+执行器): 去重条件上残留的文本比对设置会误报警告;并补上"两处身份不一致"的检测
用户报的现象:内层条件判断已经选了「去重」、身份元素也填了,但保存时**一直提示
"选了文本比对但没填比对的值"**(他看不到、也清不掉那两行)。
根因(我写的):从"元素类"切到「去重 / 屏幕状态」时,面板里"文本比对"那块是**隐藏**的,
但 params 里旧的 `cmp_op`/`cmp_value` 还留着;而校验没排除这两类不用文本比对的类型,
于是每次保存都拿那个残留去报警 —— 用户看不到源头的两行,自然无从下手。
- `static/admin/editor.js` 的 `validate`:
· 「去重 / 屏幕状态」不再报文本比对的警告(设计上就用不到);
· 这两类也不报"选择器为空"(去重用的是 `ident_value`,不是 `selector_value`);
· **顺手补一个真问题的检测**:去重的两处身份**空值也纳入比对** —— 一边填了、
另一边留空(= 用设备当身份)是最常见的不一致,只收非空值时恰好检测不到。
报错文案把"留空"写成「(留空 → 用设备当身份)」,用户一眼知道差在哪。
- `tasks/generic/task.py`:被忽略的文本比对只在**真的配了值**时才打 WARNING
(切换类型留下的空残留不必每轮刷日志)。
验证:把用户**生产任务的真实 JSON** 喂给新的 `validate()`:
假警告消失,只剩下一条真警告 ——「检查侧=账号元素 / 记账侧=留空(用设备)」身份不一致。
|
2026-09-24 13:34:53 +08:00 |
|
butubb
|
438710f830
|
Merge branch 'dev'——修复编辑器:嵌套条件判断的分支同步写错容器
|
2026-09-24 11:45:52 +08:00 |
|
butubb
|
6e1a23c5ae
|
Merge branch 'fix/nested-if-branch-sync'——修复嵌套条件判断的分支同步写错容器(填了值保存就没了 / 步骤被改名)
|
2026-09-24 11:45:46 +08:00 |
|
butubb
|
cba39562dc
|
fix(编辑器): 嵌套条件判断的分支同步写错容器 —— 修复「填了值保存就没了」+「步骤被改名」
用户报的现象(生产「评论」任务):
① 一直提示"步骤7子步骤1没有填写包名"
② 包名填了、一保存就没了
③ 那个步骤还会"自己重命名"成「指定视频评论」
根因(**2026-08-11 的 060157e 引入的老 bug,不是本批改动**):
`_syncParams` 定位 if_el 的分支容器时用的是
card.querySelector(':scope > .if-branch .step-children.if-else')
`.if-branch` 与 `.step-children` 之间是**空格(后代)**而不是 `>`(直接子级)。
于是**条件判断里再嵌一个条件判断**时,外层卡片会匹配到**内层那个 if_el 的分支容器**
(文档顺序上它更靠前),把内层的子步骤当成自己的分支去同步:
· 内层分支的「步骤名」输入框 → 写进了外层分支里那个 stop_app 的 label
(所以步骤被改名成内层容器步骤的名字「指定视频评论」);
· 内层分支里**没有**包名输入框 → 外层那个 stop_app 的 package **永远读不回来**
(所以"填了保存就没"、保存后仍报"没填包名")。
把两处都改成直接子级(`> `)即可。then 那侧此前恰好因为文档顺序能命中自己,
但写法一样脆,一并收紧。
验证:最小复现(外层 if_el 的 then 里嵌内层 if_el,外层 else 放 stop_app+screen_off)
—— 修复前:`package=""`、`label="指定视频评论"`(与用户现象逐字一致);
修复后:`package="com.ss.android.ugc.aweme"`、`label="我给它起的名"`。
另扫了生产全部 6 个任务:只有「评论」这一个任务的结构会踩到,且已经踩了
(`7.第1步 stop_app 包名为空`)—— 改动不会波及其它任务。
|
2026-09-24 11:29:53 +08:00 |
|
butubb
|
870e7138d5
|
Merge branch 'dev'——跨设备去重账本(同一个号不做两次 + 去重记录页)+ 条件判断多值比对
|
2026-09-24 11:03:32 +08:00 |
|
butubb
|
8834ecb7ef
|
Merge branch 'feat/dedup-ledger'——跨设备去重账本(同一个号不做两次 + 去重记录页)+ 条件判断多值比对
|
2026-09-24 11:00:41 +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
|
5c88cc6384
|
feat(条件判断): 比对的值支持多个(任一命中即可)
需求:`35377983067` 或 `35377983068` 都算我的号,要能一次列好几个。
- `cmp_value` 现在是**多值**:换行或 `|` 分隔,一行一个。
多个之间是 **OR**(任一命中即命中);否定式(不等于/不包含)语义是
"一个都不许出现"。**逗号刻意不当分隔符**——要比对的文本本身就常带逗号
(`抖音号:123,456`),拆了就永远匹配不上。
- 比对核心抽成 `_cmp_hit(got, op, want) -> (是否命中, 命中的那个值)`,
`_text_matches` 保留布尔版(多处调用只看结果,不改签名)。
日志因此能写清人话:
`采集到 '抖音号:35377983068' 包含 2 个候选值 → 符合,命中『35377983068』`
否定式不成立时则指出"因为出现了『xxx』"——排查时一眼看出是哪个值惹的。
- 行为统一(**刻意的变更**):候选值为空/全空白 = 误配 → 一律不命中。
以前"等于 + 两边都空"会返回 True(`"" == ""` 的巧合),等于选了"等于"
却没填值就永远命中、条件判断形同虚设;现在与编辑器告警、AI 草稿校验
的口径一致了("选了运算符没填值 → 永远不命中")。
- 前端:`cmp_value` 由单行输入换成**两行文本域**,标签与提示写明"可多行、
任一命中即可、逗号不是分隔符"。
- 文档:TASK_DEV.md §4.2 例子改成多号,并写明分隔符与 OR/否定语义。
自测:多值语义 15 项(换行/`|` 拆分、逗号不拆、空白清理、四种运算符的多值
OR 与否定语义、命中的是哪个值、候选全空不命中)+ 原有 38 项回归全过;
`_exec_if_el` 走多值路径实测命中 then 分支、日志含候选数与命中值。
|
2026-09-24 10:40:08 +08:00 |
|
butubb
|
54d9a55ff6
|
Merge branch 'dev'——设备电量监控(大屏+监控页显示、低电量 webhook 告警)、选择设备与所有通知显示设备名、条件判断支持文本比对
|
2026-09-24 10:12:41 +08:00 |
|
butubb
|
f660b1031c
|
Merge branch 'feat/if-el-text-compare'——条件判断支持「拿选中元素的文本和设定的值比」(等于/不等于/包含/不包含)
|
2026-09-24 10:07:24 +08:00 |
|
butubb
|
34a03db1b9
|
feat(条件判断): 支持「选中元素 → 拿它的文本和设定的值比」(等于/不等于/包含/不包含)
需求原话:条件判断需要能选择元素,比如我设置了一个抖音号,让它采集这个元素的
文本和我设的值做条件判断。
原来的条件判断只会判「元素在不在」(找到走 then、没找到走 else),没法判"内容对不对"。
现在加两个可选参数:
- `cmp_op`:等于 / 不等于 / 包含 / 不包含(`==`/`!=`/`contains` 这类别名也认);
留空 = 老行为,**完全向后兼容**(现有任务不用改)。
- `cmp_value`:要比对的值。
填了就变成:先按选择器取到元素文本 → 和 cmp_value 比 → 比对结果才是命中与否。
用户那个例子就是:元素 `com.ss.android.ugc.aweme:id/506` 的文本是
`抖音号:35377983067`,选「包含」、值填 `35377983067` 即可;
要抓"登录的不是这个号"就用「不等于」。
- 读取只走 `get_text()`(只读,2 秒超时),不点击、不改状态。
- **两条防误判规则**(都写进文档了):
· 元素**没找到**时一律算未命中(走 else)——"没读到"绝不能被当成"和我设的不一样",
否则界面还没加载出来就会误判成"账号被换了";
· 元素在但文本为空时按空文本参与比对(用于抓"栏位是空的"),日志会打出实际读到的值。
- 比对**不污染「选择器健康」统计**:统计用的仍是"元素在不在"——"找到了但值不对"
是条件按预期走了 else,不是选择器失效,不该攒出「连续未命中」告警。
- 支持比对的类型:所有元素类选择器 + `foreground`(比前台包名)+ `ocr`(比 OCR 命中的那段字)。
`screen`(亮/熄)没有文本可比,设了会忽略并告警。
- 前端:条件判断面板多一块「文本比对」(运算符下拉 + 比对的值),
只在元素/OCR/前台App 条件下出现;保存前校验"选了运算符却没填值"和"screen 上设了比对"。
- AI 建任务草稿校验(`core/task_draft.py`)同步拦"运算符拼错""有运算符没值"——
执行器对认不出的运算符是"一律不命中",不拦就会变成悄悄走 else。
自测:比对函数 16 项(含别名、空值、None、拼错运算符)、`_exec_if_el` 全分支 20 项
(含"没找到+不等于"这个关键边界、健康统计用存在性、screen 忽略比对、u2 抛异常不炸)、
**真机验证**(cs1 上真读一个 TextView 的文本再比对:包含/等于/不等于 全对,
元素不存在时不误判)、草稿校验 5 项、GET 路由冒烟 55 个 0 个 500。
(测试里踩到一个坑记一笔:u2 的 `XPathSelector.exists` 是纯 bool 属性,而
`UiObject.exists` 是"可调用的 Exists 对象"(`bool()` 即时判断、`exists(timeout=)` 带等待),
两套语义不能想当然统一——单测的假设备一开始没模拟对,是测试写错了不是代码写错了。)
|
2026-09-24 09:22:32 +08:00 |
|
butubb
|
bf432b4f56
|
Merge branch 'fix/device-name-display'——选择设备与所有通知显示设备名(不再显示 IP)+ 修 /locate 500/XSS
|
2026-09-24 09:17:15 +08:00 |
|
butubb
|
8fdde978ea
|
fix(设备名): 选择设备与所有通知都显示名称而不是 IP
用户反馈:① 元素抓取的选择设备列表显示的是 IP;② 所有 webhook 通知里都是 IP;
要都显示设备名。
- 通知链路统一补名字(调用点不用动):`core/notifier.py`
· 新增 `set_device_name_resolver()` + `fill_device_names()`,在 `notify()` 与
`build_message()`(预览/测试发送也走它)里把 `serial` 补成 `device_name`、
把 `serials`/`devices` 列表逐项换成名字。
· 补一处就全带名字了——标题主体、字段表、聚合样本认的都是 `device_name`。
· **做成"纯内存回调"是刻意的**:notify() 的硬红线是零 DB,不能为了取个名字去查库
(那等于在业务线程里加一次阻塞查询)。
· 降级规则:调用点自己传了 device_name 就用它的;查不到名字(没命名/不在池里)
保留原地址;解析器缺失或抛异常都只是降级,绝不影响发送。
- `core/device_pool.py`:维护 `serial → 名称` 内存快照——`init_app` 同步刷一次、
增删改名/迁址后各刷一次(改名立刻生效)、`device-names` 线程每 60s 兜底刷一次
(覆盖整库恢复这类进程外改动)。`name_of()` 只读内存,可在通知路径上安全调用。
- `web_server.py`:装配层接上 `notifier.set_device_name_resolver(device_pool.name_of)`。
- 元素抓取/测试此步骤的设备列表:`GET /api/uiauto/devices` 的 `name` 改用**平台名**,
前端 `editor.js` 新增共用的 `_devCard()`——名字做主标题(粗体),
`型号 · 地址` 作副标题。
· 池外设备**退回地址而不是 uiautodev 的 name**:实测那份 name 是设备 codename
(一柜子机器全叫 "earth"),拿它认设备等于没名字,地址至少唯一。
· 「测试此步骤」的设备列表原来连状态角标都没有,一并统一成同一个卡片。
- 顺带修一个**既有 bug(不是本次需求)**:`/locate` 设备端定位页从 ce47a5b 那次
web 蓝图拆分起就一直 500——拆分时漏掉了 `render_template_string` 与
`markupsafe.escape as _esc` 两个 import。后者不只是缺个名字:定位页把 query 里的
serial 拼进 HTML,而 `render_template_string` 的模板名是 "<template>"、
**不会自动转义**,所以那还是个反射 XSS。已显式转义(已用 `<img onerror>` 验证)。
自测:device_pool 快照/改名即时生效、fill_device_names 全分支(含列表、
未命名保留地址、不在池保留地址、解析器缺失/抛异常降级)、真消息渲染断言
**通篇不含 IP**;GET 路由冒烟 54 个 0 个 500;前端语法 + 卡片渲染截图核对。
|
2026-09-24 08:55:10 +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
|
af33d5e4a2
|
Merge branch 'dev'——录制手势纯轨迹回放(手机 getevent 真手指录制)
v2026.09.24
|
2026-09-20 15:29:09 +08:00 |
|
butubb
|
773cfbb763
|
Merge branch 'feat/gesture-record'——录制手势改为纯轨迹回放(手机 getevent 真手指录制)
|
2026-09-20 15:28:30 +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
|
f62bc8da28
|
Merge branch 'dev'——动作配置(步骤默认值 + 动作录制器)
|
2026-09-20 14:34:31 +08:00 |
|
butubb
|
97e2883fec
|
Merge branch 'feat/action-studio'——动作配置分栏(步骤默认值)+ 动作录制器
|
2026-09-20 14:34:14 +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
|
cc72591773
|
Merge branch 'dev'——拟人滑动(每台设备自己的手感)+ 任务级公共巡检
v2026.09.20
|
2026-09-16 13:14:02 +08:00 |
|
butubb
|
9a44eeba95
|
Merge branch 'feat/task-patrol'——任务级公共巡检(含与拟人滑动的合并:import、能力表两处冲突已解)
|
2026-09-16 13:10:05 +08:00 |
|
butubb
|
77eb53b4b2
|
Merge branch 'feat/human-swipe'——滑动拟人化 + 每台设备自己的手感
|
2026-09-16 13:09:31 +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
|
92d71c0e27
|
Merge branch 'dev'——日志筛选/下载 + 任务步骤明细 + 通知降噪
v2026.09.16
|
2026-09-16 10:32:57 +08:00 |
|
butubb
|
243df3071b
|
Merge branch 'fix/notify-noise'——通知降噪:多设备批次不逐台报成功、批次汇总瘦身、设备只显示名字
|
2026-09-16 10:32:51 +08:00 |
|
butubb
|
16e3e162b2
|
fix(通知): 多设备批次不再逐台报成功;批次汇总瘦身并列出失败设备;设备只显示名字
用户反馈:一条任务覆盖 13 台设备,每台成功都推一条,群里被刷屏;批次汇总里
「任务:抖音养号」与标题重复、「样本:抖音养号」毫无信息量、「stopped」还是英文。
- task_manager:
· **多设备批次不发 `task.device.success`**(批次汇总里已有成功台数;单台任务照发);
失败仍逐台发——那是少数,且要知道是哪台;
· `_BatchTracker` 收集失败设备(`名字(型号):原因`,最多 5 条)并加进批次汇总;
· 设备级事件带 `model`:型号取自**设备池快照**(批次开始时一次查好带下去),
不是 worker 每次连接现采的(那次 `d.info()` 可能超时 → 空),也不查库;
- notifier:
· 聚合窗口口径修正:事件声明 `agg_window=0`(批次结束/服务启停/备份恢复等低频
高危事件)**不再被 hook 的窗口拖住**——原先事件级设置完全失效;
· 「样本」行只在真的合并了多条(>1)时出现,且按**设备**维度写(合并多台时写
任务名每条都一样);与标题重复的字段行(「任务:x」)不再重复渲染;
· 设备名与地址同时存在时**只显示名字**(IP 是给日志看的);
· 补 `stopped`/`failed_devices` 中文标签(原先直接显示英文 key);
- notify_events:批次事件补 `skipped`/`failed_devices` 字段,设备事件补 `model`。
实测(dev 真机 + 假接收端):
批次 2 台 → 只收到 batch.started/finished,无 device.success,汇总 总数2/成功1/跳过1;
单台任务 → 收到 device.success,显示「设备:cs1 / 型号:M2010J19SC」不含 IP。
文档:doc/NOTIFY.md §3(多设备批次怎么发 + 批次消息样例)、§6(窗口口径与注意事项)。
|
2026-09-16 09:47:25 +08:00 |
|
butubb
|
49544b1985
|
Merge branch 'feat/task-step-log'——任务步骤明细表 + 日志页「步骤明细」面板
|
2026-09-16 09:10:24 +08:00 |
|
butubb
|
8879da72b3
|
Merge branch 'feat/log-query'——日志页:关键字/级别/时间过滤 + 下载(含滚动历史)
|
2026-09-16 09:10:18 +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 |
|