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 会读到上一次的内容 → 误报"写入可能被拒"(实测内容已写入却回失败)
This commit is contained in:
+47
-2
@@ -41,7 +41,7 @@ from core.device_worker import BaseWorker, _update_status
|
||||
from core.u2_helper import (ensure_app_running, wait_for_app_home, random_sleep,
|
||||
screen_is_on, current_package)
|
||||
from core.logger import get_logger
|
||||
from core import notifier, step_log, humanize, patrol, gesture, dedup
|
||||
from core import notifier, step_log, humanize, patrol, gesture, dedup, ledger
|
||||
|
||||
_log = get_logger("task.generic")
|
||||
|
||||
@@ -113,7 +113,7 @@ STEP_TYPES = [
|
||||
"params": {"children": []}},
|
||||
{"type": "if_el", "label": "条件判断", "icon": "❓",
|
||||
"params": {"selector_type": "xpath", "selector_value": "", "timeout": 3,
|
||||
"cmp_op": "", "cmp_value": "",
|
||||
"cmp_op": "", "cmp_value": "", "cmp_source": "", "cmp_group": "",
|
||||
"ident_type": "text", "ident_value": "",
|
||||
"then": [], "else": []}},
|
||||
{"type": "mark_done", "label": "记为已做", "icon": "✅",
|
||||
@@ -683,6 +683,12 @@ class GenericStepsWorker(BaseWorker):
|
||||
timeout = float(params.get("timeout", 3))
|
||||
cmp_op = str(params.get("cmp_op") or "").strip()
|
||||
cmp_val = str(params.get("cmp_value") or "")
|
||||
# 候选值可以来自「账号台账」(cmp_source)——这里只把 cmp_val 这个**字符串**
|
||||
# 换掉来路,下面 762-780 的比对、候选计数、日志文案全自动跟着对
|
||||
# (_cmp_hit 保持纯函数,不掺 DB)
|
||||
src_note = ""
|
||||
if cmp_op:
|
||||
cmp_val, src_note = self._resolve_cmp_value(params, cmp_val)
|
||||
# 去重条件不用 selector_value(身份元素在 ident_value 里),所以别按"缺选择器"拦
|
||||
if not sel_val and sel_type != "dedup":
|
||||
_log.warning(f"[{self.serial}] if_el 缺少选择器,跳过")
|
||||
@@ -783,6 +789,11 @@ class GenericStepsWorker(BaseWorker):
|
||||
# 界面上那块是隐藏的,旧值会留在 params 里,那种残留每轮都刷警告没意义
|
||||
_log.warning(f"[{self.serial}] {sel_type} 没有文本可比,已忽略文本比对设置")
|
||||
|
||||
# 候选值来源(台账/手填)也要进日志与步骤明细:取 0 个号会让这一步
|
||||
# **永远走 else 分支**,而任务照样"跑完了"——只有这里能看出原因
|
||||
if src_note:
|
||||
detail = f"{detail};{src_note}" if detail else src_note
|
||||
|
||||
if dynamic:
|
||||
_log.info(f"[{self.serial}] 条件判断 {detail} → {'命中' if found else '未命中'}")
|
||||
else:
|
||||
@@ -797,6 +808,40 @@ class GenericStepsWorker(BaseWorker):
|
||||
self._exec_steps(d, branch, depth + 1)
|
||||
return found
|
||||
|
||||
def _resolve_cmp_value(self, params, manual_raw):
|
||||
"""「条件判断」的候选值 = 手填值 + 台账取号(合并,OR 语义)。
|
||||
|
||||
返回 (候选值文本, 给日志/步骤明细的来源说明)。
|
||||
|
||||
· **合并**而不是二选一:台账取空时手填值仍然生效(台账没维护好不会把任务
|
||||
直接打哑);"台账里的号 + 临时补一个"也是自然需求 —— split_cmp_values
|
||||
与 `_cmp_hit` 本来就是多值 OR 语义。
|
||||
· 取 0 个号时**必须留痕**:候选为空 ⇒ 这一步永远走 else 分支,而任务本身
|
||||
"跑完了、显示成功",不写日志就只能靠人猜。
|
||||
· ⚠ 这里产出的只是**比对用的文本**,绝不参与去重 identity:
|
||||
身份是元素原文逐字算 key,台账里是纯号,格式不同会让去重静默失效
|
||||
(见 core/ledger.py 顶部的警告)。
|
||||
"""
|
||||
source = str(params.get("cmp_source") or "").strip()
|
||||
manual = split_cmp_values(manual_raw)
|
||||
if source not in ("device", "all", "group"):
|
||||
return manual_raw, "" # 手填(默认):行为与以前完全一致
|
||||
ids, note = ledger.douyin_ids(
|
||||
source, serial=self.serial,
|
||||
device_name=(self.ctx or {}).get("device_name", ""),
|
||||
group=str(params.get("cmp_group") or "").strip())
|
||||
merged = list(ids) + [v for v in manual if v not in ids]
|
||||
if not ids:
|
||||
_log.warning(
|
||||
f"[{self.serial}] 条件判断取号为空(来源 {source}):{note};手填候选 "
|
||||
f"{len(manual)} 个 → "
|
||||
+ ("仅用手填值" if manual else "候选为空,这一步将永远不命中(走未找到分支)"))
|
||||
src_txt = (f"候选来自台账({source}) {len(ids)} 个号" if ids
|
||||
else f"台账取号为空:{note}")
|
||||
if manual:
|
||||
src_txt += f",另加手填 {len(manual)} 个"
|
||||
return "\n".join(merged), src_txt
|
||||
|
||||
def _dedup_policy(self):
|
||||
"""任务级去重有效期(`dedup_reset` / `dedup_hours`)→ (kind, hours)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user