diff --git a/.gitignore b/.gitignore index f8f06be..d5e00a2 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,9 @@ memory/* # TMWebDriver SOP !memory/tmwebdriver_sop.md +# ADB UI tool +!memory/adb_ui.py + # Visual Studio .vs/ restore_commit.txt diff --git a/memory/adb_ui.py b/memory/adb_ui.py new file mode 100644 index 0000000..6593544 --- /dev/null +++ b/memory/adb_ui.py @@ -0,0 +1,60 @@ +# adb_ui.py - 一键dump+解析Android UI +# PITFALLS: dump已内置--compressed; 美团等动画app需先禁动画(adb shell settings put global animator_duration_scale 0 ...共3条); +# 弹窗检测: ui(clickable_only=True, raw=True) 找全屏FrameLayout+底部小ImageView(关闭X) +# 已知包名: 美团外卖=com.sankuai.meituan.takeoutnew 淘宝=com.taobao.taobao +import subprocess, xml.etree.ElementTree as ET, os, re, shutil + +ADB = shutil.which("adb") or "adb" +LOCAL_XML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui_mt.xml") + +def ui(keyword=None, clickable_only=False, raw=False): + """一键dump+解析Android UI + keyword: 过滤含关键词的节点 + clickable_only: 只显示可点击节点 + raw: 返回原始节点列表而非打印 + """ + subprocess.run([ADB, "shell", "rm", "-f", "/sdcard/ui.xml"], capture_output=True) + r = subprocess.run([ADB, "shell", "uiautomator", "dump", "--compressed", "/sdcard/ui.xml"], + capture_output=True, text=True, timeout=15) + if "dumped" not in r.stdout.lower() and "dumped" not in r.stderr.lower(): + print(f"dump failed: {r.stdout}{r.stderr}") + return [] + subprocess.run([ADB, "pull", "/sdcard/ui.xml", LOCAL_XML], capture_output=True, timeout=10) + + tree = ET.parse(LOCAL_XML) + nodes = [] + for n in tree.getroot().iter("node"): + text = n.get("text", "") + desc = n.get("content-desc", "") + bounds = n.get("bounds", "") + click = n.get("clickable") == "true" + cls = n.get("class", "").split(".")[-1] + label = text or desc + if not label and not raw: + continue + if clickable_only and not click: + continue + if keyword and keyword.lower() not in (label or "").lower(): + continue + cx, cy = 0, 0 + if bounds: + m = re.findall(r'\[(\d+),(\d+)\]', bounds) + if len(m) == 2: + cx = (int(m[0][0]) + int(m[1][0])) // 2 + cy = (int(m[0][1]) + int(m[1][1])) // 2 + nodes.append({"label": label, "click": click, "bounds": bounds, "cx": cx, "cy": cy, "class": cls}) + + if not raw: + for n in nodes: + flag = "Y" if n["click"] else " " + coord = f"({n['cx']},{n['cy']})" if n['cx'] else "" + print(f"[{flag}] {n['label']} {coord} {n['bounds']}") + print(f"\ntotal: {len(nodes)} nodes") + return nodes + +def tap(x, y): + subprocess.run([ADB, "shell", "input", "tap", str(x), str(y)], capture_output=True) + print(f"tap({x},{y}) ok") + +if __name__ == "__main__": + ui() \ No newline at end of file diff --git a/memory/autonomous_operation_sop.md b/memory/autonomous_operation_sop.md index 779d0a0..ba8e1ef 100644 --- a/memory/autonomous_operation_sop.md +++ b/memory/autonomous_operation_sop.md @@ -16,6 +16,7 @@ ### 选择原则 - **增量价值优先**:必须产生新知识/新能力,优先选择能扩展自身操作能力的任务 +- **盲区优先**:探索产出须为自身参数无法复现的知识(小众库发现与用法、环境特有的坑),学已熟知库的基础用法无价值 - **自主发现**:主动扫描用户环境(pip list、项目目录、配置文件)发现未知工具,而非等用户告知 - **假设驱动**:明确"要验证什么假设",实验必须有动手验证环节 - **禁止低价值验证**:不验证 global_mem 中的静态配置,不做无假设的巡检 diff --git a/memory/tmwebdriver_sop.md b/memory/tmwebdriver_sop.md index a040481..d05be3b 100644 --- a/memory/tmwebdriver_sop.md +++ b/memory/tmwebdriver_sop.md @@ -17,7 +17,7 @@ - 受浏览器 **InTrusted / 权限策略** 限制,有些操作不能单靠 TMWebDriver 完成: 1. **打开新窗口 / 新标签** - - 部分浏览器/脚本设置中,需要用户显式允许脚本“打开新标签/窗口”;否则相关命令会被拦截。 + - 已通过 GM_openInTab 替换 window.open 解决。潜在问题:部分浏览器可能仍需用户显式允许脚本打开新标签。 2. **上传文件等受信任交互** - 通常无法单纯用 JS 填充 `` 等敏感控件。 - 需要配合键鼠控制工具(`ljqCtrl.py` / 控制 SOP)在前台模拟真实点击和文件选择。 diff --git a/memory/web_setup_sop.md b/memory/web_setup_sop.md index b0cfa50..a0bf28c 100644 --- a/memory/web_setup_sop.md +++ b/memory/web_setup_sop.md @@ -1,18 +1,43 @@ -# Web 工具链初始化执行 SOP (精简版) +# Web 工具链初始化执行 SOP -若web_scan和web_execute_js已测试可用,无需进行此sop。 -此sop仅供初始安装时,code_run可用但web工具第一次配置使用。 +若 web_scan 和 web_execute_js 已测试可用,无需执行此 SOP。 +仅供初始安装时,code_run 可用但 web 工具尚未配置的场景。 ## 目标 -在仅具备系统级权限时,引导用户建立 Web 交互能力。 +在仅具备系统级权限(code_run)时,建立 Web 交互能力(web_scan / web_execute_js)。 -## 阶段一:引导安装 (Action: code_run) -1. **启动导航**:使用 PowerShell 启动 Chrome 并直达测试版页面: - `start chrome "https://chromewebstore.google.com/detail/%E7%AF%A1%E6%94%B9%E7%8C%B4%E6%B5%8B%E8%AF%95%E7%89%88/gcalenpjmijncebpfijmoaglllgpjagf"` +## 前置:检测浏览器 +```python +import shutil, subprocess +browser = "chrome" if shutil.which("chrome") else "msedge" # Edge内置必存在,Chrome可选 +``` -## 阶段二:脚本分发 (Action: file_read + code_run) -1. **资源提取与写入**:使用 Python 读取 `../assets/ljq_web_driver.user.js` 并通过 `pyperclip` 写入系统剪贴板。 -2. **用户交互指令**:通知用户“脚本已入剪贴板”,请求用户在插件中完成【新建-全选-粘贴-保存】操作。 +## 阶段一:安装 Tampermonkey (手动) +**状态**: 尚未实现自动化,需用户手动操作。 +1. 用 `start` 打开扩展商店页面(自动适配浏览器): + - Chrome: `start "" "https://chromewebstore.google.com/detail/篡改猴测试版/gcalenpjmijncebpfijmoaglllgpjagf"` + - Edge: `start "" "https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd"` +2. 提示用户点击"安装"并确认。 -## 阶段三:环境确认 (Action: code_run) -1. **切换驱动**:尝试调用 `web_scan` 或注入 JS 进行心跳检测,确认 `ljq_web_driver.user.js` 是否已在目标域生效。 +## 阶段二:安装 ljq_web_driver.user.js +**脚本路径**: `../assets/ljq_web_driver.user.js` + +### 方案A(自动化,优先) +本地 HTTP 服务器 + TM 中间页,用 `start` 命令打开: +1. Python 启动 `http.server` 托管脚本(Content-Type: text/javascript) +2. `start "" "https://www.tampermonkey.net/script_installation.php#url=http://127.0.0.1:{port}/ljq_web_driver.user.js"` + - ⚠️ 以上步骤均须用 `Popen` 非阻塞执行,禁止 `subprocess.run`,否则阻塞 agent +3. TM 秒弹安装确认,用户点"安装"即可 + +### 方案B(手动 fallback) +若方案A失败,用剪贴板: +1. 读取脚本内容 → `pyperclip.copy()` +2. 通知用户在 TM 中【新建脚本 → 全选 → 粘贴 → 保存】 + +## 阶段三:验证 +调用 `web_scan` 或注入 JS 心跳检测,确认脚本已生效。 + +## 避坑 (Chromium untrusted 拦截) +- ❌ 直接导航到 `localhost/.user.js` → Chromium 弹 untrusted 拦截 + "另存为",延迟约1分钟 +- ✅ 必须用 `start` 命令(系统级)打开 TM 中间页 URL → 秒弹安装,无拦截 +- 此问题 Chrome 和 Edge 均存在(Chromium 内核通病) \ No newline at end of file