# Autox.md —— 云控手机脚本开发规则

> **适用范围**：本平台「AI 写脚本」「工作空间 · JS 代码」「计划任务」下发到手机执行的 AutoX 兼容 JS 脚本。
> **真实运行环境**：Android 无障碍服务进程内的 Rhino 1.7.14 解释器 —— **不是浏览器，也不是 Node.js**。
> **地位**：本文档是脚本开发的唯一权威规则。服务端在每次 AI 脚本开发时把本文全文注入提示词；人工编辑本文件后立即生效（无需重启服务端）。清单里没有的 API 一律不存在，写了就是 `ReferenceError`。

---

## 1. 语言边界（Rhino 1.7.14）

| 可以用 | 禁止用 |
| --- | --- |
| `var` / `let` / `const` | `class` 语法（Rhino 编译不过，必崩） |
| 箭头函数、模板字符串 | `async` / `await` / `Promise`（没有事件循环，写了也不执行） |
| 对象/数组解构、`for...of` | `import` / `export` / `require`（不是模块环境） |
| 普通函数、`while` / `for` 循环 | `setTimeout` / `setInterval`（不存在，用 `while + sleep` 代替） |
| JSON 字面量 | DOM / `window` / `fetch` / `XMLHttpRequest` |

## 2. 能力边界

**存在**：控件选择器与操作、坐标手势（点击/长按/滑动/折线）、应用启动与系统按键、剪贴板、图色（找色/多点找色/找图）、OCR、`device` / `app` / `console` 三个内建对象、`log` / `toast` / `sleep` / `random` / `exit`。

**不存在（写了直接抛错或 ReferenceError）**：

- `shell(...)` —— 脚本无权执行 shell，直接抛错
- `files` / `http` / `axios` / `mqtt` / `websocket` —— 全部不存在
- `images` 模块（`images.read` / `images.matchTemplate` / `images.findImage`）—— 找图用全局 `findImage("模板名")`
- `threads` / `timers`（`setInterval`、`setTimeout`）—— 用 `while + sleep` 代替
- `storages` / `events` / `sensors` / `floaty` / `dialogs` / `canvas` / `ui` —— 不存在
- `device.getIMEI()` / `getMacAddress()` / `vibrate()` / `getTotalMem()` —— 明确抛「暂不支持」
- `app.viewFile` / `editFile` / `uninstall` / `sendBroadcast` / `startService` —— 明确抛「暂不支持」
- `importClass` / `importPackage` / `Packages` / `java` —— 沙箱里访问是 `ReferenceError`
- `auto.waitFor()` 不需要写：无障碍服务常驻，恒为 `true`

## 3. API 速查

### 3.1 全局函数

```
sleep(ms)                      // 毫秒；长 sleep 会被切成小段以便停止按钮打断
log(msg) / console.log(msg)    // 写进设备日志流，网页调试台可见
toast(msg) / toastLog(msg)     // toastLog = toast + log
exit()                         // 结束脚本
random() / random(min, max)    // 闭区间，含 max

click(x, y)                    // 坐标点击，走无障碍手势
longClick(x, y)                // 长按 600ms
press(x, y, ms)                // 按住指定时长
swipe(x1, y1, x2, y2, [ms])    // 直线滑动，缺省 300ms
gesture(ms, [x1,y1], [x2,y2])  // 折线手势，至少两个点

back() / home() / recents() / notifications()
wakeUp() / lockScreen()        // 锁屏需 Android 9+

launch(pkg) / launchPackage(pkg) / launchApp(appName)
getPackageName(appName) / getAppName(pkg)
openAppSetting(pkg) / openUrl(url)
waitForPackage(pkg) / waitForActivity(activity)   // 阻塞直到切换，无超时参数
currentPackage() / currentActivity()

setClip(text) / getClip()      // 剪贴板

text("x") / desc("x") / id("x") / className("x") / packageName("x")   // 返回选择器，可链式
selector()                     // 空选择器，再链式加条件
bounds(l,t,r,b) / boundsInside(l,t,r,b) / boundsContains(l,t,r,b)     // 区域条件
```

### 3.2 选择器条件（链式调用，返回选择器自身）

- **text 组**（互斥，后写覆盖先写）：`text` / `textContains` / `textStartsWith` / `textEndsWith` / `textMatches`
- **desc 组**（互斥）：`desc` / `descContains` / `descStartsWith` / `descEndsWith` / `descMatches`
- **id 组**（互斥）：`id` / `idContains` / `idStartsWith` / `idEndsWith` / `idMatches`
- **className 组**（互斥）：`className` / `classNameContains` / `classNameStartsWith` / `classNameEndsWith` / `classNameMatches`
- **packageName 组**（互斥）：`packageName` / `packageNameContains` / `packageNameStartsWith` / `packageNameEndsWith` / `packageNameMatches`
- **布尔条件**（传 `true` / `false`）：`clickable` / `longClickable` / `scrollable` / `editable` / `checkable` / `checked` / `selected` / `enabled` / `visibleToUser` / `focusable` / `multiLine`

### 3.3 选择器查询方法

```
findOne()        // 无参 = 阻塞直到出现；findOne(3000) 超时返回 null
findOnce()       // 只查一次，查不到返回 null；findOnce(i) 取第 i 个命中（从 0 起）
find()           // 返回集合，可能为空
untilFind()      // 阻塞到至少一个
exists()         // 只查一次，返回布尔
waitFor()        // 阻塞到出现，无返回值
```

### 3.4 命中控件（UiObject）与集合

- **动作**（返回布尔）：`click` / `longClick` / `setText` / `input` / `scrollForward` / `scrollBackward` / `scrollUp` / `scrollDown` / `scrollLeft` / `scrollRight` / `select` / `collapse` / `expand`
- **读取**（返回属性值或对象）：`id` / `text` / `desc` / `className` / `packageName` / `bounds` / `boundsInParent` / `clickable` / `longClickable` / `scrollable` / `editable` / `checkable` / `checked` / `selected` / `enabled` / `visibleToUser` / `focusable` / `focused` / `parent` / `child(i)` / `children` / `childCount` / `depth` / `path` / `findOne` / `find`
- **集合**（`find()` 的返回值）：`size()` / `get(i)` / `forEach(fn)` / `each(fn)`

### 3.5 内建对象

```
device.width / device.height          // 属性，不带括号
device.brand / model / sdkInt / release / serial
device.isScreenOn() / wakeUp() / wakeUpIfNeeded()
device.keepScreenOn([ms]) / keepScreenDim([ms]) / cancelKeepingAwake()
device.getBrightness() / setBrightness(v) / getBrightnessMode() / setBrightnessMode(v)
device.getMusicVolume() / setMusicVolume(v) / getMusicMaxVolume()
device.getBattery() / isCharging()

app.launch(pkg) / launchPackage(pkg) / launchApp(name)
app.getPackageName(name) / getAppName(pkg)
app.openAppSetting(pkg) / openUrl(url) / startActivity(...)

console.log / info / warn / error / verbose / assert / trace / time / timeEnd
// console.show / hide / clear / input 不支持，会抛错
```

### 3.6 图色与 OCR（游戏 / 自绘界面专用）

```
captureScreen()          // 返回 { width, height }；截图服务未开启返回 null
findColor(color, [options])          // 返回 {x, y} 或 null
findAllColors(color, [options])      // 数组（按色块聚类），默认最多 20 块
findMultiColors(anchorColor, [[dx, dy, color], ...], [options])   // 多点找色，全部命中才返回
findImage(templateName, [options])   // 返回 {x, y, similarity} 或 null；游戏辅助首选
pickColor(x, y)          // 取单点色值 "#rrggbb"，写 findColor 前先实测
ocr([options])           // 整屏识别，延迟 1-5 秒；返回 { text, blocks: [{ text, center, box }] }
ocrFindText(query, [options])        // 返回 { x, y, text, score } 或 null；比 ocr()+遍历省事
```

`options` 通用字段：`tolerance`（找色容差 0-255，默认 0）、`similarity`（找图相似度 0-1，默认 0.9）、`region: [x, y, w, h]`（限定区域，又快又准）、`maxResults`、`exact`（ocrFindText 是否全等）。

**图色铁律**：

1. 所有返回坐标都是**屏幕逻辑坐标**，直接喂 `click(x, y)`，不要再自己缩放。
2. 找不到时返回 `null` 而不是抛异常 —— **必须判空**，不要直接 `.x`。
3. `ocr` / `ocrFindText` 单次 1-5 秒：循环里必须加 `sleep`，或先找色缩小范围再 OCR。
4. `findImage` 的模板必须先在网页「图色」面板裁好存库，脚本只能按名字引用；分辨率变化后要重裁。
5. 优先级建议：找图 > 多点找色 > 单点找色 > OCR（外观稳定性与速度都按此排序）。

## 4. 选择器语义三条铁律

1. **同组互斥**：`text("确定").text("取消")` 永远命不中 —— 同组字符串条件后写的覆盖先写的，只保留一个。
2. **跨组 AND**：`text("确定").clickable(true)` = 「文本等于确定 **且** 可点击」。
3. **id 的两种匹配**：传完整 id（含冒号，如 `com.coolapk.market:id/bottom_navigation_container`）= 精确匹配；只传资源名（不含冒号）= 包含匹配。「点我的却点到首页」多数是这条没写对。

## 5. 编码规范（AI 生成与人写脚本共同遵守）

1. 完整脚本放在**一个** ` ```javascript ` 代码块里，面板才能识别并给出「写入编辑器」按钮。
2. 关键步骤 `log("...")` 标注进度，用户在网页调试台能看到执行到哪。
3. 优先控件选择器，其次图色/OCR，最后才硬编码坐标；坐标必须来自实测（节点查看器 / 取色器），不要凭印象写。
4. 每个查询动作考虑失败路径：`findOne(3000)` 判空 → `log` 原因 → `exit()`，不要让脚本卡死在阻塞查询上。
5. 点完 / 输入完给 300-800ms `sleep` 等界面响应；轮询用 `while` + `sleep(500)`，并设最大重试次数。
6. 不要写 `auto.waitFor()`；不要试图用 `java.` / `Packages.` 访问 Java API。
7. 选择器尽量带上第二条件（`clickable(true)` / `bounds` / 序号），同一界面 id 重复是常态。

## 6. 高频错误对照表

| 错误写法 | 后果 | 正确写法 |
| --- | --- | --- |
| `class Foo {}` | Rhino 编译报错 | 普通函数 + 对象字面量 |
| `setTimeout(fn, 1000)` | ReferenceError | `sleep(1000)` 后继续 |
| `http.get(...)` / `require("http")` | ReferenceError | 能力不存在，改在服务端/网页侧完成 |
| `images.findImage(img, "tpl")` | images 模块不存在 | `findImage("tpl")` |
| `text("a").text("b")` | 恒命不中 | 保留一个 text 条件 + 其他组条件 |
| `id("btn").click(2)` | 没有这个方法 | `id("btn").findOnce(2).click()` |
| `findOne()` 无参死等 | 界面没出现就永久卡住 | `findOne(3000)` + 判空 |
| `ocr()` 放进紧密循环 | 设备卡死、日志打爆 | 循环内 `sleep`，或 `ocrFindText(query, {region})` |
| 拿截图坐标直接点 | 点偏（截图被缩放过） | 用节点树 bounds 或图色返回的屏幕坐标 |
