Autojs是一个支持无障碍服务的Android平台上的JavaScript IDE,其发展目标是JsBox和Workflow。同时有VS Code 插件可提供基础的在桌面开发的功能。
下载地址:酷安
官方文档:https://hyb1996.github.io/AutoJs-Docs/
本软件与按键精灵等软件不同,主要区别是:
"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(
×
);
// 更新本地配置同时重绘UI
function 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的内容
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(
×
);
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;
// 监控 toast
events.onToast(function(toast) {
if (toast) {
if (toast.getPackageName().indexOf(filter) >= 0) {
counter++;
temp.push(toast.getText())
if (counter == limit) messages.setAndNotify(temp);
}
}
});
// 触发 toast
exec();
});
// 获取结果
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();
// 记录好友ID
if (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属性可以被赋予一个新的值(例如函数或对象)
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、偷能量脚本都更新到最新,然后重启手机试试。