├── .gitignore ├── icon.png ├── snippets ├── hamibot.json └── autojs.json ├── tsconfig.json ├── .vscode ├── tasks.json └── launch.json ├── .eslintrc.js ├── README.md ├── package.json └── src └── extension.ts /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamibot/vscode-hamibot/HEAD/icon.png -------------------------------------------------------------------------------- /snippets/hamibot.json: -------------------------------------------------------------------------------- 1 | { 2 | "hamibot.exit": { 3 | "prefix": "hamibot.exit", 4 | "body": ["hamibot.exit()"], 5 | "description": "结束脚本" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "out", 7 | "sourceMap": true, 8 | "strict": true, 9 | "rootDir": "src" 10 | }, 11 | "exclude": ["node_modules", ".vscode-test"] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /**@type {import('eslint').Linter.Config} */ 2 | // eslint-disable-next-line no-undef 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | plugins: ['@typescript-eslint'], 7 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 8 | rules: { 9 | semi: [2, 'always'], 10 | '@typescript-eslint/no-unused-vars': 0, 11 | '@typescript-eslint/no-explicit-any': 0, 12 | '@typescript-eslint/explicit-module-boundary-types': 0, 13 | '@typescript-eslint/no-non-null-assertion': 0, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hamibot for Visual Studio Code 2 | 3 | Hamibot 智能提示、调试。 4 | 5 | 安装地址:[VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=hamibot.vscode-hamibot) 6 | 7 | [Hamibot](https://hamibot.com/) 是一款安卓平台自动化工具,无需 root。 8 | 9 | Hamibot 10 | 11 | ## ✅ 准备 12 | 13 | 在使用前需要先设置 `开发者令牌`、`脚本 ID`、`机器人 ID`。 14 | 15 | 1. 打开 VSCode 设置(快捷键 `Ctrl+,`) 16 | 2. 在插件设置中找到 Hamibot,或者搜索 `hamibot` 17 | 3. 获取方式参考下方表格 18 | 19 | | | 路径 | 链接 | 示例 | 20 | | ---------- | ------------------------------------- | ----------------------------------------------------------- | ----------- | 21 | | 开发者令牌 | 控制台 > 设置 > 令牌 > 生成令牌 | [令牌](https://hamibot.com/account/tokens) | hmp_9b86... | 22 | | 脚本 ID | 控制台 > 开发 > 更多 > 设置 > 复制 ID | [脚本控制台](https://hamibot.com/dashboard/scripts/console) | 5ef1... | 23 | | 机器人 ID | 控制台 > 机器人 > 设置 > 复制 ID | [机器人](https://hamibot.com/dashboard/robots) | 5eae... | 24 | 25 | ## 📖 使用 26 | 27 | 首先打开 Hamibot 脚本(`*.js`)文件,插件提供多种操作方式:右键菜单、快捷键、图标按钮 28 | 29 | ### 运行脚本 30 | 31 | 在机器人上运行脚本 32 | 33 | - 右键选择 `运行 Hamibot 脚本` 34 | - 快捷键 `F9` 35 | 36 | ### 上传脚本并运行 37 | 38 | 自动更新代码到脚本,并在机器人上运行脚本 39 | 40 | - 右键选择 `保存并运行 Hamibot 脚本` 41 | - 快捷键 `F10` 42 | 43 | ### 停止脚本 44 | 45 | 在机器人上停止脚本 46 | 47 | - 右键选择 `停止 Hamibot 脚本` 48 | - 快捷键 `F11` 49 | 50 | ## 🔗 链接 51 | 52 | - [官网](https://hamibot.com/) 53 | - [脚本市场](https://hamibot.com/marketplace/) 54 | - [快速上手](https://hamibot.com/guide/) 55 | - [开发文档](https://docs.hamibot.com/) 56 | - [博客](https://blog.hamibot.com/) 57 | - [REST API](https://docs.hamibot.com/rest/overview) 58 | - [GitHub](https://github.com/hamibot/hamibot) 59 | - [开源脚本](https://github.com/hamibot/awesome-hamibot) 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-hamibot", 3 | "displayName": "Hamibot", 4 | "description": "Hamibot 智能提示、调试。", 5 | "version": "0.2.2", 6 | "publisher": "Hamibot", 7 | "repository": "https://github.com/hamibot/vscode-hamibot", 8 | "engines": { 9 | "vscode": "^1.34.0" 10 | }, 11 | "categories": [ 12 | "Snippets", 13 | "Other" 14 | ], 15 | "keywords": [ 16 | "hamibot", 17 | "autojs" 18 | ], 19 | "icon": "icon.png", 20 | "activationEvents": [ 21 | "onCommand:hamibot.run", 22 | "onCommand:hamibot.saveAndRun", 23 | "onCommand:hamibot.stop", 24 | "onCommand:hamibot.openSetting" 25 | ], 26 | "main": "./out/extension.js", 27 | "contributes": { 28 | "commands": [ 29 | { 30 | "command": "hamibot.run", 31 | "title": "运行 Hamibot 脚本", 32 | "icon": "$(run)", 33 | "when": "editorLangId == javascript" 34 | }, 35 | { 36 | "command": "hamibot.saveAndRun", 37 | "title": "保存并运行 Hamibot 脚本", 38 | "icon": "$(save)", 39 | "when": "editorLangId == javascript" 40 | }, 41 | { 42 | "command": "hamibot.stop", 43 | "title": "停止 Hamibot 脚本", 44 | "icon": "$(debug-stop)", 45 | "when": "editorLangId == javascript" 46 | }, 47 | { 48 | "command": "hamibot.openSetting", 49 | "title": "设置 Hamibot", 50 | "when": "editorLangId == javascript" 51 | } 52 | ], 53 | "keybindings": [ 54 | { 55 | "command": "hamibot.run", 56 | "key": "f9", 57 | "when": "editorLangId == javascript" 58 | }, 59 | { 60 | "command": "hamibot.saveAndRun", 61 | "key": "f10", 62 | "when": "editorLangId == javascript" 63 | }, 64 | { 65 | "command": "hamibot.stop", 66 | "key": "f11", 67 | "when": "editorLangId == javascript" 68 | } 69 | ], 70 | "menus": { 71 | "editor/context": [ 72 | { 73 | "command": "hamibot.run", 74 | "group": "navigation@0", 75 | "when": "editorLangId == javascript" 76 | }, 77 | { 78 | "command": "hamibot.saveAndRun", 79 | "group": "navigation@1", 80 | "when": "editorLangId == javascript" 81 | }, 82 | { 83 | "command": "hamibot.stop", 84 | "group": "navigation@1", 85 | "when": "editorLangId == javascript" 86 | }, 87 | { 88 | "command": "hamibot.openSetting", 89 | "group": "navigation@1", 90 | "when": "editorLangId == javascript" 91 | } 92 | ], 93 | "editor/title": [ 94 | { 95 | "command": "hamibot.run", 96 | "group": "navigation@0", 97 | "when": "editorLangId == javascript" 98 | }, 99 | { 100 | "command": "hamibot.saveAndRun", 101 | "group": "navigation@1", 102 | "when": "editorLangId == javascript" 103 | }, 104 | { 105 | "command": "hamibot.stop", 106 | "group": "navigation@1", 107 | "when": "editorLangId == javascript" 108 | } 109 | ] 110 | }, 111 | "configuration": { 112 | "type": "object", 113 | "title": "Hamibot", 114 | "properties": { 115 | "hamibot.token": { 116 | "type": "string", 117 | "default": "", 118 | "description": "开发者令牌" 119 | }, 120 | "hamibot.scriptId": { 121 | "type": "string", 122 | "default": "", 123 | "description": "脚本 id" 124 | }, 125 | "hamibot.robotId": { 126 | "type": "string", 127 | "default": "", 128 | "description": "机器人 id" 129 | } 130 | } 131 | }, 132 | "snippets": [ 133 | { 134 | "language": "javascript", 135 | "path": "./snippets/autojs.json" 136 | }, 137 | { 138 | "language": "javascript", 139 | "path": "./snippets/hamibot.json" 140 | } 141 | ] 142 | }, 143 | "scripts": { 144 | "vscode:prepublish": "npm run compile", 145 | "compile": "tsc -p ./", 146 | "lint": "eslint . --ext .ts,.tsx", 147 | "watch": "tsc -watch -p ./" 148 | }, 149 | "devDependencies": { 150 | "@types/node": "^16.11.7", 151 | "@types/node-fetch": "^2.6.2", 152 | "@types/vscode": "^1.34.0", 153 | "@typescript-eslint/eslint-plugin": "^5.30.0", 154 | "@typescript-eslint/parser": "^5.30.0", 155 | "eslint": "^8.13.0", 156 | "typescript": "^4.7.2" 157 | }, 158 | "dependencies": { 159 | "form-data": "^4.0.0", 160 | "node-fetch": "^2.6.1" 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import fs = require('fs'); 3 | import path = require('path'); 4 | import fetch from 'node-fetch'; 5 | import * as FormData from 'form-data'; 6 | 7 | export function activate(context: vscode.ExtensionContext) { 8 | const version = vscode.extensions.getExtension('hamibot.vscode-hamibot') 9 | ?.packageJSON.version; 10 | const userAgent = `vscode-hamibot ${version}`; 11 | 12 | const getConf = () => { 13 | const config = vscode.workspace.getConfiguration(); 14 | const token = config.get('hamibot.token'); 15 | const scriptId = config.get('hamibot.scriptId'); 16 | const robotId = config.get('hamibot.robotId'); 17 | if (!token) { 18 | vscode.window.showErrorMessage('需要填写 Token'); 19 | vscode.commands.executeCommand( 20 | 'workbench.action.openSettings', 21 | 'hamibot.token' 22 | ); 23 | return; 24 | } 25 | if (!scriptId) { 26 | vscode.window.showErrorMessage('需要填写 Script ID'); 27 | vscode.commands.executeCommand( 28 | 'workbench.action.openSettings', 29 | 'hamibot.scriptId' 30 | ); 31 | return; 32 | } 33 | if (!robotId) { 34 | vscode.window.showErrorMessage('需要填写 Robot ID'); 35 | vscode.commands.executeCommand( 36 | 'workbench.action.openSettings', 37 | 'hamibot.robotId' 38 | ); 39 | return; 40 | } 41 | return { token, scriptId, robotId }; 42 | }; 43 | 44 | const run = vscode.commands.registerCommand('hamibot.run', async () => { 45 | try { 46 | const config = getConf(); 47 | if (!config) return; 48 | const response = await fetch( 49 | `https://api.hamibot.com/v1/devscripts/${config.scriptId}/run`, 50 | { 51 | method: 'POST', 52 | headers: { 53 | 'User-Agent': userAgent, 54 | 'Content-Type': 'application/json', 55 | Authorization: `token ${config.token}`, 56 | }, 57 | body: JSON.stringify({ 58 | robots: [ 59 | { 60 | _id: config.robotId, 61 | name: 'Hamibot VSCode', 62 | }, 63 | ], 64 | }), 65 | } 66 | ); 67 | if (response.ok) { 68 | vscode.window.showInformationMessage('运行成功'); 69 | } else { 70 | const data = await response.json(); 71 | vscode.window.showErrorMessage('运行失败:' + JSON.stringify(data)); 72 | } 73 | } catch (e) { 74 | console.error(e); 75 | vscode.window.showErrorMessage('运行异常'); 76 | } 77 | }); 78 | 79 | const saveAndRun = vscode.commands.registerCommand( 80 | 'hamibot.saveAndRun', 81 | async () => { 82 | try { 83 | const config = getConf(); 84 | if (!config) return; 85 | const editor = vscode.window.activeTextEditor; 86 | if (!editor) return; 87 | const { fileName } = editor.document; 88 | const ext = path.extname(fileName); 89 | if (ext !== '.js') { 90 | vscode.window.showErrorMessage('不是 .js 文件,无法保存'); 91 | return; 92 | } 93 | await editor.document.save(); 94 | const body = new FormData(); 95 | body.append('file', fs.readFileSync(fileName), { 96 | contentType: 'application/javascript', 97 | filename: path.basename(fileName), 98 | }); 99 | const response = await fetch( 100 | `https://api.hamibot.com/v1/devscripts/${config.scriptId}/files`, 101 | { 102 | method: 'PUT', 103 | headers: { 104 | 'User-Agent': userAgent, 105 | Authorization: `token ${config.token}`, 106 | }, 107 | body, 108 | } 109 | ); 110 | if (response.ok) { 111 | //vscode.window.showInformationMessage('保存成功'); 112 | vscode.commands.executeCommand('hamibot.run'); 113 | } else { 114 | const data = await response.json(); 115 | vscode.window.showErrorMessage('保存失败:' + JSON.stringify(data)); 116 | } 117 | } catch (e) { 118 | console.error(e); 119 | vscode.window.showErrorMessage('保存异常'); 120 | } 121 | } 122 | ); 123 | 124 | const stop = vscode.commands.registerCommand('hamibot.stop', async () => { 125 | try { 126 | const config = getConf(); 127 | if (!config) return; 128 | const response = await fetch( 129 | `https://api.hamibot.com/v1/devscripts/${config.scriptId}/run`, 130 | { 131 | method: 'DELETE', 132 | headers: { 133 | 'User-Agent': userAgent, 134 | 'Content-Type': 'application/json', 135 | Authorization: `token ${config.token}`, 136 | }, 137 | body: JSON.stringify({ 138 | robots: [ 139 | { 140 | _id: config.robotId, 141 | name: 'Hamibot VSCode', 142 | }, 143 | ], 144 | }), 145 | } 146 | ); 147 | if (response.ok) { 148 | vscode.window.showInformationMessage('停止成功'); 149 | } else { 150 | const data = await response.json(); 151 | vscode.window.showErrorMessage('停止失败:' + JSON.stringify(data)); 152 | } 153 | } catch (e) { 154 | console.error(e); 155 | vscode.window.showErrorMessage('停止异常'); 156 | } 157 | }); 158 | 159 | const openSetting = vscode.commands.registerCommand( 160 | 'hamibot.openSetting', 161 | async () => { 162 | vscode.window.showInformationMessage('打开设置'); 163 | vscode.commands.executeCommand( 164 | 'workbench.action.openSettings', 165 | 'hamibot' 166 | ); 167 | } 168 | ); 169 | 170 | context.subscriptions.push(...[run, saveAndRun, stop, openSetting]); 171 | } 172 | -------------------------------------------------------------------------------- /snippets/autojs.json: -------------------------------------------------------------------------------- 1 | { 2 | "app.launchApp_0": { 3 | "prefix": "app.launchApp", 4 | "body": ["app.launchApp($1)"], 5 | "description": "通过应用名称启动应用。如果该名称对应的应用不存在,则返回false; 否则返回true。如果该名称对应多个应用,则只启动其中某一个。\n\n该函数也可以作为全局函数使用。\n\n launchApp(\"Hamibot\");" 6 | }, 7 | "app.launch_1": { 8 | "prefix": "app.launch", 9 | "body": ["app.launch($1)"], 10 | "description": "通过应用包名启动应用。如果该包名对应的应用不存在,则返回false;否则返回true。\n\n该函数也可以作为全局函数使用。\n\n //启动微信\n launch(\"com.tencent.mm\");" 11 | }, 12 | "app.launchPackage_2": { 13 | "prefix": "app.launchPackage", 14 | "body": ["app.launchPackage($1)"], 15 | "description": "相当于`app.launch(packageName)`。" 16 | }, 17 | "app.getPackageName_3": { 18 | "prefix": "app.getPackageName", 19 | "body": ["app.getPackageName($1)"], 20 | "description": "获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回null;如果该名称对应多个应用,则只返回其中某一个的包名。\n\n该函数也可以作为全局函数使用。\n\n var name = getPackageName(\"QQ\"); //返回\"com.tencent.mobileqq\"" 21 | }, 22 | "app.getAppName_4": { 23 | "prefix": "app.getAppName", 24 | "body": ["app.getAppName($1)"], 25 | "description": "获取应用包名对应的已安装的应用的名称。如果该找不到该应用,返回null。\n\n该函数也可以作为全局函数使用。\n\n var name = getAppName(\"com.tencent.mobileqq\"); //返回\"QQ\"" 26 | }, 27 | "app.openAppSetting_5": { 28 | "prefix": "app.openAppSetting", 29 | "body": ["app.openAppSetting($1)"], 30 | "description": "打开应用的详情页(设置页)。如果找不到该应用,返回false; 否则返回true。\n\n该函数也可以作为全局函数使用。" 31 | }, 32 | "app.viewFile_6": { 33 | "prefix": "app.viewFile", 34 | "body": ["app.viewFile($1)"], 35 | "description": "用其他应用查看文件。文件不存在的情况由查看文件的应用处理。\n\n如果找不出可以查看该文件的应用,则抛出`ActivityNotException`。\n\n //查看文本文件\n app.viewFile(\"/sdcard/1.txt\");" 36 | }, 37 | "app.editFile_7": { 38 | "prefix": "app.editFile", 39 | "body": ["app.editFile($1)"], 40 | "description": "用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。\n\n如果找不出可以编辑该文件的应用,则抛出`ActivityNotException`。\n\n //编辑文本文件\n app.editFile(\"/sdcard/1.txt/);" 41 | }, 42 | "app.uninstall_8": { 43 | "prefix": "app.uninstall", 44 | "body": ["app.uninstall($1)"], 45 | "description": "卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装,由应用卸载程序处理,可能弹出\"未找到应用\"的提示。\n\n //卸载QQ\n app.uninstall(\"com.tencent.mobileqq\");" 46 | }, 47 | "app.openUrl_9": { 48 | "prefix": "app.openUrl", 49 | "body": ["app.openUrl($1)"], 50 | "description": "用浏览器打开网站url。\n\n如果没有安装浏览器应用,则抛出`ActivityNotException`。" 51 | }, 52 | "app.sendEmail_10": { 53 | "prefix": "app.sendEmail", 54 | "body": ["app.sendEmail($1)"], 55 | "description": "根据选项options调用邮箱应用发送邮件。这些选项均是可选的。\n\n如果没有安装邮箱应用,则抛出`ActivityNotException`。\n\n //发送邮件给10086@qq.com和10001@qq.com。\n app.sendEmail({\n email: [\"10086@qq.com\", \"10001@qq.com\"],\n subject: \"这是一个邮件标题\",\n text: \"这是邮件正文\"\n });" 56 | }, 57 | "app.startActivity_11": { 58 | "prefix": "app.startActivity", 59 | "body": ["app.startActivity($1)"], 60 | "description": "启动 Hamibot 的特定界面。该函数在 Hamibot 内运行则会打开 Hamibot 内的界面,在打包应用中运行则会打开打包应用的相应界面。\n\n app.startActivity(\"console\");" 61 | }, 62 | "app.versionCode_12": { 63 | "prefix": "app.versionCode", 64 | "body": ["app.versionCode"], 65 | "description": "当前软件版本号,整数值。例如160, 256等。\n\n如果在 Hamibot 中运行则为 Hamibot 的版本号;在打包的软件中则为打包软件的版本号。\n\n toastLog(app.versionCode);" 66 | }, 67 | "app.versionName_13": { 68 | "prefix": "app.versionName", 69 | "body": ["app.versionName"], 70 | "description": "当前软件的版本名称,例如\"3.0.0 Beta\"。\n\n如果在 Hamibot 中运行则为 Hamibot 的版本名称;在打包的软件中则为打包软件的版本名称。\n\n toastLog(app.verionName);" 71 | }, 72 | "intent.intent_14": { 73 | "prefix": "intent.intent", 74 | "body": ["intent.intent($1)"], 75 | "description": "根据选项,构造一个意图Intent对象。\n\n例如:\n\n //打开应用来查看图片文件\n var i = app.intent({\n action: \"VIEW\",\n type: \"image/png\",\n data: \"file:///sdcard/1.png\"\n });\n context.startActivity(i);\n \n\n需要注意的是,除非应用专门暴露Activity出来,否则在没有root权限的情况下使用intent是无法跳转到特定Activity、应用的特定界面的。例如我们能通过Intent跳转到QQ的分享界面,是因为QQ对外暴露了分享的Activity;而在没有root权限的情况下,我们无法通过intent跳转到QQ的设置界面,因为QQ并没有暴露这个Activity。\n\n但如果有root权限,则在intent的参数加上`\"root\": true`即可。例如使用root权限跳转到 Hamibot 的设置界面为:\n\n app.startActivity({\n packageName: \"com.hamibot.hamibot\",\n className: \"com.hamibot.hamibot.ui.settings.SettingsActivity_\",\n root: true\n });\n \n\n另外,关于intent的参数如何获取的问题,一些intent是意外发现并且在网络中传播的(例如跳转QQ聊天窗口是因为QQ给网页提供了跳转到客服QQ的方法),如果要自己获取活动的intent的参数,可以通过例如\"intent记录\",\"隐式启动\"等应用拦截内部intent或者查询暴露的intent。其中拦截内部intent需要XPosed框架,或者可以通过反编译等手段获取参数。总之,没有简单直接的方法。\n\n更多信息,请百度[安卓Intent](https://www.baidu.com/s?wd=android%20Intent)或参考[Android指南: Intent](https://developer.android.com/guide/components/intents-filters.html#Types)。" 76 | }, 77 | "intent.startActivity_15": { 78 | "prefix": "intent.startActivity", 79 | "body": ["intent.startActivity($1)"], 80 | "description": "根据选项构造一个Intent,并启动该Activity。\n\n app.startActivity({\n action: \"SEND\",\n type: \"text/plain\",\n data: \"file:///sdcard/1.txt\"\n });" 81 | }, 82 | "intent.sendBroadcast_16": { 83 | "prefix": "intent.sendBroadcast", 84 | "body": ["intent.sendBroadcast($1)"], 85 | "description": "根据选项构造一个Intent,并发送该广播。" 86 | }, 87 | "intent.startService_17": { 88 | "prefix": "intent.startService", 89 | "body": ["intent.startService($1)"], 90 | "description": "根据选项构造一个Intent,并启动该服务。" 91 | }, 92 | "intent.sendBroadcast_18": { 93 | "prefix": "intent.sendBroadcast", 94 | "body": ["intent.sendBroadcast($1)"], 95 | "description": "**\\[v4.1.0新增\\]**\n\n* `name` {string} 特定的广播名称,包括:\n * `inspect_layout_hierarchy` 布局层次分析\n * `inspect_layout_bounds` 布局范围\n\n发送以上特定名称的广播可以触发 Hamibot 的布局分析,方便脚本调试。这些广播在 Hamibot 发送才有效,在打包的脚本上运行将没有任何效果。\n\n app.sendBroadcast(\"inspect_layout_bounds\");" 96 | }, 97 | "intent.intentToShell_19": { 98 | "prefix": "intent.intentToShell", 99 | "body": ["intent.intentToShell($1)"], 100 | "description": "**\\[v4.1.0新增\\]**\n\n* `options` {Object} 选项\n\n根据选项构造一个Intent,转换为对应的shell的intent命令的参数。\n\n例如:\n\n shell(\"am start \" + app.intentToShell({\n packageName: \"com.hamibot.hamibot\",\n className: \"com.hamibot.hamibot.ui.settings.SettingsActivity_\"\n }), true);\n \n\n参见[intent参数的规范](https://developer.android.com/studio/command-line/adb#IntentSpec)。" 101 | }, 102 | "intent.parseUri_20": { 103 | "prefix": "intent.parseUri", 104 | "body": ["intent.parseUri($1)"], 105 | "description": "**\\[v4.1.0新增\\]**\n\n* `uri` {string} 一个代表Uri的字符串,例如\"file:///sdcard/1.txt\", \"[https://www.autojs.org](https://www.autojs.org)\"\n* 返回 {Uri} 一个代表Uri的对象,参见[android.net.Uri](https://developer.android.com/reference/android/net/Uri)。\n\n解析uri字符串并返回相应的Uri对象。即使Uri格式错误,该函数也会返回一个Uri对象,但之后如果访问该对象的scheme, path等值可能因解析失败而返回`null`。\n\n需要注意的是,在高版本Android上,由于系统限制直接在Uri暴露文件的绝对路径,因此如果uri字符串是文件`file://...`,返回的Uri会是诸如`content://...`的形式。" 106 | }, 107 | "intent.getUriForFile_21": { 108 | "prefix": "intent.getUriForFile", 109 | "body": ["intent.getUriForFile($1)"], 110 | "description": "**\\[v4.1.0新增\\]**\n\n* `path` {string} 文件路径,例如\"/sdcard/1.txt\"\n* 返回 {Uri} 一个指向该文件的Uri的对象,参见[android.net.Uri](https://developer.android.com/reference/android/net/Uri)。\n\n从一个文件路径创建一个uri对象。需要注意的是,在高版本Android上,由于系统限制直接在Uri暴露文件的绝对路径,因此返回的Uri会是诸如`content://...`的形式。" 111 | }, 112 | "canvas.drawARGB_0": { 113 | "prefix": "canvas.drawARGB", 114 | "body": ["canvas.drawARGB($1, $2, $3, $4)"], 115 | "description": "" 116 | }, 117 | "canvas.draw_1": { 118 | "prefix": "canvas.draw", 119 | "body": ["canvas.draw"], 120 | "description": "" 121 | }, 122 | "console.show_0": { 123 | "prefix": "console.show", 124 | "body": ["console.show()"], 125 | "description": "显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。" 126 | }, 127 | "console.hide_1": { 128 | "prefix": "console.hide", 129 | "body": ["console.hide()"], 130 | "description": "隐藏控制台悬浮窗。" 131 | }, 132 | "console.clear_2": { 133 | "prefix": "console.clear", 134 | "body": ["console.clear()"], 135 | "description": "清空控制台。" 136 | }, 137 | "console.log_3": { 138 | "prefix": "console.log", 139 | "body": ["console.log($1, $2)"], 140 | "description": "打印到控制台,并带上换行符。 可以传入多个参数,第一个参数作为主要信息,其他参数作为类似于 [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html) 中的代替值(参数都会传给 util.format())。\n\n const count = 5;\n console.log('count: %d', count);\n // 打印: count: 5 到 stdout\n console.log('count:', count);\n // 打印: count: 5 到 stdout\n \n\n详见 util.format()。\n\n该函数也可以作为全局函数使用。" 141 | }, 142 | "console.verbose_4": { 143 | "prefix": "console.verbose", 144 | "body": ["console.verbose($1, $2)"], 145 | "description": "与console.log类似,但输出结果以灰色字体显示。输出优先级低于log,用于输出观察性质的信息。" 146 | }, 147 | "console.info_5": { 148 | "prefix": "console.info", 149 | "body": ["console.info($1, $2)"], 150 | "description": "与console.log类似,但输出结果以绿色字体显示。输出优先级高于log, 用于输出重要信息。" 151 | }, 152 | "console.warn_6": { 153 | "prefix": "console.warn", 154 | "body": ["console.warn($1, $2)"], 155 | "description": "与console.log类似,但输出结果以蓝色字体显示。输出优先级高于info, 用于输出警告信息。" 156 | }, 157 | "console.error_7": { 158 | "prefix": "console.error", 159 | "body": ["console.error($1, $2)"], 160 | "description": "与console.log类似,但输出结果以红色字体显示。输出优先级高于warn, 用于输出错误信息。" 161 | }, 162 | "console.assert_8": { 163 | "prefix": "console.assert", 164 | "body": ["console.assert($1, $2)"], 165 | "description": "断言。如果value为false则输出错误信息message并停止脚本运行。\n\n var a = 1 + 1;\n console.assert(a == 2, \"加法出错啦\");" 166 | }, 167 | "console.time_9": { 168 | "prefix": "console.time", 169 | "body": ["console.time($1)"], 170 | "description": "**\\[v4.1.0新增\\]**\n\n* `label` {String} 计时器标签,可省略\n\n启动一个定时器,用以计算一个操作的持续时间。 定时器由一个唯一的 `label` 标识。 当调用 `console.timeEnd()` 时,可以使用相同的 `label` 来停止定时器,并以毫秒为单位将持续时间输出到控制台。 重复启动同一个标签的定时器会覆盖之前启动同一标签的定时器。" 171 | }, 172 | "console.timeEnd_10": { 173 | "prefix": "console.timeEnd", 174 | "body": ["console.timeEnd($1)"], 175 | "description": "**\\[v4.1.0新增\\]**\n\n* `label` {String} 计时器标签\n\n停止之前通过调用 `console.time()` 启动的定时器,并打印结果到控制台。 调用 `console.timeEnd()` 后定时器会被删除。如果不存在标签指定的定时器则会打印 `NaNms`。\n\n console.time('求和');\n var sum = 0;\n for(let i = 0; i < 100000; i++){\n sum += i;\n }\n console.timeEnd('求和');\n // 打印 求和: xxx ms" 176 | }, 177 | "console.trace_11": { 178 | "prefix": "console.trace", 179 | "body": ["console.trace($1, $2)"], 180 | "description": "**\\[v4.1.0新增\\]**\n\n* `data` {any}\n* `...args` {any}\n\n与console.log类似,同时会打印出调用这个函数所在的调用栈信息(即当前运行的文件、行数等信息)。\n\n console.trace('Show me');\n // 打印: (堆栈跟踪会根据被调用的跟踪的位置而变化)\n // Show me\n // at :7" 181 | }, 182 | "console.input_12": { 183 | "prefix": "console.input", 184 | "body": ["console.input($1, $2)"], 185 | "description": "与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。\n\n**部分机型可能会有控制台不显示输入框的情况,属于bug。**\n\n例如:\n\n var n = console.input(\"请输入一个数字:\"); \n //输入123之后:\n toast(n + 1);\n //显示124" 186 | }, 187 | "console.rawInput_13": { 188 | "prefix": "console.rawInput", 189 | "body": ["console.rawInput($1, $2)"], 190 | "description": "与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。\n\n部分机型可能会有控制台不显示输入框的情况,属于bug。\n\n例如:\n\n var n = console.rawInput(\"请输入一个数字:\"); \n //输入123之后:\n toast(n + 1);\n //显示1231" 191 | }, 192 | "console.setSize_14": { 193 | "prefix": "console.setSize", 194 | "body": ["console.setSize($1, $2)"], 195 | "description": "设置控制台的大小,单位像素。\n\n console.show();\n //设置控制台大小为屏幕的四分之一\n console.setSize(device.width / 2, device.height / 2);" 196 | }, 197 | "console.setPosition_15": { 198 | "prefix": "console.setPosition", 199 | "body": ["console.setPosition($1, $2)"], 200 | "description": "设置控制台的位置,单位像素。\n\n console.show();\n console.setPosition(100, 100);" 201 | }, 202 | "console.setGlobalLogConfig_16": { 203 | "prefix": "console.setGlobalLogConfig", 204 | "body": ["console.setGlobalLogConfig($1)"], 205 | "description": "**\\[v4.1.0新增\\]**\n\n* `config` {Object} 日志配置,可选的项有:\n * `file` {string} 日志文件路径,将会把日志写入该文件中\n * `maxFileSize` {number} 最大文件大小,单位字节,默认为512 \\* 1024 (512KB)\n * `rootLevel` {string} 写入的日志级别,默认为\"ALL\"(所有日志),可以为\"OFF\"(关闭), \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\", \"FATAL\"等。\n * `maxBackupSize` {number} 日志备份文件最大数量,默认为5\n * `filePattern` {string} 日志写入格式,参见[PatternLayout](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html)\n\n设置日志保存的路径和配置。例如把日志保存到\"/sdcard/1.txt\":\n\n console.setGlobalLogConfig({\n \"file\": \"/sdcard/1.txt\"\n });\n \n\n注意该函数会影响所有脚本的日志记录。" 206 | }, 207 | "console.print_17": { 208 | "prefix": "console.print", 209 | "body": ["console.print($1)"], 210 | "description": "相当于`log(text)`。" 211 | }, 212 | "基于坐标的触摸模拟.setScreenMetrics_0": { 213 | "prefix": "基于坐标的触摸模拟.setScreenMetrics", 214 | "body": ["基于坐标的触摸模拟.setScreenMetrics($1, $2)"], 215 | "description": "设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。\n\n例如在1920\\*1080的设备中,某个操作的代码为\n\n setScreenMetrics(1080, 1920);\n click(800, 200);\n longClick(300, 500);\n \n\n那么在其他设备上AutoJs会自动放缩坐标以便脚本仍然有效。例如在540 \\* 960的屏幕中`click(800, 200)`实际上会点击位置(400, 100)。" 216 | }, 217 | "rootautomator.tap_1": { 218 | "prefix": "rootautomator.tap", 219 | "body": ["rootautomator.tap($1, $2, $3)"], 220 | "description": "点击位置(x, y)。其中id是一个整数值,用于区分多点触摸,不同的id表示不同的\"手指\",例如:\n\n var ra = new RootAutomator();\n //让\"手指1\"点击位置(100, 100)\n ra.tap(100, 100, 1);\n //让\"手指2\"点击位置(200, 200);\n ra.tap(200, 200, 2);\n ra.exit();\n \n\n如果不需要多点触摸,则不需要id这个参数。 多点触摸通常用于手势或游戏操作,例如模拟双指捏合、双指上滑等。\n\n某些情况下可能存在tap点击无反应的情况,这时可以用`RootAutomator.press()`函数代替。" 221 | }, 222 | "rootautomator.swipe_2": { 223 | "prefix": "rootautomator.swipe", 224 | "body": ["rootautomator.swipe($1, $2, $3, $4, $5, $6)"], 225 | "description": "模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。" 226 | }, 227 | "rootautomator.press_3": { 228 | "prefix": "rootautomator.press", 229 | "body": ["rootautomator.press($1, $2, $3, $4)"], 230 | "description": "模拟按下位置(x, y),时长为duration毫秒。" 231 | }, 232 | "rootautomator.longPress_4": { 233 | "prefix": "rootautomator.longPress", 234 | "body": ["rootautomator.longPress($1, $2, $3, $4)"], 235 | "description": "模拟长按位置(x, y)。\n\n以上为简单模拟触摸操作的函数。如果要模拟一些复杂的手势,需要更底层的函数。" 236 | }, 237 | "rootautomator.touchDown_5": { 238 | "prefix": "rootautomator.touchDown", 239 | "body": ["rootautomator.touchDown($1, $2, $3)"], 240 | "description": "模拟手指按下位置(x, y)。" 241 | }, 242 | "rootautomator.touchMove_6": { 243 | "prefix": "rootautomator.touchMove", 244 | "body": ["rootautomator.touchMove($1, $2, $3)"], 245 | "description": "模拟移动手指到位置(x, y)。" 246 | }, 247 | "rootautomator.touchUp_7": { 248 | "prefix": "rootautomator.touchUp", 249 | "body": ["rootautomator.touchUp($1)"], 250 | "description": "模拟手指弹起。" 251 | }, 252 | "使用root权限点击和滑动的简单命令.Tap_8": { 253 | "prefix": "使用root权限点击和滑动的简单命令.Tap", 254 | "body": ["使用root权限点击和滑动的简单命令.Tap($1, $2)"], 255 | "description": "点击位置(x, y), 您可以通过\"开发者选项\"开启指针位置来确定点击坐标。" 256 | }, 257 | "使用root权限点击和滑动的简单命令.Swipe_9": { 258 | "prefix": "使用root权限点击和滑动的简单命令.Swipe", 259 | "body": ["使用root权限点击和滑动的简单命令.Swipe($1, $2, $3, $4, $5)"], 260 | "description": "滑动。从(x1, y1)位置滑动到(x2, y2)位置。" 261 | }, 262 | "device.getIMEI_0": { 263 | "prefix": "device.getIMEI", 264 | "body": ["device.getIMEI($1)"], 265 | "description": "返回设备的IMEI." 266 | }, 267 | "device.getAndroidId_1": { 268 | "prefix": "device.getAndroidId", 269 | "body": ["device.getAndroidId($1)"], 270 | "description": "返回设备的Android ID。\n\nAndroid ID为一个用16进制字符串表示的64位整数,在设备第一次使用时随机生成,之后不会更改,除非恢复出厂设置。" 271 | }, 272 | "device.getMacAddress_2": { 273 | "prefix": "device.getMacAddress", 274 | "body": ["device.getMacAddress($1)"], 275 | "description": "返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取,否则会返回null。\n\n**可能的后续修改**:未来可能增加有root权限的情况下通过root权限获取,从而在没有WLAN连接的情况下也能返回正确的Mac地址,因此请勿使用此函数判断WLAN连接。" 276 | }, 277 | "device.getBrightness_3": { 278 | "prefix": "device.getBrightness", 279 | "body": ["device.getBrightness($1)"], 280 | "description": "返回当前的(手动)亮度。范围为0~255。" 281 | }, 282 | "device.getBrightnessMode_4": { 283 | "prefix": "device.getBrightnessMode", 284 | "body": ["device.getBrightnessMode($1)"], 285 | "description": "返回当前亮度模式,0为手动亮度,1为自动亮度。" 286 | }, 287 | "device.setBrightness_5": { 288 | "prefix": "device.setBrightness", 289 | "body": ["device.setBrightness($1)"], 290 | "description": "设置当前手动亮度。如果当前是自动亮度模式,该函数不会影响屏幕的亮度。\n\n此函数需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。" 291 | }, 292 | "device.setBrightnessMode_6": { 293 | "prefix": "device.setBrightnessMode", 294 | "body": ["device.setBrightnessMode($1)"], 295 | "description": "设置当前亮度模式。\n\n此函数需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。" 296 | }, 297 | "device.getMusicVolume_7": { 298 | "prefix": "device.getMusicVolume", 299 | "body": ["device.getMusicVolume($1)"], 300 | "description": "返回当前媒体音量。" 301 | }, 302 | "device.getNotificationVolume_8": { 303 | "prefix": "device.getNotificationVolume", 304 | "body": ["device.getNotificationVolume($1)"], 305 | "description": "返回当前通知音量。" 306 | }, 307 | "device.getAlarmVolume_9": { 308 | "prefix": "device.getAlarmVolume", 309 | "body": ["device.getAlarmVolume($1)"], 310 | "description": "返回当前闹钟音量。" 311 | }, 312 | "device.getMusicMaxVolume_10": { 313 | "prefix": "device.getMusicMaxVolume", 314 | "body": ["device.getMusicMaxVolume($1)"], 315 | "description": "返回媒体音量的最大值。" 316 | }, 317 | "device.getNotificationMaxVolume_11": { 318 | "prefix": "device.getNotificationMaxVolume", 319 | "body": ["device.getNotificationMaxVolume($1)"], 320 | "description": "返回通知音量的最大值。" 321 | }, 322 | "device.getAlarmMaxVolume_12": { 323 | "prefix": "device.getAlarmMaxVolume", 324 | "body": ["device.getAlarmMaxVolume($1)"], 325 | "description": "返回闹钟音量的最大值。" 326 | }, 327 | "device.setMusicVolume_13": { 328 | "prefix": "device.setMusicVolume", 329 | "body": ["device.setMusicVolume($1)"], 330 | "description": "设置当前媒体音量。\n\n此函数需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。" 331 | }, 332 | "device.setNotificationVolume_14": { 333 | "prefix": "device.setNotificationVolume", 334 | "body": ["device.setNotificationVolume($1)"], 335 | "description": "设置当前通知音量。\n\n此函数需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。" 336 | }, 337 | "device.setAlarmVolume_15": { 338 | "prefix": "device.setAlarmVolume", 339 | "body": ["device.setAlarmVolume($1)"], 340 | "description": "设置当前闹钟音量。\n\n此函数需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。" 341 | }, 342 | "device.getBattery_16": { 343 | "prefix": "device.getBattery", 344 | "body": ["device.getBattery($1)"], 345 | "description": "返回当前电量百分比。" 346 | }, 347 | "device.isCharging_17": { 348 | "prefix": "device.isCharging", 349 | "body": ["device.isCharging($1)"], 350 | "description": "返回设备是否正在充电。" 351 | }, 352 | "device.getTotalMem_18": { 353 | "prefix": "device.getTotalMem", 354 | "body": ["device.getTotalMem($1)"], 355 | "description": "返回设备内存总量,单位字节(B)。1MB = 1024 \\* 1024B。" 356 | }, 357 | "device.getAvailMem_19": { 358 | "prefix": "device.getAvailMem", 359 | "body": ["device.getAvailMem($1)"], 360 | "description": "返回设备当前可用的内存,单位字节(B)。" 361 | }, 362 | "device.isScreenOn_20": { 363 | "prefix": "device.isScreenOn", 364 | "body": ["device.isScreenOn($1)"], 365 | "description": "返回设备屏幕是否是亮着的。如果屏幕亮着,返回`true`; 否则返回`false`。\n\n需要注意的是,类似于vivo xplay系列的息屏时钟不属于\"屏幕亮着\"的情况,虽然屏幕确实亮着但只能显示时钟而且不可交互,此时`isScreenOn()`也会返回`false`。" 366 | }, 367 | "device.wakeUp_21": { 368 | "prefix": "device.wakeUp", 369 | "body": ["device.wakeUp()"], 370 | "description": "唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。" 371 | }, 372 | "device.wakeUpIfNeeded_22": { 373 | "prefix": "device.wakeUpIfNeeded", 374 | "body": ["device.wakeUpIfNeeded()"], 375 | "description": "如果屏幕没有点亮,则唤醒设备。" 376 | }, 377 | "device.keepScreenOn_23": { 378 | "prefix": "device.keepScreenOn", 379 | "body": ["device.keepScreenOn($1)"], 380 | "description": "保持屏幕常亮。\n\n此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。\n\n在某些设备上,如果不加参数timeout,只能在 Hamibot 的界面保持屏幕常亮,在其他界面会自动失效,这是因为设备的省电策略造成的。因此,建议使用比较长的时长来代替\"一直保持屏幕常亮\"的功能,例如`device.keepScreenOn(3600 * 1000)`。\n\n可以使用`device.cancelKeepingAwake()`来取消屏幕常亮。\n\n //一直保持屏幕常亮\n device.keepScreenOn()" 381 | }, 382 | "device.keepScreenDim_24": { 383 | "prefix": "device.keepScreenDim", 384 | "body": ["device.keepScreenDim($1)"], 385 | "description": "保持屏幕常亮,但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作,不需要用户观看屏幕,可以让屏幕变暗来节省电量。\n\n此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。\n\n可以使用`device.cancelKeepingAwake()`来取消屏幕常亮。" 386 | }, 387 | "device.cancelKeepingAwake_25": { 388 | "prefix": "device.cancelKeepingAwake", 389 | "body": ["device.cancelKeepingAwake()"], 390 | "description": "取消设备保持唤醒状态。用于取消`device.keepScreenOn()`, `device.keepScreenDim()`等函数设置的屏幕常亮。" 391 | }, 392 | "device.vibrate_26": { 393 | "prefix": "device.vibrate", 394 | "body": ["device.vibrate($1)"], 395 | "description": "使设备震动一段时间。\n\n //震动两秒\n device.vibrate(2000);" 396 | }, 397 | "device.cancelVibration_27": { 398 | "prefix": "device.cancelVibration", 399 | "body": ["device.cancelVibration()"], 400 | "description": "如果设备处于震动状态,则取消震动。" 401 | }, 402 | "device.width_28": { 403 | "prefix": "device.width", 404 | "body": ["device.width"], 405 | "description": "设备屏幕分辨率宽度。例如1080。" 406 | }, 407 | "device.height_29": { 408 | "prefix": "device.height", 409 | "body": ["device.height"], 410 | "description": "设备屏幕分辨率高度。例如1920。" 411 | }, 412 | "device.buildId_30": { 413 | "prefix": "device.buildId", 414 | "body": ["device.buildId"], 415 | "description": "Either a changelist number, or a label like \"M4-rc20\".\n\n修订版本号,或者诸如\"M4-rc20\"的标识。" 416 | }, 417 | "device.broad_31": { 418 | "prefix": "device.broad", 419 | "body": ["device.broad"], 420 | "description": "The name of the underlying board, like \"goldfish\".\n\n设备的主板(?)型号。" 421 | }, 422 | "device.brand_32": { 423 | "prefix": "device.brand", 424 | "body": ["device.brand"], 425 | "description": "The consumer-visible brand with which the product/hardware will be associated, if any.\n\n与产品或硬件相关的厂商品牌,如\"Xiaomi\", \"Huawei\"等。" 426 | }, 427 | "device.device_33": { 428 | "prefix": "device.device", 429 | "body": ["device.device"], 430 | "description": "The name of the industrial design.\n\n设备在工业设计中的名称。" 431 | }, 432 | "device.model_34": { 433 | "prefix": "device.model", 434 | "body": ["device.model"], 435 | "description": "The end-user-visible name for the end product.\n\n设备型号。" 436 | }, 437 | "device.product_35": { 438 | "prefix": "device.product", 439 | "body": ["device.product"], 440 | "description": "The name of the overall product.\n\n整个产品的名称。" 441 | }, 442 | "device.bootloader_36": { 443 | "prefix": "device.bootloader", 444 | "body": ["device.bootloader"], 445 | "description": "The system bootloader version number.\n\n设备Bootloader的版本。" 446 | }, 447 | "device.hardware_37": { 448 | "prefix": "device.hardware", 449 | "body": ["device.hardware"], 450 | "description": "The name of the hardware (from the kernel command line or /proc).\n\n设备的硬件名称(来自内核命令行或者/proc)。" 451 | }, 452 | "device.fingerprint_38": { 453 | "prefix": "device.fingerprint", 454 | "body": ["device.fingerprint"], 455 | "description": "A string that uniquely identifies this build. Do not attempt to parse this value.\n\n构建(build)的唯一标识码。" 456 | }, 457 | "device.serial_39": { 458 | "prefix": "device.serial", 459 | "body": ["device.serial"], 460 | "description": "A hardware serial number, if available. Alphanumeric only, case-insensitive.\n\n硬件序列号。" 461 | }, 462 | "device.sdkInt_40": { 463 | "prefix": "device.sdkInt", 464 | "body": ["device.sdkInt"], 465 | "description": "The user-visible SDK version of the framework; its possible values are defined in Build.VERSION\\_CODES.\n\n安卓系统API版本。例如安卓4.4的sdkInt为19。" 466 | }, 467 | "device.incremental_41": { 468 | "prefix": "device.incremental", 469 | "body": ["device.incremental"], 470 | "description": "The internal value used by the underlying source control to represent this build. E.g., a perforce changelist number or a git hash." 471 | }, 472 | "device.release_42": { 473 | "prefix": "device.release", 474 | "body": ["device.release"], 475 | "description": "The user-visible version string. E.g., \"1.0\" or \"3.4b5\".\n\nAndroid系统版本号。例如\"5.0\", \"7.1.1\"。" 476 | }, 477 | "device.baseOS_43": { 478 | "prefix": "device.baseOS", 479 | "body": ["device.baseOS"], 480 | "description": "The base OS build the product is based on." 481 | }, 482 | "device.securityPatch_44": { 483 | "prefix": "device.securityPatch", 484 | "body": ["device.securityPatch"], 485 | "description": "The user-visible security patch level.\n\n安全补丁程序级别。" 486 | }, 487 | "device.codename_45": { 488 | "prefix": "device.codename", 489 | "body": ["device.codename"], 490 | "description": "The current development codename, or the string \"REL\" if this is a release build.\n\n开发代号,例如发行版是\"REL\"。" 491 | }, 492 | "dialogs.alert_0": { 493 | "prefix": "dialogs.alert", 494 | "body": ["dialogs.alert($1, $2, $3)"], 495 | "description": "显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n\n该函数也可以作为全局函数使用。\n\n alert(\"出现错误~\", \"出现未知错误,请联系脚本作者”);\n \n\n在ui模式下该函数返回一个`Promise`。例如:\n\n \"ui\";\n alert(\"嘿嘿嘿\").then(()=>{\n //当点击确定后会执行这里\n });" 496 | }, 497 | "dialogs.confirm_1": { 498 | "prefix": "dialogs.confirm", 499 | "body": ["dialogs.confirm($1, $2, $3)"], 500 | "description": "显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 `true` ,否则返回 `false` 。\n\n该函数也可以作为全局函数使用。\n\n在ui模式下该函数返回一个`Promise`。例如:\n\n \"ui\";\n confirm(\"确定吗\").then(value=>{\n //当点击确定后会执行这里, value为true或false, 表示点击\"确定\"或\"取消\"\n });" 501 | }, 502 | "dialogs.rawInput_2": { 503 | "prefix": "dialogs.rawInput", 504 | "body": ["dialogs.rawInput($1, $2, $3)"], 505 | "description": "显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。\n\n该函数也可以作为全局函数使用。\n\n var name = rawInput(\"请输入您的名字\", \"小明\");\n alert(\"您的名字是\" + name);\n \n\n在ui模式下该函数返回一个`Promise`。例如:\n\n \"ui\";\n rawInput(\"请输入您的名字\", \"小明\").then(name => {\n alert(\"您的名字是\" + name);\n });\n \n\n当然也可以使用回调函数,例如:\n\n rawInput(\"请输入您的名字\", \"小明\", name => {\n alert(\"您的名字是\" + name);\n });" 506 | }, 507 | "dialogs.input_3": { 508 | "prefix": "dialogs.input", 509 | "body": ["dialogs.input($1, $2, $3)"], 510 | "description": "等效于 `eval(dialogs.rawInput(title, prefill, callback))`, 该函数和rawInput的区别在于,会把输入的字符串用eval计算一遍再返回,返回的可能不是字符串。\n\n可以用该函数输入数字、数组等。例如:\n\n var age = dialogs.input(\"请输入您的年龄\", \"18\");\n // new Date().getYear() + 1900 可获取当前年份\n var year = new Date().getYear() + 1900 - age;\n alert(\"您的出生年份是\" + year);\n \n\n在ui模式下该函数返回一个`Promise`。例如:\n\n \"ui\";\n dialogs.input(\"请输入您的年龄\", \"18\").then(age => {\n var year = new Date().getYear() + 1900 - age;\n alert(\"您的出生年份是\" + year);\n });" 511 | }, 512 | "dialogs.prompt_4": { 513 | "prefix": "dialogs.prompt", 514 | "body": ["dialogs.prompt($1, $2, $3)"], 515 | "description": "相当于 `dialogs.rawInput()`;" 516 | }, 517 | "dialogs.select_5": { 518 | "prefix": "dialogs.select", 519 | "body": ["dialogs.select($1, $2, $3)"], 520 | "description": "显示一个带有选项列表的对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。\n\n var options = [\"选项A\", \"选项B\", \"选项C\", \"选项D\"]\n var i = dialogs.select(\"请选择一个选项\", options);\n if(i >= 0){\n toast(\"您选择的是\" + options[i]);\n }else{\n toast(\"您取消了选择\");\n }\n \n\n在ui模式下该函数返回一个`Promise`。例如:\n\n \"ui\";\n dialogs.select(\"请选择一个选项\", [\"选项A\", \"选项B\", \"选项C\", \"选项D\"])\n .then(i => {\n toast(i);\n });" 521 | }, 522 | "dialogs.singleChoice_6": { 523 | "prefix": "dialogs.singleChoice", 524 | "body": ["dialogs.singleChoice($1, $2, $3, $4)"], 525 | "description": "显示一个单选列表对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。\n\n在ui模式下该函数返回一个`Promise`。" 526 | }, 527 | "dialogs.multiChoice_7": { 528 | "prefix": "dialogs.multiChoice", 529 | "body": ["dialogs.multiChoice($1, $2, $3, $4)"], 530 | "description": "显示一个多选列表对话框,等待用户选择,返回用户选择的选项索引的数组。如果用户取消了选择,返回`[]`。\n\n在ui模式下该函数返回一个`Promise`。" 531 | }, 532 | "dialogs.build_8": { 533 | "prefix": "dialogs.build", 534 | "body": ["dialogs.build($1, $2)"], 535 | "description": "创建一个可自定义的对话框,例如:\n\n dialogs.build({\n //对话框标题\n title: \"发现新版本\",\n //对话框内容\n content: \"更新日志: 新增了若干了BUG\",\n //确定键内容\n positive: \"下载\",\n //取消键内容\n negative: \"取消\",\n //中性键内容\n neutral: \"到浏览器下载\",\n //勾选框内容\n checkBoxPrompt: \"不再提示\"\n }).on(\"positive\", ()=>{\n //监听确定键\n toast(\"开始下载....\");\n }).on(\"neutral\", ()=>{\n //监听中性键\n app.openUrl(\"https://www.autojs.org\");\n }).on(\"check\", (checked)=>{\n //监听勾选框\n log(checked);\n }).show();\n \n\n选项properties可供配置的项目为:\n\n* `title` {string} 对话框标题\n* `titleColor` {string} | {number} 对话框标题的颜色\n* `buttonRippleColor` {string} | {number} 对话框按钮的波纹效果颜色\n* `icon` {string} | {Image} 对话框的图标,是一个URL或者图片对象\n* `content` {string} 对话框文字内容\n* `contentColor`{string} | {number} 对话框文字内容的颜色\n* `contentLineSpacing`{number} 对话框文字内容的行高倍数,1.0为一倍行高\n* `items` {Array} 对话框列表的选项\n* `itemsColor` {string} | {number} 对话框列表的选项的文字颜色\n* `itemsSelectMode` {string} 对话框列表的选项选择模式,可以为:\n * `select` 普通选择模式\n * `single` 单选模式\n * `multi` 多选模式\n* `itemsSelectedIndex` {number} | {Array} 对话框列表中预先选中的项目索引,如果是单选模式为一个索引;多选模式则为数组\n* `positive` {string} 对话框确定按钮的文字内容(最右边按钮)\n* `positiveColor` {string} | {number} 对话框确定按钮的文字颜色(最右边按钮)\n* `neutral` {string} 对话框中立按钮的文字内容(最左边按钮)\n* `neutralColor` {string} | {number} 对话框中立按钮的文字颜色(最左边按钮)\n* `negative` {string} 对话框取消按钮的文字内容(确定按钮左边的按钮)\n* `negativeColor` {string} | {number} 对话框取消按钮的文字颜色(确定按钮左边的按钮)\n* `checkBoxPrompt` {string} 勾选框文字内容\n* `checkBoxChecked` {boolean} 勾选框是否勾选\n* `progress` {Object} 配置对话框进度条的对象:\n * `max` {number} 进度条的最大值,如果为-1则为无限循环的进度条\n * `horizontal` {boolean} 如果为true, 则对话框无限循环的进度条为水平进度条\n * `showMinMax` {boolean} 是否显示进度条的最大值和最小值\n* `cancelable` {boolean} 对话框是否可取消,如果为false,则对话框只能用代码手动取消\n* `canceledOnTouchOutside` {boolean} 对话框是否在点击对话框以外区域时自动取消,默认为true\n* `inputHint` {string} 对话框的输入框的输入提示\n* `inputPrefill` {string} 对话框输入框的默认输入内容\n\n通过这些选项可以自定义一个对话框,并通过监听返回的Dialog对象的按键、输入事件来实现交互。下面是一些例子。\n\n模拟alert对话框:\n\n dialogs.build({\n title: \"你好\",\n content: \"今天也要元气满满哦\",\n positive: \"好的\"\n }).show();\n \n\n模拟confirm对话框:\n\n dialogs.build({\n title: \"你好\",\n content: \"请问你是笨蛋吗?\",\n positive: \"是的\",\n negative: \"我是大笨蛋\"\n }).on(\"positive\", ()=>{\n alert(\"哈哈哈笨蛋\");\n }).on(\"negative\", ()=>{\n alert(\"哈哈哈大笨蛋\");\n }).show();\n \n\n模拟单选框:\n\n dialogs.build({\n title: \"单选\",\n items: [\"选项1\", \"选项2\", \"选项3\", \"选项4\"],\n itemsSelectMode: \"single\",\n itemsSelectedIndex: 3\n }).on(\"single_choice\", (index, item)=>{\n toast(\"您选择的是\" + item);\n }).show();\n \n\n\"处理中\"对话框:\n\n var d = dialogs.build({\n title: \"下载中...\",\n progress: {\n max: -1\n },\n cancelable: false\n }).show();\n \n setTimeout(()=>{\n d.dismiss();\n }, 3000);\n \n\n输入对话框:\n\n dialogs.build({\n title: \"请输入您的年龄\",\n inputPrefill: \"18\"\n }).on(\"input\", (input)=>{\n var age = parseInt(input);\n toastLog(age);\n }).show();\n \n\n使用这个函数来构造对话框,一个明显的不同是需要使用回调函数而不能像dialogs其他函数一样同步地返回结果;但也可以通过threads模块的方法来实现。例如显示一个输入框并获取输入结果为:\n\n var input = threads.disposable();\n dialogas.build({\n title: \"请输入您的年龄\",\n inputPrefill: \"18\"\n }).on(\"input\", text => {\n input.setAndNotify(text);\n }).show();\n var age = parseInt(input.blockedGet());\n tosatLog(age);" 536 | }, 537 | "dialog.getProgress_9": { 538 | "prefix": "dialog.getProgress", 539 | "body": ["dialog.getProgress($1)"], 540 | "description": "获取当前进度条的进度值,是一个整数" 541 | }, 542 | "dialog.getMaxProgress_10": { 543 | "prefix": "dialog.getMaxProgress", 544 | "body": ["dialog.getMaxProgress($1)"], 545 | "description": "获取当前进度条的最大进度值,是一个整数" 546 | }, 547 | "dialog.getActionButton_11": { 548 | "prefix": "dialog.getActionButton", 549 | "body": ["dialog.getActionButton($1)"], 550 | "description": "" 551 | }, 552 | "engines.execScript_0": { 553 | "prefix": "engines.execScript", 554 | "body": ["engines.execScript($1, $2, $3)"], 555 | "description": "在新的脚本环境中运行脚本script。返回一个[ScriptExectuion](#engines_scriptexecution)对象。\n\n所谓新的脚本环境,指定是,脚本中的变量和原脚本的变量是不共享的,并且,脚本会在新的线程中运行。\n\n最简单的例子如下:\n\n engines.execScript(\"hello world\", \"toast('hello world')\");\n \n\n如果要循环运行,则:\n\n //每隔3秒运行一次脚本,循环10次\n engines.execScript(\"hello world\", \"toast('hello world')\", {\n loopTimes: 10,\n interval: 3000\n });\n \n\n用字符串来编写脚本非常不方便,可以结合 `Function.toString()`的方法来执行特定函数:\n\n function helloWorld(){\n //注意,这里的变量和脚本主体的变量并不共享\n toast(\"hello world\");\n }\n engines.execScript(\"hello world\", \"helloWorld();\\n\" + helloWorld.toString());\n \n\n如果要传递变量,则可以把这些封装成一个函数:\n\n function exec(action, args){\n args = args || {};\n engines.execScript(action.name, action + \"(\" + JSON.stringify(args) + \");\\n\" + action.toString());\n }\n \n //要执行的函数,是一个简单的加法\n function add(args){\n toast(args.a + args.b);\n }\n \n //在新的脚本环境中执行 1 + 2\n exec(add, {a: 1, b:2});" 556 | }, 557 | "engines.execScriptFile_1": { 558 | "prefix": "engines.execScriptFile", 559 | "body": ["engines.execScriptFile($1, $2)"], 560 | "description": "在新的脚本环境中运行脚本文件path。返回一个[ScriptExecution](#ScriptExecution)对象。\n\n engines.execScriptFile(\"/sdcard/脚本/1.js\");" 561 | }, 562 | "engines.execAutoFile_2": { 563 | "prefix": "engines.execAutoFile", 564 | "body": ["engines.execAutoFile($1, $2)"], 565 | "description": "在新的脚本环境中运行录制文件path。返回一个[ScriptExecution](#ScriptExecution)对象。\n\n engines.execAutoFile(\"/sdcard/脚本/1.auto\");" 566 | }, 567 | "engines.stopAll_3": { 568 | "prefix": "engines.stopAll", 569 | "body": ["engines.stopAll()"], 570 | "description": "停止所有正在运行的脚本。包括当前脚本自身。" 571 | }, 572 | "engines.stopAllAndToast_4": { 573 | "prefix": "engines.stopAllAndToast", 574 | "body": ["engines.stopAllAndToast()"], 575 | "description": "停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。" 576 | }, 577 | "engines.myEngine_5": { 578 | "prefix": "engines.myEngine", 579 | "body": ["engines.myEngine()"], 580 | "description": "返回当前脚本的脚本引擎对象([ScriptEngine](#engines_scriptengine))\n\n**\\[v4.1.0新增\\]** 特别的,该对象可以通过`execArgv`来获取他的运行参数,包括外部参数、intent等。例如:\n\n log(engines.myEngine().execArgv);\n \n\n普通脚本的运行参数通常为空,通过定时任务的广播启动的则可以获取到启动的intent。" 581 | }, 582 | "engines.all_6": { 583 | "prefix": "engines.all", 584 | "body": ["engines.all($1)"], 585 | "description": "返回当前所有正在运行的脚本的脚本引擎[ScriptEngine](#engines_scriptengine)的数组。\n\n log(engines.all());" 586 | }, 587 | "scriptexecution.getEngine_7": { 588 | "prefix": "scriptexecution.getEngine", 589 | "body": ["scriptexecution.getEngine()"], 590 | "description": "返回执行该脚本的脚本引擎对象([ScriptEngine](#engines_scriptengine))" 591 | }, 592 | "scriptexecution.getConfig_8": { 593 | "prefix": "scriptexecution.getConfig", 594 | "body": ["scriptexecution.getConfig()"], 595 | "description": "返回该脚本的运行配置([ScriptConfig](#engines_scriptconfig))" 596 | }, 597 | "scriptengine.forceStop_9": { 598 | "prefix": "scriptengine.forceStop", 599 | "body": ["scriptengine.forceStop()"], 600 | "description": "停止脚本引擎的执行。" 601 | }, 602 | "scriptengine.cwd_10": { 603 | "prefix": "scriptengine.cwd", 604 | "body": ["scriptengine.cwd($1)"], 605 | "description": "返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹;对于其他脚本,例如字符串脚本,则为`null`或者执行时的设置值。" 606 | }, 607 | "scriptengine.getSource_11": { 608 | "prefix": "scriptengine.getSource", 609 | "body": ["scriptengine.getSource($1)"], 610 | "description": "返回当前脚本引擎正在执行的脚本对象。\n\n log(engines.myEngine().getSource());" 611 | }, 612 | "scriptengine.emit_12": { 613 | "prefix": "scriptengine.emit", 614 | "body": ["scriptengine.emit($1, $2)"], 615 | "description": "向该脚本引擎发送一个事件,该事件可以在该脚本引擎对应的脚本的events模块监听到并在脚本主线程执行事件处理。\n\n例如脚本receiver.js的内容如下:\n\n //监听say事件\n events.on(\"say\", function(words){\n toastLog(words);\n });\n //保持脚本运行\n setInterval(()=>{}, 1000);\n \n\n同一目录另一脚本可以启动他并发送该事件:\n\n //运行脚本\n var e = engines.execScriptFile(\"./receiver.js\");\n //等待脚本启动\n sleep(2000);\n //向该脚本发送事件\n e.getEngine().emit(\"say\", \"你好\");" 616 | }, 617 | "scriptconfig.getPath_13": { 618 | "prefix": "scriptconfig.getPath", 619 | "body": ["scriptconfig.getPath($1)"], 620 | "description": "返回一个字符串数组表示脚本运行时模块寻找的路径。" 621 | }, 622 | "events.emitter_0": { 623 | "prefix": "events.emitter", 624 | "body": ["events.emitter()"], 625 | "description": "返回一个新的[EventEmitter](#events_eventemitter)。这个EventEmitter没有内置任何事件。" 626 | }, 627 | "events.observeKey_1": { 628 | "prefix": "events.observeKey", 629 | "body": ["events.observeKey()"], 630 | "description": "启用按键监听,例如音量键、Home键。按键监听使用无障碍服务实现,如果无障碍服务未启用会抛出异常并提示开启。\n\n只有这个函数成功执行后, `onKeyDown`, `onKeyUp`等按键事件的监听才有效。\n\n该函数在安卓4.3以上才能使用。" 631 | }, 632 | "events.onKeyDown_2": { 633 | "prefix": "events.onKeyDown", 634 | "body": ["events.onKeyDown($1, $2)"], 635 | "description": "注册一个按键监听函数,当有keyName对应的按键被按下会调用该函数。可用的按键名称参见[Keys](#events_keys)。\n\n例如:\n\n //启用按键监听\n events.observeKey();\n //监听音量上键按下\n events.onKeyDown(\"volume_up\", function(event){\n toast(\"音量上键被按下了\");\n });\n //监听菜单键按下\n events.onKeyDown(\"menu\", function(event){\n toast(\"菜单键被按下了\");\n exit();\n });" 636 | }, 637 | "events.onKeyUp_3": { 638 | "prefix": "events.onKeyUp", 639 | "body": ["events.onKeyUp($1, $2)"], 640 | "description": "注册一个按键监听函数,当有keyName对应的按键弹起会调用该函数。可用的按键名称参见[Keys](#events_keys)。\n\n一次完整的按键动作包括了按键按下和弹起。按下事件会在手指按下一个按键的\"瞬间\"触发, 弹起事件则在手指放开这个按键时触发。\n\n例如:\n\n //启用按键监听\n events.observeKey();\n //监听音量下键弹起\n events.onKeyDown(\"volume_down\", function(event){\n toast(\"音量上键弹起\");\n });\n //监听Home键弹起\n events.onKeyDown(\"home\", function(event){\n toast(\"Home键弹起\");\n exit();\n });" 641 | }, 642 | "events.onceKeyDown_4": { 643 | "prefix": "events.onceKeyDown", 644 | "body": ["events.onceKeyDown($1, $2)"], 645 | "description": "注册一个按键监听函数,当有keyName对应的按键被按下时会调用该函数,之后会注销该按键监听器。\n\n也就是listener只有在onceKeyDown调用后的第一次按键事件被调用一次。" 646 | }, 647 | "events.onceKeyUp_5": { 648 | "prefix": "events.onceKeyUp", 649 | "body": ["events.onceKeyUp($1, $2)"], 650 | "description": "注册一个按键监听函数,当有keyName对应的按键弹起时会调用该函数,之后会注销该按键监听器。\n\n也就是listener只有在onceKeyUp调用后的第一次按键事件被调用一次。" 651 | }, 652 | "events.removeAllKeyDownListeners_6": { 653 | "prefix": "events.removeAllKeyDownListeners", 654 | "body": ["events.removeAllKeyDownListeners($1)"], 655 | "description": "删除该按键的KeyDown(按下)事件的所有监听。" 656 | }, 657 | "events.removeAllKeyUpListeners_7": { 658 | "prefix": "events.removeAllKeyUpListeners", 659 | "body": ["events.removeAllKeyUpListeners($1)"], 660 | "description": "删除该按键的KeyUp(弹起)事件的所有监听。" 661 | }, 662 | "events.setKeyInterceptionEnabled_8": { 663 | "prefix": "events.setKeyInterceptionEnabled", 664 | "body": ["events.setKeyInterceptionEnabled($1, $2)"], 665 | "description": "设置按键屏蔽是否启用。所谓按键屏蔽指的是,屏蔽原有按键的功能,例如使得音量键不再能调节音量,但此时仍然能通过按键事件监听按键。\n\n如果不加参数key则会屏蔽所有按键。\n\n例如,调用`events.setKeyInterceptionEnabled(true)`会使系统的音量、Home、返回等键不再具有调节音量、回到主页、返回的作用,但此时仍然能通过按键事件监听按键。\n\n该函数通常于按键监听结合,例如想监听音量键并使音量键按下时不弹出音量调节框则为:\n\n events.setKeyInterceptionEnabled(\"volume_up\", true);\n events.observeKey();\n events.onKeyDown(\"volume_up\", ()=>{\n log(\"音量上键被按下\");\n });\n \n\n只要有一个脚本屏蔽了某个按键,该按键便会被屏蔽;当脚本退出时,会自动解除所有按键屏蔽。" 666 | }, 667 | "events.observeTouch_9": { 668 | "prefix": "events.observeTouch", 669 | "body": ["events.observeTouch()"], 670 | "description": "启用屏幕触摸监听。(需要root权限)\n\n只有这个函数被成功执行后, 触摸事件的监听才有效。\n\n没有root权限调用该函数则什么也不会发生。" 671 | }, 672 | "events.setTouchEventTimeout_10": { 673 | "prefix": "events.setTouchEventTimeout", 674 | "body": ["events.setTouchEventTimeout($1)"], 675 | "description": "设置两个触摸事件分发的最小时间间隔。\n\n例如间隔为10毫秒的话,前一个触摸事件发生并被注册的监听器处理后,至少要过10毫秒才能分发和处理下一个触摸事件,这10毫秒之间的触摸将会被忽略。\n\n建议在满足需要的情况下尽量提高这个间隔。一个简单滑动动作可能会连续触发上百个触摸事件,如果timeout设置过低可能造成事件拥堵。强烈建议不要设置timeout为0。" 676 | }, 677 | "events.getTouchEventTimeout_11": { 678 | "prefix": "events.getTouchEventTimeout", 679 | "body": ["events.getTouchEventTimeout()"], 680 | "description": "返回触摸事件的最小时间间隔。" 681 | }, 682 | "events.onTouch_12": { 683 | "prefix": "events.onTouch", 684 | "body": ["events.onTouch($1)"], 685 | "description": "注册一个触摸监听函数。相当于`on(\"touch\", listener)`。\n\n例如:\n\n //启用触摸监听\n events.observeTouch();\n //注册触摸监听器\n events.onTouch(function(p){\n //触摸事件发生时, 打印出触摸的点的坐标\n log(p.x + \", \" + p.y);\n });" 686 | }, 687 | "events.removeAllTouchListeners_13": { 688 | "prefix": "events.removeAllTouchListeners", 689 | "body": ["events.removeAllTouchListeners()"], 690 | "description": "删除所有事件监听函数。" 691 | }, 692 | "events.observeNotification_14": { 693 | "prefix": "events.observeNotification", 694 | "body": ["events.observeNotification()"], 695 | "description": "开启通知监听。例如QQ消息、微信消息、推送等通知。\n\n通知监听依赖于通知服务,如果通知服务没有运行,会抛出异常并跳转到通知权限开启界面。(有时即使通知权限已经开启通知服务也没有运行,这时需要关闭权限再重新开启一次)\n\n例如:\n\n events.obverseNotification();\n events.onNotification(function(notification){\n log(notification.getText());\n });" 696 | }, 697 | "events.observeToast_15": { 698 | "prefix": "events.observeToast", 699 | "body": ["events.observeToast()"], 700 | "description": "开启Toast监听。\n\nToast监听依赖于无障碍服务,因此此函数会确保无障碍服务运行。" 701 | }, 702 | "notification.getPackageName_16": { 703 | "prefix": "notification.getPackageName", 704 | "body": ["notification.getPackageName($1)"], 705 | "description": "获取发出通知的应用包名。" 706 | }, 707 | "notification.getTitle_17": { 708 | "prefix": "notification.getTitle", 709 | "body": ["notification.getTitle($1)"], 710 | "description": "获取通知的标题。" 711 | }, 712 | "notification.getText_18": { 713 | "prefix": "notification.getText", 714 | "body": ["notification.getText($1)"], 715 | "description": "获取通知的内容。" 716 | }, 717 | "notification.click_19": { 718 | "prefix": "notification.click", 719 | "body": ["notification.click()"], 720 | "description": "点击该通知。例如对于一条QQ消息,点击会进入具体的聊天界面。" 721 | }, 722 | "notification.delete_20": { 723 | "prefix": "notification.delete", 724 | "body": ["notification.delete()"], 725 | "description": "删除该通知。该通知将从通知栏中消失。" 726 | }, 727 | "notification.number_21": { 728 | "prefix": "notification.number", 729 | "body": ["notification.number"], 730 | "description": "通知数量。例如QQ连续收到两条消息时number为2。" 731 | }, 732 | "notification.when_22": { 733 | "prefix": "notification.when", 734 | "body": ["notification.when"], 735 | "description": "通知发出时间的时间戳,可以用于构造`Date`对象。例如:\n\n events.observeNotification();\n events.on(\"notification\", function(n){\n log(\"通知时间为}\" + new Date(n.when));\n });" 736 | }, 737 | "keyevent.getAction_23": { 738 | "prefix": "keyevent.getAction", 739 | "body": ["keyevent.getAction()"], 740 | "description": "返回事件的动作。包括:\n\n* `KeyEvent.ACTION_DOWN` 按下事件\n* `KeyEvent.ACTION_UP` 弹起事件" 741 | }, 742 | "keyevent.getKeyCode_24": { 743 | "prefix": "keyevent.getKeyCode", 744 | "body": ["keyevent.getKeyCode()"], 745 | "description": "返回按键的键值。包括:\n\n* `KeyEvent.KEYCODE_HOME` 主页键\n* `KeyEvent.KEYCODE_BACK` 返回键\n* `KeyEvent.KEYCODE_MENU` 菜单键\n* `KeyEvent.KEYCODE_VOLUME_UP` 音量上键\n* `KeyEvent.KEYCODE_VOLUME_DOWN` 音量下键" 746 | }, 747 | "keyevent.getEventTime_25": { 748 | "prefix": "keyevent.getEventTime", 749 | "body": ["keyevent.getEventTime($1)"], 750 | "description": "返回事件发生的时间戳。" 751 | }, 752 | "keyevent.getDownTime_26": { 753 | "prefix": "keyevent.getDownTime", 754 | "body": ["keyevent.getDownTime()"], 755 | "description": "返回最近一次按下事件的时间戳。如果本身是按下事件,则与`getEventTime()`相同。" 756 | }, 757 | "keyevent.keyCodeToString_27": { 758 | "prefix": "keyevent.keyCodeToString", 759 | "body": ["keyevent.keyCodeToString($1)"], 760 | "description": "把键值转换为字符串。例如KEYCODE\\_HOME转换为\"KEYCODE\\_HOME\"。" 761 | }, 762 | "eventemitter.addListener_28": { 763 | "prefix": "eventemitter.addListener", 764 | "body": ["eventemitter.addListener($1, $2)"], 765 | "description": "emitter.on(eventName, listener) 的别名。" 766 | }, 767 | "eventemitter.emit_29": { 768 | "prefix": "eventemitter.emit", 769 | "body": ["eventemitter.emit($1, $2)"], 770 | "description": "按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。\n\n如果事件有监听器,则返回 true ,否则返回 false。" 771 | }, 772 | "eventemitter.eventNames_30": { 773 | "prefix": "eventemitter.eventNames", 774 | "body": ["eventemitter.eventNames()"], 775 | "description": "返回一个列出触发器已注册监听器的事件的数组。 数组中的值为字符串或符号。\n\n const myEE = events.emitter();\n myEE.on('foo', () => {});\n myEE.on('bar', () => {});\n \n const sym = Symbol('symbol');\n myEE.on(sym, () => {});\n \n console.log(myEE.eventNames());\n // 打印: [ 'foo', 'bar', Symbol(symbol) ]" 776 | }, 777 | "eventemitter.getMaxListeners_31": { 778 | "prefix": "eventemitter.getMaxListeners", 779 | "body": ["eventemitter.getMaxListeners()"], 780 | "description": "返回 EventEmitter 当前的最大监听器限制值,该值可以通过 emitter.setMaxListeners(n) 设置或默认为 EventEmitter.defaultMaxListeners。" 781 | }, 782 | "eventemitter.listenerCount_32": { 783 | "prefix": "eventemitter.listenerCount", 784 | "body": ["eventemitter.listenerCount($1)"], 785 | "description": "返回正在监听名为 eventName 的事件的监听器的数量。" 786 | }, 787 | "eventemitter.listeners_33": { 788 | "prefix": "eventemitter.listeners", 789 | "body": ["eventemitter.listeners($1)"], 790 | "description": "返回名为 eventName 的事件的监听器数组的副本。\n\n server.on('connection', (stream) => {\n console.log('someone connected!');\n });\n console.log(util.inspect(server.listeners('connection')));\n // 打印: [ [Function] ]" 791 | }, 792 | "eventemitter.on_34": { 793 | "prefix": "eventemitter.on", 794 | "body": ["eventemitter.on($1, $2)"], 795 | "description": "添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。\n\n server.on('connection', (stream) => {\n console.log('有连接!');\n });\n \n\n返回一个 EventEmitter 引用,可以链式调用。\n\n默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependListener() 方法可用于将事件监听器添加到监听器数组的开头。\n\n const myEE = events.emitter();\n myEE.on('foo', () => console.log('a'));\n myEE.prependListener('foo', () => console.log('b'));\n myEE.emit('foo');\n // 打印:\n // b\n // a" 796 | }, 797 | "eventemitter.once_35": { 798 | "prefix": "eventemitter.once", 799 | "body": ["eventemitter.once($1, $2)"], 800 | "description": "添加一个单次 listener 函数到名为 eventName 的事件。 下次触发 eventName 事件时,监听器会被移除,然后调用。\n\n server.once('connection', (stream) => {\n console.log('首次调用!');\n });\n \n\n返回一个 EventEmitter 引用,可以链式调用。\n\n默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependOnceListener() 方法可用于将事件监听器添加到监听器数组的开头。\n\n const myEE = events.emitter();\n myEE.once('foo', () => console.log('a'));\n myEE.prependOnceListener('foo', () => console.log('b'));\n myEE.emit('foo');\n // 打印:\n // b\n // a" 801 | }, 802 | "eventemitter.prependListener_36": { 803 | "prefix": "eventemitter.prependListener", 804 | "body": ["eventemitter.prependListener($1, $2)"], 805 | "description": "添加 listener 函数到名为 eventName 的事件的监听器数组的开头。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。\n\n server.prependListener('connection', (stream) => {\n console.log('有连接!');\n });\n \n\n返回一个 EventEmitter 引用,可以链式调用。" 806 | }, 807 | "eventemitter.prependOnceListener_37": { 808 | "prefix": "eventemitter.prependOnceListener", 809 | "body": ["eventemitter.prependOnceListener($1, $2)"], 810 | "description": "添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。 下次触发 eventName 事件时,监听器会被移除,然后调用。\n\n server.prependOnceListener('connection', (stream) => {\n console.log('首次调用!');\n });\n \n\n返回一个 EventEmitter 引用,可以链式调用。" 811 | }, 812 | "eventemitter.removeAllListeners_38": { 813 | "prefix": "eventemitter.removeAllListeners", 814 | "body": ["eventemitter.removeAllListeners($1)"], 815 | "description": "移除全部或指定 eventName 的监听器。\n\n注意,在代码中移除其他地方添加的监听器是一个不好的做法,尤其是当 EventEmitter 实例是其他组件或模块创建的。\n\n返回一个 EventEmitter 引用,可以链式调用。" 816 | }, 817 | "eventemitter.removeListener_39": { 818 | "prefix": "eventemitter.removeListener", 819 | "body": ["eventemitter.removeListener($1, $2)"], 820 | "description": "从名为 eventName 的事件的监听器数组中移除指定的 listener。\n\n const callback = (stream) => {\n console.log('有连接!');\n };\n server.on('connection', callback);\n // ...\n server.removeListener('connection', callback);\n \n\nremoveListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定 eventName 的监听器数组中,则必须多次调用 removeListener 才能移除每个实例。\n\n注意,一旦一个事件被触发,所有绑定到它的监听器都会按顺序依次触发。 这意味着,在事件触发后、最后一个监听器完成执行前,任何 removeListener() 或 removeAllListeners() 调用都不会从 emit() 中移除它们。 随后的事件会像预期的那样发生。\n\n const myEmitter = events.emitter();\n \n const callbackA = () => {\n console.log('A');\n myEmitter.removeListener('event', callbackB);\n };\n \n const callbackB = () => {\n console.log('B');\n };\n \n myEmitter.on('event', callbackA);\n \n myEmitter.on('event', callbackB);\n \n // callbackA 移除了监听器 callbackB,但它依然会被调用。\n // 触发是内部的监听器数组为 [callbackA, callbackB]\n myEmitter.emit('event');\n // 打印:\n // A\n // B\n \n // callbackB 被移除了。\n // 内部监听器数组为 [callbackA]\n myEmitter.emit('event');\n // 打印:\n // A\n \n\n因为监听器是使用内部数组进行管理的,所以调用它会改变在监听器被移除后注册的任何监听器的位置索引。 虽然这不会影响监听器的调用顺序,但意味着由 emitter.listeners() 方法返回的监听器数组副本需要被重新创建。\n\n返回一个 EventEmitter 引用,可以链式调用。" 821 | }, 822 | "eventemitter.setMaxListeners_40": { 823 | "prefix": "eventemitter.setMaxListeners", 824 | "body": ["eventemitter.setMaxListeners($1)"], 825 | "description": "默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 此限制有助于寻找内存泄露。 但是,并不是所有的事件都要被限为 10 个。 emitter.setMaxListeners() 方法允许修改指定的 EventEmitter 实例的限制。 值设为 Infinity(或 0)表明不限制监听器的数量。\n\n返回一个 EventEmitter 引用,可以链式调用。" 826 | }, 827 | "eventemitter.defaultMaxListeners_41": { 828 | "prefix": "eventemitter.defaultMaxListeners", 829 | "body": ["eventemitter.defaultMaxListeners"], 830 | "description": "每个事件默认可以注册最多 10 个监听器。 单个 EventEmitter 实例的限制可以使用 emitter.setMaxListeners(n) 方法改变。 所有 EventEmitter 实例的默认值可以使用 EventEmitter.defaultMaxListeners 属性改变。\n\n设置 EventEmitter.defaultMaxListeners 要谨慎,因为会影响所有 EventEmitter 实例,包括之前创建的。 因而,调用 emitter.setMaxListeners(n) 优先于 EventEmitter.defaultMaxListeners。\n\n注意,与Node.js不同,**这是一个硬性限制**。 EventEmitter 实例不允许添加更多的监听器,监听器超过最大数量时会抛出TooManyListenersException。\n\n emitter.setMaxListeners(emitter.getMaxListeners() + 1);\n emitter.once('event', () => {\n // 做些操作\n emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0));\n });" 831 | }, 832 | "files.isFile_0": { 833 | "prefix": "files.isFile", 834 | "body": ["files.isFile($1, $2)"], 835 | "description": "返回路径path是否是文件。\n\n log(files.isDir(\"/sdcard/文件夹/\")); //返回false\n log(files.isDir(\"/sdcard/文件.txt\")); //返回true" 836 | }, 837 | "files.isDir_1": { 838 | "prefix": "files.isDir", 839 | "body": ["files.isDir($1, $2)"], 840 | "description": "返回路径path是否是文件夹。\n\n log(files.isDir(\"/sdcard/文件夹/\")); //返回true\n log(files.isDir(\"/sdcard/文件.txt\")); //返回false" 841 | }, 842 | "files.isEmptyDir_2": { 843 | "prefix": "files.isEmptyDir", 844 | "body": ["files.isEmptyDir($1, $2)"], 845 | "description": "返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回`false`。" 846 | }, 847 | "files.join_3": { 848 | "prefix": "files.join", 849 | "body": ["files.join($1, $2, $3)"], 850 | "description": "连接两个路径并返回,例如`files.join(\"/sdcard/\", \"1.txt\")`返回\"/sdcard/1.txt\"。" 851 | }, 852 | "files.create_4": { 853 | "prefix": "files.create", 854 | "body": ["files.create($1, $2)"], 855 | "description": "创建一个文件或文件夹并返回是否创建成功。如果文件已经存在,则直接返回`false`。\n\n files.create(\"/sdcard/新文件夹/\");" 856 | }, 857 | "files.createWithDirs_5": { 858 | "prefix": "files.createWithDirs", 859 | "body": ["files.createWithDirs($1, $2)"], 860 | "description": "创建一个文件或文件夹并返回是否创建成功。如果文件所在文件夹不存在,则先创建他所在的一系列文件夹。如果文件已经存在,则直接返回`false`。\n\n files.createWithDirs(\"/sdcard/新文件夹/新文件夹/新文件夹/1.txt\");" 861 | }, 862 | "files.exists_6": { 863 | "prefix": "files.exists", 864 | "body": ["files.exists($1, $2)"], 865 | "description": "返回在路径path处的文件是否存在。" 866 | }, 867 | "files.ensureDir_7": { 868 | "prefix": "files.ensureDir", 869 | "body": ["files.ensureDir($1)"], 870 | "description": "确保路径path所在的文件夹存在。如果该路径所在文件夹不存在,则创建该文件夹。\n\n例如对于路径\"/sdcard/Download/ABC/1.txt\",如果/Download/文件夹不存在,则会先创建Download,再创建ABC文件夹。" 871 | }, 872 | "files.read_8": { 873 | "prefix": "files.read", 874 | "body": ["files.read($1, $2, $3)"], 875 | "description": "读取文本文件path的所有内容并返回。如果文件不存在,则抛出`FileNotFoundException`。\n\n log(files.read(\"/sdcard/1.txt\"));" 876 | }, 877 | "files.readBytes_9": { 878 | "prefix": "files.readBytes", 879 | "body": ["files.readBytes($1, $2)"], 880 | "description": "读取文件path的所有内容并返回一个字节数组。如果文件不存在,则抛出`FileNotFoundException`。\n\n注意,该数组是Java的数组,不具有JavaScript数组的forEach, slice等函数。\n\n一个以16进制形式打印文件的例子如下:\n\n var data = files.readBytes(\"/sdcard/1.png\");\n var sb = new java.lang.StringBuilder();\n for(var i = 0; i < data.length; i++){\n sb.append(data[i].toString(16));\n }\n log(sb.toString());" 881 | }, 882 | "files.write_10": { 883 | "prefix": "files.write", 884 | "body": ["files.write($1, $2, $3)"], 885 | "description": "把text写入到文件path中。如果文件存在则覆盖,不存在则创建。\n\n var text = \"文件内容\";\n //写入文件\n files.write(\"/sdcard/1.txt\", text);\n //用其他应用查看文件\n app.viewFile(\"/sdcard/1.txt\");" 886 | }, 887 | "files.writeBytes_11": { 888 | "prefix": "files.writeBytes", 889 | "body": ["files.writeBytes($1, $2)"], 890 | "description": "把bytes写入到文件path中。如果文件存在则覆盖,不存在则创建。" 891 | }, 892 | "files.append_12": { 893 | "prefix": "files.append", 894 | "body": ["files.append($1, $2, $3)"], 895 | "description": "把text追加到文件path的末尾。如果文件不存在则创建。\n\n var text = \"追加的文件内容\";\n files.append(\"/sdcard/1.txt\", text);\n files.append(\"/sdcard/1.txt\", text);\n //用其他应用查看文件\n app.viewFile(\"/sdcard/1.txt\");" 896 | }, 897 | "files.appendBytes_13": { 898 | "prefix": "files.appendBytes", 899 | "body": ["files.appendBytes($1, $2, $3)"], 900 | "description": "把bytes追加到文件path的末尾。如果文件不存在则创建。" 901 | }, 902 | "files.copy_14": { 903 | "prefix": "files.copy", 904 | "body": ["files.copy($1, $2, $3)"], 905 | "description": "复制文件,返回是否复制成功。例如`files.copy(\"/sdcard/1.txt\", \"/sdcard/Download/1.txt\")`。" 906 | }, 907 | "files.move_15": { 908 | "prefix": "files.move", 909 | "body": ["files.move($1, $2, $3)"], 910 | "description": "移动文件,返回是否移动成功。例如`files.move(\"/sdcard/1.txt\", \"/sdcard/Download/1.txt\")`会把1.txt文件从sd卡根目录移动到Download文件夹。" 911 | }, 912 | "files.rename_16": { 913 | "prefix": "files.rename", 914 | "body": ["files.rename($1, $2, $3)"], 915 | "description": "重命名文件,并返回是否重命名成功。例如`files.rename(\"/sdcard/1.txt\", \"2.txt\")`。" 916 | }, 917 | "files.renameWithoutExtension_17": { 918 | "prefix": "files.renameWithoutExtension", 919 | "body": ["files.renameWithoutExtension($1, $2, $3)"], 920 | "description": "重命名文件,不包含拓展名,并返回是否重命名成功。例如`files.rename(\"/sdcard/1.txt\", \"2\")`会把\"1.txt\"重命名为\"2.txt\"。" 921 | }, 922 | "files.getName_18": { 923 | "prefix": "files.getName", 924 | "body": ["files.getName($1, $2)"], 925 | "description": "返回文件的文件名。例如`files.getName(\"/sdcard/1.txt\")`返回\"1.txt\"。" 926 | }, 927 | "files.getNameWithoutExtension_19": { 928 | "prefix": "files.getNameWithoutExtension", 929 | "body": ["files.getNameWithoutExtension($1, $2)"], 930 | "description": "返回不含拓展名的文件的文件名。例如`files.getName(\"/sdcard/1.txt\")`返回\"1\"。" 931 | }, 932 | "files.getExtension_20": { 933 | "prefix": "files.getExtension", 934 | "body": ["files.getExtension($1, $2)"], 935 | "description": "返回文件的拓展名。例如`files.getExtension(\"/sdcard/1.txt\")`返回\"txt\"。" 936 | }, 937 | "files.remove_21": { 938 | "prefix": "files.remove", 939 | "body": ["files.remove($1, $2)"], 940 | "description": "删除文件或**空文件夹**,返回是否删除成功。" 941 | }, 942 | "files.removeDir_22": { 943 | "prefix": "files.removeDir", 944 | "body": ["files.removeDir($1, $2, $3)"], 945 | "description": "删除文件夹,如果文件夹不为空,则删除该文件夹的所有内容再删除该文件夹,返回是否全部删除成功。" 946 | }, 947 | "files.getSdcardPath_23": { 948 | "prefix": "files.getSdcardPath", 949 | "body": ["files.getSdcardPath($1)"], 950 | "description": "返回SD卡路径。所谓SD卡,即外部存储器。" 951 | }, 952 | "files.cwd_24": { 953 | "prefix": "files.cwd", 954 | "body": ["files.cwd($1)"], 955 | "description": "返回脚本的\"当前工作文件夹路径\"。该路径指的是,如果脚本本身为脚本文件,则返回这个脚本文件所在目录;否则返回`null`获取其他设定路径。\n\n例如,对于脚本文件\"/sdcard/脚本/1.js\"运行`files.cwd()`返回\"/sdcard/脚本/\"。" 956 | }, 957 | "files.path_25": { 958 | "prefix": "files.path", 959 | "body": ["files.path($1, $2)"], 960 | "description": "返回相对路径对应的绝对路径。例如`files.path(\"./1.png\")`,如果运行这个语句的脚本位于文件夹\"/sdcard/脚本/\"中,则返回`\"/sdcard/脚本/1.png\"`。" 961 | }, 962 | "files.listDir_26": { 963 | "prefix": "files.listDir", 964 | "body": ["files.listDir($1, $2)"], 965 | "description": "列出文件夹path下的满足条件的文件和文件夹的名称的数组。如果不加filter参数,则返回所有文件和文件夹。\n\n列出sdcard目录下所有文件和文件夹为:\n\n var arr = files.listDir(\"/sdcard/\");\n log(arr);\n \n\n列出脚本目录下所有js脚本文件为:\n\n var dir = \"/sdcard/脚本/\";\n var jsFiles = files.listDir(dir, function(name){\n return name.endsWith(\".js\") && files.isFile(files.join(dir, name));\n });\n log(jsFiles);" 966 | }, 967 | "files.open_27": { 968 | "prefix": "files.open", 969 | "body": ["files.open($1, $2, $3, $4)"], 970 | "description": "打开一个文件。根据打开模式返回不同的文件对象。包括:\n\n* \"r\": 返回一个ReadableTextFile对象。\n* \"w\", \"a\": 返回一个WritableTextFile对象。\n\n对于\"w\"模式,如果文件并不存在,则会创建一个,已存在则会清空该文件内容;其他模式文件不存在会抛出FileNotFoundException。" 971 | }, 972 | "readabletextfile.read_28": { 973 | "prefix": "readabletextfile.read", 974 | "body": ["readabletextfile.read()"], 975 | "description": "返回该文件剩余的所有内容的字符串。" 976 | }, 977 | "readabletextfile.read_29": { 978 | "prefix": "readabletextfile.read", 979 | "body": ["readabletextfile.read($1)"], 980 | "description": "读取该文件接下来最长为maxCount的字符串并返回。即使文件剩余内容不足maxCount也不会出错。" 981 | }, 982 | "readabletextfile.readline_30": { 983 | "prefix": "readabletextfile.readline", 984 | "body": ["readabletextfile.readline()"], 985 | "description": "读取一行并返回(不包含换行符)。" 986 | }, 987 | "readabletextfile.readlines_31": { 988 | "prefix": "readabletextfile.readlines", 989 | "body": ["readabletextfile.readlines()"], 990 | "description": "读取剩余的所有行,并返回它们按顺序组成的字符串数组。" 991 | }, 992 | "readabletextfile.close_32": { 993 | "prefix": "readabletextfile.close", 994 | "body": ["readabletextfile.close()"], 995 | "description": "关闭该文件。\n\n**打开一个文件不再使用时务必关闭**" 996 | }, 997 | "pwritabletextfile.write_33": { 998 | "prefix": "pwritabletextfile.write", 999 | "body": ["pwritabletextfile.write($1)"], 1000 | "description": "把文本内容text写入到文件中。" 1001 | }, 1002 | "pwritabletextfile.writeline_34": { 1003 | "prefix": "pwritabletextfile.writeline", 1004 | "body": ["pwritabletextfile.writeline($1)"], 1005 | "description": "把文本line写入到文件中并写入一个换行符。" 1006 | }, 1007 | "pwritabletextfile.writelines_35": { 1008 | "prefix": "pwritabletextfile.writelines", 1009 | "body": ["pwritabletextfile.writelines($1)"], 1010 | "description": "把很多行写入到文件中...." 1011 | }, 1012 | "pwritabletextfile.flush_36": { 1013 | "prefix": "pwritabletextfile.flush", 1014 | "body": ["pwritabletextfile.flush()"], 1015 | "description": "把缓冲区内容输出到文件中。" 1016 | }, 1017 | "pwritabletextfile.close_37": { 1018 | "prefix": "pwritabletextfile.close", 1019 | "body": ["pwritabletextfile.close()"], 1020 | "description": "关闭文件。同时会被缓冲区内容输出到文件。\n\n**打开一个文件写入后,不再使用时务必关闭,否则文件可能会丢失**" 1021 | }, 1022 | "floaty.window_0": { 1023 | "prefix": "floaty.window", 1024 | "body": ["floaty.window($1)"], 1025 | "description": "指定悬浮窗的布局,创建并**显示**一个悬浮窗,返回一个`FloatyWindow`对象。\n\n该悬浮窗自带关闭、调整大小、调整位置按键,可根据需要调用`setAdjustEnabled()`函数来显示或隐藏。\n\n其中layout参数可以是xml布局或者一个View,更多信息参见ui模块的说明。\n\n例子:\n\n var w = floaty.window(\n \n 悬浮文字\n \n );\n setTimeout(()=>{\n w.close();\n }, 2000);\n \n\n这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。\n\n另外,因为脚本运行的线程不是UI线程,而所有对控件的修改操作需要在UI线程执行,此时需要用`ui.run`,例如:\n\n ui.run(function(){\n w.text.setText(\"文本\");\n });\n \n\n有关返回的`FloatyWindow`对象的说明,参见下面的`FloatyWindow`章节。" 1026 | }, 1027 | "floaty.rawWindow_1": { 1028 | "prefix": "floaty.rawWindow", 1029 | "body": ["floaty.rawWindow($1)"], 1030 | "description": "指定悬浮窗的布局,创建并**显示**一个原始悬浮窗,返回一个`FloatyRawWindow`对象。\n\n与`floaty.window()`函数不同的是,该悬浮窗不会增加任何额外设施(例如调整大小、位置按钮),您可以根据自己需要编写任何布局。\n\n而且,该悬浮窗支持完全全屏,可以覆盖状态栏,因此可以做护眼模式之类的应用。\n\n var w = floaty.rawWindow(\n \n 悬浮文字\n \n );\n \n w.setPosition(500, 500);\n \n setTimeout(()=>{\n w.close();\n }, 2000);\n \n\n这段代码运行后将会在屏幕上显示悬浮文字,并在两秒后消失。\n\n有关返回的`FloatyRawWindow`对象的说明,参见下面的`FloatyRawWindow`章节。" 1031 | }, 1032 | "floaty.closeAll_2": { 1033 | "prefix": "floaty.closeAll", 1034 | "body": ["floaty.closeAll()"], 1035 | "description": "关闭所有本脚本的悬浮窗。" 1036 | }, 1037 | "floatywindow.setAdjustEnabled_3": { 1038 | "prefix": "floatywindow.setAdjustEnabled", 1039 | "body": ["floatywindow.setAdjustEnabled($1)"], 1040 | "description": "如果enabled为true,则在悬浮窗左上角、右上角显示可供位置、大小调整的标示,就像控制台一样; 如果enabled为false,则隐藏上述标示。" 1041 | }, 1042 | "floatywindow.setPosition_4": { 1043 | "prefix": "floatywindow.setPosition", 1044 | "body": ["floatywindow.setPosition($1, $2)"], 1045 | "description": "设置悬浮窗位置。" 1046 | }, 1047 | "floatywindow.getX_5": { 1048 | "prefix": "floatywindow.getX", 1049 | "body": ["floatywindow.getX()"], 1050 | "description": "返回悬浮窗位置的X坐标。" 1051 | }, 1052 | "floatywindow.getY_6": { 1053 | "prefix": "floatywindow.getY", 1054 | "body": ["floatywindow.getY()"], 1055 | "description": "返回悬浮窗位置的Y坐标。" 1056 | }, 1057 | "floatywindow.setSize_7": { 1058 | "prefix": "floatywindow.setSize", 1059 | "body": ["floatywindow.setSize($1, $2)"], 1060 | "description": "设置悬浮窗宽高。" 1061 | }, 1062 | "floatywindow.getWidht_8": { 1063 | "prefix": "floatywindow.getWidht", 1064 | "body": ["floatywindow.getWidht()"], 1065 | "description": "返回悬浮窗宽度。" 1066 | }, 1067 | "floatywindow.getHeight_9": { 1068 | "prefix": "floatywindow.getHeight", 1069 | "body": ["floatywindow.getHeight()"], 1070 | "description": "返回悬浮窗高度。" 1071 | }, 1072 | "floatywindow.close_10": { 1073 | "prefix": "floatywindow.close", 1074 | "body": ["floatywindow.close()"], 1075 | "description": "关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。\n\n被关闭后的悬浮窗不能再显示。" 1076 | }, 1077 | "floatywindow.exitOnClose_11": { 1078 | "prefix": "floatywindow.exitOnClose", 1079 | "body": ["floatywindow.exitOnClose()"], 1080 | "description": "使悬浮窗被关闭时自动结束脚本运行。" 1081 | }, 1082 | "floatyrawwindow.setTouchable_12": { 1083 | "prefix": "floatyrawwindow.setTouchable", 1084 | "body": ["floatyrawwindow.setTouchable($1)"], 1085 | "description": "设置悬浮窗是否可触摸,如果为true, 则悬浮窗将接收到触摸、点击等事件并且无法继续传递到悬浮窗下面;如果为false, 悬浮窗上的触摸、点击等事件将被直接传递到悬浮窗下面。处于安全考虑,被悬浮窗接收的触摸事情无法再继续传递到下层。\n\n可以用此特性来制作护眼模式脚本。\n\n var w = floaty.rawWindow(\n \n );\n \n w.setSize(-1, -1);\n w.setTouchable(false);\n \n setTimeout(()=>{\n w.close();\n }, 4000);" 1086 | }, 1087 | "floatyrawwindow.setPosition_13": { 1088 | "prefix": "floatyrawwindow.setPosition", 1089 | "body": ["floatyrawwindow.setPosition($1, $2)"], 1090 | "description": "设置悬浮窗位置。" 1091 | }, 1092 | "floatyrawwindow.getX_14": { 1093 | "prefix": "floatyrawwindow.getX", 1094 | "body": ["floatyrawwindow.getX()"], 1095 | "description": "返回悬浮窗位置的X坐标。" 1096 | }, 1097 | "floatyrawwindow.getY_15": { 1098 | "prefix": "floatyrawwindow.getY", 1099 | "body": ["floatyrawwindow.getY()"], 1100 | "description": "返回悬浮窗位置的Y坐标。" 1101 | }, 1102 | "floatyrawwindow.setSize_16": { 1103 | "prefix": "floatyrawwindow.setSize", 1104 | "body": ["floatyrawwindow.setSize($1, $2)"], 1105 | "description": "设置悬浮窗宽高。\n\n特别地,如果设置为-1,则为占满全屏;设置为-2则为根据悬浮窗内容大小而定。例如:\n\n var w = floaty.rawWindow(\n \n 悬浮文字\n \n );\n \n w.setSize(-1, -1);\n \n setTimeout(()=>{\n w.close();\n }, 2000);" 1106 | }, 1107 | "floatyrawwindow.getWidht_17": { 1108 | "prefix": "floatyrawwindow.getWidht", 1109 | "body": ["floatyrawwindow.getWidht()"], 1110 | "description": "返回悬浮窗宽度。" 1111 | }, 1112 | "floatyrawwindow.getHeight_18": { 1113 | "prefix": "floatyrawwindow.getHeight", 1114 | "body": ["floatyrawwindow.getHeight()"], 1115 | "description": "返回悬浮窗高度。" 1116 | }, 1117 | "floatyrawwindow.close_19": { 1118 | "prefix": "floatyrawwindow.close", 1119 | "body": ["floatyrawwindow.close()"], 1120 | "description": "关闭悬浮窗。如果悬浮窗已经是关闭状态,则此函数将不执行任何操作。\n\n被关闭后的悬浮窗不能再显示。" 1121 | }, 1122 | "floatyrawwindow.exitOnClose_20": { 1123 | "prefix": "floatyrawwindow.exitOnClose", 1124 | "body": ["floatyrawwindow.exitOnClose()"], 1125 | "description": "使悬浮窗被关闭时自动结束脚本运行。" 1126 | }, 1127 | "sleep_0": { 1128 | "prefix": "sleep", 1129 | "body": ["sleep($1)"], 1130 | "description": "暂停运行n**毫秒**的时间。1秒等于1000毫秒。\n\n //暂停5毫秒\n sleep(5000);" 1131 | }, 1132 | "currentPackage_1": { 1133 | "prefix": "currentPackage", 1134 | "body": ["currentPackage($1)"], 1135 | "description": "返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。\n\n此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。" 1136 | }, 1137 | "currentActivity_2": { 1138 | "prefix": "currentActivity", 1139 | "body": ["currentActivity($1)"], 1140 | "description": "返回最近一次监测到的正在运行的Activity的名称,一般可以认为就是当前正在运行的Activity的名称。\n\n此函数依赖于无障碍服务,如果服务未启动,则抛出异常并提示用户启动。" 1141 | }, 1142 | "setClip_3": { 1143 | "prefix": "setClip", 1144 | "body": ["setClip($1)"], 1145 | "description": "设置剪贴板内容。此剪贴板即系统剪贴板,在一般应用的输入框中\"粘贴\"既可使用。\n\n setClip(\"剪贴板文本\");" 1146 | }, 1147 | "getClip_4": { 1148 | "prefix": "getClip", 1149 | "body": ["getClip($1)"], 1150 | "description": "返回系统剪贴板的内容。\n\n toast(\"剪贴板内容为:\" + getClip());" 1151 | }, 1152 | "toast_5": { 1153 | "prefix": "toast", 1154 | "body": ["toast($1)"], 1155 | "description": "以气泡显示信息message几秒。(具体时间取决于安卓系统,一般都是2秒)\n\n注意,信息的显示是\"异步\"执行的,并且,不会等待信息消失程序才继续执行。如果在循环中执行该命令,可能出现脚本停止运行后仍然有不断的气泡信息出现的情况。 例如:\n\n for(var i = 0; i < 100; i++){\n toast(i);\n }\n \n\n运行这段程序以后,会很快执行完成,且不断弹出消息,在任务管理中关闭所有脚本也无法停止。 要保证气泡消息才继续执行可以用:\n\n for(var i = 0; i < 100; i++){\n toast(i);\n sleep(2000);\n }\n \n\n或者修改toast函数:\n\n var _toast_ = toast;\n toast = function(message){\n _toast_(message);\n sleep(2000);\n }\n for(var i = 0; i < 100; i++){\n toast(i);\n }" 1156 | }, 1157 | "toastLog_6": { 1158 | "prefix": "toastLog", 1159 | "body": ["toastLog($1)"], 1160 | "description": "相当于`toast(message);log(message)`。显示信息message并在控制台中输出。参见console.log。" 1161 | }, 1162 | "waitForActivity_7": { 1163 | "prefix": "waitForActivity", 1164 | "body": ["waitForActivity($1, $2)"], 1165 | "description": "等待指定的Activity出现,period为检查Activity的间隔。" 1166 | }, 1167 | "waitForPackage_8": { 1168 | "prefix": "waitForPackage", 1169 | "body": ["waitForPackage($1, $2)"], 1170 | "description": "等待指定的应用出现。例如`waitForPackage(\"com.tencent.mm\")`为等待当前界面为微信。" 1171 | }, 1172 | "exit_9": { 1173 | "prefix": "exit", 1174 | "body": ["exit()"], 1175 | "description": "立即停止脚本运行。\n\n立即停止是通过抛出`ScriptInterrupttedException`来实现的,因此如果用`try...catch`把exit()函数的异常捕捉,则脚本不会立即停止,仍会运行几行后再停止。" 1176 | }, 1177 | "random_10": { 1178 | "prefix": "random", 1179 | "body": ["random($1, $2, $3)"], 1180 | "description": "返回一个在\\[min...max\\]之间的随机数。例如random(0, 2)可能产生0, 1, 2。" 1181 | }, 1182 | "random_11": { 1183 | "prefix": "random", 1184 | "body": ["random($1)"], 1185 | "description": "返回在\\[0, 1)的随机浮点数。" 1186 | }, 1187 | "requiresApi_12": { 1188 | "prefix": "requiresApi", 1189 | "body": ["requiresApi($1)"], 1190 | "description": "表示此脚本需要Android API版本达到指定版本才能运行。例如`requiresApi(19)`表示脚本需要在Android 4.4以及以上运行。\n\n调用该函数时会判断运行脚本的设备系统的版本号,如果没有达到要求则抛出异常。\n\n可以参考以下Android API和版本的对照表:\n\n平台版本: API级别\n\nAndroid 7.0: 24\n\nAndroid 6.0: 23\n\nAndroid 5.1: 22\n\nAndroid 5.0: 21\n\nAndroid 4.4W: 20\n\nAndroid 4.4: 19\n\nAndroid 4.3: 18" 1191 | }, 1192 | "requestPermissions_14": { 1193 | "prefix": "requestPermissions", 1194 | "body": ["requestPermissions($1)"], 1195 | "description": "动态申请安卓的权限。例如:\n\n //请求GPS权限\n runtime.requestPermissions([\"access_fine_location\"]);\n \n\n尽管安卓有很多权限,但必须写入Manifest才能动态申请,为了防止权限的滥用,目前 Hamibot 只能额外申请两个权限:\n\n* `access_fine_location` GPS权限\n* `record_audio` 录音权限\n\n您可以通过APK编辑器来增加 Hamibot 以及 Hamibot 打包的应用的权限。\n\n安卓所有的权限列表参见[Permissions Overview](https://developer.android.com/guide/topics/permissions/overview)。(并没有用)" 1196 | }, 1197 | "loadJar_15": { 1198 | "prefix": "loadJar", 1199 | "body": ["loadJar($1)"], 1200 | "description": "加载目标jar文件,加载成功后将可以使用该Jar文件的类。\n\n // 加载jsoup.jar\n runtime.loadJar(\"./jsoup.jar\");\n // 使用jsoup解析html\n importClass(org.jsoup.Jsoup);\n log(Jsoup.parse(files.read(\"./test.html\")));\n \n\n(jsoup是一个Java实现的解析Html DOM的库,可以在[Jsoup](https://jsoup.org/download)下载)" 1201 | }, 1202 | "loadDex_16": { 1203 | "prefix": "loadDex", 1204 | "body": ["loadDex($1)"], 1205 | "description": "加载目标dex文件,加载成功后将可以使用该dex文件的类。\n\n因为加载jar实际上是把jar转换为dex再加载的,因此加载dex文件会比jar文件快得多。可以使用Android SDK的build tools的dx工具把jar转换为dex。" 1206 | }, 1207 | "http.get_0": { 1208 | "prefix": "http.get", 1209 | "body": ["http.get($1, $2, $3)"], 1210 | "description": "对地址url进行一次HTTP GET 请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\\[Response\\]\\[\\])。\n\n最简单GET请求如下:\n\n console.show();\n var r = http.get(\"www.baidu.com\");\n log(\"code = \" + r.statusCode);\n log(\"html = \" + r.body.string());\n \n\n采用回调形式的GET请求如下:\n\n console.show();\n http.get(\"www.baidu.com\", {}, function(res, err){\n if(err){\n console.error(err);\n return;\n }\n log(\"code = \" + res.statusCode);\n log(\"html = \" + res.body.string());\n });\n \n\n如果要增加HTTP头部信息,则在options参数中添加,例如:\n\n console.show();\n var r = http.get(\"www.baidu.com\", {\n headers: {\n 'Accept-Language': 'zh-cn,zh;q=0.5',\n 'User-Agent': 'Mozilla/5.0(Macintosh;IntelMacOSX10_7_0)AppleWebKit/535.11(KHTML,likeGecko)Chrome/17.0.963.56Safari/535.11'\n }\n });\n log(\"code = \" + r.statusCode);\n log(\"html = \" + r.body.string());\n \n\n一个请求天气并解析返回的天气JSON结果的例子如下:\n\n var city = \"广州\";\n var res = http.get(\"http://www.sojson.com/open/api/weather/json.shtml?city=\" + city);\n if(res.statusCode != 200){\n toast(\"请求失败: \" + res.statusCode + \" \" + res.statusMessage);\n }else{\n var weather = res.body.json();\n log(weather);\n toast(util.format(\"温度: %s 湿度: %s 空气质量: %s\", weather.data.wendu,\n weather.data.shidu, weather.quality));\n }" 1211 | }, 1212 | "http.post_1": { 1213 | "prefix": "http.post", 1214 | "body": ["http.post($1, $2, $3, $4)"], 1215 | "description": "对地址url进行一次HTTP POST 请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\\[Response\\]\\[\\])。\n\n其中POST数据可以是字符串或键值对。具体含义取决于options.contentType的值。默认为\"application/x-www-form-urlencoded\"(表单提交), 这种方式是JQuery的ajax函数的默认方式。\n\n一个模拟表单提交登录淘宝的例子如下:\n\n var url = \"https://login.taobao.com/member/login.jhtml\";\n var username = \"你的用户名\";\n var password = \"你的密码\";\n var res = http.post(url, {\n \"TPL_username\": username,\n \"TPL_password\": password\n });\n var html = res.body.string();\n if(html.contains(\"页面跳转中\")){\n toast(\"登录成功\");\n }else{\n toast(\"登录失败\");\n }" 1216 | }, 1217 | "http.postJson_2": { 1218 | "prefix": "http.postJson", 1219 | "body": ["http.postJson($1, $2, $3, $4)"], 1220 | "description": "以JSON格式向目标Url发起POST请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\\[Response\\]\\[\\])。\n\nJSON格式指的是,将会调用`JSON.stringify()`把data对象转换为JSON字符串,并在HTTP头部信息中把\"Content-Type\"属性置为\"application/json\"。这种方式是AngularJS的ajax函数的默认方式。\n\n一个调用图灵机器人接口的例子如下:\n\n var url = \"http://www.tuling123.com/openapi/api\";\n r = http.postJson(url, {\n key: \"65458a5df537443b89b31f1c03202a80\",\n info: \"你好啊\",\n userid: \"1\",\n });\n toastLog(r.body.string());" 1221 | }, 1222 | "http.postMultipart_3": { 1223 | "prefix": "http.postMultipart", 1224 | "body": ["http.postMultipart($1, $2, $3, $4)"], 1225 | "description": "向目标地址发起类型为multipart/form-data的请求(通常用于文件上传等), 其中files参数是{name1: value1, name2: value2, ...}的键值对,value的格式可以是以下几种情况:\n\n1. `string`\n2. 文件类型,即open()返回的类型\n3. \\[fileName, filePath\\]\n4. \\[fileName, mimeType, filePath\\]\n\n其中1属于非文件参数,2、3、4为文件参数。举个例子,最简单的文件上传的请求为:\n\n var res = http.postMultipart(url, {\n file: open(\"/sdcard/1.txt\")\n });\n log(res.body.string());\n \n\n如果使用格式2,则代码为\n\n var res = http.postMultipart(url, {\n file: [\"1.txt\", \"/sdcard/1.txt\"]\n });\n log(res.body.string());\n \n\n如果使用格式3,则代码为\n\n var res = http.postMultipart(url, {\n file: [\"1.txt\", \"text/plain\", \"/sdcard/1.txt\"]\n });\n log(res.body.string());\n \n\n如果使用格式2的同时要附带非文件参数\"appId=abcdefghijk\",则为:\n\n var res = http.postMultipart(url, {\n appId: \"adcdefghijk\",\n file: open(\"/sdcard/1.txt\")\n });\n log(res.body.string());" 1226 | }, 1227 | "http.request_4": { 1228 | "prefix": "http.request", 1229 | "body": ["http.request($1, $2, $3)"], 1230 | "description": "对目标地址url发起一次HTTP请求。如果没有回调函数,则在请求完成或失败时返回此次请求的响应(参见\\[Response\\]\\[\\])。\n\n选项options可以包含以下属性:\n\n* `headers` {Object} 键值对形式的HTTP头部信息。有关HTTP头部信息,参见[菜鸟教程:HTTP响应头信息](http://www.runoob.com/http/http-header-fields.html)。\n* `method` {string} HTTP请求方法。包括\"GET\", \"POST\", \"PUT\", \"DELET\", \"PATCH\"。\n* `contentType` {string} HTTP头部信息中的\"Content-Type\", 表示HTTP请求的内容类型。例如\"text/plain\", \"application/json\"。更多信息参见[菜鸟教程:HTTP contentType](http://www.runoob.com/http/http-content-type.html)。\n* `body` {string} | {Array} | {Function} HTTP请求的内容。可以是一个字符串,也可以是一个字节数组;或者是一个以[BufferedSink](https://github.com/square/okio/blob/master/okio/src/main/java/okio/BufferedSink.java)为参数的函数。\n\n该函数是get, post, postJson等函数的基础函数。因此除非是PUT, DELET等请求,或者需要更高定制的HTTP请求,否则直接使用get, post, postJson等函数会更加方便。" 1231 | }, 1232 | "response.statusCode_5": { 1233 | "prefix": "response.statusCode", 1234 | "body": ["response.statusCode"], 1235 | "description": "当前响应的HTTP状态码。例如200(OK), 404(Not Found)等。\n\n有关HTTP状态码的信息,参见[菜鸟教程:HTTP状态码](http://www.runoob.com/http/http-status-codes.html)。" 1236 | }, 1237 | "response.statusMessage_6": { 1238 | "prefix": "response.statusMessage", 1239 | "body": ["response.statusMessage"], 1240 | "description": "当前响应的HTTP状态信息。例如\"OK\", \"Bad Request\", \"Forbidden\"。\n\n有关HTTP状态码的信息,参见[菜鸟教程:HTTP状态码](http://www.runoob.com/http/http-status-codes.html)。\n\n例子:\n\n var res = http.get(\"www.baidu.com\");\n if(res.statusCode >= 200 && res.statusCode < 300){\n toast(\"页面获取成功!\");\n }else if(res.statusCode == 404){\n toast(\"页面没找到哦...\");\n }else{\n toast(\"错误: \" + res.statusCode + \" \" + res.statusMessage);\n }" 1241 | }, 1242 | "response.headers_7": { 1243 | "prefix": "response.headers", 1244 | "body": ["response.headers"], 1245 | "description": "当前响应的HTTP头部信息。该对象的键是响应头名称,值是各自的响应头值。 所有响应头名称都是小写的(吗)。\n\n有关HTTP头部信息,参见[菜鸟教程:HTTP响应头信息](http://www.runoob.com/http/http-header-fields.html)。\n\n例子:\n\n console.show();\n var res = http.get(\"www.qq.com\");\n console.log(\"HTTP Headers:\")\n for(var headerName in res.headers){\n console.log(\"%s: %s\", headerName, res.headers[headerName]);\n }" 1246 | }, 1247 | "response.body_8": { 1248 | "prefix": "response.body", 1249 | "body": ["response.body"], 1250 | "description": "当前响应的内容。他有以下属性和函数:\n\n* bytes() {Array} 以字节数组形式返回响应内容\n* string() {string} 以字符串形式返回响应内容\n* json() {Object} 把响应内容作为JSON格式的数据并调用JSON.parse,返回解析后的对象\n* contentType {string} 当前响应的内容类型" 1251 | }, 1252 | "response.url_9": { 1253 | "prefix": "response.url", 1254 | "body": ["response.url"], 1255 | "description": "当前响应所对应的请求URL。" 1256 | }, 1257 | "response.method_10": { 1258 | "prefix": "response.method", 1259 | "body": ["response.method"], 1260 | "description": "当前响应所对应的HTTP请求的方法。例如\"GET\", \"POST\", \"PUT\"等。" 1261 | }, 1262 | "colors.toString_0": { 1263 | "prefix": "colors.toString", 1264 | "body": ["colors.toString($1, $2)"], 1265 | "description": "返回颜色值的字符串,格式为 \"#AARRGGBB\"。" 1266 | }, 1267 | "colors.red_1": { 1268 | "prefix": "colors.red", 1269 | "body": ["colors.red($1, $2)"], 1270 | "description": "返回颜色color的R通道的值,范围0~255." 1271 | }, 1272 | "colors.green_2": { 1273 | "prefix": "colors.green", 1274 | "body": ["colors.green($1, $2)"], 1275 | "description": "返回颜色color的G通道的值,范围0~255." 1276 | }, 1277 | "colors.blue_3": { 1278 | "prefix": "colors.blue", 1279 | "body": ["colors.blue($1, $2)"], 1280 | "description": "返回颜色color的B通道的值,范围0~255." 1281 | }, 1282 | "colors.alpha_4": { 1283 | "prefix": "colors.alpha", 1284 | "body": ["colors.alpha($1, $2)"], 1285 | "description": "返回颜色color的Alpha通道的值,范围0~255." 1286 | }, 1287 | "colors.rgb_5": { 1288 | "prefix": "colors.rgb", 1289 | "body": ["colors.rgb($1, $2, $3, $4)"], 1290 | "description": "返回这些颜色通道构成的整数颜色值。Alpha通道将是255(不透明)。" 1291 | }, 1292 | "colors.argb_6": { 1293 | "prefix": "colors.argb", 1294 | "body": ["colors.argb($1, $2, $3, $4, $5)"], 1295 | "description": "返回这些颜色通道构成的整数颜色值。" 1296 | }, 1297 | "colors.parseColor_7": { 1298 | "prefix": "colors.parseColor", 1299 | "body": ["colors.parseColor($1, $2)"], 1300 | "description": "返回颜色的整数值。" 1301 | }, 1302 | "colors.isSimilar_8": { 1303 | "prefix": "colors.isSimilar", 1304 | "body": ["colors.isSimilar($1, $2, $3, $4, $5)"], 1305 | "description": "返回两个颜色是否相似。" 1306 | }, 1307 | "colors.equals_9": { 1308 | "prefix": "colors.equals", 1309 | "body": ["colors.equals($1, $2, $3)"], 1310 | "description": "返回两个颜色是否相等。_\\*注意该函数会忽略Alpha通道的值进行比较_。\n\n log(colors.equals(\"#112233\", \"#112234\"));\n log(colors.equals(0xFF112233, 0xFF223344));" 1311 | }, 1312 | "images.图片处理_10": { 1313 | "prefix": "images.图片处理", 1314 | "body": ["images.图片处理($1)"], 1315 | "description": "读取在路径path的图片文件并返回一个Image对象。如果文件不存在或者文件无法解码则返回null。" 1316 | }, 1317 | "images.read_11": { 1318 | "prefix": "images.read", 1319 | "body": ["images.read($1)"], 1320 | "description": "读取在路径path的图片文件并返回一个Image对象。如果文件不存在或者文件无法解码则返回null。" 1321 | }, 1322 | "images.load_12": { 1323 | "prefix": "images.load", 1324 | "body": ["images.load($1)"], 1325 | "description": "加载在地址URL的网络图片并返回一个Image对象。如果地址不存在或者图片无法解码则返回null。" 1326 | }, 1327 | "images.copy_13": { 1328 | "prefix": "images.copy", 1329 | "body": ["images.copy($1, $2)"], 1330 | "description": "复制一张图片并返回新的副本。该函数会完全复制img对象的数据。" 1331 | }, 1332 | "images.save_14": { 1333 | "prefix": "images.save", 1334 | "body": ["images.save($1, $2, $3, $4)"], 1335 | "description": "把图片image以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。\n\n //把图片压缩为原来的一半质量并保存\n var img = images.read(\"/sdcard/1.png\");\n images.save(img, \"/sdcard/1.jpg\", \"jpg\", 50);\n app.viewFile(\"/sdcard/1.jpg\");" 1336 | }, 1337 | "images.fromBase64_15": { 1338 | "prefix": "images.fromBase64", 1339 | "body": ["images.fromBase64($1, $2)"], 1340 | "description": "解码Base64数据并返回解码后的图片Image对象。如果base64无法解码则返回`null`。" 1341 | }, 1342 | "images.toBase64_16": { 1343 | "prefix": "images.toBase64", 1344 | "body": ["images.toBase64($1, $2, $3, $4)"], 1345 | "description": "把图片编码为base64数据并返回。" 1346 | }, 1347 | "images.fromBytes_17": { 1348 | "prefix": "images.fromBytes", 1349 | "body": ["images.fromBytes($1)"], 1350 | "description": "解码字节数组bytes并返回解码后的图片Image对象。如果bytes无法解码则返回`null`。" 1351 | }, 1352 | "images.toBytes_18": { 1353 | "prefix": "images.toBytes", 1354 | "body": ["images.toBytes($1, $2, $3, $4)"], 1355 | "description": "把图片编码为字节数组并返回。" 1356 | }, 1357 | "images.clip_19": { 1358 | "prefix": "images.clip", 1359 | "body": ["images.clip($1, $2, $3, $4, $5, $6)"], 1360 | "description": "从图片img的位置(x, y)处剪切大小为w \\* h的区域,并返回该剪切区域的新图片。\n\n var src = images.read(\"/sdcard/1.png\");\n var clip = images.clip(src, 100, 100, 400, 400);\n images.save(clip, \"/sdcard/clip.png\");" 1361 | }, 1362 | "images.resize_20": { 1363 | "prefix": "images.resize", 1364 | "body": ["images.resize($1, $2, $3)"], 1365 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `size` {Array} 两个元素的数组\\[w, h\\],分别表示宽度和高度;如果只有一个元素,则宽度和高度相等\n* `interpolation` {string} 插值方法,可选,默认为\"LINEAR\"(线性插值),可选的值有:\n \n * `NEAREST` 最近邻插值\n * `LINEAR` 线性插值(默认)\n * `AREA` 区域插值\n * `CUBIC` 三次样条插值\n * `LANCZOS4` Lanczos插值 参见[InterpolationFlags](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121)\n* 返回 {Image}\n \n\n调整图片大小,并返回调整后的图片。例如把图片放缩为200\\*300:`images.resize(img, [200, 300])`。\n\n参见[Imgproc.resize](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d)。" 1366 | }, 1367 | "images.scale_21": { 1368 | "prefix": "images.scale", 1369 | "body": ["images.scale($1, $2, $3, $4)"], 1370 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `fx` {number} 宽度放缩倍数\n* `fy` {number} 高度放缩倍数\n* `interpolation` {string} 插值方法,可选,默认为\"LINEAR\"(线性插值),可选的值有:\n \n * `NEAREST` 最近邻插值\n * `LINEAR` 线性插值(默认)\n * `AREA` 区域插值\n * `CUBIC` 三次样条插值\n * `LANCZOS4` Lanczos插值 参见[InterpolationFlags](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga5bb5a1fea74ea38e1a5445ca803ff121)\n* 返回 {Image}\n \n\n放缩图片,并返回放缩后的图片。例如把图片变成原来的一半:`images.scale(img, 0.5, 0.5)`。\n\n参见[Imgproc.resize](https://docs.opencv.org/3.4.4/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d)。" 1371 | }, 1372 | "images.rotate_22": { 1373 | "prefix": "images.rotate", 1374 | "body": ["images.rotate($1, $2, $3, $4)"], 1375 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `degress` {number} 旋转角度。\n* `x` {number} 旋转中心x坐标,默认为图片中点\n* `y` {number} 旋转中心y坐标,默认为图片中点\n* 返回 {Image}\n\n将图片逆时针旋转degress度,返回旋转后的图片对象。\n\n例如逆时针旋转90度为`images.rotate(img, 90)`。" 1376 | }, 1377 | "images.concat_23": { 1378 | "prefix": "images.concat", 1379 | "body": ["images.concat($1, $2, $3)"], 1380 | "description": "**\\[v4.1.0新增\\]**\n\n* `img1` {Image} 图片1\n* `img2` {Image} 图片2\n* direction {string} 连接方向,默认为\"RIGHT\",可选的值有:\n * `LEFT` 将图片2接到图片1左边\n * `RIGHT` 将图片2接到图片1右边\n * `TOP` 将图片2接到图片1上边\n * `BOTTOM` 将图片2接到图片1下边\n* 返回 {Image}\n\n连接两张图片,并返回连接后的图像。如果两张图片大小不一致,小的那张将适当居中。" 1381 | }, 1382 | "images.grayscale_24": { 1383 | "prefix": "images.grayscale", 1384 | "body": ["images.grayscale($1)"], 1385 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* 返回 {Image}\n\n灰度化图片,并返回灰度化后的图片。" 1386 | }, 1387 | "images.threshold_25": { 1388 | "prefix": "images.threshold", 1389 | "body": ["images.threshold($1, $2, $3, $4)"], 1390 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `threshold` {number} 阈值\n* `maxVal` {number} 最大值\n* `type` {string} 阈值化类型,默认为\"BINARY\",参见[ThresholdTypes](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576), 可选的值:\n \n * `BINARY`\n * `BINARY_INV`\n * `TRUNC`\n * `TOZERO`\n * `TOZERO_INV`\n * `OTSU`\n * `TRIANGLE`\n* 返回 {Image}\n \n\n将图片阈值化,并返回处理后的图像。可以用这个函数进行图片二值化。例如:`images.threshold(img, 100, 255, \"BINARY\")`,这个代码将图片中大于100的值全部变成255,其余变成0,从而达到二值化的效果。如果img是一张灰度化图片,这个代码将会得到一张黑白图片。\n\n可以参考有关博客(比如[threshold函数的使用](https://blog.csdn.net/u012566751/article/details/77046445))或者OpenCV文档[threshold](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57)。" 1391 | }, 1392 | "images.adaptiveThreshold_26": { 1393 | "prefix": "images.adaptiveThreshold", 1394 | "body": ["images.adaptiveThreshold($1, $2, $3, $4, $5, $6)"], 1395 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `maxValue` {number} 最大值\n* `adaptiveMethod` {string} 在一个邻域内计算阈值所采用的算法,可选的值有:\n * `MEAN_C` 计算出领域的平均值再减去参数C的值\n * `GAUSSIAN_C` 计算出领域的高斯均值再减去参数C的值\n* `thresholdType` {string} 阈值化类型,可选的值有:\n * `BINARY`\n * `BINARY_INV`\n* `blockSize` {number} 邻域块大小\n* `C` {number} 偏移值调整量\n* 返回 {Image}\n\n对图片进行自适应阈值化处理,并返回处理后的图像。\n\n可以参考有关博客(比如[threshold与adaptiveThreshold](https://blog.csdn.net/guduruyu/article/details/68059450))或者OpenCV文档[adaptiveThreshold](https://docs.opencv.org/3.4.4/d7/d1b/group__imgproc__misc.html#ga72b913f352e4a1b1b397736707afcde3)。" 1396 | }, 1397 | "images.cvtColor_27": { 1398 | "prefix": "images.cvtColor", 1399 | "body": ["images.cvtColor($1, $2, $3)"], 1400 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `code` {string} 颜色空间转换的类型,可选的值有一共有205个(参见[ColorConversionCodes](https://docs.opencv.org/3.4.4/d8/d01/group__imgproc__color__conversions.html#ga4e0972be5de079fed4e3a10e24ef5ef0)),这里只列出几个:\n * `BGR2GRAY` BGR转换为灰度\n * `BGR2HSV` BGR转换为HSV\n * \\`\\`\n* `dstCn` {number} 目标图像的颜色通道数量,如果不填写则根据其他参数自动决定。\n* 返回 {Image}\n\n对图像进行颜色空间转换,并返回转换后的图像。\n\n可以参考有关博客(比如[颜色空间转换](https://blog.csdn.net/u011574296/article/details/70896811?locationNum=14&fps=1))或者OpenCV文档[cvtColor](https://docs.opencv.org/3.4.4/d8/d01/group__imgproc__color__conversions.html#ga397ae87e1288a81d2363b61574eb8cab)。" 1401 | }, 1402 | "images.inRange_28": { 1403 | "prefix": "images.inRange", 1404 | "body": ["images.inRange($1, $2, $3)"], 1405 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `lowerBound` {string} | {number} 颜色下界\n* `upperBound` {string} | {number} 颜色下界\n* 返回 {Image}\n\n将图片二值化,在lowerBound~upperBound范围以外的颜色都变成0,在范围以内的颜色都变成255。\n\n例如`images.inRange(img, \"#000000\", \"#222222\")`。" 1406 | }, 1407 | "images.interval_29": { 1408 | "prefix": "images.interval", 1409 | "body": ["images.interval($1, $2, $3)"], 1410 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `color` {string} | {number} 颜色值\n* `interval` {number} 每个通道的范围间隔\n* 返回 {Image}\n\n将图片二值化,在color-interval ~ color+interval范围以外的颜色都变成0,在范围以内的颜色都变成255。这里对color的加减是对每个通道而言的。\n\n例如`images.interval(img, \"#888888\", 16)`,每个通道的颜色值均为0x88,加减16后的范围是\\[0x78, 0x98\\],因此这个代码将把#787878~#989898的颜色变成#FFFFFF,而把这个范围以外的变成#000000。" 1411 | }, 1412 | "images.blur_30": { 1413 | "prefix": "images.blur", 1414 | "body": ["images.blur($1, $2, $3, $4)"], 1415 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `size` {Array} 定义滤波器的大小,如\\[3, 3\\]\n* `anchor` {Array} 指定锚点位置(被平滑点),默认为图像中心\n* `type` {string} 推断边缘像素类型,默认为\"DEFAULT\",可选的值有:\n * `CONSTANT` iiiiii|abcdefgh|iiiiiii with some specified i\n * `REPLICATE` aaaaaa|abcdefgh|hhhhhhh\n * `REFLECT` fedcba|abcdefgh|hgfedcb\n * `WRAP` cdefgh|abcdefgh|abcdefg\n * `REFLECT_101` gfedcb|abcdefgh|gfedcba\n * `TRANSPARENT` uvwxyz|abcdefgh|ijklmno\n * `REFLECT101` same as BORDER\\_REFLECT\\_101\n * `DEFAULT` same as BORDER\\_REFLECT\\_101\n * `ISOLATED` do not look outside of ROI\n* 返回 {Image}\n\n对图像进行模糊(平滑处理),返回处理后的图像。\n\n可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者OpenCV文档[blur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#ga8c45db9afe636703801b0b2e440fce37)。" 1416 | }, 1417 | "images.medianBlur_31": { 1418 | "prefix": "images.medianBlur", 1419 | "body": ["images.medianBlur($1, $2)"], 1420 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `size` {Array} 定义滤波器的大小,如\\[3, 3\\]\n* 返回 {Image}\n\n对图像进行中值滤波,返回处理后的图像。\n\n可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者OpenCV文档[blur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9)。" 1421 | }, 1422 | "images.gaussianBlur_32": { 1423 | "prefix": "images.gaussianBlur", 1424 | "body": ["images.gaussianBlur($1, $2, $3, $4, $5)"], 1425 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 图片\n* `size` {Array} 定义滤波器的大小,如\\[3, 3\\]\n* `sigmaX` {number} x方向的标准方差,不填写则自动计算\n* `sigmaY` {number} y方向的标准方差,不填写则自动计算\n* `type` {string} 推断边缘像素类型,默认为\"DEFAULT\",参见`images.blur`\n* 返回 {Image}\n\n对图像进行高斯模糊,返回处理后的图像。\n\n可以参考有关博客(比如[实现图像平滑处理](https://www.cnblogs.com/denny402/p/3848316.html))或者OpenCV文档[GaussianBlur](https://docs.opencv.org/3.4.4/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1)。" 1426 | }, 1427 | "images.matToImage_33": { 1428 | "prefix": "images.matToImage", 1429 | "body": ["images.matToImage($1)"], 1430 | "description": "**\\[v4.1.0新增\\]**\n\n* `mat` {Mat} OpenCV的Mat对象\n* 返回 {Image}\n\n把Mat对象转换为Image对象。" 1431 | }, 1432 | "images.找图找色_34": { 1433 | "prefix": "images.找图找色", 1434 | "body": ["images.找图找色($1)"], 1435 | "description": "向系统申请屏幕截图权限,返回是否请求成功。\n\n第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。\n\n这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是`captureScreen()`。\n\n该函数在截图脚本中只需执行一次,而无需每次调用`captureScreen()`都调用一次。\n\n**如果不指定landscape值,则截图方向由当前设备屏幕方向决定**,因此务必注意执行该函数时的屏幕方向。\n\n建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象。\n\n示例:\n\n //请求截图\n if(!requestScreenCapture()){\n toast(\"请求截图失败\");\n exit();\n }\n //连续截图10张图片(间隔1秒)并保存到存储卡目录\n for(var i = 0; i < 10; i++){\n captureScreen(\"/sdcard/screencapture\" + i + \".png\");\n sleep(1000);\n }\n \n\n该函数也可以作为全局函数使用。" 1436 | }, 1437 | "images.requestScreenCapture_35": { 1438 | "prefix": "images.requestScreenCapture", 1439 | "body": ["images.requestScreenCapture($1)"], 1440 | "description": "向系统申请屏幕截图权限,返回是否请求成功。\n\n第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。\n\n这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是`captureScreen()`。\n\n该函数在截图脚本中只需执行一次,而无需每次调用`captureScreen()`都调用一次。\n\n**如果不指定landscape值,则截图方向由当前设备屏幕方向决定**,因此务必注意执行该函数时的屏幕方向。\n\n建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏现象。\n\n示例:\n\n //请求截图\n if(!requestScreenCapture()){\n toast(\"请求截图失败\");\n exit();\n }\n //连续截图10张图片(间隔1秒)并保存到存储卡目录\n for(var i = 0; i < 10; i++){\n captureScreen(\"/sdcard/screencapture\" + i + \".png\");\n sleep(1000);\n }\n \n\n该函数也可以作为全局函数使用。" 1441 | }, 1442 | "images.captureScreen_36": { 1443 | "prefix": "images.captureScreen", 1444 | "body": ["images.captureScreen()"], 1445 | "description": "截取当前屏幕并返回一个Image对象。\n\n没有截图权限时执行该函数会抛出SecurityException。\n\n该函数不会返回null,两次调用可能返回相同的Image对象。这是因为设备截图的更新需要一定的时间,短时间内(一般来说是16ms)连续调用则会返回同一张截图。\n\n截图需要转换为Bitmap格式,从而该函数执行需要一定的时间(0~20ms)。\n\n另外在requestScreenCapture()执行成功后需要一定时间后才有截图可用,因此如果立即调用captureScreen(),会等待一定时间后(一般为几百ms)才返回截图。\n\n例子:\n\n //请求横屏截图\n requestScreenCapture(true);\n //截图\n var img = captureScreen();\n //获取在点(100, 100)的颜色值\n var color = images.pixel(img, 100, 100);\n //显示该颜色值\n toast(colors.toString(color));\n \n\n该函数也可以作为全局函数使用。" 1446 | }, 1447 | "images.captureScreen_37": { 1448 | "prefix": "images.captureScreen", 1449 | "body": ["images.captureScreen($1)"], 1450 | "description": "截取当前屏幕并以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。\n\n该函数不会返回任何值。该函数也可以作为全局函数使用。" 1451 | }, 1452 | "images.pixel_38": { 1453 | "prefix": "images.pixel", 1454 | "body": ["images.pixel($1, $2, $3)"], 1455 | "description": "返回图片image在点(x, y)处的像素的ARGB值。\n\n该值的格式为0xAARRGGBB,是一个\"32位整数\"(虽然JavaScript中并不区分整数类型和其他数值类型)。\n\n坐标系以图片左上角为原点。以图片左侧边为y轴,上侧边为x轴。" 1456 | }, 1457 | "images.findColor_39": { 1458 | "prefix": "images.findColor", 1459 | "body": ["images.findColor($1, $2, $3)"], 1460 | "description": "在图片中寻找颜色color。找到时返回找到的点Point,找不到时返回null。\n\n选项包括:\n\n* `region` {Array} 找色区域。是一个两个或四个元素的数组。(region\\[0\\], region\\[1\\])表示找色区域的左上角;region\\[2\\]\\*region\\[3\\]表示找色区域的宽高。如果只有region只有两个元素,则找色区域为(region\\[0\\], region\\[1\\])到屏幕右下角。如果不指定region选项,则找色区域为整张图片。\n* `threshold` {number} 找色时颜色相似度的临界值,范围为0~255(越小越相似,0为颜色相等,255为任何颜色都能匹配)。默认为4。threshold和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255.\n\n该函数也可以作为全局函数使用。\n\n一个循环找色的例子如下:\n\n requestScreenCapture();\n \n //循环找色,找到红色(#ff0000)时停止并报告坐标\n while(true){\n var img = captureScreen();\n var point = findColor(img, \"#ff0000\");\n if(point){\n toast(\"找到红色,坐标为(\" + point.x + \", \" + point.y + \")\");\n }\n }\n \n\n一个区域找色的例子如下:\n\n //读取本地图片/sdcard/1.png\n var img = images.read(\"/sdcard/1.png\");\n //判断图片是否加载成功\n if(!img){\n toast(\"没有该图片\");\n exit();\n }\n //在该图片中找色,指定找色区域为在位置(400, 500)的宽为300长为200的区域,指定找色临界值为4\n var point = findColor(img, \"#00ff00\", {\n region: [400, 500, 300, 200],\n threshold: 4\n });\n if(point){\n toast(\"找到啦:\" + point);\n }else{\n toast(\"没找到\");\n }" 1461 | }, 1462 | "images.findColorInRegion_40": { 1463 | "prefix": "images.findColorInRegion", 1464 | "body": ["images.findColorInRegion($1, $2, $3, $4, $5, $6, $7)"], 1465 | "description": "区域找色的简便方法。\n\n相当于\n\n images.findColor(img, color, {\n region: [x, y, width, height],\n threshold: threshold\n });\n \n\n该函数也可以作为全局函数使用。" 1466 | }, 1467 | "images.findColorEquals_41": { 1468 | "prefix": "images.findColorEquals", 1469 | "body": ["images.findColorEquals($1, $2, $3, $4, $5, $6, $7)"], 1470 | "description": "在图片img指定区域中找到颜色和color完全相等的某个点,并返回该点的左边;如果没有找到,则返回`null`。\n\n找色区域通过`x`, `y`, `width`, `height`指定,如果不指定找色区域,则在整张图片中寻找。\n\n该函数也可以作为全局函数使用。\n\n示例: (通过找QQ红点的颜色来判断是否有未读消息)\n\n requestScreenCapture();\n launchApp(\"QQ\");\n sleep(1200);\n var p = findColorEquals(captureScreen(), \"#f64d30\");\n if(p){\n toast(\"有未读消息\");\n }else{\n toast(\"没有未读消息\");\n }" 1471 | }, 1472 | "images.findMultiColors_42": { 1473 | "prefix": "images.findMultiColors", 1474 | "body": ["images.findMultiColors($1, $2, $3, $4)"], 1475 | "description": "多点找色,类似于按键精灵的多点找色,其过程如下:\n\n1. 在图片img中找到颜色firstColor的位置(x0, y0)\n2. 对于数组colors的每个元素\\[x, y, color\\],检查图片img在位置(x + x0, y + y0)上的像素是否是颜色color,是的话返回(x0, y0),否则继续寻找firstColor的位置,重新执行第1步\n3. 整张图片都找不到时返回`null`\n\n例如,对于代码`images.findMultiColors(img, \"#123456\", [[10, 20, \"#ffffff\"], [30, 40, \"#000000\"]])`,假设图片在(100, 200)的位置的颜色为#123456, 这时如果(110, 220)的位置的颜色为#fffff且(130, 240)的位置的颜色为#000000,则函数返回点(100, 200)。\n\n如果要指定找色区域,则在options中指定,例如:\n\n var p = images.findMultiColors(img, \"#123456\", [[10, 20, \"#ffffff\"], [30, 40, \"#000000\"]], {\n region: [0, 960, 1080, 960]\n });" 1476 | }, 1477 | "images.detectsColor_43": { 1478 | "prefix": "images.detectsColor", 1479 | "body": ["images.detectsColor($1, $2, $3, $4, $5, $6)"], 1480 | "description": "返回图片image在位置(x, y)处是否匹配到颜色color。用于检测图片中某个位置是否是特定颜色。\n\n一个判断微博客户端的某个微博是否被点赞过的例子:\n\n requestScreenCapture();\n //找到点赞控件\n var like = id(\"ly_feed_like_icon\").findOne();\n //获取该控件中点坐标\n var x = like.bounds().centerX();\n var y = like.bounds().centerY();\n //截图\n var img = captureScreen();\n //判断在该坐标的颜色是否为橙红色\n if(images.detectsColor(img, \"#fed9a8\", x, y)){\n //是的话则已经是点赞过的了,不做任何动作\n }else{\n //否则点击点赞按钮\n like.click();\n }" 1481 | }, 1482 | "images.findImage_44": { 1483 | "prefix": "images.findImage", 1484 | "body": ["images.findImage($1, $2, $3)"], 1485 | "description": "找图。在大图片img中查找小图片template的位置(模块匹配),找到时返回位置坐标(Point),找不到时返回null。\n\n选项包括:\n\n* `threshold` {number} 图片相似度。取值范围为0~1的浮点数。默认值为0.9。\n* `region` {Array} 找图区域。参见findColor函数关于region的说明。\n* `level` {number} **一般而言不必修改此参数**。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level参数表示金字塔的层次, level越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。\n\n该函数也可以作为全局函数使用。\n\n一个最简单的找图例子如下:\n\n var img = images.read(\"/sdcard/大图.png\");\n var templ = images.read(\"/sdcard/小图.png\");\n var p = findImage(img, templ);\n if(p){\n toast(\"找到啦:\" + p);\n }else{\n toast(\"没找到\");\n }\n \n\n稍微复杂点的区域找图例子如下:\n\n auto();\n requestScreenCapture();\n var wx = images.read(\"/sdcard/微信图标.png\");\n //返回桌面\n home();\n //截图并找图\n var p = findImage(captureScreen(), wx, {\n region: [0, 50],\n threshold: 0.8\n });\n if(p){\n toast(\"在桌面找到了微信图标啦: \" + p);\n }else{\n toast(\"在桌面没有找到微信图标\");\n }" 1486 | }, 1487 | "images.findImageInRegion_45": { 1488 | "prefix": "images.findImageInRegion", 1489 | "body": ["images.findImageInRegion($1, $2, $3, $4, $5, $6, $7)"], 1490 | "description": "区域找图的简便方法。相当于:\n\n images.findImage(img, template, {\n region: [x, y, width, height],\n threshold: threshold\n })\n \n\n该函数也可以作为全局函数使用。" 1491 | }, 1492 | "images.matchTemplate_46": { 1493 | "prefix": "images.matchTemplate", 1494 | "body": ["images.matchTemplate($1, $2, $3)"], 1495 | "description": "**\\[v4.1.0新增\\]**\n\n* `img` {Image} 大图片\n* `template` {Image} 小图片(模板)\n* `options` {Object} 找图选项:\n * `threshold` {number} 图片相似度。取值范围为0~1的浮点数。默认值为0.9。\n * `region` {Array} 找图区域。参见findColor函数关于region的说明。\n * `max` {number} 找图结果最大数量,默认为5\n * `level` {number} **一般而言不必修改此参数**。不加此参数时该参数会根据图片大小自动调整。找图算法是采用图像金字塔进行的, level参数表示金字塔的层次, level越大可能带来越高的找图效率,但也可能造成找图失败(图片因过度缩小而无法分辨)或返回错误位置。因此,除非您清楚该参数的意义并需要进行性能调优,否则不需要用到该参数。\n* 返回 {MatchingResult}\n\n在大图片中搜索小图片,并返回搜索结果MatchingResult。该函数可以用于找图时找出多个位置,可以通过max参数控制最大的结果数量。也可以对匹配结果进行排序、求最值等操作。" 1496 | }, 1497 | "matchingresult.points_47": { 1498 | "prefix": "matchingresult.points", 1499 | "body": ["matchingresult.points($1)"], 1500 | "description": "* {Array} 匹配位置的数组。" 1501 | }, 1502 | "matchingresult.first_48": { 1503 | "prefix": "matchingresult.first", 1504 | "body": ["matchingresult.first($1)"], 1505 | "description": "第一个匹配结果。如果没有任何匹配,则返回`null`。" 1506 | }, 1507 | "matchingresult.last_49": { 1508 | "prefix": "matchingresult.last", 1509 | "body": ["matchingresult.last($1)"], 1510 | "description": "最后一个匹配结果。如果没有任何匹配,则返回`null`。" 1511 | }, 1512 | "matchingresult.leftmost_50": { 1513 | "prefix": "matchingresult.leftmost", 1514 | "body": ["matchingresult.leftmost($1)"], 1515 | "description": "位于大图片最左边的匹配结果。如果没有任何匹配,则返回`null`。" 1516 | }, 1517 | "matchingresult.topmost_51": { 1518 | "prefix": "matchingresult.topmost", 1519 | "body": ["matchingresult.topmost($1)"], 1520 | "description": "位于大图片最上边的匹配结果。如果没有任何匹配,则返回`null`。" 1521 | }, 1522 | "matchingresult.rightmost_52": { 1523 | "prefix": "matchingresult.rightmost", 1524 | "body": ["matchingresult.rightmost($1)"], 1525 | "description": "位于大图片最右边的匹配结果。如果没有任何匹配,则返回`null`。" 1526 | }, 1527 | "matchingresult.bottommost_53": { 1528 | "prefix": "matchingresult.bottommost", 1529 | "body": ["matchingresult.bottommost($1)"], 1530 | "description": "位于大图片最下边的匹配结果。如果没有任何匹配,则返回`null`。" 1531 | }, 1532 | "matchingresult.best_54": { 1533 | "prefix": "matchingresult.best", 1534 | "body": ["matchingresult.best($1)"], 1535 | "description": "相似度最高的匹配结果。如果没有任何匹配,则返回`null`。" 1536 | }, 1537 | "matchingresult.worst_55": { 1538 | "prefix": "matchingresult.worst", 1539 | "body": ["matchingresult.worst($1)"], 1540 | "description": "相似度最低的匹配结果。如果没有任何匹配,则返回`null`。" 1541 | }, 1542 | "matchingresult.sortBy_56": { 1543 | "prefix": "matchingresult.sortBy", 1544 | "body": ["matchingresult.sortBy($1, $2)"], 1545 | "description": "对匹配结果进行排序,并返回排序后的结果。\n\n var result = images.matchTemplate(img, template, {\n max: 100\n });\n log(result.sortBy(\"top-right\"));" 1546 | }, 1547 | "image.getWidth_57": { 1548 | "prefix": "image.getWidth", 1549 | "body": ["image.getWidth()"], 1550 | "description": "返回以像素为单位图片宽度。" 1551 | }, 1552 | "image.getHeight_58": { 1553 | "prefix": "image.getHeight", 1554 | "body": ["image.getHeight()"], 1555 | "description": "返回以像素为单位的图片高度。" 1556 | }, 1557 | "image.saveTo_59": { 1558 | "prefix": "image.saveTo", 1559 | "body": ["image.saveTo($1)"], 1560 | "description": "把图片保存到路径path。(如果文件存在则覆盖)" 1561 | }, 1562 | "image.pixel_60": { 1563 | "prefix": "image.pixel", 1564 | "body": ["image.pixel($1, $2)"], 1565 | "description": "返回图片image在点(x, y)处的像素的ARGB值。\n\n该值的格式为0xAARRGGBB,是一个\"32位整数\"(虽然JavaScript中并不区分整数类型和其他数值类型)。\n\n坐标系以图片左上角为原点。以图片左侧边为y轴,上侧边为x轴。\n\n##" 1566 | }, 1567 | "point.x_61": { 1568 | "prefix": "point.x", 1569 | "body": ["point.x"], 1570 | "description": "横坐标。" 1571 | }, 1572 | "point.y_62": { 1573 | "prefix": "point.y", 1574 | "body": ["point.y"], 1575 | "description": "纵坐标。" 1576 | }, 1577 | "keys.back_0": { 1578 | "prefix": "keys.back", 1579 | "body": ["keys.back($1)"], 1580 | "description": "模拟按下返回键。返回是否执行成功。 此函数依赖于无障碍服务。" 1581 | }, 1582 | "keys.home_1": { 1583 | "prefix": "keys.home", 1584 | "body": ["keys.home($1)"], 1585 | "description": "模拟按下Home键。返回是否执行成功。 此函数依赖于无障碍服务。" 1586 | }, 1587 | "keys.powerDialog_2": { 1588 | "prefix": "keys.powerDialog", 1589 | "body": ["keys.powerDialog($1)"], 1590 | "description": "弹出电源键菜单。返回是否执行成功。 此函数依赖于无障碍服务。" 1591 | }, 1592 | "keys.notifications_3": { 1593 | "prefix": "keys.notifications", 1594 | "body": ["keys.notifications($1)"], 1595 | "description": "拉出通知栏。返回是否执行成功。 此函数依赖于无障碍服务。" 1596 | }, 1597 | "keys.quickSettings_4": { 1598 | "prefix": "keys.quickSettings", 1599 | "body": ["keys.quickSettings($1)"], 1600 | "description": "显示快速设置(下拉通知栏到底)。返回是否执行成功。 此函数依赖于无障碍服务。" 1601 | }, 1602 | "keys.recents_5": { 1603 | "prefix": "keys.recents", 1604 | "body": ["keys.recents($1)"], 1605 | "description": "显示最近任务。返回是否执行成功。 此函数依赖于无障碍服务。" 1606 | }, 1607 | "keys.splitScreen_6": { 1608 | "prefix": "keys.splitScreen", 1609 | "body": ["keys.splitScreen($1)"], 1610 | "description": "分屏。返回是否执行成功。 此函数依赖于无障碍服务, 并且需要系统自身功能的支持。" 1611 | }, 1612 | "keys.Home_7": { 1613 | "prefix": "keys.Home", 1614 | "body": ["keys.Home()"], 1615 | "description": "模拟按下Home键。 此函数依赖于root权限。" 1616 | }, 1617 | "keys.Back_8": { 1618 | "prefix": "keys.Back", 1619 | "body": ["keys.Back()"], 1620 | "description": "模拟按下返回键。 此函数依赖于root权限。" 1621 | }, 1622 | "keys.Power_9": { 1623 | "prefix": "keys.Power", 1624 | "body": ["keys.Power()"], 1625 | "description": "模拟按下电源键。 此函数依赖于root权限。" 1626 | }, 1627 | "keys.Menu_10": { 1628 | "prefix": "keys.Menu", 1629 | "body": ["keys.Menu()"], 1630 | "description": "模拟按下菜单键。 此函数依赖于root权限。" 1631 | }, 1632 | "keys.VolumeUp_11": { 1633 | "prefix": "keys.VolumeUp", 1634 | "body": ["keys.VolumeUp()"], 1635 | "description": "按下音量上键。 此函数依赖于root权限。" 1636 | }, 1637 | "keys.VolumeDown_12": { 1638 | "prefix": "keys.VolumeDown", 1639 | "body": ["keys.VolumeDown()"], 1640 | "description": "按键音量上键。 此函数依赖于root权限。" 1641 | }, 1642 | "keys.Camera_13": { 1643 | "prefix": "keys.Camera", 1644 | "body": ["keys.Camera()"], 1645 | "description": "模拟按下照相键。" 1646 | }, 1647 | "keys.Up_14": { 1648 | "prefix": "keys.Up", 1649 | "body": ["keys.Up()"], 1650 | "description": "模拟按下物理按键上。 此函数依赖于root权限。" 1651 | }, 1652 | "keys.Down_15": { 1653 | "prefix": "keys.Down", 1654 | "body": ["keys.Down()"], 1655 | "description": "模拟按下物理按键下。 此函数依赖于root权限。" 1656 | }, 1657 | "keys.Left_16": { 1658 | "prefix": "keys.Left", 1659 | "body": ["keys.Left()"], 1660 | "description": "模拟按下物理按键左。 此函数依赖于root权限。" 1661 | }, 1662 | "keys.Right_17": { 1663 | "prefix": "keys.Right", 1664 | "body": ["keys.Right()"], 1665 | "description": "模拟按下物理按键右。 此函数依赖于root权限。" 1666 | }, 1667 | "keys.OK_18": { 1668 | "prefix": "keys.OK", 1669 | "body": ["keys.OK()"], 1670 | "description": "模拟按下物理按键确定。 此函数依赖于root权限。" 1671 | }, 1672 | "keys.Text_19": { 1673 | "prefix": "keys.Text", 1674 | "body": ["keys.Text($1)"], 1675 | "description": "" 1676 | }, 1677 | "keys.KeyCode_20": { 1678 | "prefix": "keys.KeyCode", 1679 | "body": ["keys.KeyCode($1)"], 1680 | "description": "" 1681 | }, 1682 | "media.scanFile_0": { 1683 | "prefix": "media.scanFile", 1684 | "body": ["media.scanFile($1)"], 1685 | "description": "扫描路径path的媒体文件,将它加入媒体库中;或者如果该文件以及被删除,则通知媒体库移除该文件。\n\n媒体库包括相册、音乐库等,因此该函数可以用于把某个图片文件加入相册。\n\n //请求截图\n requestScreenCapture(false);\n //截图\n var im = captureScreen();\n var path = \"/sdcard/screenshot.png\";\n //保存图片\n im.saveTo(path);\n //把图片加入相册\n media.scanFile(path);" 1686 | }, 1687 | "media.playMusic_1": { 1688 | "prefix": "media.playMusic", 1689 | "body": ["media.playMusic($1, $2, $3)"], 1690 | "description": "播放音乐文件path。该函数不会显示任何音乐播放界面。如果文件不存在或者文件不是受支持的音乐格式,则抛出`UncheckedIOException`异常。\n\n //播放音乐\n media.playMusic(\"/sdcard/1.mp3\");\n //让音乐播放完\n sleep(media.getMusicDuration());\n \n\n如果要循环播放音乐,则使用looping参数:\n\n//传递第三个参数为true以循环播放音乐 media.playMusic(\"/sdcard/1.mp3\", 1, true); //等待三次播放的时间 sleep(media.getMusicDuration() \\* 3);\n\n如果要使用音乐播放器播放音乐,调用`app.viewFile(path)`函数。" 1691 | }, 1692 | "media.musicSeekTo_2": { 1693 | "prefix": "media.musicSeekTo", 1694 | "body": ["media.musicSeekTo($1)"], 1695 | "description": "把当前播放进度调整到时间msec的位置。如果当前没有在播放音乐,则调用函数没有任何效果。\n\n例如,要把音乐调到1分钟的位置,为`media.musicSeekTo(60 * 1000)`。\n\n //播放音乐\n media.playMusic(\"/sdcard/1.mp3\");\n //调整到30秒的位置\n media.musicSeekTo(30 * 1000);\n //等待音乐播放完成\n sleep(media.getMusicDuration() - 30 * 1000);" 1696 | }, 1697 | "media.pauseMusic_3": { 1698 | "prefix": "media.pauseMusic", 1699 | "body": ["media.pauseMusic()"], 1700 | "description": "暂停音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。" 1701 | }, 1702 | "media.resumeMusic_4": { 1703 | "prefix": "media.resumeMusic", 1704 | "body": ["media.resumeMusic()"], 1705 | "description": "继续音乐播放。如果当前没有播放过音乐,则调用该函数没有任何效果。" 1706 | }, 1707 | "media.stopMusic_5": { 1708 | "prefix": "media.stopMusic", 1709 | "body": ["media.stopMusic()"], 1710 | "description": "停止音乐播放。如果当前没有在播放音乐,则调用函数没有任何效果。" 1711 | }, 1712 | "media.isMusicPlaying_6": { 1713 | "prefix": "media.isMusicPlaying", 1714 | "body": ["media.isMusicPlaying($1)"], 1715 | "description": "返回当前是否正在播放音乐。" 1716 | }, 1717 | "media.getMusicDuration_7": { 1718 | "prefix": "media.getMusicDuration", 1719 | "body": ["media.getMusicDuration($1)"], 1720 | "description": "返回当前音乐的时长。单位毫秒。" 1721 | }, 1722 | "media.getMusicCurrentPosition_8": { 1723 | "prefix": "media.getMusicCurrentPosition", 1724 | "body": ["media.getMusicCurrentPosition($1)"], 1725 | "description": "返回当前音乐的播放进度(已经播放的时间),单位毫秒。" 1726 | }, 1727 | "sensors.register_0": { 1728 | "prefix": "sensors.register", 1729 | "body": ["sensors.register($1, $2, $3)"], 1730 | "description": "注册一个传感器监听并返回[SensorEventEmitter](#sensors_sensoreventemitter)。\n\n例如:\n\n console.show();\n //注册传感器监听\n var sensor = sensors.register(\"gravity\");\n if(sensor == null){\n toast(\"不支持重力传感器\");\n exit();\n }\n //监听数据\n sensor.on(\"change\", (gx, gy, gz)=>{\n log(\"重力加速度: %d, %d, %d\", gx, gy, gz);\n });\n \n\n可以通过delay参数来指定传感器数据的更新频率,例如:\n\n var sensor = sensors.register(\"gravity\", sensors.delay.game);\n \n\n另外,如果不支持`sensorName`所指定的传感器,那么该函数将返回`null`;但如果`sensors.ignoresUnsupportedSensor`的值被设置为`true`, 则该函数会返回一个不会分发任何传感器事件的[SensorEventEmitter](#sensors_sensoreventemitter)。\n\n例如:\n\n sensors.ignoresUnsupportedSensor = true;\n //无需null判断\n sensors.register(\"gravity\").on(\"change\", (gx, gy, gz)=>{\n log(\"重力加速度: %d, %d, %d\", gx, gy, gz);\n });\n \n\n更多信息,参见[SensorEventEmitter](#sensors_sensoreventemitter)和[sensors.ignoresUnsupportedSensor](#sensors_sensors_ignoresUnsupportedSensor)。" 1731 | }, 1732 | "sensors.unregister_1": { 1733 | "prefix": "sensors.unregister", 1734 | "body": ["sensors.unregister($1)"], 1735 | "description": "注销该传感器监听器。被注销的监听器将不再能监听传感器数据。\n\n //注册一个传感器监听器\n var sensor = sensors.register(\"gravity\");\n if(sensor == null){\n exit();\n }\n //2秒后注销该监听器\n setTimeout(()=> {\n sensors.unregister(sensor);\n }, 2000);" 1736 | }, 1737 | "sensors.unregisterAll_2": { 1738 | "prefix": "sensors.unregisterAll", 1739 | "body": ["sensors.unregisterAll()"], 1740 | "description": "注销所有传感器监听器。" 1741 | }, 1742 | "sensors.ignoresUnsupportedSensor_3": { 1743 | "prefix": "sensors.ignoresUnsupportedSensor", 1744 | "body": ["sensors.ignoresUnsupportedSensor"], 1745 | "description": "表示是否忽略不支持的传感器。如果该值被设置为`true`,则函数`sensors.register()`即使对不支持的传感器也会返回一个无任何数据的虚拟传感器监听,也就是`sensors.register()`不会返回`null`从而避免非空判断,并且此时会触发`sensors`的\"unsupported\\_sensor\"事件。\n\n //忽略不支持的传感器\n sensors.ignoresUnsupportedSensor = true;\n //监听有不支持的传感器时的事件\n sensors.on(\"unsupported_sensor\", function(sensorName){\n toastLog(\"不支持的传感器: \" + sensorName);\n });\n //随便注册一个不存在的传感器。\n log(sensors.register(\"aaabbb\"));" 1746 | }, 1747 | "storages.create_0": { 1748 | "prefix": "storages.create", 1749 | "body": ["storages.create($1)"], 1750 | "description": "创建一个本地存储并返回一个`Storage`对象。不同名称的本地存储的数据是隔开的,而相同名称的本地存储的数据是共享的。\n\n例如在一个脚本中,创建名称为ABC的存储并存入a=123:\n\n var storage = storages.create(\"ABC\");\n storage.put(\"a\", 123);\n \n\n而在另一个脚本中是可以获取到ABC以及a的值的:\n\n var storage = storages.create(\"ABC\");\n log(\"a = \" + storage.get(\"a\"));\n \n\n因此,本地存储的名称比较重要,尽量使用含有域名、作者邮箱等唯一信息的名称来避免冲突,例如:\n\n var storage = storages.create(\"123@qq.com:ABC\");" 1751 | }, 1752 | "storages.remove_1": { 1753 | "prefix": "storages.remove", 1754 | "body": ["storages.remove($1)"], 1755 | "description": "删除一个本地存储以及他的全部数据。如果该存储不存在,返回false;否则返回true。" 1756 | }, 1757 | "storages.get_2": { 1758 | "prefix": "storages.get", 1759 | "body": ["storages.get($1, $2)"], 1760 | "description": "从本地存储中取出键值为key的数据并返回。\n\n如果该存储中不包含该数据,这时若指定了默认值参数则返回默认值,否则返回undefined。\n\n返回的数据可能是任意数据类型,这取决于使用`Storage.put`保存该键值的数据时的数据类型。" 1761 | }, 1762 | "storages.put_3": { 1763 | "prefix": "storages.put", 1764 | "body": ["storages.put($1, $2)"], 1765 | "description": "把值value保存到本地存储中。value可以是undefined以外的任意数据类型。如果value为undefined则抛出TypeError。\n\n存储的过程实际上是使用JSON.stringify把value转换为字符串再保存,因此value必须是可JSON化的才能被接受。" 1766 | }, 1767 | "storages.remove_4": { 1768 | "prefix": "storages.remove", 1769 | "body": ["storages.remove($1)"], 1770 | "description": "移除键值为key的数据。不返回任何值。" 1771 | }, 1772 | "storages.contains_5": { 1773 | "prefix": "storages.contains", 1774 | "body": ["storages.contains($1)"], 1775 | "description": "返回该本地存储是否包含键值为key的数据。是则返回true,否则返回false。" 1776 | }, 1777 | "storages.clear_6": { 1778 | "prefix": "storages.clear", 1779 | "body": ["storages.clear()"], 1780 | "description": "移除该本地存储的所有数据。不返回任何值。" 1781 | }, 1782 | "threads.start_0": { 1783 | "prefix": "threads.start", 1784 | "body": ["threads.start($1, $2)"], 1785 | "description": "启动一个新线程并执行action。\n\n例如:\n\n threads.start(function(){\n //在新线程执行的代码\n while(true){\n log(\"子线程\");\n }\n });\n while(true){\n log(\"脚本主线程\");\n }\n \n\n通过该函数返回的[Thread](#threads_thread)对象可以获取该线程的状态,控制该线程的运行中。例如:\n\n var thread = threads.start(function(){\n while(true){\n log(\"子线程\");\n }\n });\n //停止线程执行\n thread.interrupt();\n \n\n更多信息参见[Thread](#threads_thread)。" 1786 | }, 1787 | "threads.shutDownAll_1": { 1788 | "prefix": "threads.shutDownAll", 1789 | "body": ["threads.shutDownAll()"], 1790 | "description": "停止所有通过`threads.start()`启动的子线程。" 1791 | }, 1792 | "threads.currentThread_2": { 1793 | "prefix": "threads.currentThread", 1794 | "body": ["threads.currentThread($1)"], 1795 | "description": "返回当前线程。" 1796 | }, 1797 | "threads.disposable_3": { 1798 | "prefix": "threads.disposable", 1799 | "body": ["threads.disposable($1)"], 1800 | "description": "新建一个Disposable对象,用于等待另一个线程的某个一次性结果。更多信息参见[线程通信](#threads_线程通信)以及[Disposable](#threads_disposable)。" 1801 | }, 1802 | "threads.atomic_4": { 1803 | "prefix": "threads.atomic", 1804 | "body": ["threads.atomic($1, $2)"], 1805 | "description": "新建一个整数原子变量。更多信息参见[线程安全](#threads_线程安全)以及[AtomicLong](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html)。" 1806 | }, 1807 | "threads.lock_5": { 1808 | "prefix": "threads.lock", 1809 | "body": ["threads.lock($1)"], 1810 | "description": "新建一个可重入锁。更多信息参见[线程安全](#threads_线程安全)以及[ReentrantLock](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantLock.html)。" 1811 | }, 1812 | "thread.interrupt_6": { 1813 | "prefix": "thread.interrupt", 1814 | "body": ["thread.interrupt()"], 1815 | "description": "中断线程运行。" 1816 | }, 1817 | "thread.join_7": { 1818 | "prefix": "thread.join", 1819 | "body": ["thread.join($1)"], 1820 | "description": "等待线程执行完成。如果timeout为0,则会一直等待直至该线程执行完成;否则最多等待timeout毫秒的时间。\n\n例如:\n\n var sum = 0;\n //启动子线程计算1加到10000\n var thread = threads.start(function(){\n for(var i = 0; i < 10000; i++){\n sum += i;\n }\n });\n //等待该线程完成\n thread.join();\n toast(\"sum = \" + sum);" 1821 | }, 1822 | "thread.isAlive_8": { 1823 | "prefix": "thread.isAlive", 1824 | "body": ["thread.isAlive($1)"], 1825 | "description": "返回线程是否存活。如果线程仍未开始或已经结束,返回`false`; 如果线程已经开始或者正在运行中,返回`true`。" 1826 | }, 1827 | "thread.waitFor_9": { 1828 | "prefix": "thread.waitFor", 1829 | "body": ["thread.waitFor()"], 1830 | "description": "等待线程开始执行。调用`threads.start()`以后线程仍然需要一定时间才能开始执行,因此调用此函数会等待线程开始执行;如果线程已经处于执行状态则立即返回。\n\n var thread = threads.start(function(){\n //do something\n });\n thread.waitFor();\n thread.setTimeout(function(){\n //do something\n }, 1000);" 1831 | }, 1832 | "thread.setTimeout_10": { 1833 | "prefix": "thread.setTimeout", 1834 | "body": ["thread.setTimeout($1, $2, $3)"], 1835 | "description": "参见[timers.setTimeout()](timers.html#timers_settimeout_callback_delay_args)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。\n\n log(\"当前线程(主线程):\" + threads.currentThread());\n \n var thread = threads.start(function(){\n //设置一个空的定时来保持线程的运行状态\n setInterval(function(){}, 1000);\n });\n \n sleep(1000);\n thread.setTimeout(function(){\n log(\"当前线程(子线程):\" + threads.currentThread());\n exit();\n }, 1000);" 1836 | }, 1837 | "thread.setInterval_11": { 1838 | "prefix": "thread.setInterval", 1839 | "body": ["thread.setInterval($1, $2, $3)"], 1840 | "description": "参见[timers.setInterval()](timers.html#timers_setinterval_callback_delay_args)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。" 1841 | }, 1842 | "thread.setImmediate_12": { 1843 | "prefix": "thread.setImmediate", 1844 | "body": ["thread.setImmediate($1, $2)"], 1845 | "description": "参见[timers.setImmediate()](timers.html#timers_setimmediate_callback_delay_args)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。" 1846 | }, 1847 | "thread.clearInterval_13": { 1848 | "prefix": "thread.clearInterval", 1849 | "body": ["thread.clearInterval($1)"], 1850 | "description": "参见[timers.clearInterval()](timers.html#timers_clearinterval_id)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。" 1851 | }, 1852 | "thread.clearTimeout_14": { 1853 | "prefix": "thread.clearTimeout", 1854 | "body": ["thread.clearTimeout($1)"], 1855 | "description": "参见[timers.clearTimeout()](timers.html#timers_cleartimeout_id)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。" 1856 | }, 1857 | "thread.clearImmediate_15": { 1858 | "prefix": "thread.clearImmediate", 1859 | "body": ["thread.clearImmediate($1)"], 1860 | "description": "参见[timers.clearImmediate()](timers.html#timers_clearimmediate_id)。\n\n区别在于, 该定时器会在该线程执行。如果当前线程仍未开始执行或已经执行结束,则抛出`IllegalStateException`。" 1861 | }, 1862 | "线程安全.sync_16": { 1863 | "prefix": "线程安全.sync", 1864 | "body": ["线程安全.sync($1, $2)"], 1865 | "description": "给函数func加上同步锁并作为一个新函数返回。\n\n var i = 0;\n function add(x){\n i += x;\n }\n \n var syncAdd = sync(add);\n syncAdd(10);\n toast(i);" 1866 | }, 1867 | "timers.setInterval_0": { 1868 | "prefix": "timers.setInterval", 1869 | "body": ["timers.setInterval($1, $2, $3)"], 1870 | "description": "预定每隔 delay 毫秒重复执行的 callback。 返回一个用于 clearInterval() 的 id。\n\n当 delay 小于 0 时,delay 会被设为 0。" 1871 | }, 1872 | "timers.setTimeout_1": { 1873 | "prefix": "timers.setTimeout", 1874 | "body": ["timers.setTimeout($1, $2, $3)"], 1875 | "description": "预定在 delay 毫秒之后执行的单次 callback。 返回一个用于 clearTimeout() 的 id。\n\ncallback 可能不会精确地在 delay 毫秒被调用。 Hamibot 不能保证回调被触发的确切时间,也不能保证它们的顺序。 回调会在尽可能接近所指定的时间上调用。\n\n当 delay 小于 0 时,delay 会被设为 0。" 1876 | }, 1877 | "timers.setImmediate_2": { 1878 | "prefix": "timers.setImmediate", 1879 | "body": ["timers.setImmediate($1, $2)"], 1880 | "description": "预定立即执行的 callback,它是在 I/O 事件的回调之后被触发。 返回一个用于 clearImmediate() 的 id。\n\n当多次调用 setImmediate() 时,callback 函数会按照它们被创建的顺序依次执行。 每次事件循环迭代都会处理整个回调队列。 如果一个立即定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。\n\nsetImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。" 1881 | }, 1882 | "timers.clearInterval_3": { 1883 | "prefix": "timers.clearInterval", 1884 | "body": ["timers.clearInterval($1)"], 1885 | "description": "取消一个由 setInterval() 创建的循环定时任务。\n\n例如:\n\n //每5秒就发出一次hello\n var id = setInterval(function(){\n toast(\"hello\");\n }, 5000);\n //1分钟后取消循环\n setTimeout(function(){\n clearInterval(id);\n }, 60 * 1000);" 1886 | }, 1887 | "timers.clearTimeout_4": { 1888 | "prefix": "timers.clearTimeout", 1889 | "body": ["timers.clearTimeout($1)"], 1890 | "description": "取消一个由 setTimeout() 创建的定时任务。" 1891 | }, 1892 | "timers.clearImmediate_5": { 1893 | "prefix": "timers.clearImmediate", 1894 | "body": ["timers.clearImmediate($1)"], 1895 | "description": "取消一个由 setImmediate() 创建的 Immediate 对象。" 1896 | }, 1897 | "Tab: tab.statusBarColor_0": { 1898 | "prefix": "Tab: tab.statusBarColor", 1899 | "body": ["Tab: tab.statusBarColor($1, $2)"], 1900 | "description": "" 1901 | }, 1902 | "Tab: tab.showPopupMenu_1": { 1903 | "prefix": "Tab: tab.showPopupMenu", 1904 | "body": ["Tab: tab.showPopupMenu($1, $2)"], 1905 | "description": "" 1906 | }, 1907 | "ui.statusBarColor_2": { 1908 | "prefix": "ui.statusBarColor", 1909 | "body": ["ui.statusBarColor($1, $2)"], 1910 | "description": "" 1911 | }, 1912 | "ui.showPopupMenu_3": { 1913 | "prefix": "ui.showPopupMenu", 1914 | "body": ["ui.showPopupMenu($1, $2)"], 1915 | "description": "" 1916 | }, 1917 | "util.callbackify_0": { 1918 | "prefix": "util.callbackify", 1919 | "body": ["util.callbackify($1)"], 1920 | "description": "Takes an `async` function (or a function that returns a Promise) and returns a function following the Node.js error first callback style. In the callback, the first argument will be the rejection reason (or `null` if the Promise resolved), and the second argument will be the resolved value.\n\nFor example:\n\n const util = require('util');\n \n async function fn() {\n return await Promise.resolve('hello world');\n }\n const callbackFunction = util.callbackify(fn);\n \n callbackFunction((err, ret) => {\n if (err) throw err;\n console.log(ret);\n });\n \n\nWill print:\n\n hello world\n \n\n_Note_:\n\n* The callback is executed asynchronously, and will have a limited stack trace. If the callback throws, the process will emit an [`'uncaughtException'`](process.html#process_event_uncaughtexception) event, and if not handled will exit.\n \n* Since `null` has a special meaning as the first argument to a callback, if a wrapped function rejects a `Promise` with a falsy value as a reason, the value is wrapped in an `Error` with the original value stored in a field named `reason`.\n \n function fn() {\n return Promise.reject(null);\n }\n const callbackFunction = util.callbackify(fn);\n \n callbackFunction((err, ret) => {\n // When the Promise was rejected with `null` it is wrapped with an Error and\n // the original value is stored in `reason`.\n err && err.hasOwnProperty('reason') && err.reason === null; // true\n });" 1921 | }, 1922 | "util.debuglog_1": { 1923 | "prefix": "util.debuglog", 1924 | "body": ["util.debuglog($1)"], 1925 | "description": "The `util.debuglog()` method is used to create a function that conditionally writes debug messages to `stderr` based on the existence of the `NODE_DEBUG` environment variable. If the `section` name appears within the value of that environment variable, then the returned function operates similar to [`console.error()`](console.html#console_console_error_data_args). If not, then the returned function is a no-op.\n\nFor example:\n\n const util = require('util');\n const debuglog = util.debuglog('foo');\n \n debuglog('hello from foo [%d]', 123);\n \n\nIf this program is run with `NODE_DEBUG=foo` in the environment, then it will output something like:\n\n FOO 3245: hello from foo [123]\n \n\nwhere `3245` is the process id. If it is not run with that environment variable set, then it will not print anything.\n\nMultiple comma-separated `section` names may be specified in the `NODE_DEBUG` environment variable. For example: `NODE_DEBUG=fs,net,tls`." 1926 | }, 1927 | "util.deprecate_2": { 1928 | "prefix": "util.deprecate", 1929 | "body": ["util.deprecate($1, $2)"], 1930 | "description": "The `util.deprecate()` method wraps the given `function` or class in such a way that it is marked as deprecated.\n\n const util = require('util');\n \n exports.puts = util.deprecate(function() {\n for (let i = 0, len = arguments.length; i < len; ++i) {\n process.stdout.write(arguments[i] + '\\n');\n }\n }, 'util.puts: Use console.log instead');\n \n\nWhen called, `util.deprecate()` will return a function that will emit a `DeprecationWarning` using the `process.on('warning')` event. By default, this warning will be emitted and printed to `stderr` exactly once, the first time it is called. After the warning is emitted, the wrapped `function` is called.\n\nIf either the `--no-deprecation` or `--no-warnings` command line flags are used, or if the `process.noDeprecation` property is set to `true` _prior_ to the first deprecation warning, the `util.deprecate()` method does nothing.\n\nIf the `--trace-deprecation` or `--trace-warnings` command line flags are set, or the `process.traceDeprecation` property is set to `true`, a warning and a stack trace are printed to `stderr` the first time the deprecated function is called.\n\nIf the `--throw-deprecation` command line flag is set, or the `process.throwDeprecation` property is set to `true`, then an exception will be thrown when the deprecated function is called.\n\nThe `--throw-deprecation` command line flag and `process.throwDeprecation` property take precedence over `--trace-deprecation` and `process.traceDeprecation`." 1931 | }, 1932 | "util.format_3": { 1933 | "prefix": "util.format", 1934 | "body": ["util.format($1, $2)"], 1935 | "description": "The `util.format()` method returns a formatted string using the first argument as a `printf`\\-like format.\n\nThe first argument is a string containing zero or more _placeholder_ tokens. Each placeholder token is replaced with the converted value from the corresponding argument. Supported placeholders are:\n\n* `%s` - String.\n* `%d` - Number (integer or floating point value).\n* `%i` - Integer.\n* `%f` - Floating point value.\n* `%j` - JSON. Replaced with the string `'[Circular]'` if the argument contains circular references.\n* `%o` - Object. A string representation of an object with generic JavaScript object formatting. Similar to `util.inspect()` with options `{ showHidden: true, depth: 4, showProxy: true }`. This will show the full object including non-enumerable symbols and properties.\n* `%O` - Object. A string representation of an object with generic JavaScript object formatting. Similar to `util.inspect()` without options. This will show the full object not including non-enumerable symbols and properties.\n* `%%` - single percent sign (`'%'`). This does not consume an argument.\n\nIf the placeholder does not have a corresponding argument, the placeholder is not replaced.\n\n util.format('%s:%s', 'foo');\n // Returns: 'foo:%s'\n \n\nIf there are more arguments passed to the `util.format()` method than the number of placeholders, the extra arguments are coerced into strings then concatenated to the returned string, each delimited by a space. Excessive arguments whose `typeof` is `'object'` or `'symbol'` (except `null`) will be transformed by `util.inspect()`.\n\n util.format('%s:%s', 'foo', 'bar', 'baz'); // 'foo:bar baz'\n \n\nIf the first argument is not a string then `util.format()` returns a string that is the concatenation of all arguments separated by spaces. Each argument is converted to a string using `util.inspect()`.\n\n util.format(1, 2, 3); // '1 2 3'\n \n\nIf only one argument is passed to `util.format()`, it is returned as it is without any formatting.\n\n util.format('%% %s'); // '%% %s'" 1936 | }, 1937 | "util.inherits_4": { 1938 | "prefix": "util.inherits", 1939 | "body": ["util.inherits($1, $2)"], 1940 | "description": "_Note_: Usage of `util.inherits()` is discouraged. Please use the ES6 `class` and `extends` keywords to get language level inheritance support. Also note that the two styles are [semantically incompatible](https://github.com/nodejs/node/issues/4179).\n\n* `constructor` {Function}\n* `superConstructor` {Function}\n\nInherit the prototype methods from one [constructor](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor) into another. The prototype of `constructor` will be set to a new object created from `superConstructor`.\n\nAs an additional convenience, `superConstructor` will be accessible through the `constructor.super_` property.\n\n const util = require('util');\n const EventEmitter = require('events');\n \n function MyStream() {\n EventEmitter.call(this);\n }\n \n util.inherits(MyStream, EventEmitter);\n \n MyStream.prototype.write = function(data) {\n this.emit('data', data);\n };\n \n const stream = new MyStream();\n \n console.log(stream instanceof EventEmitter); // true\n console.log(MyStream.super_ === EventEmitter); // true\n \n stream.on('data', (data) => {\n console.log(`Received data: \"${data}\"`);\n });\n stream.write('It works!'); // Received data: \"It works!\"\n \n\nES6 example using `class` and `extends`\n\n const EventEmitter = require('events');\n \n class MyStream extends EventEmitter {\n write(data) {\n this.emit('data', data);\n }\n }\n \n const stream = new MyStream();\n \n stream.on('data', (data) => {\n console.log(`Received data: \"${data}\"`);\n });\n stream.write('With ES6');" 1941 | }, 1942 | "util.inspect_5": { 1943 | "prefix": "util.inspect", 1944 | "body": ["util.inspect($1, $2)"], 1945 | "description": "The `util.inspect()` method returns a string representation of `object` that is primarily useful for debugging. Additional `options` may be passed that alter certain aspects of the formatted string.\n\nThe following example inspects all properties of the `util` object:\n\n const util = require('util');\n \n console.log(util.inspect(util, { showHidden: true, depth: null }));\n \n\nValues may supply their own custom `inspect(depth, opts)` functions, when called these receive the current `depth` in the recursive inspection, as well as the options object passed to `util.inspect()`." 1946 | }, 1947 | "util.promisify_6": { 1948 | "prefix": "util.promisify", 1949 | "body": ["util.promisify($1)"], 1950 | "description": "Takes a function following the common Node.js callback style, i.e. taking a `(err, value) => ...` callback as the last argument, and returns a version that returns promises.\n\nFor example:\n\n const util = require('util');\n const fs = require('fs');\n \n const stat = util.promisify(fs.stat);\n stat('.').then((stats) => {\n // Do something with `stats`\n }).catch((error) => {\n // Handle the error.\n });\n \n\nOr, equivalently using `async function`s:\n\n const util = require('util');\n const fs = require('fs');\n \n const stat = util.promisify(fs.stat);\n \n async function callStat() {\n const stats = await stat('.');\n console.log(`This directory is owned by ${stats.uid}`);\n }\n \n\nIf there is an `original[util.promisify.custom]` property present, `promisify` will return its value, see [Custom promisified functions](#util_custom_promisified_functions).\n\n`promisify()` assumes that `original` is a function taking a callback as its final argument in all cases, and the returned function will result in undefined behavior if it does not." 1951 | }, 1952 | "auto_0": { 1953 | "prefix": "auto", 1954 | "body": ["auto($1)"], 1955 | "description": "检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为mode。mode的可选值为:\n\n* `fast` 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。\n* `normal` 正常模式,默认。\n\n如果不加mode参数,则为正常模式。\n\n建议使用`auto.waitFor()`和`auto.setMode()`代替该函数,因为`auto()`函数如果无障碍服务未启动会停止脚本;而`auto.waitFor()`则会在在无障碍服务启动后继续运行。\n\n示例:\n\n auto(\"fast\");\n \n\n示例2:\n\n auto();" 1956 | }, 1957 | "waitFor_1": { 1958 | "prefix": "waitFor", 1959 | "body": ["waitFor()"], 1960 | "description": "检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。\n\n因为该函数是阻塞的,因此除非是有协程特性,否则不能在ui模式下运行该函数,建议在ui模式下使用`auto()`函数。" 1961 | }, 1962 | "setMode_2": { 1963 | "prefix": "setMode", 1964 | "body": ["setMode($1)"], 1965 | "description": "设置无障碍模式为mode。mode的可选值为:\n\n* `fast` 快速模式。该模式下会启用控件缓存,从而选择器获取屏幕控件更快。对于需要快速的控件查看和操作的脚本可以使用该模式,一般脚本则没有必要使用该函数。\n* `normal` 正常模式,默认。" 1966 | }, 1967 | "setFlags_3": { 1968 | "prefix": "setFlags", 1969 | "body": ["setFlags($1)"], 1970 | "description": "**\\[v4.1.0新增\\]**\n\n* `flags` {string} | {Array} 一些标志,来启用和禁用某些特性,包括:\n * `findOnUiThread` 使用该特性后,选择器搜索时会在主进程进行。该特性用于解决线程安全问题导致的次生问题,不过目前貌似已知问题并不是线程安全问题。\n * `useUsageStats` 使用该特性后,将会以\"使用情况统计\"服务的结果来检测当前正在运行的应用包名(需要授予\"查看使用情况统计\"权限)。如果觉得currentPackage()返回的结果不太准确,可以尝试该特性。\n * `useShell` 使用该特性后,将使用shell命令获取当前正在运行的应用的包名、活动名称,但是需要root权限。\n\n启用有关automator的一些特性。例如:\n\n auto.setFlags([\"findOnUiThread\", \"useShell\"]);" 1971 | }, 1972 | "setWindowFilter_4": { 1973 | "prefix": "setWindowFilter", 1974 | "body": ["setWindowFilter($1)"], 1975 | "description": "**\\[v4.1.0新增\\]**\n\n* `filter` {Function} 参数为窗口([AccessibilityWindowInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityWindowInfo)),返回值为Boolean的函数。\n\n设置窗口过滤器。这个过滤器可以决定哪些窗口是目标窗口,并影响选择器的搜索。例如,如果想要选择器在所有窗口(包括状态栏、输入法等)中搜索,只需要使用以下代码:\n\n auto.setWindowFilter(function(window){\n //不管是如何窗口,都返回true,表示在该窗口中搜索\n return true;\n });\n \n\n又例如,当前使用了分屏功能,屏幕上有 Hamibot 和QQ两个应用,但我们只想选择器对QQ界面进行搜索,则:\n\n auto.setWindowFilter(function(window){\n // 对于应用窗口,他的title属性就是应用的名称,因此可以通过title属性来判断一个应用\n return window.title == \"QQ\";\n });\n \n\n选择器默认是在当前活跃的窗口中搜索,不会搜索诸如悬浮窗、状态栏之类的,使用WindowFilter则可以控制搜索的窗口。\n\n需要注意的是, 如果WindowFilter返回的结果均为false,则选择器的搜索结果将为空。\n\n另外setWindowFilter函数也会影响`auto.windowRoots`的结果。\n\n该函数需要Android 5.0以上才有效。" 1976 | }, 1977 | "serivce_5": { 1978 | "prefix": "serivce", 1979 | "body": ["serivce"], 1980 | "description": "**\\[v4.1.0新增\\]**\n\n* [AccessibilityService](https://developer.android.com/reference/android/accessibilityservice/AccessibilityService)\n\n获取无障碍服务。如果无障碍服务没有启动,则返回`null`。\n\n参见[AccessibilityService](https://developer.android.com/reference/android/accessibilityservice/AccessibilityService)。" 1981 | }, 1982 | "windows_6": { 1983 | "prefix": "windows", 1984 | "body": ["windows"], 1985 | "description": "**\\[v4.1.0新增\\]**\n\n* {Array}\n\n当前所有窗口([AccessibilityWindowInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityWindowInfo))的数组,可能包括状态栏、输入法、当前应用窗口,弹出窗口、悬浮窗、分屏应用窗口等。可以分别获取每个窗口的布局信息。\n\n该函数需要Android 5.0以上才能运行。" 1986 | }, 1987 | "root_7": { 1988 | "prefix": "root", 1989 | "body": ["root"], 1990 | "description": "**\\[v4.1.0新增\\]**\n\n* {UiObject}\n\n当前窗口的布局根元素。如果无障碍服务未启动或者WindowFilter均返回false,则会返回`null`。\n\n如果不设置windowFilter,则当前窗口即为活跃的窗口(获取到焦点、正在触摸的窗口);如果设置了windowFilter,则获取的是过滤的窗口中的第一个窗口。\n\n如果系统是Android5.0以下,则始终返回当前活跃的窗口的布局根元素。" 1991 | }, 1992 | "rootInActiveWindow_8": { 1993 | "prefix": "rootInActiveWindow", 1994 | "body": ["rootInActiveWindow"], 1995 | "description": "**\\[v4.1.0新增\\]**\n\n* {UiObject}\n\n当前活跃的窗口(获取到焦点、正在触摸的窗口)的布局根元素。如果无障碍服务未启动则为`null`。" 1996 | }, 1997 | "windowRoots_9": { 1998 | "prefix": "windowRoots", 1999 | "body": ["windowRoots"], 2000 | "description": "**\\[v4.1.0新增\\]**\n\n* {Array}\n\n返回当前被WindowFilter过滤的窗口的布局根元素组成的数组。\n\n如果系统是Android5.0以下,则始终返回当前活跃的窗口的布局根元素的数组。" 2001 | }, 2002 | "simpleactionautomator.click_10": { 2003 | "prefix": "simpleactionautomator.click", 2004 | "body": ["simpleactionautomator.click($1, $2)"], 2005 | "description": "返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。\n\n该函数可以点击大部分包含文字的按钮。例如微信主界面下方的\"微信\", \"联系人\", \"发现\", \"我\"的按钮。 \n通常与while同时使用以便点击按钮直至成功。例如:\n\n while(!click(\"扫一扫\"));\n \n\n当不指定参数i时则会尝试点击屏幕上出现的所有文字text并返回是否全部点击成功。\n\ni是从0开始计算的, 也就是, `click(\"啦啦啦\", 0)`表示点击屏幕上第一个\"啦啦啦\", `click(\"啦啦啦\", 1)`表示点击屏幕上第二个\"啦啦啦\"。\n\n> 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。" 2006 | }, 2007 | "simpleactionautomator.click_11": { 2008 | "prefix": "simpleactionautomator.click", 2009 | "body": ["simpleactionautomator.click($1, $2, $3, $4)"], 2010 | "description": "**注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般不要使用该函数。**\n\n点击在指定区域的控件。当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。\n\n有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text, i)`来点击,可以通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。\n\n至于要定位点击的区域,可以在悬浮窗使用布局分析工具查看控件的bounds属性。\n\n通过无障碍服务录制脚本会生成该语句。" 2011 | }, 2012 | "simpleactionautomator.scrollUp_12": { 2013 | "prefix": "simpleactionautomator.scrollUp", 2014 | "body": ["simpleactionautomator.scrollUp($1)"], 2015 | "description": "找到第i+1个可滑动控件上滑或**左滑**。返回是否操作成功。屏幕上没有可滑动的控件时返回false。\n\n另外不加参数时`scrollUp()`会寻找面积最大的可滑动的控件上滑或左滑,例如微信消息列表等。\n\n参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0)`为滑动第一个可滑动控件。" 2016 | }, 2017 | "simpleactionautomator.scrollDown_13": { 2018 | "prefix": "simpleactionautomator.scrollDown", 2019 | "body": ["simpleactionautomator.scrollDown($1)"], 2020 | "description": "找到第i+1个可滑动控件下滑或**右滑**。返回是否操作成功。屏幕上没有可滑动的控件时返回false。\n\n另外不加参数时`scrollUp()`会寻找面积最大的可滑动的控件下滑或右滑。\n\n参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0)`为滑动第一个可滑动控件。" 2021 | }, 2022 | "simpleactionautomator.setText_14": { 2023 | "prefix": "simpleactionautomator.setText", 2024 | "body": ["simpleactionautomator.setText($1, $2)"], 2025 | "description": "返回是否输入成功。当找不到对应的文本框时返回false。\n\n不加参数i则会把所有输入框的文本都置为text。例如`setText(\"测试\")`。\n\n这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。" 2026 | }, 2027 | "simpleactionautomator.input_15": { 2028 | "prefix": "simpleactionautomator.input", 2029 | "body": ["simpleactionautomator.input($1, $2)"], 2030 | "description": "返回是否输入成功。当找不到对应的文本框时返回false。\n\n不加参数i则会把所有输入框的文本追加内容text。例如`input(\"测试\")`。" 2031 | }, 2032 | "uiselector.selector_16": { 2033 | "prefix": "uiselector.selector", 2034 | "body": ["uiselector.selector($1)"], 2035 | "description": "创建一个新的选择器。但一般情况不需要使用该函数,因为可以直接用相应条件的语句创建选择器。\n\n由于历史遗留原因,本不应该这样设计(不应该让`id()`, `text()`等作为全局函数,而是应该用`By.id()`, `By.text()`),但为了后向兼容性只能保留这个设计。\n\n这样的API设计会污染全局变量,后续可能会支持\"去掉这些全局函数而使用By._\\*_\"的选项。" 2036 | }, 2037 | "uiselector.algorithm_17": { 2038 | "prefix": "uiselector.algorithm", 2039 | "body": ["uiselector.algorithm($1)"], 2040 | "description": "**\\[v4.1.0新增\\]**\n\n* `algorithm` {string} 搜索算法,可选的值有:\n * `DFS` 深度优先算法,选择器的默认算法\n * `BFS` 广度优先算法\n\n指定选择器的搜索算法。例如:\n\n log(selector().text(\"文本\").algorithm(\"BFS\").find());\n \n\n广度优先在控件所在层次较低时,或者布局的层次不多时,通常能更快找到控件。" 2041 | }, 2042 | "uiselector.text_18": { 2043 | "prefix": "uiselector.text", 2044 | "body": ["uiselector.text($1, $2)"], 2045 | "description": "为当前选择器附加控件\"text等于字符串str\"的筛选条件。\n\n控件的text(文本)属性是文本控件上的显示的文字,例如微信左上角的\"微信\"文本。" 2046 | }, 2047 | "uiselector.textContains_19": { 2048 | "prefix": "uiselector.textContains", 2049 | "body": ["uiselector.textContains($1)"], 2050 | "description": "为当前选择器附加控件\"text需要包含字符串str\"的筛选条件。\n\n这是一个比较有用的条件,例如QQ动态页和微博发现页上方的\"大家都在搜....\"的控件可以用`textContains(\"大家都在搜\").findOne()`来获取。" 2051 | }, 2052 | "uiselector.textStartsWith_20": { 2053 | "prefix": "uiselector.textStartsWith", 2054 | "body": ["uiselector.textStartsWith($1)"], 2055 | "description": "为当前选择器附加控件\"text需要以prefix开头\"的筛选条件。\n\n这也是一个比较有用的条件,例如要找出 Hamibot 脚本列表中名称以\"QQ\"开头的脚本的代码为`textStartsWith(\"QQ\").find()`。" 2056 | }, 2057 | "uiselector.textEndsWith_21": { 2058 | "prefix": "uiselector.textEndsWith", 2059 | "body": ["uiselector.textEndsWith($1)"], 2060 | "description": "为当前选择器附加控件\"text需要以suffix结束\"的筛选条件。" 2061 | }, 2062 | "uiselector.textMatches_22": { 2063 | "prefix": "uiselector.textMatches", 2064 | "body": ["uiselector.textMatches($1)"], 2065 | "description": "为当前选择器附加控件\"text需要满足正则表达式reg\"的条件。\n\n有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。\n\n需要注意的是,如果正则表达式是字符串,则需要使用`\\\\`来表达`\\`(也即Java正则表达式的形式),例如`textMatches(\"\\\\d+\")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\\d+/)`。但如果使用字符串的正则表达式则该字符串不能以\"/\"同时以\"/\"结束,也即不能写诸如`textMatches(\"/\\\\d+/\")`的表达式,否则会被开头的\"/\"和结尾的\"/\"会被忽略。" 2066 | }, 2067 | "uiselector.desc_23": { 2068 | "prefix": "uiselector.desc", 2069 | "body": ["uiselector.desc($1, $2)"], 2070 | "description": "为当前选择器附加控件\"desc等于字符串str\"的筛选条件。\n\n控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。\n\ndesc属性同样是定位控件的利器。" 2071 | }, 2072 | "uiselector.descContains_24": { 2073 | "prefix": "uiselector.descContains", 2074 | "body": ["uiselector.descContains($1)"], 2075 | "description": "为当前选择器附加控件\"desc需要包含字符串str\"的筛选条件。" 2076 | }, 2077 | "uiselector.descStartsWith_25": { 2078 | "prefix": "uiselector.descStartsWith", 2079 | "body": ["uiselector.descStartsWith($1)"], 2080 | "description": "为当前选择器附加控件\"desc需要以prefix开头\"的筛选条件。" 2081 | }, 2082 | "uiselector.descEndsWith_26": { 2083 | "prefix": "uiselector.descEndsWith", 2084 | "body": ["uiselector.descEndsWith($1)"], 2085 | "description": "为当前选择器附加控件\"desc需要以suffix结束\"的筛选条件。" 2086 | }, 2087 | "uiselector.descMatches_27": { 2088 | "prefix": "uiselector.descMatches", 2089 | "body": ["uiselector.descMatches($1)"], 2090 | "description": "为当前选择器附加控件\"desc需要满足正则表达式reg\"的条件。\n\n有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。\n\n需要注意的是,如果正则表达式是字符串,则需要使用`\\\\`来表达`\\`(也即Java正则表达式的形式),例如`textMatches(\"\\\\d+\")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\\d+/)`。但如果使用字符串的正则表达式则该字符串不能以\"/\"同时以\"/\"结束,也即不能写诸如`textMatches(\"/\\\\d+/\")`的表达式,否则会被开头的\"/\"和结尾的\"/\"会被忽略。" 2091 | }, 2092 | "uiselector.id_28": { 2093 | "prefix": "uiselector.id", 2094 | "body": ["uiselector.id($1)"], 2095 | "description": "为当前选择器附加\"id等于resId\"的筛选条件。\n\n控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况。例如微信的联系人列表,每个头像的id都是一样的。此时不能用id来唯一确定控件。\n\n在QQ界面经常会出现多个id为\"name\"的控件,在微信上则每个版本的id都会变化。对于这些软件而言比较难用id定位控件。" 2096 | }, 2097 | "uiselector.idContains_29": { 2098 | "prefix": "uiselector.idContains", 2099 | "body": ["uiselector.idContains($1)"], 2100 | "description": "为当前选择器附加控件\"id包含字符串str\"的筛选条件。比较少用。" 2101 | }, 2102 | "uiselector.idStartsWith_30": { 2103 | "prefix": "uiselector.idStartsWith", 2104 | "body": ["uiselector.idStartsWith($1)"], 2105 | "description": "为当前选择器附加\"id需要以prefix开头\"的筛选条件。比较少用。" 2106 | }, 2107 | "uiselector.idEndsWith_31": { 2108 | "prefix": "uiselector.idEndsWith", 2109 | "body": ["uiselector.idEndsWith($1)"], 2110 | "description": "为当前选择器附加\"id需要以suffix结束\"的筛选条件。比较少用。" 2111 | }, 2112 | "uiselector.idMatches_32": { 2113 | "prefix": "uiselector.idMatches", 2114 | "body": ["uiselector.idMatches($1)"], 2115 | "description": "附加id需要满足正则表达式。\n\n需要注意的是,如果正则表达式是字符串,则需要使用`\\\\`来表达`\\`(也即Java正则表达式的形式),例如`textMatches(\"\\\\d+\")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\\d+/)`。但如果使用字符串的正则表达式则该字符串不能以\"/\"同时以\"/\"结束,也即不能写诸如`textMatches(\"/\\\\d+/\")`的表达式,否则会被开头的\"/\"和结尾的\"/\"会被忽略。\n\n idMatches(\"[a-zA-Z]+\")" 2116 | }, 2117 | "uiselector.className_33": { 2118 | "prefix": "uiselector.className", 2119 | "body": ["uiselector.className($1, $2)"], 2120 | "description": "为当前选择器附加控件\"className等于字符串str\"的筛选条件。\n\n控件的className(类名)表示一个控件的类别,例如文本控件的类名为android.widget.TextView。\n\n如果一个控件的类名以\"android.widget.\"开头,则可以省略这部分,例如文本控件可以直接用`className(\"TextView\")`的选择器。\n\n常见控件的类名如下:\n\n* `android.widget.TextView` 文本控件\n* `android.widget.ImageView` 图片控件\n* `android.widget.Button` 按钮控件\n* `android.widget.EditText` 输入框控件\n* `android.widget.AbsListView` 列表控件\n* `android.widget.LinearLayout` 线性布局\n* `android.widget.FrameLayout` 帧布局\n* `android.widget.RelativeLayout` 相对布局\n* `android.widget.RelativeLayout` 相对布局\n* `android.support.v7.widget.RecyclerView` 通常也是列表控件" 2121 | }, 2122 | "uiselector.classNameContains_34": { 2123 | "prefix": "uiselector.classNameContains", 2124 | "body": ["uiselector.classNameContains($1)"], 2125 | "description": "为当前选择器附加控件\"className需要包含字符串str\"的筛选条件。" 2126 | }, 2127 | "uiselector.classNameStartsWith_35": { 2128 | "prefix": "uiselector.classNameStartsWith", 2129 | "body": ["uiselector.classNameStartsWith($1)"], 2130 | "description": "为当前选择器附加控件\"className需要以prefix开头\"的筛选条件。" 2131 | }, 2132 | "uiselector.classNameEndsWith_36": { 2133 | "prefix": "uiselector.classNameEndsWith", 2134 | "body": ["uiselector.classNameEndsWith($1)"], 2135 | "description": "为当前选择器附加控件\"className需要以suffix结束\"的筛选条件。" 2136 | }, 2137 | "uiselector.classNameMatches_37": { 2138 | "prefix": "uiselector.classNameMatches", 2139 | "body": ["uiselector.classNameMatches($1)"], 2140 | "description": "为当前选择器附加控件\"className需要满足正则表达式reg\"的条件。\n\n有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。\n\n需要注意的是,如果正则表达式是字符串,则需要使用`\\\\`来表达`\\`(也即Java正则表达式的形式),例如`textMatches(\"\\\\d+\")`匹配多位数字;但如果使用JavaScript语法的正则表达式则不需要,例如`textMatches(/\\d+/)`。但如果使用字符串的正则表达式则该字符串不能以\"/\"同时以\"/\"结束,也即不能写诸如`textMatches(\"/\\\\d+/\")`的表达式,否则会被开头的\"/\"和结尾的\"/\"会被忽略。" 2141 | }, 2142 | "uiselector.packageName_38": { 2143 | "prefix": "uiselector.packageName", 2144 | "body": ["uiselector.packageName($1, $2)"], 2145 | "description": "为当前选择器附加控件\"packageName等于字符串str\"的筛选条件。\n\n控件的packageName表示控件所属界面的应用包名。例如微信的包名为\"com.tencent.mm\", 那么微信界面的控件的packageName为\"com.tencent.mm\"。\n\n要查看一个应用的包名,可以用函数`app.getPackageName()`获取,例如`toast(app.getPackageName(\"微信\"))`。" 2146 | }, 2147 | "uiselector.packageNameContains_39": { 2148 | "prefix": "uiselector.packageNameContains", 2149 | "body": ["uiselector.packageNameContains($1)"], 2150 | "description": "为当前选择器附加控件\"packageName需要包含字符串str\"的筛选条件。" 2151 | }, 2152 | "uiselector.packageNameStartsWith_40": { 2153 | "prefix": "uiselector.packageNameStartsWith", 2154 | "body": ["uiselector.packageNameStartsWith($1)"], 2155 | "description": "为当前选择器附加控件\"packageName需要以prefix开头\"的筛选条件。" 2156 | }, 2157 | "uiselector.packageNameEndsWith_41": { 2158 | "prefix": "uiselector.packageNameEndsWith", 2159 | "body": ["uiselector.packageNameEndsWith($1)"], 2160 | "description": "为当前选择器附加控件\"packageName需要以suffix结束\"的筛选条件。" 2161 | }, 2162 | "uiselector.packageNameMatches_42": { 2163 | "prefix": "uiselector.packageNameMatches", 2164 | "body": ["uiselector.packageNameMatches($1)"], 2165 | "description": "为当前选择器附加控件\"packageName需要满足正则表达式reg\"的条件。\n\n有关正则表达式,可以查看[正则表达式 - 菜鸟教程](http://www.runoob.com/Stringp/Stringp-example.html)。" 2166 | }, 2167 | "uiselector.bounds_43": { 2168 | "prefix": "uiselector.bounds", 2169 | "body": ["uiselector.bounds($1, $2, $3, $4)"], 2170 | "description": "一个控件的bounds属性为这个控件在屏幕上显示的范围。我们可以用这个范围来定位这个控件。尽管用这个方法定位控件对于静态页面十分准确,却无法兼容不同分辨率的设备;同时对于列表页面等动态页面无法达到效果,因此使用不推荐该选择器。\n\n注意参数的这四个数字不能随意填写,必须精确的填写控件的四个边界才能找到该控件。例如,要点击QQ主界面的右上角加号,我们用布局分析查看该控件的属性,如下图:\n\n可以看到bounds属性为(951, 67, 1080, 196),此时使用代码`bounds(951, 67, 1080, 196).clickable().click()`即可点击该控件。" 2171 | }, 2172 | "uiselector.boundsInside_44": { 2173 | "prefix": "uiselector.boundsInside", 2174 | "body": ["uiselector.boundsInside($1, $2, $3, $4)"], 2175 | "description": "为当前选择器附加控件\"bounds需要在left, top, right, buttom构成的范围里面\"的条件。\n\n这个条件用于限制选择器在某一个区域选择控件。例如要在屏幕上半部分寻找文本控件TextView,代码为:\n\n var w = className(\"TextView\").boundsInside(0, 0, device.width, device.height / 2).findOne();\n log(w.text());\n \n\n其中我们使用了`device.width`来获取屏幕宽度,`device.height`来获取屏幕高度。" 2176 | }, 2177 | "uiselector.boundsContains_45": { 2178 | "prefix": "uiselector.boundsContains", 2179 | "body": ["uiselector.boundsContains($1, $2, $3, $4)"], 2180 | "description": "为当前选择器附加控件\"bounds需要包含left, top, right, buttom构成的范围\"的条件。\n\n这个条件用于限制控件的范围必须包含所给定的范围。例如给定一个点(500, 300), 寻找在这个点上的可点击控件的代码为:\n\n var w = boundsContains(500, 300, device.width - 500, device.height - 300).clickable().findOne();\n w.click();" 2181 | }, 2182 | "uiselector.drawingOrder_46": { 2183 | "prefix": "uiselector.drawingOrder", 2184 | "body": ["uiselector.drawingOrder($1)"], 2185 | "description": "为当前选择器附加控件\"drawingOrder等于order\"的条件。\n\ndrawingOrder为一个控件在父控件中的绘制顺序,通常可以用于区分同一层次的控件。\n\n但该属性在Android 7.0以上才能使用。" 2186 | }, 2187 | "uiselector.clickable_47": { 2188 | "prefix": "uiselector.clickable", 2189 | "body": ["uiselector.clickable($1)"], 2190 | "description": "为当前选择器附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。对于自定义控件(例如显示类名为android.view.View的控件)很多的clickable属性都为false都却能点击。\n\n需要注意的是,可以省略参数`b`而表示选择那些可以点击的控件,例如`className(\"ImageView\").clickable()`表示可以点击的图片控件的条件,`className(\"ImageView\").clickable(false)`表示不可点击的图片控件的条件。" 2191 | }, 2192 | "uiselector.longClickable_48": { 2193 | "prefix": "uiselector.longClickable", 2194 | "body": ["uiselector.longClickable($1)"], 2195 | "description": "为当前选择器附加控件是否可长按的条件。" 2196 | }, 2197 | "uiselector.checkable_49": { 2198 | "prefix": "uiselector.checkable", 2199 | "body": ["uiselector.checkable($1)"], 2200 | "description": "为当前选择器附加控件是否可勾选的条件。勾选通常是对于勾选框而言的,例如图片多选时左上角通常有一个勾选框。" 2201 | }, 2202 | "uiselector.selected_50": { 2203 | "prefix": "uiselector.selected", 2204 | "body": ["uiselector.selected($1)"], 2205 | "description": "为当前选择器附加控件是否已选中的条件。被选中指的是,例如QQ聊天界面点击下方的\"表情按钮\"时,会出现自己收藏的表情,这时\"表情按钮\"便处于选中状态,其selected属性为true。" 2206 | }, 2207 | "uiselector.enabled_51": { 2208 | "prefix": "uiselector.enabled", 2209 | "body": ["uiselector.enabled($1)"], 2210 | "description": "为当前选择器附加控件是否已启用的条件。大多数控件都是启用的状态(enabled为true),处于“禁用”状态通常是灰色并且不可点击。" 2211 | }, 2212 | "uiselector.scrollable_52": { 2213 | "prefix": "uiselector.scrollable", 2214 | "body": ["uiselector.scrollable($1)"], 2215 | "description": "为当前选择器附加控件是否可滑动的条件。滑动包括上下滑动和左右滑动。\n\n可以用这个条件来寻找可滑动控件来滑动界面。例如滑动 Hamibot 的脚本列表的代码为:\n\n className(\"android.support.v7.widget.RecyclerView\").scrollable().findOne().scrollForward();\n //或者classNameEndsWith(\"RecyclerView\").scrollable().findOne().scrollForward();" 2216 | }, 2217 | "uiselector.editable_53": { 2218 | "prefix": "uiselector.editable", 2219 | "body": ["uiselector.editable($1)"], 2220 | "description": "为当前选择器附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText),但不是所有的输入框(EditText)都可编辑。" 2221 | }, 2222 | "uiselector.multiLine_54": { 2223 | "prefix": "uiselector.multiLine", 2224 | "body": ["uiselector.multiLine($1)"], 2225 | "description": "为当前选择器附加控件是否文本或输入框控件是否是多行显示的条件。" 2226 | }, 2227 | "uiselector.findOne_55": { 2228 | "prefix": "uiselector.findOne", 2229 | "body": ["uiselector.findOne($1)"], 2230 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。\n\n需要注意的是,如果屏幕上一直没有出现所描述的控件,则该函数会阻塞,直至所描述的控件出现为止。因此此函数不会返回`null`。\n\n该函数本来应该命名为`untilFindOne()`,但由于历史遗留原因已经无法修改。如果想要只在屏幕上搜索一次而不是一直搜索,请使用`findOnce()`。\n\n另外,如果屏幕上有多个满足条件的控件,`findOne()`采用深度优先搜索(DFS),会返回该搜索算法找到的第一个控件。注意控件找到的顺序有时会起到作用。" 2231 | }, 2232 | "uiselector.findOne_56": { 2233 | "prefix": "uiselector.findOne", 2234 | "body": ["uiselector.findOne($1, $2)"], 2235 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件;如果在timeout毫秒的时间内没有找到符合条件的控件,则终止搜索并返回`null`。\n\n该函数类似于不加参数的`findOne()`,只不过加上了时间限制。" 2236 | }, 2237 | "uiselector.findOnce_57": { 2238 | "prefix": "uiselector.findOnce", 2239 | "body": ["uiselector.findOnce($1)"], 2240 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,如果找到符合条件的控件则返回该控件;否则返回`null`。" 2241 | }, 2242 | "uiselector.findOnce_58": { 2243 | "prefix": "uiselector.findOnce", 2244 | "body": ["uiselector.findOnce($1)"], 2245 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,并返回第 i + 1 个符合条件的控件;如果没有找到符合条件的控件,或者符合条件的控件个数 < i, 则返回`null`。\n\n注意这里的控件次序,是搜索算法深度优先搜索(DSF)决定的。" 2246 | }, 2247 | "uiselector.find_59": { 2248 | "prefix": "uiselector.find", 2249 | "body": ["uiselector.find($1)"], 2250 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一定会找到,因而会出现返回的控件集合为空的情况。\n\n不同于`findOne()`或者`findOnce()`只找到一个控件并返回一个控件,`find()`函数会找出所有满足条件的控件并返回一个控件集合。之后可以对控件集合进行操作。\n\n可以通过empty()函数判断找到的是否为空。例如:\n\n var c = className(\"AbsListView\").find();\n if(c.empty()){\n toast(\"找到啦\");\n }else{\n toast(\"没找到╭(╯^╰)╮\");\n }" 2251 | }, 2252 | "uiselector.untilFind_60": { 2253 | "prefix": "uiselector.untilFind", 2254 | "body": ["uiselector.untilFind($1)"], 2255 | "description": "根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。\n\n该函数与`find()`函数的区别在于,该函数永远不会返回空集合;但是,如果屏幕上一直没有出现满足条件的控件,则该函数会保持阻塞。" 2256 | }, 2257 | "uiselector.exists_61": { 2258 | "prefix": "uiselector.exists", 2259 | "body": ["uiselector.exists($1)"], 2260 | "description": "判断屏幕上是否存在控件符合选择器所确定的条件。例如要判断某个文本出现就执行某个动作,可以用:\n\n if(text(\"某个文本\").exists()){\n //要支持的动作\n }" 2261 | }, 2262 | "uiselector.waitFor_62": { 2263 | "prefix": "uiselector.waitFor", 2264 | "body": ["uiselector.waitFor()"], 2265 | "description": "等待屏幕上出现符合条件的控件;在满足该条件的控件出现之前,该函数会一直保持阻塞。\n\n例如要等待包含\"哈哈哈\"的文本控件出现的代码为:\n\n textContains(\"哈哈哈\").waitFor();" 2266 | }, 2267 | "uiselector.filter_63": { 2268 | "prefix": "uiselector.filter", 2269 | "body": ["uiselector.filter($1)"], 2270 | "description": "为当前选择器附加自定义的过滤条件。\n\n例如,要找出屏幕上所有文本长度为10的文本控件的代码为:\n\n var uc = className(\"TextView\").filter(function(w){\n return w.text().length == 10;\n });" 2271 | }, 2272 | "uiobject.click_64": { 2273 | "prefix": "uiobject.click", 2274 | "body": ["uiobject.click($1)"], 2275 | "description": "点击该控件,并返回是否点击成功。\n\n如果该函数返回false,可能是该控件不可点击(clickable为false),当前界面无法响应该点击等。" 2276 | }, 2277 | "uiobject.longClick_65": { 2278 | "prefix": "uiobject.longClick", 2279 | "body": ["uiobject.longClick($1)"], 2280 | "description": "长按该控件,并返回是否点击成功。\n\n如果该函数返回false,可能是该控件不可点击(longClickable为false),当前界面无法响应该点击等。" 2281 | }, 2282 | "uiobject.setText_66": { 2283 | "prefix": "uiobject.setText", 2284 | "body": ["uiobject.setText($1, $2)"], 2285 | "description": "设置输入框控件的文本内容,并返回是否设置成功。\n\n该函数只对可编辑的输入框(editable为true)有效。" 2286 | }, 2287 | "uiobject.copy_67": { 2288 | "prefix": "uiobject.copy", 2289 | "body": ["uiobject.copy($1)"], 2290 | "description": "对输入框文本的选中内容进行复制,并返回是否操作成功。\n\n该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过`setSelection()`函数来设置输入框选中的内容。\n\n var et = className(\"EditText\").findOne();\n //选中前两个字\n et.setSelection(0, 2);\n //对选中内容进行复制\n if(et.copy()){\n toast(\"复制成功\");\n }else{\n toast(\"复制失败\");\n }" 2291 | }, 2292 | "uiobject.cut_68": { 2293 | "prefix": "uiobject.cut", 2294 | "body": ["uiobject.cut()"], 2295 | "description": "对输入框文本的选中内容进行剪切,并返回是否操作成功。\n\n该函数只能用于输入框控件,并且当前输入框控件有选中的文本。可以通过`setSelection()`函数来设置输入框选中的内容。" 2296 | }, 2297 | "uiobject.paste_69": { 2298 | "prefix": "uiobject.paste", 2299 | "body": ["uiobject.paste($1)"], 2300 | "description": "对输入框控件进行粘贴操作,把剪贴板内容粘贴到输入框中,并返回是否操作成功。\n\n //设置剪贴板内容为“你好”\n setClip(\"你好\");\n var et = className(\"EditText\").findOne();\n et.paste();" 2301 | }, 2302 | "uiobject.setSelection_70": { 2303 | "prefix": "uiobject.setSelection", 2304 | "body": ["uiobject.setSelection($1, $2, $3)"], 2305 | "description": "对输入框控件设置选中的文字内容,并返回是否操作成功。\n\n索引是从0开始计算的;并且,选中内容不包含end位置的字符。例如,如果一个输入框内容为\"123456789\",要选中\"4567\"的文字的代码为`et.setSelection(3, 7)`。\n\n该函数也可以用来设置光标位置,只要参数的end等于start,即可把输入框光标设置在start的位置。例如`et.setSelection(1, 1)`会把光标设置在第一个字符的后面。" 2306 | }, 2307 | "uiobject.scrollForward_71": { 2308 | "prefix": "uiobject.scrollForward", 2309 | "body": ["uiobject.scrollForward($1)"], 2310 | "description": "对控件执行向前滑动的操作,并返回是否操作成功。\n\n向前滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行`scrollForward()`的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。" 2311 | }, 2312 | "uiobject.scrollBackward_72": { 2313 | "prefix": "uiobject.scrollBackward", 2314 | "body": ["uiobject.scrollBackward($1)"], 2315 | "description": "对控件执行向后滑动的操作,并返回是否操作成功。\n\n向后滑动包括了向右和向下滑动。如果一个控件既可以向右滑动和向下滑动,那么执行`scrollForward()`的行为是未知的(这是因为Android文档没有指出这一点,同时也没有充分的测试可供参考)。" 2316 | }, 2317 | "uiobject.select_73": { 2318 | "prefix": "uiobject.select", 2319 | "body": ["uiobject.select($1)"], 2320 | "description": "对控件执行\"选中\"操作,并返回是否操作成功。\"选中\"和`isSelected()`的属性相关,但该操作十分少用。" 2321 | }, 2322 | "uiobject.collapse_74": { 2323 | "prefix": "uiobject.collapse", 2324 | "body": ["uiobject.collapse($1)"], 2325 | "description": "对控件执行折叠操作,并返回是否操作成功。" 2326 | }, 2327 | "uiobject.expand_75": { 2328 | "prefix": "uiobject.expand", 2329 | "body": ["uiobject.expand($1)"], 2330 | "description": "对控件执行操作,并返回是否操作成功。" 2331 | }, 2332 | "uiobject.show_76": { 2333 | "prefix": "uiobject.show", 2334 | "body": ["uiobject.show()"], 2335 | "description": "对集合中所有控件执行显示操作,并返回是否全部操作成功。" 2336 | }, 2337 | "uiobject.scrollUp_77": { 2338 | "prefix": "uiobject.scrollUp", 2339 | "body": ["uiobject.scrollUp()"], 2340 | "description": "对集合中所有控件执行向上滑的操作,并返回是否全部操作成功。" 2341 | }, 2342 | "uiobject.scrollDown_78": { 2343 | "prefix": "uiobject.scrollDown", 2344 | "body": ["uiobject.scrollDown()"], 2345 | "description": "对集合中所有控件执行向下滑的操作,并返回是否全部操作成功。" 2346 | }, 2347 | "uiobject.scrollLeft_79": { 2348 | "prefix": "uiobject.scrollLeft", 2349 | "body": ["uiobject.scrollLeft()"], 2350 | "description": "对集合中所有控件执行向左滑的操作,并返回是否全部操作成功。" 2351 | }, 2352 | "uiobject.scrollRight_80": { 2353 | "prefix": "uiobject.scrollRight", 2354 | "body": ["uiobject.scrollRight($1)"], 2355 | "description": "返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如:\n\n className(\"AbsListView\").findOne().children()\n .forEach(function(child){\n log(child.className());\n });" 2356 | }, 2357 | "uiobject.children_81": { 2358 | "prefix": "uiobject.children", 2359 | "body": ["uiobject.children($1)"], 2360 | "description": "返回该控件的所有子控件组成的控件集合。可以用于遍历一个控件的子控件,例如:\n\n className(\"AbsListView\").findOne().children()\n .forEach(function(child){\n log(child.className());\n });" 2361 | }, 2362 | "uiobject.childCount_82": { 2363 | "prefix": "uiobject.childCount", 2364 | "body": ["uiobject.childCount($1)"], 2365 | "description": "返回子控件数目。" 2366 | }, 2367 | "uiobject.child_83": { 2368 | "prefix": "uiobject.child", 2369 | "body": ["uiobject.child($1, $2)"], 2370 | "description": "返回第i+1个子控件。如果i>=控件数目或者小于0,则抛出异常。\n\n需要注意的是,由于布局捕捉的问题,该函数可能返回`null`,也就是可能获取不到某个子控件。\n\n遍历子控件的示例:\n\n var list = className(\"AbsListView\").findOne();\n for(var i = 0; i < list.childCount(); i++){\n var child = list.child(i);\n log(child.className());\n }" 2371 | }, 2372 | "uiobject.parent_84": { 2373 | "prefix": "uiobject.parent", 2374 | "body": ["uiobject.parent($1)"], 2375 | "description": "返回该控件的父控件。如果该控件没有父控件,返回`null`。" 2376 | }, 2377 | "uiobject.bounds_85": { 2378 | "prefix": "uiobject.bounds", 2379 | "body": ["uiobject.bounds($1)"], 2380 | "description": "返回控件在屏幕上的范围,其值是一个[Rect](https://hyb1996.github.io/AutoJs-Docs/widgets-based-automation.html#widgets_based_automation_rect)对象。\n\n示例:\n\n var b = text(\"Hamibot\").findOne().bounds();\n toast(\"控件在屏幕上的范围为\" + b);\n \n\n如果一个控件本身无法通过`click()`点击,那么我们可以利用`bounds()`函数获取其坐标,再利用坐标点击。例如:\n\n var b = desc(\"打开侧拉菜单\").findOne().bounds();\n click(b.centerX(), b.centerY());\n //如果使用root权限,则用 Tap(b.centerX(), b.centerY());" 2381 | }, 2382 | "uiobject.boundsInParent_86": { 2383 | "prefix": "uiobject.boundsInParent", 2384 | "body": ["uiobject.boundsInParent($1)"], 2385 | "description": "返回控件在父控件中的范围,其值是一个[Rect](https://hyb1996.github.io/AutoJs-Docs/widgets-based-automation.html#widgets_based_automation_rect)对象。" 2386 | }, 2387 | "uiobject.drawingOrder_87": { 2388 | "prefix": "uiobject.drawingOrder", 2389 | "body": ["uiobject.drawingOrder($1)"], 2390 | "description": "返回控件在父控件中的绘制次序。该函数在安卓7.0及以上才有效,7.0以下版本调用会返回0。" 2391 | }, 2392 | "uiobject.id_88": { 2393 | "prefix": "uiobject.id", 2394 | "body": ["uiobject.id($1)"], 2395 | "description": "获取控件的id,如果一个控件没有id,则返回`null`。" 2396 | }, 2397 | "uiobject.text_89": { 2398 | "prefix": "uiobject.text", 2399 | "body": ["uiobject.text($1)"], 2400 | "description": "获取控件的文本,如果控件没有文本,返回`\"\"`。" 2401 | }, 2402 | "uiobject.findByText_90": { 2403 | "prefix": "uiobject.findByText", 2404 | "body": ["uiobject.findByText($1, $2)"], 2405 | "description": "根据文本text在子控件中递归地寻找并返回文本或描述(desc)**包含**这段文本str的控件,返回它们组成的集合。\n\n该函数会在当前控件的子控件,孙控件,曾孙控件...中搜索text或desc包含str的控件,并返回它们组合的集合。" 2406 | }, 2407 | "uiobject.findOne_91": { 2408 | "prefix": "uiobject.findOne", 2409 | "body": ["uiobject.findOne($1, $2)"], 2410 | "description": "根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回`null`。\n\n例如,对于酷安动态列表,我们可以遍历他的子控件(每个动态列表项),并在每个子控件中依次寻找点赞数量和图标,对于点赞数量小于10的点赞:\n\n //找出动态列表\n var list = id(\"recycler_view\").findOne();\n //遍历动态\n list.children().forEach(function(child){\n //找出点赞图标\n var like = child.findOne(id(\"feed_action_view_like\"));\n //找出点赞数量\n var likeCount = child.findOne(id(\"text_view\"));\n //如果这两个控件没有找到就不继续了\n if(like == null || likeCount == null){\n return;\n }\n //判断点赞数量是否小于10\n if(parseInt(likeCount.text()) < 10){\n //点赞\n like.click();\n }\n });" 2411 | }, 2412 | "uiobject.find_92": { 2413 | "prefix": "uiobject.find", 2414 | "body": ["uiobject.find($1, $2)"], 2415 | "description": "根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回它们组合的集合。" 2416 | }, 2417 | "uicollection.size_93": { 2418 | "prefix": "uicollection.size", 2419 | "body": ["uicollection.size($1)"], 2420 | "description": "返回集合中的控件数。\n\n历史遗留函数,相当于属性length。" 2421 | }, 2422 | "uicollection.get_94": { 2423 | "prefix": "uicollection.get", 2424 | "body": ["uicollection.get($1, $2)"], 2425 | "description": "返回集合中第i+1个控件(UiObject)。\n\n历史遗留函数,建议直接使用数组下标的方式访问元素。" 2426 | }, 2427 | "uicollection.each_95": { 2428 | "prefix": "uicollection.each", 2429 | "body": ["uicollection.each($1)"], 2430 | "description": "遍历集合。\n\n历史遗留函数,相当于`forEach`。参考[forEach](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)。" 2431 | }, 2432 | "uicollection.empty_96": { 2433 | "prefix": "uicollection.empty", 2434 | "body": ["uicollection.empty($1)"], 2435 | "description": "返回控件集合是否为空。" 2436 | }, 2437 | "uicollection.nonEmpty_97": { 2438 | "prefix": "uicollection.nonEmpty", 2439 | "body": ["uicollection.nonEmpty($1)"], 2440 | "description": "返回控件集合是否非空。" 2441 | }, 2442 | "uicollection.find_98": { 2443 | "prefix": "uicollection.find", 2444 | "body": ["uicollection.find($1, $2)"], 2445 | "description": "根据selector所确定的条件在该控件集合的控件、子控件、孙控件...中找到所有符合条件的控件并返回找到的控件集合。\n\n注意这会递归地遍历控件集合里所有的控件以及他们的子控件。和数组的`filter`函数不同。\n\n例如:\n\n var names = id(\"name\").find();\n //在集合\n var clickableNames = names.find(clickable());" 2446 | }, 2447 | "uicollection.findOne_99": { 2448 | "prefix": "uicollection.findOne", 2449 | "body": ["uicollection.findOne($1, $2)"], 2450 | "description": "根据选择器selector在该控件集合的控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回`null`。" 2451 | }, 2452 | "rect.centerX_100": { 2453 | "prefix": "rect.centerX", 2454 | "body": ["rect.centerX($1)"], 2455 | "description": "长方形中点x坐标。" 2456 | }, 2457 | "rect.centerY_101": { 2458 | "prefix": "rect.centerY", 2459 | "body": ["rect.centerY($1)"], 2460 | "description": "长方形中点y坐标。" 2461 | }, 2462 | "rect.width_102": { 2463 | "prefix": "rect.width", 2464 | "body": ["rect.width($1)"], 2465 | "description": "长方形宽度。通常可以作为控件宽度。" 2466 | }, 2467 | "rect.height_103": { 2468 | "prefix": "rect.height", 2469 | "body": ["rect.height($1)"], 2470 | "description": "长方形高度。通常可以作为控件高度。" 2471 | }, 2472 | "rect.contains_104": { 2473 | "prefix": "rect.contains", 2474 | "body": ["rect.contains($1)"], 2475 | "description": "返回是否包含另一个长方形r。包含指的是,长方形r在该长方形的里面(包含边界重叠的情况)。" 2476 | }, 2477 | "rect.intersect_105": { 2478 | "prefix": "rect.intersect", 2479 | "body": ["rect.intersect($1)"], 2480 | "description": "返回是否和另一个长方形相交。" 2481 | }, 2482 | "rect.left_106": { 2483 | "prefix": "rect.left", 2484 | "body": ["rect.left"], 2485 | "description": "长方形左边界的x坐标、" 2486 | }, 2487 | "rect.right_107": { 2488 | "prefix": "rect.right", 2489 | "body": ["rect.right"], 2490 | "description": "长方形右边界的x坐标、" 2491 | }, 2492 | "rect.top_108": { 2493 | "prefix": "rect.top", 2494 | "body": ["rect.top"], 2495 | "description": "长方形上边界的y坐标、" 2496 | }, 2497 | "rect.bottom_109": { 2498 | "prefix": "rect.bottom", 2499 | "body": ["rect.bottom"], 2500 | "description": "长方形下边界的y坐标、" 2501 | } 2502 | } 2503 | --------------------------------------------------------------------------------