简介

Autojs是一个支持无障碍服务的Android平台上的JavaScript IDE,其发展目标是JsBox和Workflow。同时有VS Code 插件可提供基础的在桌面开发的功能。

下载地址:酷安
官方文档:https://hyb1996.github.io/AutoJs-Docs/

特性

  1. 由无障碍服务实现的简单易用的自动操作函数
  2. 悬浮窗录制和运行
  3. 更专业&强大的选择器API,提供对屏幕上的控件的寻找、遍历、获取信息、操作等。类似于Google的UI测试框架UiAutomator,您也可以把他当做移动版UI测试框架使用
  4. 采用JavaScript为脚本语言,并支持代码补全、变量重命名、代码格式化、查找替换等功能,可以作为一个JavaScript IDE使用
  5. 支持使用e4x编写界面,并可以将JavaScript打包为apk文件,您可以用它来开发小工具应用
  6. 支持使用Root权限以提供更强大的屏幕点击、滑动、录制功能和运行shell命令。录制录制可产生js文件或二进制文件,录制动作的回放比较流畅
  7. 提供截取屏幕、保存截图、图片找色、找图等函数
  8. 可作为Tasker插件使用,结合Tasker可胜任日常工作流
  9. 带有界面分析工具,类似Android Studio的LayoutInspector,可以分析界面层次和范围、获取界面上的控件信息

本软件与按键精灵等软件不同,主要区别是:

  1. Auto.js主要以自动化、工作流为目标,更多地是方便日常生活工作,例如启动游戏时自动屏蔽通知、一键与特定联系人微信视频(知乎上出现过该问题,老人难以进行复杂的操作和子女进行微信视频)等
  2. Auto.js兼容性更好。以坐标为基础的按键精灵、脚本精灵很容易出现分辨率问题,而以控件为基础的Auto.js则没有这个问题
  3. Auto.js执行大部分任务不需要root权限。只有需要精确坐标点击、滑动的相关函数才需要root权限
  4. Auto.js可以提供界面编写等功能,不仅仅是作为一个脚本软件而存在

信息

  • 官方论坛: autojs.org
  • 文档:可在这里查看在线文档。目前文档仍然不完善。
  • 示例:可在这里查看一些示例,或者直接在应用内查看和运行。

典型应用

关键补充说明

  • _show_floaty: floaty.window(layout) 指定悬浮窗的布局,创建并显示一个悬浮窗,返回一个FloatyWindow对象。该悬浮窗自带关闭、调整大小、调整位置按键,可根据需要调用setAdjustEnabled()函数来显示或隐藏。可以注释掉_show_floaty,如果用不好的话。
  • "ui": ui模块提供了编写用户界面的支持。带有ui的脚本的的最前面必须使用"ui";指定ui模式,否则脚本将不会以ui模式运行。字符串"ui"的前面可以有注释、空行和空格[v4.1.0新增],但是不能有其他代码。也就是说,老版本autojs,"ui";前面不能有注释,不能有空格,空行等等。
  • 计时不对: 锁屏(关屏不一定加锁)状态下,计时走时缓慢,或者干脆就不走时了。

如果想要在电脑而不是手机上开发Auto.js,可以使用VS Code以及相应的Auto.js插件使得在电脑上编辑的脚本能推送到手机运行,参见前面提到的VS code插件。

例子程序

根目录

update.js

importClass(java.io.File);
importClass(java.io.IOException);
importClass(java.io.InputStream);
importClass(java.io.FileOutputStream);
importClass(java.security.MessageDigest);/*** 向服务器查询发生更改的文件列表* * @param {*} server 提供更新文件的服务器地址* @param {*} path 本地需要更新文件的目录路径*/
function GetChangedFileList(server, path) {const _server = server,_path = path,_processor = "generate_change_list.php";let _ignore_list = [".", "..", ".git"];const _generate_md5 = function(file) {let md5 = MessageDigest.getInstance("MD5");let hex = [];md5.update(file);md5.digest().forEach((byte) => {let temp = (0xFF & byte).toString(16);while (temp.length < 2) temp = "0" + temp;hex.push(temp);});return hex.join("");};const _generate_postdata = function func(path, data) {data = data || {};if (files.isEmptyDir(path)) return data;files.listDir(path).forEach(function(file_name) {let new_path = files.join(path, file_name);if (_ignore_list.indexOf(file_name) < 0) {if (!files.isDir(new_path)) {let file = files.readBytes(new_path);data[new_path.replace(_path, "")] = _generate_md5(file);} else {func(new_path, data);}}});return data;};return {get ignore_list() {return _ignore_list;},set ignore_list(arr) {_ignore_list = arr;},exec: function() {const postdata = _generate_postdata(_path);let url = _server + "/" + _processor;let res = http.postJson(url, postdata);if (res.statusCode != 200) {toastLog("请求失败: " + res.statusCode + " " + res.statusMessage);} else {return res.body.json();}}}
}/*** 下载工具类,可监听下载进度* * @param {*} url 下载链接* @param {*} path 保存地址* @param {*} listener 下载监听*/
function DownloadUtil(url, path, listener) {const _url = url,_path = path,_listener = listener;let _len = -1,_total_bytes = 0,_input_stream = null,_output_stream = null,_file_temp = null,_file_dir = null,_buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 2048);return {download: function() {let client = new OkHttpClient();let request = new Request.Builder().url(_url).get().addHeader("Accept-Encoding", "identity").build();client.newCall(request).enqueue(new Callback({onFailure: function(call, err) {toast("请求失败");console.error("请求失败:" + err);},onResponse: function(call, res) {try {if (res.code() != 200) throw res.code() + " " + res.message();_total_bytes = res.body().contentLength();_input_stream = res.body().byteStream();_file_temp = new File(_path);_file_dir = _file_temp.getParentFile();if(!_file_dir.exists()) _file_dir.mkdirs();_output_stream = new FileOutputStream(_file_temp);while ((_len = _input_stream.read(_buffer)) != -1) {_output_stream.write(_buffer, 0, _len);_listener.onDownloading((_len / _total_bytes) * 100);}_output_stream.flush();_listener.onDownloadSuccess();} catch (err) {_listener.onDownloadFailed(err);} finally {try {if (_input_stream != null)_input_stream.close();} catch (err) {toast("文件流处理失败");console.error("文件流处理失败:" + err);}}}}));}}
}(function main() {let server = "http://www.infiniture.cn";let local = "/sdcard/脚本/Ant-Forest-autoscript";let changed_files = new GetChangedFileList(server, local).exec();let remove_files = changed_files.remove || [];let update_files = changed_files.update || [];if (remove_files.length) {remove_files.forEach((file) => {let dir = files.join(local, file).replace(files.getName(file), '');files.remove(files.join(local, file));if (files.isEmptyDir(dir)) files.removeDir(dir);toastLog("更新完成");});} else if (update_files.length) {let downloadDialog = null;let res = http.get(server + "/ant-forest/CHANGELOG.md");if (res.statusCode != 200) {toastLog("请求失败: " + res.statusCode + " " + res.statusMessage);} else {dialogs.build({ title: "发现新版本",content: res.body.string(),positive: "更新",negative: "取消",}).on("positive", () => {downloadDialog = dialogs.build({title: "更新中...",negative: "取消",progress: {max: 100,showMinMax: true},autoDismiss: false}).on("negative", () => {downloadDialog.dismiss();downloadDialog = null;}).show();let counter = 0,total = 0,realurl = update_files.map((uri) => {return server + "/ant-forest" + uri}),abspath = update_files.map((uri) => {return files.join(local, uri)});let callback = {onDownloadSuccess: function(file) {if (counter == update_files.length - 1) {downloadDialog.dismiss();downloadDialog = null;toastLog("更新完成");} else {counter++;new DownloadUtil(realurl[counter], abspath[counter], callback).download();}},onDownloading: function(progress) {downloadDialog.setProgress((total += progress) / update_files.length);},onDownloadFailed: function(err) {toast("下载失败");console.error("下载失败:" + err);}};new DownloadUtil(realurl[counter], abspath[counter], callback).download();}).show();} } else {toastLog("当前已经是最新版本了");}
})();

config.js

"ui";//带有ui的脚本的的最前面必须使用"ui";指定ui模式,否则脚本将不会以ui模式运行。var config = storages.create("ant_forest_config");
if (!config.contains("color_offset")) {toastLog("使用默认配置");// 默认执行配置var default_conf = {color_offset: 50,password: "",help_friend: true,is_cycle: false,cycle_times: 10,delay_unlock: 1000,timeout_findOne: 1000,max_collect_wait_time: 20,white_list: []};// 储存默认配置到本地Object.keys(default_conf).forEach(function(key) {config.put(key, default_conf[key]);});
}
//一个表示给定对象的所有可枚举属性的字符串数组。
//.forEach对每一个都做一定的操作function draw_view() {ui.layout(<ScrollView><vertical id="viewport"><frame><appbar><toolbar id="toolbar" title="执行配置" /></appbar></frame><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="设置执行模式:" textColor="#666666" textSize="14sp" /><radiogroup id="exec_pattern" orientation="horizontal" margin="0 10"><radio text="计时" checked="{{!config.get('is_cycle')}}" /><radio text="循环" checked="{{config.get('is_cycle')}}" marginLeft="20" /></radiogroup><vertical visibility="{{config.get('is_cycle') ? 'visible' : 'gone'}}" w="*" gravity="left" layout_gravity="left"><text text="循环次数:" textColor="#666666" textSize="14sp" /><input id="cycle_times" inputType="number" text="{{config.get('cycle_times')}}" /></vertical></vertical><horizontal w="*" h="1sp" bg="#cccccc" margin="10 0"></horizontal><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="帮好友收取:" textColor="#666666" textSize="14sp" /><radiogroup id="is_help_fris" orientation="horizontal" margin="0 10"><radio text="是" checked="{{config.get('help_friend')}}" /><radio text="否" checked="{{!config.get('help_friend')}}" marginLeft="20" /></radiogroup></vertical><horizontal w="*" h="1sp" bg="#cccccc" margin="10 0"></horizontal><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="颜色偏移量:" textColor="#666666" textSize="14sp" /><input id="color_offset" inputType="number" text="{{config.get('color_offset')}}" /></vertical><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="解锁密码:" textColor="#666666" textSize="14sp" /><input id="password" inputType="textPassword" text="{{config.get('password')}}" /></vertical><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="最大等待时间(分钟):" textColor="#666666" textSize="14sp" /><input id="max_collect_wait_time" inputType="number" text="{{config.get('max_collect_wait_time')}}" /></vertical><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="解锁操作时延:" textColor="#666666" textSize="14sp" /><input id="delay_unlock" inputType="number" text="{{config.get('delay_unlock')}}" /></vertical><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="控件搜索超时:" textColor="#666666" textSize="14sp" /><input id="timeout_findOne" inputType="number" text="{{config.get('timeout_findOne')}}" /></vertical><vertical w="*" gravity="left" layout_gravity="left" margin="10"><text text="白名单:" textColor="#666666" textSize="14sp" /><text visibility="{{config.get('white_list').length == 0 ? 'visible' : 'gone'}}"  w="*" h="80" gravity="center" layout_gravity="center" text="白名单为空" textColor="#999999" textSize="18sp" margin="0 20" bg="#eeeeee" /><frame><list id="white_list"><horizontal w="*" h="40" gravity="left" bg="#efefef" margin="0 5"><text id="name" layout_weight='1' h="30" gravity="left|center" layout_gravity="left|center" textSize="16sp" text="{{name}}" margin="10 0" /><card id="delete" w="30" h = "30" cardBackgroundColor = "#fafafa" cardCornerRadius = "15dp" layout_gravity="center" marginRight="10"><text textSize = "16dp" textColor = "#555555" gravity="center">×</text></card></horizontal></list></frame><button w="*" id="add" text="添加" gravity="center" layout_gravity="center" /></vertical><horizontal w="*" h="1sp" bg="#cccccc" margin="10 0"></horizontal><vertical w="*" gravity="left" layout_gravity="left" margin="10"><button w="*" id="clear" text="清除本地储存" gravity="center" layout_gravity="center" /></vertical></vertical></ScrollView>);// 更新本地配置同时重绘UIfunction update(target, new_val) {config.put(target, new_val);if (target == "is_cycle" || target == "white_list") draw_view();}// 格式化function format(val) {return val.toString();//结合 Function.toString()的方法来执行特定函数:}// 更新选中的执行方法ui.exec_pattern.setOnCheckedChangeListener(function(radioGroup, id) {let index = (id + 1) % radioGroup.getChildCount();//toast(radioGroup.getChildAt(index).getText());if (radioGroup.getChildAt(index).getText() == "循环") {update("is_cycle", true);} else {update("is_cycle", false);}});// 更新是否帮助好友ui.is_help_fris.setOnCheckedChangeListener(function(radioGroup, id) {let index = (id + 1) % radioGroup.getChildCount();//toast(radioGroup.getChildAt(index).getText());if (radioGroup.getChildAt(index).getText() == "是") {update("help_friend", true);} else {update("help_friend", false);}});// 更新颜色偏移ui.emitter.on("pause", () => {if (config.contains("color_offset")) {update("cycle_times", format(ui.cycle_times.getText()));update("color_offset", format(ui.color_offset.getText()));update("password", format(ui.password.getText()));update("max_collect_wait_time", format(ui.max_collect_wait_time.getText()));update("delay_unlock", format(ui.delay_unlock.getText()));update("timeout_findOne", format(ui.timeout_findOne.getText()));}});// 白名单缓存var list_temp = config.get("white_list").map(i => {return {name: i}});// 生成白名单ui.white_list.setDataSource(list_temp);// 从白名单中删除ui.white_list.on("item_bind", function(itemView, itemHolder){itemView.delete.on("click", function() {list_temp.splice(itemHolder.position, 1);update("white_list", list_temp.map(i => i['name']));});});// 添加到白名单ui.add.on("click", () => {dialogs.rawInput("请输入好友昵称").then(fri_name => {if (!fri_name) return;list_temp.push({name: fri_name});update("white_list", list_temp.map(i => i['name']));});});// 清除本地储存ui.clear.on("click", () => {confirm("确定要清除本地储存吗?").then(ok => {if (ok) {storages.remove("ant_forest_config");toastLog("清除成功");}});});
}draw_view();

main.js

/************************ 初始化***********************/
// 检查手机是否开启无障碍服务
auto();//基于控件的操作依赖于无障碍服务,因此最好在脚本开头使用auto()函数来确保无障碍服务已经启用。// 检查脚本是否重复运行
engines.all().slice(1).forEach(script => {if (script.getSource().getName().indexOf(engines.myEngine().getSource())) {toastLog("脚本正在运行中");//显示信息message并在控制台中输出engines.myEngine().forceStop();//engines.myEngine()返回当前脚本的脚本引擎对象(ScriptEngine)}
});
//engines.all(),返回当前所有正在运行的脚本的脚本引擎ScriptEngine的数组。
//slice() 方法可从已有的数组中返回选定的元素,1规定从何处开始选取。
//使用forEach函数来遍历,script是返回的变量,=>表示对变量进行如下操作
//getSource()返回当前脚本引擎正在执行的脚本对象
//indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置
//从1脚本之后,如果判断字符出现当前脚本,那么当前脚本为重复运行// 请求截图权限
if (! requestScreenCapture()) {toast("请求截图失败");//以气泡显示信息message几秒。exit();
}
//requestScreenCapture向系统申请屏幕截图权限,返回是否请求成功。
/************************* 依赖加载***********************/// 加载本地配置
var config = storages.create("ant_forest_config");
//创建一个本地存储并返回一个Storage对象。不同名称的本地存储的数据是隔开的,而相同名称的本地存储的数据是共享的。
if (!config.contains("color_offset")) {toastLog("请完善配置后再运行");engines.execScriptFile("./config.js");//在新的脚本环境中运行脚本文件path。engines.myEngine().forceStop();}//.contains返回该本地存储是否包含键值为key的数据。var Automator = require("./lib/Automator.js");//require() 以下的对象是特定于 Auto.js 的。
var Unlock = require("./lib/Unlock.js");
var Ant_forest = require("./core/Ant_forest.js");var automator = Automator();//实例化对象
var unlock = Unlock(automator);
var ant_forest = Ant_forest(automator, unlock);/************************* 主程序***********************/
ant_forest.exec();//运行程序,exec为return的内容
core目录

Ant_forest.js

/** @Author: NickHopps* @Last Modified by: NickHopps* @Last Modified time: 2019-03-14 10:29:30* @Description: 蚂蚁森林操作集*/function Ant_forest(automator, unlock) {const _automator = automator,//const定义的变量不可以修改,而且必须初始化。_unlock = unlock,_config = storages.create("ant_forest_config"),//创建一个本地存储并返回一个Storage对象。//storages模块提供了保存简单数据、用户配置等的支持。保存的数据除非应用被卸载或者被主动删除,否则会一直保留。_package_name = "com.eg.android.AlipayGphone";let _pre_energy = 0,       // 记录收取前能量值_post_energy = 0,      // 记录收取后能量值_timestamp = 0,        // 记录获取自身能量倒计时_min_countdown = 0,    // 最小可收取倒计时_current_time = 0,     // 当前收集次数_fisrt_running = true, // 是否第一次进入蚂蚁森林_has_next = true,      // 是否下一次运行_avil_list = [],       // 可收取好友列表_has_protect = [];     // 开启能量罩好友//let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。/************************ 综合操作***********************/// 进入蚂蚁森林主页const _start_app = function() {app.startActivity({        action: "VIEW",data: "alipays://platformapi/startapp?appId=60000002",    });}
// 启动Auto.js的特定界面。// 关闭提醒弹窗const _clear_popup = function() {// 合种/添加快捷方式提醒threads.start(function() {let popup = idEndsWith("J_pop_treedialog_close").findOne(_config.get("timeout_findOne"));if (popup) popup.click();});// 活动threads.start(function() {let popup = descEndsWith("关闭蒙层").findOne(_config.get("timeout_findOne"));if (popup) popup.click();}); }//idEndsWith此方法测试id字符串是否以指定的后缀结束//threads.start启动一个新线程并执行action。 //findOne根据选择器selector在该控件的子控件、孙控件...中搜索符合该选择器条件的控件,并返回找到的第一个控件;如果没有找到符合条件的控件则返回null。// 显示文字悬浮窗const _show_floaty = function(text) {let window = floaty.window(<card cardBackgroundColor = "#aa000000" cardCornerRadius = "20dp"><horizontal w = "250" h = "40" paddingLeft = "15" gravity="center"><text id = "log" w = "180" h = "30" textSize = "12dp" textColor = "#ffffff" layout_gravity="center" gravity="left|center"></text><card id = "stop" w = "30" h = "30" cardBackgroundColor = "#fafafa" cardCornerRadius = "15dp" layout_gravity="right|center" paddingRight = "-15"><text w = "30" h = "30" textSize = "16dp" textColor = "#000000" layout_gravity="center" gravity="center">×</text></card></horizontal></card>);window.stop.on("click", () => {engines.stopAll();});setInterval(()=>{ui.run(function(){window.log.text(text)});}, 0);}//floaty模块提供了悬浮窗的相关函数,可以在屏幕上显示自定义悬浮窗,控制悬浮窗大小、位置等。// 同步获取 toast 内容const _get_toast_sync = function(filter, limit, exec) {filter = (typeof filter == null) ? "" : filter;let messages = threads.disposable();// 在新线程中开启监听let thread = threads.start(function() {let temp = [];let counter = 0;// 监控 toastevents.onToast(function(toast) {if (toast) {if (toast.getPackageName().indexOf(filter) >= 0) {counter++;temp.push(toast.getText())if (counter == limit) messages.setAndNotify(temp);}}});// 触发 toastexec();});// 获取结果let result = messages.blockedGet();thread.interrupt();return result;}/************************ 获取下次运行倒计时***********************/// 获取自己的能量球中可收取倒计时的最小值const _get_min_countdown_own = function() {let target = className("Button").descMatches(/\s/).filter(function(obj) {return obj.bounds().height() / obj.bounds().width() > 1.1; });if (target.exists()) {let ball = target.untilFind();let temp = [];let toasts = _get_toast_sync(_package_name, ball.length, function() {ball.forEach(function(obj) {_automator.clickCenter(obj);sleep(500);});});toasts.forEach(function(toast) {let countdown = toast.match(/\d+/g);temp.push(countdown[0] * 60 - (-countdown[1]));});_min_countdown = Math.min.apply(null, temp);_timestamp = new Date();} else {_min_countdown = null;log("无可收取能量");}}// 确定下一次收取倒计时const _get_min_countdown = function() {let temp = [];if (_min_countdown && _timestamp instanceof Date) {let countdown_own = _min_countdown - Math.floor((new Date() - _timestamp) / 60000);countdown_own >= 0 ? temp.push(countdown_own) : temp.push(0);}if (descEndsWith("’").exists()) {descEndsWith("’").untilFind().forEach(function(countdown) {let countdown_fri = parseInt(countdown.desc().match(/\d+/));temp.push(countdown_fri);});}if (!temp.length) return;_min_countdown = Math.min.apply(null, temp);}/************************ 构建下次运行操作***********************/// 构建下一次运行const _generate_next = function() {if (_config.get("is_cycle")) {if (_current_time < _config.get("cycle_times")) {_has_next = true;} else {_has_next = false;}} else {if (_min_countdown != null && _min_countdown <= _config.get("max_collect_wait_time")) {_has_next = true;} else {_has_next = false;} }}//只有当我们获取到的最小时间小于最大等待时间,才有下一个// 按分钟延时const _delay = function(minutes) {minutes = (typeof minutes != null) ? minutes : 0;for (let i = 0; i < minutes; i++) {log("距离下次运行还有 " + (minutes - i) + " 分钟");//要打印到控制台的信息sleep(60000);}}/************************ 记录能量***********************/// 记录当前能量const _get_current_energy = function() {if (descEndsWith("背包").exists()) {return parseInt(descEndsWith("g").findOne(_config.get("timeout_findOne")).desc().match(/\d+/));}}// 记录初始能量值const _get_pre_energy = function() {if (_fisrt_running && _has_next) {_pre_energy = _get_current_energy();log("当前能量:" + _pre_energy);}}// 记录最终能量值const _get_post_energy = function() {if (!_fisrt_running && !_has_next) {if (descEndsWith("返回").exists()) descEndsWith("返回").findOne(_config.get("timeout_findOne")).click();descEndsWith("背包").waitFor();_post_energy = _get_current_energy();log("当前能量:" + _post_energy);log("共收取:" + (_post_energy - _pre_energy) + "g 能量");// _show_floaty("共收取:" + (_post_energy - _pre_energy) + "g 能量");}if (descEndsWith("关闭").exists()) descEndsWith("关闭").findOne(_config.get("timeout_findOne")).click();home();}/************************ 收取能量***********************/// 收取能量const _collect = function() {if (descEndsWith("克").exists()) {descEndsWith("克").untilFind().forEach(function(ball) {_automator.clickCenter(ball);sleep(500);});}}// 收取能量同时帮好友收取const _collect_and_help = function() {let screen = captureScreen();// 收取好友能量_collect();// 帮助好友收取能量if (className("Button").descMatches(/\s/).exists()) {className("Button").descMatches(/\s/).untilFind().forEach(function(ball) {let x = ball.bounds().left,y = ball.bounds().top,w = ball.bounds().width(),h = ball.bounds().height(),t = _config.get("color_offset");if (images.findColor(screen, "#f99236", {region: [x, y, w, h], threshold: t})) {_automator.clickCenter(ball);sleep(500);}});}}// 判断是否可收取const _is_obtainable = function(obj, screen) {let len = obj.childCount();let x = obj.child(len - 3).bounds().right,y = obj.bounds().top,w = 5,h = obj.bounds().height() - 10,t = _config.get("color_offset");if (h > 0 && !obj.child(len - 2).childCount()) {if (_config.get("help_friend")) {return images.findColor(screen, "#1da06a", {region: [x, y, w, h], threshold: t}) || images.findColor(screen, "#f99236", {region: [x, y, w, h], threshold: t});} else {return images.findColor(screen, "#1da06a", {region: [x, y, w, h], threshold: t});}} else {return false;}}// 记录好友信息const _record_avil_list = function(fri) {let temp = {};// 记录可收取对象temp.target = fri.bounds();// 记录好友IDif (fri.child(1).desc() == "") {temp.name = fri.child(2).desc();} else {temp.name = fri.child(1).desc();}// 记录是否有保护罩temp.protect = false;_has_protect.forEach(function(obj) {if (temp.name == obj) temp.protect = true});// 添加到可收取列表if (_config.get("white_list").indexOf(temp.name) < 0) _avil_list.push(temp);}// 判断并记录保护罩const _record_protected = function(toast) {if (toast.indexOf("能量罩") > 0) {let title = textContains("的蚂蚁森林").findOne(_config.get("timeout_findOne")).text();_has_protect.push(title.substring(0, title.indexOf("的")));}}// 检测能量罩const _protect_detect = function(filter) {filter = (typeof filter == null) ? "" : filter;// 在新线程中开启监听return threads.start(function() {events.onToast(function(toast) {if (toast.getPackageName().indexOf(filter) >= 0) _record_protected(toast.getText());});});}// 根据可收取列表收取好友const _collect_avil_list = function() {while (_avil_list.length) {let obj = _avil_list.shift();if (!obj.protect) {let temp = _protect_detect(_package_name);_automator.click(obj.target.centerX(), obj.target.centerY());descEndsWith("浇水").waitFor();if (_config.get("help_friend")) {_collect_and_help();} else {_collect();}_automator.back();temp.interrupt();while(!textContains("好友排行榜").exists()) sleep(1000);}}}// 识别可收取好友并记录const _find_and_collect = function() {while (!(descEndsWith("没有更多了").exists() && descEndsWith("没有更多了").findOne(_config.get("timeout_findOne")).bounds().centerY() < device.height)) {let screen = captureScreen();let friends_list = idEndsWith("J_rank_list").findOne(_config.get("timeout_findOne"));if (friends_list) {friends_list.children().forEach(function(fri) {if (fri.visibleToUser() && fri.childCount() > 3)if (_is_obtainable(fri, screen)) _record_avil_list(fri);});_collect_avil_list();}scrollDown();sleep(1000);}}/************************ 主要函数***********************/// 收取自己的能量const _collect_own = function() {log("开始收集自己能量");if (!textContains("蚂蚁森林").exists()) _start_app();descEndsWith("背包").waitFor();_clear_popup();_get_pre_energy();_collect();if (!_config.get("is_cycle")) _get_min_countdown_own();//不是循环收取的话,我们获取最小时间_fisrt_running = false;}// 收取好友的能量const _collect_friend = function() {log("开始收集好友能量");descEndsWith("查看更多好友").findOne(_config.get("timeout_findOne")).click();while(!textContains("好友排行榜").exists()) sleep(1000);_find_and_collect();if (!_config.get("is_cycle")) _get_min_countdown();_generate_next();_get_post_energy();}return {exec: function() {let thread = threads.start(function() {events.setMaxListeners(0);//返回 EventEmitter 当前的最大监听器限制值,该值可以通过.setMaxListeners(n) 设置或默认。//0表示不监听events.observeToast();//开启Toast监听。Toast监听依赖于无障碍服务,因此此函数会确保无障碍服务运行。});while (true) {_delay(_min_countdown);log("第 " + (++_current_time) + " 次运行");_unlock.exec();//执行实例化的unlock_collect_own();//收集自己的能量_collect_friend();//收集别人的能量if (_config.get("is_cycle")) sleep(1000);//循环收取的话,sleep一下events.removeAllListeners();if (_has_next == false) {log("收取结束");//没有可收的,就收取结束break;}}thread.interrupt();//中断线程}}
}module.exports = Ant_forest;
//模块内的本地变量是私有的,不会影响到加载他的脚本的变量环境。
//module.exports属性可以被赋予一个新的值(例如函数或对象)
lib目录

Automator.js

function Automation_root() {this.check_root = function() {if (!(files.exists("/sbin/su") || files.exists("/system/xbin/su") || files.exists("/system/bin/su"))) throw new Error("未获取ROOT权限");}this.click = function (x, y) {this.check_root();return (shell("input tap " + x + " " + y, true).code === 0);}this.swipe = function (x1, y1, x2, y2, duration) {this.check_root();return (shell("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + duration, true).code === 0);}this.gesture = function(duration, points) {this.check_root();let len = points.length,step = duration / len,start = points.shift();// 使用 RootAutomator 模拟手势,仅适用于安卓5.0及以上let ra = new RootAutomator();ra.touchDown(start[0], start[1]);sleep(step);points.forEach(function(el) {ra.touchMove(el[0], el[1]);sleep(step);});ra.touchUp();ra.exit();return true;}this.back = function() {this.check_root();return (shell("input keyevent KEYCODE_BACK", true).code === 0);}
}function Automation() {this.click = function (x, y) {return click(x, y);}this.swipe = function (x1, y1, x2, y2, duration) {return swipe(x1, y1, x2, y2, duration);}this.gesture = function(duration, points) {return gesture(duration, points);}this.back = function() {return back();}
}// 工厂方法
function Automator() {const _automator = (device.sdkInt < 24) ? new Automation_root() : new Automation();return {click: function (x, y) {return _automator.click(x, y);},clickCenter: function (obj) {return _automator.click(obj.bounds().centerX(), obj.bounds().centerY());},swipe: function (x1, y1, x2, y2, duration) {return _automator.swipe(x1, y1, x2, y2, duration);},gesture: function(duration, points) {return _automator.gesture(duration, points);},back: function () {return _automator.back();}}
}module.exports = Automator;

Unlock.js

var Devices = {HUAWEI_EMUI8: function(obj) {this.__proto__ = obj;// 图形密码解锁this.unlock_pattern = function(password) {if (typeof password !== "string") throw new Error("密码应为字符串!");let pattern_view = id("com.android.systemui:id/lockPatternView").findOne(this.config.get("timeout_findOne")).bounds(),pattern_size = 3,len = password.length,view_x = pattern_view.left,view_y = pattern_view.top,width = (pattern_view.right - pattern_view.left) / pattern_size,height = (pattern_view.bottom - pattern_view.top) / pattern_size,points = [],ges_param = [];// 记录图形点信息for (let i = 0; i < pattern_size; i++) {for (let j = 0; j < pattern_size; j++) {let index = pattern_size * i + (j + 1);points[index] = [parseInt(view_x + j * width + width / 2), parseInt(view_y + i * height + height / 2)];}}// 构造滑动参数for (var i = 0; i < len; i++) ges_param.push(points[password[i]]);// 使用手势解锁this.automator.gesture(300 * len, ges_param);return this.check_unlock();}// 密码解锁(仅ROOT可用)this.unlock_password = function(password) {if (typeof password !== "string") throw new Error("密码应为字符串!");// 直接在控件中输入密码setText(0, password);// 执行确认操作KeyCode("KEYCODE_ENTER");return this.check_unlock();}// PIN解锁this.unlock_pin = function(password) {if (typeof password !== "string") throw new Error("密码应为字符串!");// 模拟按键for (let i = 0; i < password.length; i++) {let key_id = "com.android.systemui:id/key" + password[i];id(key_id).findOne(this.config.get("timeout_findOne")).click();sleep(100);}return this.check_unlock();}// 判断解锁方式并解锁this.unlock = function(password) {if (id("com.android.systemui:id/lockPatternView").exists()) {return this.unlock_pattern(password);} else if (id("com.android.systemui:id/passwordEntry").exists()) {return this.unlock_password(password);} else if (id("com.android.systemui:id/pinEntry").exists()) {return this.unlock_pin(password);} else {toastLog("识别锁定方式失败,型号:" + device.brand + " " + device.product + " " + device.release);return this.check_unlock();}}}
}var MyDevice = Devices.HUAWEI_EMUI8;//实例化一个类function Unlocker(automator) {const _device = new MyDevice(this),_HEIGHT = device.height,_WIDTH = device.width,_km = context.getSystemService(context.KEYGUARD_SERVICE);this.automator = automator;this.config = storages.create("ant_forest_config");// 设备是否锁屏this.is_locked = function() {return _km.inKeyguardRestrictedInputMode();}// 设备是否加密this.is_passwd = function() {return _km.isKeyguardSecure();}// 解锁失败this.failed = function() {log("解锁失败,停止运行");exit();}// 检测是否解锁成功this.check_unlock = function() {sleep(this.config.get("delay_unlock"));if (this.is_locked() == true) this.failed();return !this.is_locked();}// 唤醒设备this.wakeup = function() {while (!device.isScreenOn()) {device.wakeUp();sleep(this.config.get("delay_unlock"));}}// 划开图层this.swipe_layer = function() {let x = _WIDTH / 2;let y = _HEIGHT / 4;this.automator.swipe(x, (3 * y), x, y, 300);sleep(this.config.get("delay_unlock"));}// 执行解锁操作this.run_unlock = function() {// 如果已经解锁则返回if (!this.is_locked()) return true;// 首先点亮屏幕this.wakeup();// 打开滑动层this.swipe_layer();// 如果有锁屏密码则输入密码if (this.is_passwd()) _device.unlock(this.config.get("password"))// 检测是否解锁成功this.check_unlock();}
}function Unlock(automator) {const _unlocker = new Unlocker(automator);//使用构造函数return {exec: function() {//运行这个解锁程序,_unlocker.run_unlock();}}
}module.exports = Unlock;

不想用vs code也可以使用Total Control进行实时编辑,也可以利用其中的文件管理功能进行复制移动文件。

ps:出了问题,记得更新蚂蚁脚本和auto.js的版本,最新的auto.js在其官方提供的qq群里面有。用新不用旧问题会少很多。如果出现了一些之前没出现的问题,又能确保自己什么都没干的话,不妨把支付宝、autojs、偷能量脚本都更新到最新,然后重启手机试试。

auto.js小记:蚂蚁森林定时自动收取能量脚本相关推荐

  1. python支付宝自动收能量_叫你蚂蚁森林如何自动收取能量(Python)

    Python 兄弟姐妹们谁年轻是没有玩过农场,牧场游戏,现在有了蚂蚁森林,你是否有因忘记收取能量而被好友收取的经历呢? 如果你不是蚂蚁森林重度用户,被别人收取了能量可能对你来说没什么. 但如果你是蚂蚁 ...

  2. 基于Auto.js的蚂蚁森林能量收集脚本

    最近支付宝把"查看更多好友"和"没有更多了",这两个键改成图片格式了,不能识别文字,导致了无法正常的进入更多好友的界面及收集完无法正常退出. 更新内容(已修改下 ...

  3. 使用Auto.js实现蚂蚁森林自动收取能量

    在网上看了一些自动收能量的脚本 根据自己的手机型号 华为荣耀9 分辨率为1980*1080 写了一个脚本 使用AutoJs运行 定时每天早上7点开始收能量(再也不用担心我的能量被偷啦 哈哈~) Aut ...

  4. 用auto.js偷蚂蚁森林能量

    (一)下载地址: auto.js下载 这个app(autoJs-V4.1.1.Alpha2-common-armeabi-v7a-debug下载地址: 来自https://github.com/Eri ...

  5. autojs通知栏_基于Auto.js的蚂蚁森林智能脚本 (长期维护) (JavaScript语言)

    * `新增` 定时循环功能 ·  定时任务自动管理 ·  好友排行榜样本复查 ·  主页能量球循环监测 * `新增` 脚本运行安全 ·  运行失败自动重试 ·  单次运行最大时间限制 ·  排他性任务 ...

  6. Autojs实现蚂蚁森林自动收取能量球

    警告: 本文较为基础,大佬绕行,不喜勿喷 下面正式开始步骤 一:安装Autojs 开源下载链接:https://github.com/Ericwyn/Auto.js/releases 在Github上 ...

  7. 如何使用 AccessibilityService 实现蚂蚁森林自动收取能量,无需Root,无需连接电脑

    如何使用 AccessibilityService 实现蚂蚁森林自动收取能量,无需Root,无需连接电脑 AccessibilityService 设计初衷在于帮助残障用户使用android设备和应用 ...

  8. 【Appium】Python+Appium实现支付宝蚂蚁森林自动收取能量的一种解决方案

    代码有更新,适配新版支付宝,参见最新文章: [Appium][更新]Python+Appium实现支付宝蚂蚁森林自动收取能量 一.环境准备 首先,你需要一个能够运行代码的环境,这里包括: Node.j ...

  9. 苹果 python蚂蚁森林自动收能量_GitHub - dxp432/adb_python_alipay_AntForest: 蚂蚁森林自动收取能量、偷取能量、浇水(使用adb、python)...

    蚂蚁森林自动收取能量.偷取能量.浇水 蚂蚁森林自动收取能量.偷取能量.浇水(使用adb.python)adb_python_alipay_AntForest 涉及到的技术: 1.python 2.ad ...

最新文章

  1. 使用 ViS2005 进行单元测试
  2. oracle的那些事
  3. 机器学习 集成学习篇——python实现Bagging和AdaBOOST算法
  4. 实现auto_ptr的两种方法
  5. rpm方式安装mysql-5.7.11
  6. python 控制手机摄像头_python+open cv调用手机摄像头,保存文件
  7. Ubuntu系统显卡驱动、CUDA、CUDNN安装(二CUDA和CUDNN)
  8. [Oracle][ODBC SQL Server Driver][SQL Server]对象名 'RECOVER.HS_TRANSACTION_LOG' 无效(转)
  9. OpenPose 参数说明
  10. CVPR2004/风格分解:Separating Style and Content on a Nonlinear Manifold在非线性流形上分离样式和内容
  11. Spring(六)——声明式事物控制
  12. windows服务器设置开机启动的几种方式
  13. 百亿级企业级 RPC 框架开源了!
  14. 大数据丨ClickHouse在京东能源管理平台的实践
  15. trove 基本介绍
  16. 【ROS2】【机器人导航navigation2】参数调整分析
  17. VisualSVN Server的使用
  18. 【C语言中如何表示无穷大】
  19. 浅谈精益数字化工厂(Lean Digital Factory, LDF)
  20. 【笔记】微信开发者工具自定义编译模式(编译时,携带 指定参数 直接跳转 指定页面)

热门文章

  1. Moralis去中心化Web3应用开发教程
  2. 顶不住攻击的压力,Zoom为用户推出了端到端加密
  3. TeamViewer无法连接对方/正在初始化问题
  4. python第三方聊天机器人_用 Python 来做一个聊天机器人吧!(特别篇)
  5. 这些年工作以来自己读过的书
  6. [MTK][FAQ23389] 如何配置AUXADC检测Battery ID或检测其他sensor
  7. C均值聚类算法 Excel数据分类处理(介绍+Python实现)
  8. 计算机项目经理专业,IT项目经理的对口专业
  9. Tomcat 服务器 配置 缺省主页
  10. 少侠学代码系列(一)-JS起源