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

简介

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(
    
      
        
          
            
          
        
        
          
          
            
            
          
          
            
            
          
        
        
        
          
          
            
            
          
        
        
          
            
            
          
          
            
            
          
          
            
            
          
          
            
            
          
          
            
            
          
          
            
            
            
              
                
                  
                  
                    ×
                  
                
              
            
            

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(
      
        
          
          
            ×
          
        
      
    );
    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属性可以被赋予一个新的值(例如函数或对象)

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、偷能量脚本都更新到最新,然后重启手机试试。

你可能感兴趣的:(java,杂类)