├── .gitignore ├── LICENSE ├── README.md ├── config.js ├── core └── CreditRunner.js ├── extends ├── ExternalUnlockDevice-demo.js └── LockScreen-demo.js ├── lib ├── AesUtil.js ├── Base64.js ├── DateUtil.js ├── SingletonRequirer.js ├── Unlock.js ├── color-region-center.dex ├── crypto-js.js ├── download.dex └── prototype │ ├── AlipayUnlocker.js │ ├── Automator.js │ ├── CommonFunction.js │ ├── CrashCatcher.js │ ├── FileUtils.js │ ├── FloatyUtil.js │ ├── LockableStorage.js │ ├── LogUtils.js │ ├── RunningQueueDispatcher.js │ ├── Timers.js │ ├── TryRequestScreenCapture.js │ └── WidgetUtils.js ├── main.js ├── project.json ├── resources └── for_update │ └── download.dex ├── test ├── AES_Test.js ├── GestureLockTest.js ├── TestFamilyCredits.js ├── TestLockScreen.js ├── TestLockStorage.js └── queueTest │ ├── Task1.js │ ├── Task2.js │ ├── Task3.js │ └── TaskTestManager.js ├── unit ├── 功能测试-任务队列测试.js ├── 功能测试-查看运行队列.js ├── 功能测试-清空任务队列.js ├── 功能测试-重置默认配置.js └── 获取当前页面的布局信息.js └── update ├── 检测更新.js └── 说明-重要.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.zip 3 | *.log 4 | *.ign.js 5 | LockScreen.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 蚂蚁积分签到 此项目已废弃,不再开发 签到已经整合到了聚合签到中 3 | 4 | - [聚合签到脚本传送门](https://github.com/TonyJiangWJ/Unify-Sign) 5 | 6 | ## 其他脚本 7 | 8 | - [蚂蚁森林脚本传送门](https://github.com/TonyJiangWJ/Ant-Forest) 9 | - [蚂蚁庄园脚本传送门](https://github.com/TonyJiangWJ/Ant-Manor) 10 | - [聚合签到脚本传送门](https://github.com/TonyJiangWJ/Unify-Sign) 11 | 12 | ## 基于AutoJS实现的自动领取支付宝积分脚本 13 | 14 | - 每日领取支付宝积分 15 | 16 | ## 使用说明 17 | 18 | - 下载安装 [AutoJs 4.1.1 alpha2](http://47.110.40.234/autojs/autojs-4.1.1-alpha2.apk) 之后把整个脚本项目放进 **"/sdcard/脚本/"** 文件夹下面。打开软件后下拉刷新,然后运行项目或者 main 即可。 19 | - 给与软件必要权限 `后台弹出界面`、`显示悬浮窗`、`自启动`,并将软件保持后台运行 20 | - 定时启动脚本,点击 `main.js` 的菜单,选择 `更多` `定时任务` 即可配置定时启动 21 | - 运行config.js 修改配置,设置密码 22 | 23 | ## 配置 24 | 25 | - 配置导出导入功能,点击右上角菜单即可导出当前配置到local_config.cfg中,默认已加密加密密码为通过以下方法获取`device.getAndriodId()` 如果需要在免费版和付费版AutoJS之间同步 需要自行获取该值并按提示输入密码 26 | 27 | ## 添加解锁设备 28 | 29 | - 脚本根目录下新建extends文件夹,然后创建ExternalUnlockDevice.js文件,内容格式如下自定义 30 | - 具体可以参考ExternalUnlockDevice-demo.js 31 | 32 | ```javascript 33 | module.exports = function (obj) { 34 | this.__proto__ = obj 35 | 36 | this.unlock = function(password) { 37 | // 此处为自行编写的解锁代码 38 | 39 | // 在结尾返回此语句用于判断是否解锁成功 40 | return this.check_unlock() 41 | } 42 | 43 | } 44 | ``` 45 | 46 | ## 添加自定义锁屏代码 47 | 48 | - 同解锁设备,在extends文件夹下创建LockScreen.js,内容可以参考LockScreen-demo.js 实现自定义解锁 49 | 50 | ```javascript 51 | let { config: _config } = require('../config.js')(runtime, this) 52 | 53 | module.exports = function () { 54 | // MIUI 12 偏右上角下拉新控制中心 55 | swipe(800, 10, 800, 1000, 500) 56 | // 等待动画执行完毕 57 | sleep(500) 58 | // 点击锁屏按钮 59 | click(parseInt(_config.lock_x), parseInt(_config.lock_y)) 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-09 20:42:08 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-11-12 20:32:26 6 | * @Description: 7 | */ 8 | 'ui'; 9 | 10 | let currentEngine = engines.myEngine().getSource() + '' 11 | let isRunningMode = currentEngine.endsWith('/config.js') && typeof module === 'undefined' 12 | 13 | 14 | importClass(android.text.TextWatcher) 15 | importClass(android.view.View) 16 | importClass(android.view.MotionEvent) 17 | importClass(java.util.concurrent.LinkedBlockingQueue) 18 | importClass(java.util.concurrent.ThreadPoolExecutor) 19 | importClass(java.util.concurrent.TimeUnit) 20 | 21 | let default_config = { 22 | password: '', 23 | is_alipay_locked: false, 24 | alipay_lock_password: '', 25 | timeout_unlock: 1000, 26 | timeout_findOne: 1000, 27 | timeout_existing: 8000, 28 | capture_waiting_time: 500, 29 | show_debug_log: true, 30 | show_engine_id: false, 31 | develop_mode: false, 32 | auto_lock: false, 33 | lock_x: 150, 34 | lock_y: 970, 35 | // 锁屏启动关闭提示框 36 | dismiss_dialog_if_locked: true, 37 | request_capture_permission: true, 38 | // 是否保存日志文件,如果设置为保存,则日志文件会按时间分片备份在logback/文件夹下 39 | saveLogFile: true, 40 | back_size: '100', 41 | 42 | // 单脚本模式 是否只运行一个脚本 不会同时使用其他的 开启单脚本模式 会取消任务队列的功能。 43 | // 比如同时使用蚂蚁庄园 则保持默认 false 否则设置为true 无视其他运行中的脚本 44 | single_script: false, 45 | // 是否使用模拟的滑动,如果滑动有问题开启这个 当前默认关闭 经常有人手机上有虚拟按键 然后又不看文档注释的 46 | useCustomScrollDown: true, 47 | // 排行榜列表下滑速度 200毫秒 不要太低否则滑动不生效 仅仅针对useCustomScrollDown=true的情况 48 | scrollDownSpeed: 200, 49 | bottomHeight: 200, 50 | 51 | // 延迟启动时延 5秒 悬浮窗中进行的倒计时时间 52 | delayStartTime: 5, 53 | device_width: device.width, 54 | device_height: device.height, 55 | auto_set_bang_offset: true, 56 | updated_temp_flag_1325: true, 57 | bang_offset: 0 58 | } 59 | let CONFIG_STORAGE_NAME = 'alipay_credits_version' 60 | let PROJECT_NAME = '支付宝领积分' 61 | let config = {} 62 | let storageConfig = storages.create(CONFIG_STORAGE_NAME) 63 | Object.keys(default_config).forEach(key => { 64 | let storedVal = storageConfig.get(key) 65 | if (typeof storedVal !== 'undefined') { 66 | config[key] = storedVal 67 | } else { 68 | config[key] = default_config[key] 69 | } 70 | }) 71 | 72 | 73 | if (!isRunningMode) { 74 | if (config.device_height <= 10 || config.device_width <= 10) { 75 | if (!currentEngine.endsWith('/config.js')) { 76 | toastLog('请先运行config.js并输入设备宽高') 77 | exit() 78 | } 79 | } 80 | module.exports = function (__runtime__, scope) { 81 | if (typeof scope.config_instance === 'undefined') { 82 | scope.config_instance = { 83 | config: config, 84 | default_config: default_config, 85 | storage_name: CONFIG_STORAGE_NAME, 86 | project_name: PROJECT_NAME 87 | } 88 | } 89 | return scope.config_instance 90 | } 91 | } else { 92 | 93 | let loadingDialog = null 94 | 95 | let _hasRootPermission = files.exists("/sbin/su") || files.exists("/system/xbin/su") || files.exists("/system/bin/su") 96 | 97 | let AesUtil = require('./lib/AesUtil.js') 98 | 99 | let setScrollDownUiVal = function () { 100 | 101 | ui.delayStartTimeInpt.text(config.delayStartTime + '') 102 | 103 | 104 | ui.useCustomScrollDownChkBox.setChecked(config.useCustomScrollDown) 105 | ui.scrollDownContainer.setVisibility(config.useCustomScrollDown ? View.VISIBLE : View.INVISIBLE) 106 | ui.bottomHeightContainer.setVisibility(config.useCustomScrollDown ? View.VISIBLE : View.GONE) 107 | ui.scrollDownSpeedInpt.text(config.scrollDownSpeed + '') 108 | ui.bottomHeightInpt.text(config.bottomHeight + '') 109 | 110 | } 111 | 112 | 113 | let inputDeviceSize = function () { 114 | return Promise.resolve().then(() => { 115 | return dialogs.rawInput('请输入设备宽度:', config.device_width + '') 116 | }).then(x => { 117 | if (x) { 118 | let xVal = parseInt(x) 119 | if (isFinite(xVal) && xVal > 0) { 120 | config.device_width = xVal 121 | } else { 122 | toast('输入值无效') 123 | } 124 | } 125 | }).then(() => { 126 | return dialogs.rawInput('请输入设备高度:', config.device_height + '') 127 | }).then(y => { 128 | if (y) { 129 | let yVal = parseInt(y) 130 | if (isFinite(yVal) && yVal > 0) { 131 | config.device_height = yVal 132 | } else { 133 | toast('输入值无效') 134 | } 135 | } 136 | }) 137 | } 138 | 139 | let setDeviceSizeText = function () { 140 | ui.deviceSizeText.text(config.device_width + 'px ' + config.device_height + 'px') 141 | } 142 | 143 | let resetUiValues = function () { 144 | config.device_width = config.device_width > 0 ? config.device_width : 1 145 | config.device_height = config.device_height > 0 ? config.device_height : 1 146 | 147 | // 基本配置 148 | ui.password.text(config.password + '') 149 | ui.alipayLockPasswordInpt.text(config.alipay_lock_password + '') 150 | ui.isAlipayLockedChkBox.setChecked(config.is_alipay_locked) 151 | ui.alipayLockPasswordContainer.setVisibility(config.is_alipay_locked ? View.VISIBLE : View.GONE) 152 | 153 | 154 | ui.showDebugLogChkBox.setChecked(config.show_debug_log) 155 | ui.saveLogFileChkBox.setChecked(config.saveLogFile) 156 | ui.showEngineIdChkBox.setChecked(config.show_engine_id) 157 | ui.developModeChkBox.setChecked(config.develop_mode) 158 | ui.fileSizeInpt.text(config.back_size + '') 159 | ui.fileSizeContainer.setVisibility(config.saveLogFile ? View.VISIBLE : View.INVISIBLE) 160 | 161 | ui.requestCapturePermissionChkBox.setChecked(config.request_capture_permission) 162 | 163 | ui.lockX.text(config.lock_x + '') 164 | ui.lockXSeekBar.setProgress(parseInt(config.lock_x / config.device_width * 100)) 165 | ui.lockY.text(config.lock_y + '') 166 | ui.lockYSeekBar.setProgress(parseInt(config.lock_y / config.device_height * 100)) 167 | ui.autoLockChkBox.setChecked(config.auto_lock) 168 | ui.lockPositionContainer.setVisibility(config.auto_lock && !_hasRootPermission ? View.VISIBLE : View.INVISIBLE) 169 | ui.lockDescNoRoot.setVisibility(!_hasRootPermission ? View.VISIBLE : View.INVISIBLE) 170 | 171 | ui.dismissDialogIfLockedChkBox.setChecked(config.dismiss_dialog_if_locked) 172 | 173 | ui.timeoutUnlockInpt.text(config.timeout_unlock + '') 174 | ui.timeoutFindOneInpt.text(config.timeout_findOne + '') 175 | ui.timeoutExistingInpt.text(config.timeout_existing + '') 176 | ui.captureWaitingTimeInpt.text(config.capture_waiting_time + '') 177 | 178 | // 进阶配置 179 | ui.singleScriptChkBox.setChecked(config.single_script) 180 | setScrollDownUiVal() 181 | 182 | setDeviceSizeText() 183 | } 184 | 185 | threads.start(function () { 186 | loadingDialog = dialogs.build({ 187 | title: "加载中...", 188 | progress: { 189 | max: -1 190 | }, 191 | cancelable: false 192 | }).show() 193 | setTimeout(function () { 194 | loadingDialog.dismiss() 195 | }, 3000) 196 | }) 197 | 198 | let TextWatcherBuilder = function (textCallback) { 199 | return new TextWatcher({ 200 | onTextChanged: (text) => { 201 | textCallback(text + '') 202 | }, 203 | beforeTextChanged: function (s) { } 204 | , 205 | afterTextChanged: function (s) { } 206 | }) 207 | } 208 | 209 | setTimeout(function () { 210 | let start = new Date().getTime() 211 | ui.layout( 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | {/* 锁屏密码 */} 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | {/* 脚本延迟启动 */} 242 | 243 | 244 | 245 | 246 | {/* 是否显示debug日志 */} 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | {/* 是否自动点击授权录屏权限 */} 259 | 260 | 261 | {/* 收集一轮后自动锁屏 */} 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | {/* 是否锁屏启动关闭弹框提示 */} 283 | 284 | {/* 基本不需要修改的 */} 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | {/* 单脚本使用,无视多任务队列 */} 309 | 310 | 311 | 312 | 313 | {/* 使用模拟手势来实现上下滑动 */} 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | {/* 虚拟按键高度 */} 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | {/* 控件文本配置 */} 334 | 335 | 336 | 337 | 338 | 339 | 340 | ) 341 | 342 | // 创建选项菜单(右上角) 343 | ui.emitter.on("create_options_menu", menu => { 344 | menu.add("全部重置为默认") 345 | menu.add("从配置文件导入") 346 | menu.add("导出到配置文件") 347 | }) 348 | // 监听选项菜单点击 349 | ui.emitter.on("options_item_selected", (e, item) => { 350 | let local_config_path = files.cwd() + '/local_config.cfg' 351 | let runtime_store_path = files.cwd() + '/runtime_store.cfg' 352 | let aesKey = device.getAndroidId() 353 | switch (item.getTitle()) { 354 | case "全部重置为默认": 355 | confirm('确定要将所有配置重置为默认值吗?').then(ok => { 356 | if (ok) { 357 | Object.keys(default_config).forEach(key => { 358 | let defaultValue = default_config[key] 359 | config[key] = defaultValue 360 | storageConfig.put(key, defaultValue) 361 | }) 362 | log('重置默认值') 363 | resetUiValues() 364 | } 365 | }) 366 | break 367 | case "从配置文件导入": 368 | confirm('确定要从local_config.cfg中读取配置吗?').then(ok => { 369 | if (ok) { 370 | try { 371 | if (files.exists(local_config_path)) { 372 | let refillConfigs = function (configStr) { 373 | let local_config = JSON.parse(configStr) 374 | Object.keys(default_config).forEach(key => { 375 | let defaultValue = local_config[key] 376 | if (typeof defaultValue === 'undefined') { 377 | defaultValue = default_config[key] 378 | } 379 | config[key] = defaultValue 380 | storageConfig.put(key, defaultValue) 381 | }) 382 | resetUiValues() 383 | } 384 | let configStr = AesUtil.decrypt(files.read(local_config_path), aesKey) 385 | if (!configStr) { 386 | toastLog('local_config.cfg解密失败, 请尝试输入秘钥') 387 | dialogs.rawInput('请输入秘钥,可通过device.getAndroidId()获取') 388 | .then(key => { 389 | if (key) { 390 | key = key.trim() 391 | configStr = AesUtil.decrypt(files.read(local_config_path), key) 392 | if (configStr) { 393 | refillConfigs(configStr) 394 | } else { 395 | toastLog('秘钥不正确,无法解析') 396 | } 397 | } 398 | }) 399 | } else { 400 | refillConfigs(configStr) 401 | } 402 | } else { 403 | toastLog('local_config.cfg不存在无法导入') 404 | } 405 | } catch (e) { 406 | toastLog(e) 407 | } 408 | } 409 | }) 410 | break 411 | case "导出到配置文件": 412 | confirm('确定要将配置导出到local_config.cfg吗?此操作会覆盖已有的local_config数据').then(ok => { 413 | if (ok) { 414 | Object.keys(default_config).forEach(key => { 415 | console.verbose(key + ': ' + config[key]) 416 | }) 417 | try { 418 | let configString = AesUtil.encrypt(JSON.stringify(config), aesKey) 419 | files.write(local_config_path, configString) 420 | toastLog('配置信息导出成功,刷新目录即可,local_config.cfg内容已加密仅本机可用,除非告知秘钥') 421 | } catch (e) { 422 | toastLog(e) 423 | } 424 | 425 | } 426 | }) 427 | break 428 | } 429 | e.consumed = true 430 | }) 431 | activity.setSupportActionBar(ui.toolbar) 432 | 433 | ui.viewpager.setTitles(['基本配置', '进阶配置', '控件文本配置']) 434 | ui.tabs.setupWithViewPager(ui.viewpager) 435 | if (config.device_height <= 10 || config.device_width <= 10) { 436 | inputDeviceSize().then(() => resetUiValues()) 437 | } else { 438 | resetUiValues() 439 | } 440 | 441 | 442 | ui.changeDeviceSizeBtn.on('click', () => { 443 | inputDeviceSize().then(() => setDeviceSizeText()) 444 | }) 445 | 446 | 447 | 448 | ui.showDebugLogChkBox.on('click', () => { 449 | config.show_debug_log = ui.showDebugLogChkBox.isChecked() 450 | }) 451 | 452 | ui.showEngineIdChkBox.on('click', () => { 453 | config.show_engine_id = ui.showEngineIdChkBox.isChecked() 454 | }) 455 | 456 | ui.developModeChkBox.on('click', () => { 457 | config.develop_mode = ui.developModeChkBox.isChecked() 458 | }) 459 | 460 | ui.saveLogFileChkBox.on('click', () => { 461 | config.saveLogFile = ui.saveLogFileChkBox.isChecked() 462 | ui.fileSizeContainer.setVisibility(config.saveLogFile ? View.VISIBLE : View.INVISIBLE) 463 | }) 464 | 465 | ui.requestCapturePermissionChkBox.on('click', () => { 466 | config.request_capture_permission = ui.requestCapturePermissionChkBox.isChecked() 467 | }) 468 | 469 | ui.dismissDialogIfLockedChkBox.on('click', () => { 470 | config.dismiss_dialog_if_locked = ui.dismissDialogIfLockedChkBox.isChecked() 471 | }) 472 | 473 | ui.autoLockChkBox.on('click', () => { 474 | let checked = ui.autoLockChkBox.isChecked() 475 | config.auto_lock = checked 476 | ui.lockPositionContainer.setVisibility(checked && !_hasRootPermission ? View.VISIBLE : View.INVISIBLE) 477 | }) 478 | 479 | ui.lockXSeekBar.on('touch', () => { 480 | let precent = ui.lockXSeekBar.getProgress() 481 | let trueVal = parseInt(precent * config.device_width / 100) 482 | ui.lockX.text('' + trueVal) 483 | config.lock_x = trueVal 484 | }) 485 | 486 | ui.lockYSeekBar.on('touch', () => { 487 | let precent = ui.lockYSeekBar.getProgress() 488 | let trueVal = parseInt(precent * config.device_height / 100) 489 | ui.lockY.text('' + trueVal) 490 | config.lock_y = trueVal 491 | }) 492 | 493 | ui.showLockPointConfig.on('click', () => { 494 | Promise.resolve().then(() => { 495 | return dialogs.rawInput('请输入X坐标:', config.lock_x + '') 496 | }).then(x => { 497 | if (x) { 498 | let xVal = parseInt(x) 499 | if (isFinite(xVal)) { 500 | config.lock_x = xVal 501 | } else { 502 | toast('输入值无效') 503 | } 504 | } 505 | }).then(() => { 506 | return dialogs.rawInput('请输入Y坐标:', config.lock_y + '') 507 | }).then(y => { 508 | if (y) { 509 | let yVal = parseInt(y) 510 | if (isFinite(yVal)) { 511 | config.lock_y = yVal 512 | } else { 513 | toast('输入值无效') 514 | } 515 | } 516 | }).then(() => { 517 | ui.lockX.text(config.lock_x + '') 518 | ui.lockXSeekBar.setProgress(parseInt(config.lock_x / config.device_width * 100)) 519 | ui.lockY.text(config.lock_y + '') 520 | ui.lockYSeekBar.setProgress(parseInt(config.lock_y / config.device_height * 100)) 521 | }) 522 | }) 523 | 524 | ui.password.addTextChangedListener( 525 | TextWatcherBuilder(text => { config.password = text + '' }) 526 | ) 527 | 528 | ui.alipayLockPasswordInpt.addTextChangedListener( 529 | TextWatcherBuilder(text => { config.alipay_lock_password = text + '' }) 530 | ) 531 | 532 | ui.isAlipayLockedChkBox.on('click', () => { 533 | config.is_alipay_locked = ui.isAlipayLockedChkBox.isChecked() 534 | ui.alipayLockPasswordContainer.setVisibility(config.is_alipay_locked ? View.VISIBLE : View.GONE) 535 | }) 536 | 537 | 538 | ui.timeoutUnlockInpt.addTextChangedListener( 539 | TextWatcherBuilder(text => { config.timeout_unlock = parseInt(text) }) 540 | ) 541 | 542 | ui.timeoutFindOneInpt.addTextChangedListener( 543 | TextWatcherBuilder(text => { config.timeout_findOne = parseInt(text) }) 544 | ) 545 | 546 | ui.timeoutExistingInpt.addTextChangedListener( 547 | TextWatcherBuilder(text => { config.timeout_existing = parseInt(text) }) 548 | ) 549 | 550 | ui.captureWaitingTimeInpt.addTextChangedListener( 551 | TextWatcherBuilder(text => { config.capture_waiting_time = parseInt(text) }) 552 | ) 553 | 554 | ui.fileSizeInpt.addTextChangedListener( 555 | TextWatcherBuilder(text => { config.back_size = parseInt(text) }) 556 | ) 557 | 558 | // 进阶配置 559 | ui.singleScriptChkBox.on('click', () => { 560 | config.single_script = ui.singleScriptChkBox.isChecked() 561 | }) 562 | ui.scrollDownSpeedInpt.addTextChangedListener( 563 | TextWatcherBuilder(text => { config.scrollDownSpeed = parseInt(text) }) 564 | ) 565 | 566 | ui.useCustomScrollDownChkBox.on('click', () => { 567 | config.useCustomScrollDown = ui.useCustomScrollDownChkBox.isChecked() 568 | ui.scrollDownContainer.setVisibility(config.useCustomScrollDown ? View.VISIBLE : View.INVISIBLE) 569 | ui.bottomHeightContainer.setVisibility(config.useCustomScrollDown ? View.VISIBLE : View.GONE) 570 | }) 571 | 572 | ui.bottomHeightInpt.addTextChangedListener( 573 | TextWatcherBuilder(text => { config.bottomHeight = parseInt(text) }) 574 | ) 575 | 576 | ui.delayStartTimeInpt.addTextChangedListener( 577 | TextWatcherBuilder(text => { config.delayStartTime = parseInt(text) }) 578 | ) 579 | 580 | console.verbose('界面初始化耗时' + (new Date().getTime() - start) + 'ms') 581 | setTimeout(function () { 582 | if (loadingDialog !== null) { 583 | loadingDialog.dismiss() 584 | } 585 | }, 500) 586 | }, 500) 587 | 588 | ui.emitter.on('pause', () => { 589 | Object.keys(default_config).forEach(key => { 590 | let newVal = config[key] 591 | if (typeof newVal !== 'undefined') { 592 | storageConfig.put(key, newVal) 593 | } else { 594 | storageConfig.put(key, default_config[key]) 595 | } 596 | }) 597 | }) 598 | } 599 | -------------------------------------------------------------------------------- /core/CreditRunner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-25 16:46:06 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-11-23 19:07:22 6 | * @Description: 7 | */ 8 | 9 | let { config } = require('../config.js')(runtime, this) 10 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 11 | let commonFunctions = singletonRequire('CommonFunction') 12 | let alipayUnlocker = singletonRequire('AlipayUnlocker') 13 | let widgetUtils = singletonRequire('WidgetUtils') 14 | let logUtils = singletonRequire('LogUtils') 15 | let automator = singletonRequire('Automator') 16 | let floatyUtil = singletonRequire('FloatyUtil') 17 | 18 | function CreditRunner () { 19 | 20 | let _package_name = 'com.eg.android.AlipayGphone' 21 | let _family_regex = /^\+(\d+)$/ 22 | 23 | this.collectFamily = false 24 | 25 | this.openCreditPage = function () { 26 | commonFunctions.launchPackage(_package_name, false) 27 | sleep(500) 28 | if (config.is_alipay_locked) { 29 | alipayUnlocker.unlockAlipay() 30 | } 31 | app.startActivity({ 32 | action: 'VIEW', 33 | data: 'alipays://platformapi/startapp?appId=20000160&url=%2Fwww%2FmyPoints.html', 34 | packageName: _package_name 35 | }) 36 | sleep(500) 37 | floatyUtil.setFloatyText('校验是否有打开确认弹框') 38 | let confirm = widgetUtils.widgetGetOne(/^打开$/, 3000) 39 | if (confirm) { 40 | sleep(500) 41 | floatyUtil.setFloatyInfo( 42 | { 43 | x: confirm.bounds().centerX(), 44 | y: confirm.bounds().centerY() 45 | }, 46 | '找到了确认打开按钮') 47 | automator.clickCenter(confirm) 48 | } else { 49 | floatyUtil.setFloatyText('没有打开确认弹框') 50 | } 51 | } 52 | 53 | 54 | /** 55 | * 判断比例 是不是正方形 56 | * @param {目标控件} bounds 57 | */ 58 | this.isCollectableBall = function (bounds) { 59 | if (bounds) { 60 | let flag = Math.abs(bounds.width() - bounds.height()) <= 10 && bounds.width() > 30 61 | logUtils.debugInfo(['校验控件形状是否符合:[{}, {}] result: {}', bounds.width(), bounds.height(), flag]) 62 | return flag 63 | } 64 | return false 65 | } 66 | 67 | this.canCollect = function (val) { 68 | let bounds = val.bounds() 69 | return this.isCollectableBall(bounds) 70 | } 71 | 72 | this.collectCredits = function (position, regex) { 73 | // 等待稳定 74 | sleep(1000) 75 | let widgets = widgetUtils.widgetGetAll(regex, null, true) 76 | let collected = true 77 | while (widgets && collected) { 78 | logUtils.logInfo(['总数:{}', widgets.target.length]) 79 | let targets = widgets.target 80 | let isDesc = widgets.isDesc 81 | let totalCollect = 0 82 | targets.forEach(val => { 83 | let contentInfo = isDesc ? val.desc() : val.text() 84 | if (this.canCollect(val)) { 85 | automator.clickCenter(val) 86 | logUtils.logInfo([ 87 | 'value: {}', contentInfo 88 | ]) 89 | totalCollect += parseInt(regex.exec(contentInfo)[1]) 90 | sleep(500) 91 | } 92 | }) 93 | logUtils.infoLog(['{} 总共领取:「{}」分', position, totalCollect]) 94 | collected = totalCollect > 0 95 | if (collected) { 96 | sleep(1000) 97 | // 再次检测, 缩短检测超时时间为两秒 98 | widgets = widgetUtils.widgetGetAll(regex, 2000, true) 99 | } 100 | } 101 | } 102 | 103 | this.checkAndCollect = function () { 104 | floatyUtil.setFloatyTextColor('#00ff00') 105 | floatyUtil.setFloatyText('等待会员积分控件') 106 | let target = widgetUtils.widgetGetOne(/^\s*今日签到.*(\d+)$/) 107 | if (target) { 108 | floatyUtil.setFloatyInfo( 109 | { 110 | x: target.bounds().centerX(), 111 | y: target.bounds().centerY() 112 | }, 113 | '等待会员积分控件成功,准备进入签到页面' 114 | ) 115 | automator.clickCenter(target) 116 | sleep(1000) 117 | target = widgetUtils.widgetGetOne('签到领积分') 118 | if (target) { 119 | floatyUtil.setFloatyInfo( 120 | { 121 | x: target.bounds().centerX(), 122 | y: target.bounds().centerY() 123 | }, 124 | '进入签到页面成功' 125 | ) 126 | sleep(200) 127 | automator.clickCenter(target) 128 | sleep(500) 129 | automator.back() 130 | } 131 | } else { 132 | floatyUtil.setFloatyTextColor('#ff0000') 133 | floatyUtil.setFloatyText('未找到待领取积分') 134 | logUtils.logInfo(['未找到待领取积分'], true) 135 | } 136 | sleep(500) 137 | floatyUtil.setFloatyTextColor('#00ff00') 138 | floatyUtil.setFloatyText('检测是否有今日支付积分') 139 | // 今日支付积分 140 | target = widgetUtils.widgetGetOne('全部领取') 141 | if (target) { 142 | floatyUtil.setFloatyInfo( 143 | { 144 | x: target.bounds().centerX(), 145 | y: target.bounds().centerY() 146 | }, 147 | '找到了支付积分,准备收取' 148 | ) 149 | automator.clickCenter(target) 150 | } else { 151 | floatyUtil.setFloatyTextColor('#ff0000') 152 | floatyUtil.setFloatyText('未找到今日支付积分') 153 | } 154 | sleep(500) 155 | } 156 | 157 | this.checkFamilyCredit = function () { 158 | sleep(2000) 159 | floatyUtil.setFloatyText('等待家庭积分控件') 160 | let limit = 5 161 | let target = widgetUtils.widgetGetOne(/家庭积分.*\d+|家庭积分待领取/) 162 | sleep(200) 163 | if (target) { 164 | floatyUtil.setFloatyInfo( 165 | { 166 | x: target.bounds().centerX(), 167 | y: target.bounds().centerY() 168 | }, 169 | '家庭积分' 170 | ) 171 | automator.clickCenter(target) 172 | if (widgetUtils.widgetWaiting('.*家庭共享积分.*', limit === 0 ? 2000 : null)) { 173 | floatyUtil.setFloatyText('进入家庭积分页面成功,等待3秒福袋动画结束') 174 | sleep(2000) 175 | this.collectFamily = true 176 | this.collectCredits('家庭积分', _family_regex) 177 | automator.back() 178 | } else { 179 | floatyUtil.setFloatyTextColor('#ff0000') 180 | floatyUtil.setFloatyText('进入家庭积分页面失败') 181 | logUtils.logInfo(['未找到待领取家庭积分'], true) 182 | } 183 | } else { 184 | floatyUtil.setFloatyText('未找到待领取的家庭积分') 185 | } 186 | sleep(500) 187 | } 188 | 189 | 190 | this.exec = function () { 191 | floatyUtil.init() 192 | floatyUtil.setFloatyPosition(400, 400) 193 | floatyUtil.setFloatyText('准备打开领取积分页面') 194 | this.openCreditPage() 195 | floatyUtil.setFloatyText('准备领取家庭积分') 196 | this.checkFamilyCredit() 197 | floatyUtil.setFloatyText('准备领取积分') 198 | this.checkAndCollect() 199 | floatyUtil.setFloatyText('领取完毕') 200 | commonFunctions.minimize() 201 | } 202 | } 203 | 204 | 205 | module.exports = new CreditRunner() 206 | -------------------------------------------------------------------------------- /extends/ExternalUnlockDevice-demo.js: -------------------------------------------------------------------------------- 1 | let { config: _config } = require('../config.js')(runtime, this) 2 | module.exports = function (obj) { 3 | this.__proto__ = obj 4 | 5 | this.unlock = function (password) { 6 | // 自行定制化解锁方式,这里展示PIN密码的解锁 7 | if (typeof password !== 'string') throw new Error('密码应为字符串!') 8 | // 模拟按键 9 | let button = null 10 | for (let i = 0; i < password.length; i++) { 11 | let key_id = 'com.android.systemui:id/key' + password[i] 12 | if ((button = id(key_id).findOne(_config.timeout_findOne)) !== null) { 13 | button.click() 14 | } 15 | sleep(100) 16 | } 17 | // 解锁完毕后返回check_lock方法,模块自动判断是否成功 18 | return this.check_lock() 19 | } 20 | 21 | 22 | /** 23 | * 一般情况下仅仅重写unlock即可,点亮、滑动、校验等等都在Unlock中实现了通用方式 24 | * 但是如果机型特殊,可以直接重写run_unlock()方法 25 | * 在run_unlock中编写自己的解锁方式 26 | */ 27 | this.run_unlock = function () { 28 | // 在这个里面编写解锁逻辑 29 | } 30 | 31 | /** 32 | * 又或者只有某一个小方法不适用,可以只修改对应的方法即可 33 | * 具体方法见Unlock中定义的方法 比如failed、check_unlock、swipe_layer等等 34 | */ 35 | 36 | } -------------------------------------------------------------------------------- /extends/LockScreen-demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-27 23:46:00 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-27 23:50:55 6 | * @Description: 7 | */ 8 | let { config: _config } = require('../config.js')(runtime, this) 9 | 10 | module.exports = function () { 11 | // MIUI 12 新控制中心 12 | swipe(800, 10, 800, 1000, 500) 13 | sleep(500) 14 | // 点击锁屏按钮 15 | click(parseInt(_config.lock_x), parseInt(_config.lock_y)) 16 | } -------------------------------------------------------------------------------- /lib/AesUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-01-08 17:07:28 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-01-10 17:48:09 6 | * @Description: AES加密工具 7 | */ 8 | let CryptoJS = require('./crypto-js.js') 9 | 10 | function AesUtil () { 11 | this.key = device.getAndroidId() 12 | } 13 | 14 | AesUtil.prototype.encrypt = function (message, key) { 15 | key = key || this.key 16 | if (key.length !== 8 && key.length !== 16 && key.length !== 32) { 17 | console.error('密码长度不正确必须为8/16/32位') 18 | return null 19 | } 20 | return CryptoJS.AES.encrypt(message, CryptoJS.enc.Utf8.parse(key), { 21 | mode: CryptoJS.mode.ECB, 22 | padding: CryptoJS.pad.Pkcs7 23 | }) 24 | } 25 | 26 | AesUtil.prototype.decrypt = function(encrypt, key) { 27 | key = key || this.key 28 | if (key.length !== 8 && key.length !== 16 && key.length !== 32) { 29 | console.error('密码长度不正确必须为8/16/32位') 30 | return null 31 | } 32 | try { 33 | return CryptoJS.AES.decrypt(encrypt, CryptoJS.enc.Utf8.parse(key), { 34 | mode: CryptoJS.mode.ECB, 35 | padding: CryptoJS.pad.Pkcs7 36 | }).toString(CryptoJS.enc.Utf8) 37 | } catch (e) { 38 | console.error('秘钥不正确无法解密') 39 | return null 40 | } 41 | } 42 | 43 | module.exports = new AesUtil() -------------------------------------------------------------------------------- /lib/Base64.js: -------------------------------------------------------------------------------- 1 | let Base64 = { 2 | 3 | // private property 4 | _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 5 | 6 | // public method for encoding 7 | encode: function (input) { 8 | var output = ""; 9 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 10 | var i = 0; 11 | 12 | input = Base64._utf8_encode(input); 13 | 14 | while (i < input.length) { 15 | 16 | chr1 = input.charCodeAt(i++); 17 | chr2 = input.charCodeAt(i++); 18 | chr3 = input.charCodeAt(i++); 19 | 20 | enc1 = chr1 >> 2; 21 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 22 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 23 | enc4 = chr3 & 63; 24 | 25 | if (isNaN(chr2)) { 26 | enc3 = enc4 = 64; 27 | } else if (isNaN(chr3)) { 28 | enc4 = 64; 29 | } 30 | 31 | output = output + 32 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + 33 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 34 | 35 | } 36 | 37 | return output; 38 | }, 39 | 40 | // public method for decoding 41 | decode: function (input) { 42 | var output = ""; 43 | var chr1, chr2, chr3; 44 | var enc1, enc2, enc3, enc4; 45 | var i = 0; 46 | 47 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 48 | 49 | while (i < input.length) { 50 | 51 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 52 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 53 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 54 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 55 | 56 | chr1 = (enc1 << 2) | (enc2 >> 4); 57 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 58 | chr3 = ((enc3 & 3) << 6) | enc4; 59 | 60 | output = output + String.fromCharCode(chr1); 61 | 62 | if (enc3 != 64) { 63 | output = output + String.fromCharCode(chr2); 64 | } 65 | if (enc4 != 64) { 66 | output = output + String.fromCharCode(chr3); 67 | } 68 | 69 | } 70 | 71 | output = Base64._utf8_decode(output); 72 | 73 | return output; 74 | 75 | }, 76 | 77 | // private method for UTF-8 encoding 78 | _utf8_encode: function (string) { 79 | string = string.replace(/\r\n/g, "\n"); 80 | var utftext = ""; 81 | 82 | for (var n = 0; n < string.length; n++) { 83 | 84 | var c = string.charCodeAt(n); 85 | 86 | if (c < 128) { 87 | utftext += String.fromCharCode(c); 88 | } 89 | else if ((c > 127) && (c < 2048)) { 90 | utftext += String.fromCharCode((c >> 6) | 192); 91 | utftext += String.fromCharCode((c & 63) | 128); 92 | } 93 | else { 94 | utftext += String.fromCharCode((c >> 12) | 224); 95 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 96 | utftext += String.fromCharCode((c & 63) | 128); 97 | } 98 | 99 | } 100 | 101 | return utftext; 102 | }, 103 | 104 | // private method for UTF-8 decoding 105 | _utf8_decode: function (utftext) { 106 | var string = ""; 107 | var i = 0; 108 | var c = c1 = c2 = 0; 109 | 110 | while (i < utftext.length) { 111 | 112 | c = utftext.charCodeAt(i); 113 | 114 | if (c < 128) { 115 | string += String.fromCharCode(c); 116 | i++; 117 | } 118 | else if ((c > 191) && (c < 224)) { 119 | c2 = utftext.charCodeAt(i + 1); 120 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 121 | i += 2; 122 | } 123 | else { 124 | c2 = utftext.charCodeAt(i + 1); 125 | c3 = utftext.charCodeAt(i + 2); 126 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 127 | i += 3; 128 | } 129 | 130 | } 131 | 132 | return string; 133 | } 134 | 135 | } 136 | 137 | module.exports = Base64 -------------------------------------------------------------------------------- /lib/DateUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-10 19:41:12 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-25 20:36:33 6 | * @Description: 日期格式化工具 7 | */ 8 | module.exports = function (date, fmt) { 9 | if (typeof fmt === 'undefined') { 10 | fmt = "yyyy-MM-dd HH:mm:ss" 11 | } 12 | 13 | var o = { 14 | 'M+': date.getMonth() + 1, // 月份 15 | 'd+': date.getDate(), // 日 16 | 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时 17 | 'H+': date.getHours(), // 小时 18 | 'm+': date.getMinutes(), // 分 19 | 's+': date.getSeconds(), // 秒 20 | 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 21 | 'S': date.getMilliseconds() // 毫秒 22 | } 23 | var week = { 24 | '0': '\u65e5', 25 | '1': '\u4e00', 26 | '2': '\u4e8c', 27 | '3': '\u4e09', 28 | '4': '\u56db', 29 | '5': '\u4e94', 30 | '6': '\u516d' 31 | } 32 | if (/(y+)/.test(fmt)) { 33 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 34 | } 35 | if (/(E+)/.test(fmt)) { 36 | fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + week[date.getDay() + '']) 37 | } 38 | for (var k in o) { 39 | if (new RegExp('(' + k + ')').test(fmt)) { 40 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) 41 | } 42 | } 43 | return fmt 44 | } -------------------------------------------------------------------------------- /lib/SingletonRequirer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-25 20:25:10 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-05-27 23:38:15 6 | * @Description: 导入单例模式的工具类 7 | */ 8 | module.exports = function (_runtime_, scope) { 9 | 10 | if (typeof scope.singletonRequirer === 'undefined') { 11 | scope.singletonRequirerInfo = { 12 | useCount: 0, 13 | moduleMap: {} 14 | } 15 | 16 | scope.singletonRequirer = (moduleName, showRequireInfo) => { 17 | // console.verbose('准备导入模块[' + moduleName + ']') 18 | if (typeof scope[moduleName] === 'undefined') { 19 | // console.verbose(moduleName + '暂时不存在,需要require导入') 20 | let prototypes = checkAndRequireRealModule(moduleName) 21 | if (!prototypes) { 22 | console.error('导入模块:[' + moduleName + ']失败,请检查代码') 23 | toast('导入模块:[' + moduleName + ']失败,请检查代码') 24 | exit() 25 | } 26 | scope[moduleName] = prototypes 27 | scope.singletonRequirerInfo.moduleMap[moduleName] = { 28 | useCount: 0 29 | } 30 | } else { 31 | // console.verbose(moduleName + '已经存在, 已调用次数:' + scope.singletonRequirerInfo.moduleMap[moduleName].useCount) 32 | } 33 | scope.singletonRequirerInfo.moduleMap[moduleName].useCount += 1 34 | 35 | if (showRequireInfo) { 36 | console.info('singletonRequirer调用次数:' + scope.singletonRequirerInfo.useCount) 37 | Object.keys(scope.singletonRequirerInfo.moduleMap).forEach(key => { 38 | console.info('module: ' + key + ' 调用次数:' + scope.singletonRequirerInfo.moduleMap[key].useCount) 39 | }) 40 | } 41 | return scope[moduleName] 42 | } 43 | } 44 | scope.singletonRequirerInfo.useCount += 1 45 | return scope.singletonRequirer 46 | } 47 | 48 | function tryRequire (filePath) { 49 | try { 50 | return require(filePath) 51 | } catch (e) { 52 | console.warn('加载[' + filePath + ']异常:' + e) 53 | } 54 | return null 55 | } 56 | 57 | function checkAndRequireRealModule (moduleName) { 58 | // console.verbose('准备导入模块:' + moduleName) 59 | let fileName = './prototype/' + moduleName + '.js' 60 | // 模块名称匹配 61 | let tryRequired = tryRequire(fileName) 62 | if (tryRequired) { 63 | return tryRequired 64 | } else { 65 | console.verbose('[' + fileName + ']不存在') 66 | } 67 | // 不小心输入了.js结尾 68 | fileName = './prototype/' + moduleName 69 | tryRequired = tryRequire(fileName) 70 | if (tryRequired) { 71 | return tryRequired 72 | } else { 73 | console.verbose('[' + fileName + ']不存在') 74 | } 75 | // lib下查找 模块名称匹配的 76 | fileName = './' + moduleName + '.js' 77 | tryRequired = tryRequire(fileName) 78 | if (tryRequired) { 79 | return tryRequired 80 | } else { 81 | console.verbose('[' + fileName + ']不存在') 82 | } 83 | // lib下查找 文件名称匹配的 84 | fileName = './' + moduleName 85 | tryRequired = tryRequire(fileName) 86 | if (tryRequired) { 87 | return tryRequired 88 | } else { 89 | console.verbose('[' + fileName + ']不存在') 90 | } 91 | // 都无匹配 92 | return null 93 | } -------------------------------------------------------------------------------- /lib/Unlock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-11-05 09:12:00 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-10-23 18:48:15 6 | * @Description: 7 | */ 8 | let { config: _config, storageName: _storageName } = require('../config.js')(runtime, this) 9 | let singletonRequire = require('./SingletonRequirer.js')(runtime, this) 10 | let automator = singletonRequire('Automator') 11 | let { logInfo, errorInfo, warnInfo, debugInfo } = singletonRequire('LogUtils') 12 | let FileUtils = singletonRequire('FileUtils') 13 | let _commonFunctions = singletonRequire('CommonFunction') 14 | let _runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 15 | let ExternalUnlockDevice = files.exists(FileUtils.getCurrentWorkPath() + '/extends/ExternalUnlockDevice.js') ? require('../extends/ExternalUnlockDevice.js') : null 16 | 17 | if (ExternalUnlockDevice) { 18 | logInfo('使用自定义解锁模块') 19 | } else { 20 | logInfo('使用内置解锁模块') 21 | } 22 | 23 | let XIAOMI_MIX2S = function (obj) { 24 | this.__proto__ = obj 25 | // 图形密码解锁 26 | this.unlock_pattern = function (password) { 27 | if (typeof password !== 'string') throw new Error('密码应为字符串!') 28 | let lockBounds = id('com.android.systemui:id/lockPatternView') 29 | .findOne(_config.timeout_findOne) 30 | .bounds() 31 | let boxWidth = (lockBounds.right - lockBounds.left) / 3 32 | let boxHeight = (lockBounds.bottom - lockBounds.top) / 3 33 | let positions = password.split('').map(p => { 34 | let checkVal = parseInt(p) - 1 35 | return { r: parseInt(checkVal / 3), c: parseInt(checkVal % 3) } 36 | }).map(p => { 37 | return [parseInt(lockBounds.left + (0.5 + p.c) * boxWidth), parseInt(lockBounds.top + (0.5 + p.r) * boxHeight)] 38 | }) 39 | gesture(220 * positions.length, positions) 40 | return this.check_unlock() 41 | } 42 | 43 | // 密码解锁(仅ROOT可用) 44 | this.unlock_password = function (password) { 45 | if (typeof password !== 'string') throw new Error('密码应为字符串!') 46 | // 直接在控件中输入密码 47 | setText(0, password) 48 | // 执行确认操作 49 | KeyCode('KEYCODE_ENTER') 50 | return this.check_unlock() 51 | } 52 | 53 | // PIN解锁 54 | this.unlock_pin = function (password) { 55 | if (typeof password !== 'string') throw new Error('密码应为字符串!') 56 | // 模拟按键 57 | let button = null 58 | for (let i = 0; i < password.length; i++) { 59 | let key_id = 'com.android.systemui:id/key' + password[i] 60 | if ((button = id(key_id).findOne(_config.timeout_findOne)) !== null) { 61 | button.click() 62 | } 63 | sleep(100) 64 | } 65 | return this.check_unlock() 66 | } 67 | 68 | // 判断解锁方式并解锁 69 | this.unlock = function (password) { 70 | if (typeof password === 'undefined' || password === null || password.length === 0) { 71 | errorInfo('密码为空:' + JSON.stringify(password)) 72 | throw new Error('密码为空!') 73 | } 74 | if (id('com.android.systemui:id/lockPatternView').exists()) { 75 | return this.unlock_pattern(password) 76 | } else if (id('com.android.systemui:id/passwordEntry').exists()) { 77 | return this.unlock_password(password) 78 | } else if (idMatches('com.android.systemui:id/(fixedP|p)inEntry').exists()) { 79 | return this.unlock_pin(password) 80 | } else { 81 | logInfo( 82 | '识别锁定方式失败,型号:' + device.brand + ' ' + device.product + ' ' + device.release 83 | ) 84 | return this.check_unlock() 85 | } 86 | } 87 | } 88 | 89 | 90 | const MyDevice = ExternalUnlockDevice || XIAOMI_MIX2S 91 | 92 | function Unlocker () { 93 | const _km = context.getSystemService(context.KEYGUARD_SERVICE) 94 | 95 | this.relock = false 96 | this.reTry = 0 97 | 98 | // 设备是否锁屏 99 | this.is_locked = function () { 100 | return _km.inKeyguardRestrictedInputMode() 101 | } 102 | 103 | // 设备是否加密 104 | this.is_passwd = function () { 105 | return _km.isKeyguardSecure() 106 | } 107 | 108 | // 解锁失败 109 | this.failed = function () { 110 | automator.back() 111 | this.reTry++ 112 | if (this.reTry > 3) { 113 | logInfo('解锁失败达到三次,停止运行') 114 | if (_config.auto_set_brightness) { 115 | device.setBrightnessMode(1) 116 | } 117 | _runningQueueDispatcher.removeRunningTask() 118 | this.saveNeedRelock(true) 119 | engines.myEngine().forceStop() 120 | } else { 121 | let sleepMs = 5000 * this.reTry 122 | logInfo('解锁失败,' + sleepMs + 'ms之后重试') 123 | sleep(sleepMs) 124 | this.run_unlock() 125 | } 126 | } 127 | 128 | // 检测是否解锁成功 129 | this.check_unlock = function () { 130 | sleep(_config.timeout_unlock) 131 | if ( 132 | textContains('重新').exists() || 133 | textContains('重试').exists() || 134 | textContains('错误').exists() 135 | ) { 136 | logInfo('密码错误') 137 | return false 138 | } 139 | return !this.is_locked() 140 | } 141 | 142 | // 唤醒设备 143 | this.wakeup = function () { 144 | let limit = 3 145 | while (!device.isScreenOn() && limit-- > 0) { 146 | device.wakeUp() 147 | sleep(_config.timeout_unlock) 148 | } 149 | if (!device.isScreenOn()) { 150 | console.warn('isScreenOn判定失效,无法确认是否已亮屏。直接尝试后续解锁操作') 151 | } 152 | if (this.relock && _config.auto_set_brightness) { 153 | logInfo('设置显示亮度为最低,关闭自动亮度') 154 | // 设置最低亮度 同时关闭自动亮度 155 | device.setBrightnessMode(0) 156 | device.setBrightness(0) 157 | } 158 | } 159 | 160 | // 划开图层 161 | this.swipe_layer = function () { 162 | let x = parseInt(_config.device_width * 0.2) 163 | gesture(320, [x, parseInt(_config.device_height * 0.8)], [x, parseInt(_config.device_height * 0.3)]) 164 | sleep(_config.timeout_unlock) 165 | } 166 | 167 | // 执行解锁操作 168 | this.run_unlock = function () { 169 | this.relock = this.relock || this.getRelockInfo() 170 | // 如果已经解锁则返回 171 | if (!this.is_locked()) { 172 | logInfo('已解锁') 173 | if (this.relock === true) { 174 | logInfo('前置校验需要重新锁定屏幕') 175 | } else { 176 | logInfo('不需要重新锁定屏幕') 177 | this.relock = false 178 | } 179 | return true 180 | } 181 | // 校验设备姿态 是否在裤兜内 182 | if (_config.check_device_posture) { 183 | let sensorInfo = _commonFunctions.getDistanceAndGravity(1000) 184 | if (sensorInfo.z < (_config.posture_threshod_z || 6) 185 | && (!_config.check_distance || sensorInfo.distance < 4)) { 186 | _commonFunctions.setUpAutoStart(5) 187 | warnInfo('当前设备可能在裤兜内,5分钟后尝试') 188 | _runningQueueDispatcher.removeRunningTask() 189 | engines.myEngine().forceStop() 190 | } 191 | } 192 | this.relock = true 193 | _config.notNeedRelock = false 194 | logInfo('需要重新锁定屏幕') 195 | // 首先点亮屏幕 196 | this.wakeup() 197 | // 打开滑动层 198 | this.swipe_layer() 199 | // 如果有锁屏密码则输入密码 200 | if (this.is_passwd() && !this.unlock(_config.password)) { 201 | // 如果解锁失败 202 | this.failed() 203 | } else { 204 | this.saveNeedRelock() 205 | if (_config.dismiss_dialog_if_locked) { 206 | // 锁屏状态下启动不再弹框倒计时 207 | _commonFunctions.getAndUpdateDismissReason('screen_locked') 208 | } 209 | } 210 | } 211 | 212 | this.saveNeedRelock = function (notRelock) { 213 | this.relock = this.relock || this.getRelockInfo() 214 | if (notRelock || _config.notNeedRelock) { 215 | this.relock = false 216 | } 217 | let storage = storages.create(_storageName) 218 | debugInfo('保存是否需要重新锁屏:' + this.relock) 219 | storage.put('needRelock', JSON.stringify({needRelock: this.relock && this.relock, timeout: new Date().getTime() + 30000 })) 220 | } 221 | 222 | this.getRelockInfo = function () { 223 | let storage = storages.create(_storageName) 224 | let needRelock = storage.get('needRelock') 225 | if (needRelock) { 226 | needRelock = JSON.parse(needRelock) 227 | if (needRelock && new Date().getTime() <= needRelock.timeout) { 228 | return needRelock.needRelock 229 | } 230 | } 231 | return false 232 | } 233 | } 234 | 235 | const _unlocker = new MyDevice(new Unlocker()) 236 | module.exports = { 237 | exec: function () { 238 | _unlocker.reTry = 0 239 | _unlocker.run_unlock() 240 | }, 241 | needRelock: function () { 242 | logInfo('是否需要重新锁定屏幕:' + _unlocker.relock) 243 | return _unlocker.relock 244 | }, 245 | saveNeedRelock: function (notRelock) { 246 | _unlocker.saveNeedRelock(notRelock) 247 | }, 248 | unlocker: _unlocker 249 | } 250 | -------------------------------------------------------------------------------- /lib/color-region-center.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyJiangWJ/Alipay-Credits/1b51e49ab2565af5931195471701eafc7fd72be1/lib/color-region-center.dex -------------------------------------------------------------------------------- /lib/download.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyJiangWJ/Alipay-Credits/1b51e49ab2565af5931195471701eafc7fd72be1/lib/download.dex -------------------------------------------------------------------------------- /lib/prototype/AlipayUnlocker.js: -------------------------------------------------------------------------------- 1 | let { config: _config } = require('../../config.js')(runtime, this) 2 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 3 | let WidgetUtils = singletonRequire('WidgetUtils') 4 | let automator = singletonRequire('Automator') 5 | 6 | const AlipayUnlocker = function () { 7 | } 8 | AlipayUnlocker.prototype.drawGestureByPassword = function (lockBounds) { 9 | let password = _config.alipay_lock_password 10 | let boxWidth = (lockBounds.right - lockBounds.left) / 3 11 | let boxHeight = (lockBounds.bottom - lockBounds.top) / 3 12 | let positions = password.split('').map(p => { 13 | let checkVal = parseInt(p) - 1 14 | return { r: parseInt(checkVal / 3), c: parseInt(checkVal % 3) } 15 | }).map(p => { 16 | return [parseInt(lockBounds.left + (0.5 + p.c) * boxWidth), parseInt(lockBounds.top + (0.5 + p.r) * boxHeight)] 17 | }) 18 | gesture(220 * positions.length, positions) 19 | } 20 | 21 | AlipayUnlocker.prototype.unlockAlipay = function () { 22 | let gestureButton = WidgetUtils.widgetGetOne('验证手势', 2000) 23 | if (gestureButton) { 24 | automator.clickCenter(gestureButton) 25 | sleep(500) 26 | } 27 | let lockView = WidgetUtils.widgetGetById('.*lockView.*') 28 | if (lockView) { 29 | this.drawGestureByPassword(lockView.bounds()) 30 | } 31 | } 32 | 33 | module.exports = new AlipayUnlocker() -------------------------------------------------------------------------------- /lib/prototype/Automator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-25 20:37:31 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-05-09 07:46:51 6 | * @Description: 7 | */ 8 | let { config: _config } = require('../../config.js')(runtime, this) 9 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 10 | let _logUtils = singletonRequire('LogUtils') 11 | let FileUtils = singletonRequire('FileUtils') 12 | let customLockScreen = files.exists(FileUtils.getCurrentWorkPath() + '/extends/LockScreen.js') ? require('../../extends/LockScreen.js') : null 13 | 14 | 15 | const hasRootPermission = function () { 16 | return files.exists("/sbin/su") || files.exists("/system/xbin/su") || files.exists("/system/bin/su") 17 | } 18 | 19 | const _automator = (device.sdkInt < 24 || hasRootPermission()) ? new Automation_root() : new Automation() 20 | 21 | 22 | 23 | module.exports = { 24 | click: function (x, y) { 25 | return _automator.click(x, y) 26 | }, 27 | clickCenter: function (obj) { 28 | return _automator.click(obj.bounds().centerX(), obj.bounds().centerY()) 29 | }, 30 | swipe: function (x1, y1, x2, y2, duration) { 31 | return _automator.swipe(x1, y1, x2, y2, duration) 32 | }, 33 | gesture: function (duration, points) { 34 | return _automator.gesture(duration, points) 35 | }, 36 | back: function () { 37 | return _automator.back() 38 | }, 39 | lockScreen: function () { 40 | return _automator.lockScreen() 41 | }, 42 | scrollDown: function (speed) { 43 | if (_config.useCustomScrollDown) { 44 | return _automator.scrollDown(speed) 45 | } else { 46 | return scrollDown() 47 | } 48 | }, 49 | scrollUp: function(speed) { 50 | _automator.scrollUp(speed) 51 | }, 52 | scrollUpAndDown: function (speed) { 53 | if (_config.useCustomScrollDown) { 54 | return _automator.scrollUpAndDown(speed) 55 | } else { 56 | let deviceHeight = _config.device_height || 1900 57 | // 手势下拉 58 | _automator.swipe(400, parseInt(deviceHeight / 3), 600, parseInt(deviceHeight / 3 * 2), 150) 59 | sleep(200) 60 | scrollDown() 61 | } 62 | }, 63 | clickBack: function (forceBack) { 64 | return _automator.clickBack(forceBack) 65 | }, 66 | clickClose: function () { 67 | return _automator.clickClose() 68 | }, 69 | enterFriendList: function () { 70 | return _automator.enterFriendList() 71 | } 72 | } 73 | 74 | function CommonAutomation () { 75 | this.scrollDown = function (speed) { 76 | let millis = speed || _config.scrollDownSpeed || 500 77 | let deviceHeight = _config.device_height || 1900 78 | let bottomHeight = _config.bottomHeight || 250 79 | let x = parseInt(_config.device_width / 2) 80 | this.swipe(x, deviceHeight - bottomHeight, x + 100, parseInt(deviceHeight / 5), millis) 81 | } 82 | 83 | this.scrollUp = function (speed) { 84 | let millis = parseInt((speed || _config.scrollDownSpeed || 500) / 2) 85 | let deviceHeight = _config.device_height || 1900 86 | let x = parseInt(_config.device_width / 2) 87 | // 下拉 88 | this.swipe(x, parseInt(deviceHeight / 3), x + 100, parseInt(deviceHeight / 3 * 2), millis) 89 | } 90 | 91 | this.scrollUpAndDown = function (speed) { 92 | let millis = parseInt((speed || _config.scrollDownSpeed || 500) / 2) 93 | 94 | let deviceHeight = _config.device_height || 1900 95 | let bottomHeight = _config.bottomHeight || 250 96 | let x = parseInt(_config.device_width / 2) 97 | // 下拉 98 | this.swipe(x, parseInt(deviceHeight / 3), x + 100, parseInt(deviceHeight / 3 * 2), millis) 99 | sleep(millis + 20) 100 | this.swipe(x, deviceHeight - bottomHeight, x + 100, parseInt(deviceHeight / 5), millis) 101 | } 102 | 103 | this.clickBack = function (forceBack) { 104 | let hasButton = false 105 | if (descEndsWith('返回').exists()) { 106 | descEndsWith('返回') 107 | .findOne(_config.timeout_findOne) 108 | .click() 109 | hasButton = true 110 | } else if (textEndsWith('返回').exists()) { 111 | textEndsWith('返回') 112 | .findOne(_config.timeout_findOne) 113 | .click() 114 | hasButton = true 115 | } else if (forceBack) { 116 | this.back() 117 | } 118 | if (hasButton) { 119 | sleep(200) 120 | } 121 | return hasButton 122 | } 123 | 124 | this.clickClose = function () { 125 | let hasButton = false 126 | if (descEndsWith('关闭').exists()) { 127 | descEndsWith('关闭') 128 | .findOne(_config.timeout_findOne) 129 | .click() 130 | hasButton = true 131 | } else if (textEndsWith('关闭').exists()) { 132 | textEndsWith('关闭') 133 | .findOne(_config.timeout_findOne) 134 | .click() 135 | hasButton = true 136 | } 137 | if (hasButton) { 138 | sleep(200) 139 | } 140 | return hasButton 141 | } 142 | 143 | this.enterFriendList = function (tryCount) { 144 | tryCount = tryCount || 1 145 | if (descEndsWith(_config.enter_friend_list_ui_content).exists()) { 146 | descEndsWith(_config.enter_friend_list_ui_content) 147 | .findOne(_config.timeout_findOne) 148 | .click() 149 | } else if (textEndsWith(_config.enter_friend_list_ui_content).exists()) { 150 | textEndsWith(_config.enter_friend_list_ui_content) 151 | .findOne(_config.timeout_findOne) 152 | .click() 153 | } else { 154 | if (tryCount > 3) { 155 | return 156 | } 157 | _logUtils.warnInfo(['未找到 查看更多好友 等待一秒钟后重试, 尝试次数:{}', tryCount]) 158 | // 未找到查看更多好友,等待1秒钟后重试 159 | sleep(1000) 160 | this.enterFriendList(++tryCount) 161 | } 162 | sleep(200) 163 | } 164 | } 165 | function Automation_root () { 166 | CommonAutomation.call(this) 167 | 168 | this.check_root = function () { 169 | if (!(files.exists("/sbin/su") || files.exists("/system/xbin/su") || files.exists("/system/bin/su"))) throw new Error("未获取ROOT权限") 170 | } 171 | 172 | this.click = function (x, y) { 173 | this.check_root() 174 | return (shell("input tap " + x + " " + y, true).code === 0) 175 | } 176 | 177 | this.swipe = function (x1, y1, x2, y2, duration) { 178 | this.check_root() 179 | return (shell("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + duration, true).code === 0) 180 | } 181 | 182 | this.gesture = function (duration, points) { 183 | this.check_root() 184 | let len = points.length, 185 | step = duration / len, 186 | start = points.shift() 187 | 188 | // 使用 RootAutomator 模拟手势,仅适用于安卓5.0及以上 189 | let ra = new RootAutomator() 190 | ra.touchDown(start[0], start[1]) 191 | sleep(step) 192 | points.forEach(function (el) { 193 | ra.touchMove(el[0], el[1]) 194 | sleep(step) 195 | }) 196 | ra.touchUp() 197 | ra.exit() 198 | return true 199 | } 200 | 201 | this.back = function () { 202 | this.check_root() 203 | return (shell("input keyevent KEYCODE_BACK", true).code === 0) 204 | } 205 | 206 | this.lockScreen = function () { 207 | return (shell("input keyevent 26", true).code === 0) 208 | } 209 | 210 | } 211 | 212 | function Automation () { 213 | CommonAutomation.call(this) 214 | 215 | this.click = function (x, y) { 216 | return click(x, y) 217 | } 218 | 219 | this.swipe = function (x1, y1, x2, y2, duration) { 220 | return swipe(x1, y1, x2, y2, duration) 221 | } 222 | 223 | this.gesture = function (duration, points) { 224 | return gesture(duration, points) 225 | } 226 | 227 | this.back = function () { 228 | return back() 229 | } 230 | 231 | /** 232 | * 下拉状态栏,点击锁屏按钮 233 | */ 234 | this.lockScreen = function () { 235 | if (customLockScreen) { 236 | customLockScreen() 237 | } else { 238 | swipe(500, 10, 500, 1000, 500) 239 | swipe(500, 10, 500, 1000, 500) 240 | // 点击锁屏按钮 241 | click(parseInt(_config.lock_x), parseInt(_config.lock_y)) 242 | } 243 | } 244 | 245 | } -------------------------------------------------------------------------------- /lib/prototype/CommonFunction.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-25 20:16:09 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-11-02 09:36:47 6 | * @Description: 通用工具 7 | */ 8 | importClass(android.content.Context) 9 | importClass(android.provider.Settings) 10 | 11 | let { config: _config, storage_name: _storage_name, project_name } = require('../../config.js')(runtime, this) 12 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 13 | let Timers = singletonRequire('Timers') 14 | let _runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 15 | let _FloatyInstance = singletonRequire('FloatyUtil') 16 | let automator = singletonRequire('Automator') 17 | let FileUtils = singletonRequire('FileUtils') 18 | let _logUtils = singletonRequire('LogUtils') 19 | let formatDate = require('../DateUtil.js') 20 | let RUNTIME_STORAGE = _storage_name + "_runtime" 21 | let DISMISS_AWAIT_DIALOG = 'dismissAwaitDialog' 22 | let TIMER_AUTO_START = "timerAutoStart" 23 | 24 | function CommonFunctions () { 25 | 26 | /** 27 | * 校验是否已经拥有无障碍权限 没有自动获取 前提是获取了adb权限 28 | * 原作者:MrChen 原始代码来自Pro商店 29 | * adb授权方法:开启usb调试并使用adb工具连接手机,执行 adb shell pm grant org.autojs.autojspro android.permission.WRITE_SECURE_SETTINGS 30 | * 取消授权 adb shell pm revoke org.autojs.autojspro android.permission.WRITE_SECURE_SETTINGS 31 | * 其中免费版包名为 org.autojs.autojs 32 | * @param {boolean} force 是否强制启用 33 | */ 34 | this.checkAccessibilityService = function (force) { 35 | let packageName = this.getAutoJsPackage() 36 | let requiredService = packageName + '/com.stardust.autojs.core.accessibility.AccessibilityService' 37 | try { 38 | let enabledServices = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) 39 | _logUtils.debugInfo(['当前已启用无障碍功能的服务:{}', enabledServices]) 40 | var service = null 41 | if (enabledServices.indexOf(requiredService) < 0) { 42 | service = enabledServices + ':' + requiredService 43 | } else if (force) { 44 | // 如果强制开启 45 | service = enabledServices 46 | } 47 | if (service) { 48 | Settings.Secure.putString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, service) 49 | Settings.Secure.putString(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, '1') 50 | _logUtils.infoLog('成功开启AutoJS的辅助服务', true) 51 | } 52 | 53 | return true 54 | } catch (e) { 55 | _logUtils.warnInfo('\n请确保已给予 WRITE_SECURE_SETTINGS 权限\n\n授权代码已复制,请使用adb工具连接手机执行(重启不失效)\n\n', true) 56 | let shellScript = 'adb shell pm grant ' + packageName + ' android.permission.WRITE_SECURE_SETTINGS' 57 | _logUtils.warnInfo('adb 脚本 已复制到剪切板:[' + shellScript + ']') 58 | setClip(shellScript) 59 | return false 60 | } 61 | } 62 | 63 | /** 64 | * 校验截图权限,权限失效则重新启动,根据参数释放任务队列 65 | * @param {boolean} releaseLock 是否在失败后释放任务队列 66 | * @param {number} errorLimit 失败尝试次数 67 | */ 68 | this.checkCaptureScreenPermission = function (releaseLock, errorLimit) { 69 | errorLimit = errorLimit || 3 70 | // 获取截图 用于判断是否可收取 71 | let screen = null 72 | let errorCount = 0 73 | do { 74 | this.waitFor(function () { 75 | let max_try = 10 76 | while (!screen && max_try-- > 0) { 77 | screen = captureScreen() 78 | } 79 | }, _config.capture_waiting_time || 500) 80 | if (!screen) { 81 | _logUtils.debugInfo('获取截图失败 再试一次 count:' + (++errorCount)) 82 | } 83 | } while (!screen && errorCount < errorLimit) 84 | if (!screen) { 85 | _logUtils.errorInfo(['获取截图失败多次[{}], 可能已经没有了截图权限,重新执行脚本', errorCount], true) 86 | automator.back() 87 | if (releaseLock) { 88 | _runningQueueDispatcher.removeRunningTask(true) 89 | } else { 90 | // 用于取消下一次运行的dialog 91 | this.getAndUpdateDismissReason('capture-screen-error') 92 | } 93 | _runningQueueDispatcher.executeTargetScript(FileUtils.getRealMainScriptPath()) 94 | exit() 95 | } 96 | return screen 97 | } 98 | 99 | this.getAutoJsPackage = function () { 100 | let isPro = Object.prototype.toString.call(com.stardust.autojs.core.timing.TimedTask.Companion).match(/Java(Class|Object)/) 101 | let isModify = Object.prototype.toString.call(org.autojs.autojsm.timing.TimedTask).match(/Java(Class|Object)/) 102 | return 'org.autojs.autojs' + (isPro ? 'pro' : (isModify ? 'm' : '')) 103 | } 104 | 105 | this.getAndUpdateDismissReason = function (newVal) { 106 | let storedDismissDialogInfo = this.getTodaysRuntimeStorage(DISMISS_AWAIT_DIALOG) 107 | let oldVal = storedDismissDialogInfo.dismissReason 108 | storedDismissDialogInfo.dismissReason = newVal 109 | this.updateRuntimeStorage(DISMISS_AWAIT_DIALOG, storedDismissDialogInfo) 110 | return oldVal 111 | } 112 | 113 | /** 114 | * 启动package 115 | * @param packageName 需要启动的package名称 116 | */ 117 | this.launchPackage = function (packageName, reopen) { 118 | _logUtils.debugInfo(['准备{}打开package: {}', reopen ? '重新' : '', packageName]) 119 | let currentRunning = currentPackage() 120 | app.launchPackage(packageName) 121 | sleep(1000) 122 | currentRunning = currentPackage() 123 | let waitCount = 3 124 | while (currentRunning !== packageName && waitCount-- > 0) { 125 | _logUtils.debugInfo(['未进入{},继续等待 当前所在:{}', packageName, currentRunning]) 126 | sleep(1000) 127 | currentRunning = currentPackage() 128 | } 129 | _logUtils.debugInfo(['进入[{}] {}', packageName, (packageName === currentRunning ? '成功' : '失败')]) 130 | } 131 | 132 | this.minimize = function () { 133 | _logUtils.debugInfo(['直接返回最小化']) 134 | try { 135 | let maxRepeat = 10 136 | while (maxRepeat--> 0 && (automator.clickBack() || automator.clickClose())) { 137 | sleep(500) 138 | } 139 | } catch (e) { 140 | errorInfo('尝试返回失败' + e) 141 | } 142 | back() 143 | } 144 | 145 | 146 | /** 147 | * @param checkDismissReason 是否校验跳过弹窗 148 | */ 149 | this.showDialogAndWait = function (checkDismissReason) { 150 | // 显示悬浮窗之前关闭按键监听,避免操作不当导致界面卡死 151 | events.removeAllKeyDownListeners('volume_down') 152 | if (checkDismissReason) { 153 | let dismissReason = this.getAndUpdateDismissReason('') 154 | if (dismissReason) { 155 | _logUtils.debugInfo(['不再展示延迟对话框,{}', dismissReason]) 156 | return 157 | } 158 | } 159 | 160 | let continueRunning = true 161 | let terminate = false 162 | let showDialog = true 163 | let lock = threads.lock() 164 | let complete = lock.newCondition() 165 | 166 | lock.lock() 167 | threads.start(function () { 168 | 169 | let sleepCount = _config.delayStartTime || 5 170 | let confirmDialog = dialogs.build({ 171 | title: '即将开始' + project_name, 172 | content: '将在' + sleepCount + '秒内开始', 173 | positive: '立即开始', 174 | positiveColor: '#f9a01c', 175 | negative: '终止', 176 | negativeColor: 'red', 177 | neutral: '延迟五分钟', 178 | cancelable: false 179 | }) 180 | .on('positive', () => { 181 | lock.lock() 182 | complete.signal() 183 | lock.unlock() 184 | showDialog = false 185 | confirmDialog.dismiss() 186 | }) 187 | .on('negative', () => { 188 | continueRunning = false 189 | terminate = true 190 | lock.lock() 191 | complete.signal() 192 | lock.unlock() 193 | showDialog = false 194 | confirmDialog.dismiss() 195 | }) 196 | .on('neutral', () => { 197 | continueRunning = false 198 | lock.lock() 199 | complete.signal() 200 | lock.unlock() 201 | showDialog = false 202 | confirmDialog.dismiss() 203 | }) 204 | .show() 205 | 206 | while (sleepCount-- > 0 && showDialog) { 207 | sleep(1000) 208 | confirmDialog.setContent('将在' + sleepCount + '秒内开始') 209 | } 210 | confirmDialog.setContent('即将开始') 211 | sleep(500) 212 | lock.lock() 213 | complete.signal() 214 | lock.unlock() 215 | confirmDialog.dismiss() 216 | }) 217 | complete.await() 218 | lock.unlock() 219 | if (terminate) { 220 | _logUtils.warnInfo('中止执行') 221 | if (_config.autoSetBrightness) { 222 | device.setBrightnessMode(1) 223 | } 224 | this.cancelAllTimedTasks() 225 | _runningQueueDispatcher.removeRunningTask() 226 | exit() 227 | } 228 | if (continueRunning) { 229 | _logUtils.logInfo('立即开始') 230 | } else { 231 | _logUtils.logInfo('延迟五分钟后开始') 232 | if (_config.autoSetBrightness) { 233 | device.setBrightnessMode(1) 234 | } 235 | this.setUpAutoStart(5) 236 | _runningQueueDispatcher.removeRunningTask() 237 | exit() 238 | } 239 | } 240 | 241 | /** 242 | * 关闭悬浮窗并将floatyWindow置为空,在下一次显示时重新新建悬浮窗 因为close之后的无法再次显示 243 | */ 244 | this.closeFloatyWindow = function () { 245 | _FloatyInstance.close() 246 | } 247 | 248 | this.showMiniFloaty = function (text, x, y, color) { 249 | _FloatyInstance.setFloatyInfo({ x: x || _config.min_floaty_x || 150, y: y || _config.min_floaty_y || 20 }, text) 250 | _FloatyInstance.setFloatyTextColor(color || _config.min_floaty_color || '#00FF00') 251 | } 252 | 253 | /** 254 | * 显示悬浮窗 根据配置自动显示mini悬浮窗和可关闭悬浮窗,目前来说不推荐使用可关闭悬浮窗 255 | * @param text {String} 悬浮窗文字内容 256 | */ 257 | this.showTextFloaty = function (text) { 258 | this.showMiniFloaty(text) 259 | } 260 | 261 | /** 262 | * 监听音量下键延迟执行 263 | **/ 264 | this.listenDelayStart = function () { 265 | let _this = this 266 | threads.start(function () { 267 | sleep(2000) 268 | _logUtils.infoLog('即将开始,按音量下键延迟五分钟执行', true) 269 | _logUtils.debugInfo('after setMaxListeners') 270 | events.observeKey() 271 | _logUtils.debugInfo('after observeKey') 272 | events.onceKeyDown('volume_down', function (event) { 273 | if (_config.autoSetBrightness) { 274 | device.setBrightnessMode(1) 275 | } 276 | _logUtils.warnInfo('延迟五分钟后启动脚本', true) 277 | _this.setUpAutoStart(5) 278 | engines.myEngine().forceStop() 279 | _runningQueueDispatcher.removeRunningTask() 280 | events.removeAllListeners() 281 | events.recycle() 282 | exit() 283 | }) 284 | _logUtils.debugInfo('after setOnceKeyDown') 285 | }) 286 | } 287 | 288 | this.commonDelay = function (minutes, text) { 289 | _logUtils.debugInfo('倒计时' + minutes) 290 | if (typeof text === 'undefined' || text === '') { 291 | text = '距离下次运行还有[' 292 | } 293 | 294 | minutes = typeof minutes != null ? minutes : 0 295 | if (minutes === 0) { 296 | return 297 | } 298 | let startTime = new Date().getTime() 299 | let timestampGap = minutes * 60000 300 | let i = 0 301 | let delayLogStampPoint = -1 302 | let delayLogGap = 0 303 | let showSeconds = false 304 | for (; ;) { 305 | let now = new Date().getTime() 306 | if (now - startTime > timestampGap) { 307 | break 308 | } 309 | i = (now - startTime) / 60000 310 | let left = minutes - i 311 | if (!showSeconds) { 312 | delayLogGap = i - delayLogStampPoint 313 | // 半分钟打印一次日志 314 | if (delayLogGap >= 0.5) { 315 | delayLogStampPoint = i 316 | let content = this.formatString('{}{}]分', text, left.toFixed(2)) 317 | this.showTextFloaty(content) 318 | _logUtils.debugInfo(content) 319 | } 320 | // 剩余一分钟时显示为秒 321 | if (showSeconds === false && left <= 1) { 322 | this.listenDelayStart() 323 | showSeconds = true 324 | } 325 | sleep(500) 326 | } else { 327 | let content = this.formatString('{}{}]秒', text, (left * 60).toFixed(0)) 328 | this.showTextFloaty(content) 329 | sleep(1000) 330 | } 331 | } 332 | } 333 | 334 | /** 335 | * 根据传入key创建当日缓存 336 | */ 337 | this.createTargetStore = function (key, today) { 338 | if (key === DISMISS_AWAIT_DIALOG) { 339 | return this.createDismissAwaitDialogStore(today) 340 | } 341 | } 342 | 343 | this.createDismissAwaitDialogStore = function (today) { 344 | let initStore = { 345 | dismissReason: '', 346 | date: today 347 | } 348 | let runtimeStorages = storages.create(RUNTIME_STORAGE) 349 | runtimeStorages.put(DISMISS_AWAIT_DIALOG, JSON.stringify(initStore)) 350 | return initStore 351 | } 352 | 353 | /** 354 | * 获取当天的缓存信息,不存在时创建一个初始值 355 | * @param key {String} key名称 356 | */ 357 | this.getTodaysRuntimeStorage = function (key) { 358 | let today = formatDate(new Date(), 'yyyy-MM-dd') 359 | let runtimeStorages = storages.create(RUNTIME_STORAGE) 360 | let existStoreObjStr = runtimeStorages.get(key) 361 | if (existStoreObjStr) { 362 | try { 363 | let existStoreObj = JSON.parse(existStoreObjStr) 364 | if (existStoreObj.date === today) { 365 | return existStoreObj 366 | } 367 | } catch (e) { 368 | _logUtils.debugInfo(["解析JSON数据失败, key:{} value:{} error:{}", key, existStoreObjStr, e]) 369 | } 370 | } 371 | 372 | let newStore = this.createTargetStore(key, today) 373 | return newStore 374 | } 375 | 376 | /** 377 | * 通用更新缓存方法 378 | * @param key {String} key值名称 379 | * @param valObj {Object} 存值对象 380 | */ 381 | this.updateRuntimeStorage = function (key, valObj) { 382 | let runtimeStorages = storages.create(RUNTIME_STORAGE) 383 | runtimeStorages.put(key, JSON.stringify(valObj)) 384 | } 385 | 386 | this.parseToZero = function (value) { 387 | return (!value || isNaN(value)) ? 0 : parseInt(value) 388 | } 389 | 390 | 391 | this.isEmpty = function (val) { 392 | return val === null || typeof val === 'undefined' || val === '' 393 | } 394 | 395 | this.isEmptyArray = function (array) { 396 | return array === null || typeof array === 'undefined' || array.length === 0 397 | } 398 | 399 | this.isNotEmpty = function (val) { 400 | return !this.isEmpty(val) && !this.isEmptyArray(val) 401 | } 402 | 403 | this.addOpenPlacehold = function (content) { 404 | content = "<<<<<<<" + (content || "") + ">>>>>>>" 405 | _logUtils.appendLog(content) 406 | console.verbose(content) 407 | } 408 | 409 | this.addClosePlacehold = function (content) { 410 | content = ">>>>>>>" + (content || "") + "<<<<<<<" 411 | _logUtils.appendLog(content) 412 | console.verbose(content) 413 | } 414 | 415 | /** 416 | * @deprecated: see RunningQueueDispatcher$addRunningTask 417 | * 校验是否重复运行 如果重复运行则关闭当前脚本 418 | */ 419 | this.checkDuplicateRunning = function () { 420 | let currentEngine = engines.myEngine() 421 | let runningEngines = engines.all() 422 | let runningSize = runningEngines.length 423 | let currentSource = currentEngine.getSource() + '' 424 | _logUtils.debugInfo('当前脚本信息 id:' + currentEngine.id + ' source:' + currentSource + ' 运行中脚本数量:' + runningSize) 425 | if (runningSize > 1) { 426 | runningEngines.forEach(engine => { 427 | let compareEngine = engine 428 | let compareSource = compareEngine.getSource() + '' 429 | _logUtils.debugInfo('对比脚本信息 id:' + compareEngine.id + ' source:' + compareSource) 430 | if (currentEngine.id !== compareEngine.id && compareSource === currentSource) { 431 | _logUtils.warnInfo('脚本正在运行中 退出当前脚本:' + currentSource, true) 432 | _runningQueueDispatcher.removeRunningTask(true) 433 | engines.myEngine().forceStop() 434 | exit() 435 | } 436 | }) 437 | } 438 | } 439 | 440 | /** 441 | * 关闭运行中的脚本 关闭全部同源脚本 442 | */ 443 | this.killRunningScript = function () { 444 | let runningEngines = engines.all() 445 | let runningSize = runningEngines.length 446 | let mainScriptJs = FileUtils.getRealMainScriptPath() 447 | if (runningSize > 1) { 448 | runningEngines.forEach(engine => { 449 | let compareEngine = engine 450 | let compareSource = compareEngine.getSource() + '' 451 | _logUtils.debugInfo('对比脚本信息 id:' + compareEngine.id + ' source:' + compareSource) 452 | if (compareSource === mainScriptJs) { 453 | _logUtils.warnInfo(['关闭运行中脚本:id[{}]', compareEngine.id], true) 454 | engine.forceStop() 455 | } 456 | }) 457 | } 458 | } 459 | 460 | /** 461 | * 杀死重复运行的同源脚本 462 | */ 463 | this.killDuplicateScript = function () { 464 | let currentEngine = engines.myEngine() 465 | let runningEngines = null 466 | while (runningEngines === null) { 467 | // engines.all()有并发问题,尝试多次获取 468 | try { 469 | runningEngines = engines.all() 470 | } catch (e) { 471 | sleep(200) 472 | } 473 | } 474 | let runningSize = runningEngines.length 475 | let currentSource = currentEngine.getSource() + '' 476 | _logUtils.debugInfo('当前脚本信息 id:' + currentEngine.id + ' source:' + currentSource + ' 运行中脚本数量:' + runningSize) 477 | if (runningSize > 1) { 478 | runningEngines.forEach(engine => { 479 | let compareEngine = engine 480 | let compareSource = compareEngine.getSource() + '' 481 | _logUtils.debugInfo('对比脚本信息 id:' + compareEngine.id + ' source:' + compareSource) 482 | if (currentEngine.id !== compareEngine.id && compareSource === currentSource) { 483 | _logUtils.warnInfo(['currentId:{} 退出运行中的同源脚本id:{}', currentEngine.id, compareEngine.id]) 484 | // 直接关闭同源的脚本,暂时可以无视锁的存在 485 | engine.forceStop() 486 | } 487 | }) 488 | } 489 | } 490 | 491 | /** 492 | * 设置指定时间后自动启动main脚本 493 | */ 494 | this.setUpAutoStart = function (minutes) { 495 | // 先移除所有已设置的定时任务 496 | this.cancelAllTimedTasks() 497 | let mainScriptJs = FileUtils.getRealMainScriptPath() 498 | let millis = new Date().getTime() + minutes * 60 * 1000 499 | _logUtils.infoLog('预订[' + minutes + ']分钟后的任务,时间戳:' + millis) 500 | // 预定一个{minutes}分钟后的任务 501 | let task = Timers.addDisposableTask({ 502 | path: mainScriptJs, 503 | date: millis 504 | }) 505 | _logUtils.debugInfo("定时任务预定成功: " + task.id) 506 | this.recordTimedTask(task) 507 | } 508 | 509 | this.recordTimedTask = function (task) { 510 | let runtimeStorage = storages.create(RUNTIME_STORAGE) 511 | let autoStartListStr = runtimeStorage.get(TIMER_AUTO_START) 512 | let array = [] 513 | if (autoStartListStr) { 514 | array = JSON.parse(autoStartListStr) 515 | } 516 | array.push(task) 517 | runtimeStorage.put(TIMER_AUTO_START, JSON.stringify(array)) 518 | } 519 | 520 | this.showAllAutoTimedTask = function () { 521 | let runtimeStorage = storages.create(RUNTIME_STORAGE) 522 | let autoStartListStr = runtimeStorage.get(TIMER_AUTO_START) 523 | if (autoStartListStr) { 524 | let array = JSON.parse(autoStartListStr) 525 | if (array && array.length > 0) { 526 | array.forEach(task => { 527 | _logUtils.logInfo([ 528 | '定时任务 mId: {} 目标执行时间: {} 剩余时间: {}秒', 529 | task.mId, formatDate(new Date(task.mMillis), 'yyyy-MM-dd HH:mm:ss'), ((task.mMillis - new Date().getTime()) / 1000.0).toFixed(0) 530 | ]) 531 | }) 532 | } 533 | } else { 534 | _logUtils.logInfo('当前没有自动设置的定时任务') 535 | } 536 | } 537 | 538 | this.cancelAllTimedTasks = function () { 539 | let runtimeStorage = storages.create(RUNTIME_STORAGE) 540 | let autoStartListStr = runtimeStorage.get(TIMER_AUTO_START) 541 | if (autoStartListStr) { 542 | let array = JSON.parse(autoStartListStr) 543 | if (array && array.length > 0) { 544 | array.forEach(task => { 545 | _logUtils.debugInfo('撤销自动任务:' + JSON.stringify(task)) 546 | if (task.mId) { 547 | Timers.removeTimedTask(task.mId) 548 | } 549 | }) 550 | } 551 | } 552 | // 将task队列置为空 553 | runtimeStorage.put(TIMER_AUTO_START, '') 554 | } 555 | 556 | /** 557 | * 杀死当前APP 仅适用于MIUI10+ 全面屏手势操作 558 | */ 559 | this.killCurrentApp = function () { 560 | recents() 561 | sleep(1000) 562 | gesture(320, [240, 1000], [800, 1000]) 563 | } 564 | 565 | this.waitFor = function (action, timeout) { 566 | let countDown = new java.util.concurrent.CountDownLatch(1) 567 | let timeoutThread = threads.start(function () { 568 | sleep(timeout) 569 | countDown.countDown() 570 | _logUtils.debugInfo('超时线程执行结束') 571 | }) 572 | let actionSuccess = false 573 | let actionThread = threads.start(function () { 574 | action() 575 | actionSuccess = true 576 | countDown.countDown() 577 | _logUtils.debugInfo('action执行结束') 578 | }) 579 | countDown.await() 580 | timeoutThread.interrupt() 581 | actionThread.interrupt() 582 | return actionSuccess 583 | } 584 | 585 | this.createQueue = function (size) { 586 | let queue = [] 587 | for (let i = 0; i < size; i++) { 588 | queue.push(i) 589 | } 590 | return queue 591 | } 592 | 593 | this.getQueueDistinctSize = function (queue) { 594 | return queue.reduce((a, b) => { 595 | if (a.indexOf(b) < 0) { 596 | a.push(b) 597 | } 598 | return a 599 | }, []).length 600 | } 601 | 602 | this.pushQueue = function (queue, size, val) { 603 | if (queue.length >= size) { 604 | queue.shift() 605 | } 606 | queue.push(val) 607 | } 608 | 609 | /** 610 | * eg. params '参数名:{} 参数内容:{}', name, value 611 | * result '参数名:name 参数内容:value' 612 | * 格式化字符串,定位符{} 613 | */ 614 | this.formatString = function () { 615 | let originContent = [] 616 | for (let arg in arguments) { 617 | originContent.push(arguments[arg]) 618 | } 619 | if (originContent.length === 1) { 620 | return originContent[0] 621 | } 622 | let marker = originContent[0] 623 | let args = originContent.slice(1) 624 | let regex = /(\{\})/g 625 | let matchResult = marker.match(regex) 626 | if (matchResult && args && matchResult.length > 0 && matchResult.length === args.length) { 627 | args.forEach((item, idx) => { 628 | marker = marker.replace('{}', item) 629 | }) 630 | return marker 631 | } else { 632 | console.error('参数数量不匹配' + arguments) 633 | return arguments 634 | } 635 | } 636 | 637 | 638 | /** 639 | * 自动设置刘海的偏移量 640 | */ 641 | this.autoSetUpBangOffset = function () { 642 | if (_config.auto_set_bang_offset || _config.updated_temp_flag_1325) { 643 | let DETECT_COLOR = '#10FF1F' 644 | let window = floaty.rawWindow( 645 | 646 | 647 | 648 | 649 | 650 | ) 651 | window.setPosition(100, 0) 652 | // 等待悬浮窗初始化 653 | sleep(300) 654 | let offset = null 655 | let limit = 10 656 | while (!offset && offset !== 0 && limit-- > 0) { 657 | let screen = this.checkCaptureScreenPermission() 658 | if (screen) { 659 | let point = images.findColor(screen, DETECT_COLOR, { region: [80, 0, 100, 300], threshold: 1 }) 660 | if (point && images.detectsColor(screen, DETECT_COLOR, point.x + 20, point.y) && images.detectsColor(screen, DETECT_COLOR, point.x + 30, point.y)) { 661 | offset = point.y 662 | ui.run(function () { 663 | window.text.setText('刘海偏移量为:' + offset + ' 自动关闭悬浮窗') 664 | }) 665 | _logUtils.debugInfo(['自动设置刘海偏移量为:{}', offset]) 666 | sleep(500) 667 | _logUtils.debugInfo('关闭悬浮窗') 668 | window.close() 669 | let configStorage = storages.create(_storage_name) 670 | // 设为负值 671 | _config.bang_offset = -offset 672 | configStorage.put('bang_offset', _config.bang_offset) 673 | configStorage.put('auto_set_bang_offset', false) 674 | configStorage.put('updated_temp_flag_1325', false) 675 | } else { 676 | sleep(100) 677 | } 678 | } 679 | } 680 | if (limit <= 0) { 681 | _logUtils.warnInfo('无法自动检测刘海高度,请确认是否开启了深色模式?') 682 | } 683 | } 684 | } 685 | 686 | } 687 | 688 | module.exports = new CommonFunctions() -------------------------------------------------------------------------------- /lib/prototype/CrashCatcher.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-05-27 23:08:29 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-05-28 00:36:05 6 | * @Description: AutoJS崩溃自启 7 | */ 8 | 9 | let { storage_name } = require('../../config.js')(runtime, this) 10 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 11 | let logUtils = singletonRequire('LogUtils') 12 | let fileUtils = singletonRequire('FileUtils') 13 | let timers = singletonRequire('Timers') 14 | 15 | const RUN_STATE_STORAGE = storages.create(storage_name + '_crash_catch') 16 | 17 | function CrashCatcher () { 18 | this.setOnRunning = function () { 19 | logUtils.debugInfo('设置脚本状态为执行中') 20 | RUN_STATE_STORAGE.put('running', true) 21 | } 22 | this.setDone = function () { 23 | logUtils.debugInfo('设置脚本状态为执行完毕') 24 | RUN_STATE_STORAGE.put('running', false) 25 | } 26 | this.restartIfCrash = function () { 27 | let runningStatus = RUN_STATE_STORAGE.get('running') 28 | if (runningStatus === 'true' || runningStatus === true) { 29 | logUtils.warnInfo('AutoJs可能异常崩溃且已重启,重新执行脚本') 30 | let source = fileUtils.getRealMainScriptPath() 31 | engines.execScriptFile(source, { path: source.substring(0, source.lastIndexOf('/')) }) 32 | } else { 33 | logUtils.debugInfo('AutoJs可能异常崩溃且已重启,但脚本以正常走完流程,不重新执行') 34 | } 35 | } 36 | } 37 | 38 | let crashCatcher = new CrashCatcher() 39 | 40 | if (typeof module === 'undefined') { 41 | // running mode 42 | crashCatcher.restartIfCrash() 43 | } else { 44 | function getOnStartAction () { 45 | let is_modify = Object.prototype.toString.call(org.autojs.autojsm.timing.TimedTask).match(/Java(Class|Object)/) 46 | if (is_modify) { 47 | return "org.autojs.autojsm.action.startup" 48 | } else { 49 | return "org.autojs.autojs.action.startup" 50 | } 51 | } 52 | let intentTask = { 53 | isLocal: true, 54 | path: fileUtils.getCurrentWorkPath() + '/lib/prototype/CrashCatcher.js', 55 | action: getOnStartAction() 56 | } 57 | let existTask = timers.queryIntentTasks(intentTask) 58 | if (!existTask || existTask.length === 0) { 59 | logUtils.debugInfo('创建异常终止后的重启任务') 60 | timers.addIntentTask(intentTask) 61 | } else { 62 | logUtils.debugInfo(['异常终止的重启任务已存在: {}', JSON.stringify(existTask)]) 63 | } 64 | module.exports = crashCatcher 65 | } -------------------------------------------------------------------------------- /lib/prototype/FileUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-08-05 14:36:13 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-25 15:24:10 6 | * @Description: 7 | */ 8 | const getRealMainScriptPath = function (parentDirOnly) { 9 | let currentPath = files.cwd() 10 | if (files.exists(currentPath + '/main.js')) { 11 | return currentPath + (parentDirOnly ? '' : '/main.js') 12 | } 13 | let paths = currentPath.split('/') 14 | 15 | do { 16 | paths = paths.slice(0, paths.length - 1) 17 | currentPath = paths.reduce((a, b) => a += '/' + b) 18 | } while (!files.exists(currentPath + '/main.js') && paths.length > 0) 19 | if (paths.length > 0) { 20 | return currentPath + (parentDirOnly ? '' : '/main.js') 21 | } 22 | } 23 | 24 | /** 25 | * 获取当前脚本的运行工作路径,main.js所在的文件夹 26 | */ 27 | const getCurrentWorkPath = function () { 28 | return getRealMainScriptPath(true) 29 | } 30 | 31 | module.exports = { 32 | getRealMainScriptPath: getRealMainScriptPath, 33 | getCurrentWorkPath: getCurrentWorkPath 34 | } -------------------------------------------------------------------------------- /lib/prototype/FloatyUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-02 19:05:01 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-09-17 19:48:03 6 | * @Description: 悬浮窗工具,单独提出来作为单例使用 7 | */ 8 | let { config: _config } = require('../../config.js')(runtime, this) 9 | let FloatyUtil = function () { 10 | this.floatyWindow = null 11 | this.floatyInitStatus = false 12 | this.floatyLock = null 13 | this.floatyCondition = null 14 | } 15 | FloatyUtil.prototype.init = function () { 16 | this.floatyLock = threads.lock() 17 | this.floatyCondition = this.floatyLock.newCondition() 18 | let _this = this 19 | threads.start(function () { 20 | // 延迟初始化,避免死机 21 | sleep(400) 22 | _this.floatyLock.lock() 23 | try { 24 | _this.floatyWindow = floaty.rawWindow( 25 | 26 | 27 | 28 | ) 29 | _this.floatyWindow.setTouchable(false) 30 | _this.floatyWindow.setPosition(50, 50 + _config.bang_offset) 31 | _this.floatyWindow.content.text('悬浮窗初始化成功') 32 | _this.floatyInitStatus = true 33 | } catch (e) { 34 | console.error('悬浮窗初始化失败' + e) 35 | _this.floatyInitStatus = false 36 | } finally { 37 | _this.floatyCondition.signalAll() 38 | _this.floatyLock.unlock() 39 | } 40 | }) 41 | this.floatyLock.lock() 42 | if (this.floatyInitStatus === false) { 43 | console.verbose('等待悬浮窗初始化') 44 | this.floatyCondition.await() 45 | } 46 | this.floatyLock.unlock() 47 | console.verbose('悬浮窗初始化' + (this.floatyInitStatus ? '成功' : '失败')) 48 | return this.floatyInitStatus 49 | } 50 | 51 | FloatyUtil.prototype.close = function () { 52 | if (this.floatyInitStatus) { 53 | this.floatyLock.lock() 54 | if (this.floatyWindow !== null) { 55 | this.floatyWindow.close() 56 | this.floatyWindow = null 57 | } 58 | this.floatyInitStatus = false 59 | this.floatyLock.unlock() 60 | } 61 | } 62 | 63 | FloatyUtil.prototype.setFloatyInfo = function (position, text, option) { 64 | option = option || {} 65 | if (this.floatyWindow === null) { 66 | this.init() 67 | } 68 | let _this = this 69 | ui.run(function () { 70 | _this.floatyLock.lock() 71 | if (position && isFinite(position.x) && isFinite(position.y)) { 72 | _this.floatyWindow.setPosition(parseInt(position.x), parseInt(position.y) + _config.bang_offset) 73 | } 74 | if (text) { 75 | _this.floatyWindow.content.text(text) 76 | } 77 | if (option.textSize) { 78 | _this.floatyWindow.content.setTextSize(option.textSize) 79 | } 80 | if (typeof option.touchable !== 'undefined') { 81 | _this.floatyWindow.setTouchable(option.touchable) 82 | } 83 | _this.floatyLock.unlock() 84 | }) 85 | } 86 | 87 | 88 | FloatyUtil.prototype.setFloatyTextColor = function (colorStr) { 89 | if (this.floatyWindow === null) { 90 | this.init() 91 | } 92 | if (/^#[\dabcdef]{6,8}$/i.test(colorStr)) { 93 | let colorInt = colors.parseColor(colorStr) 94 | if (colorInt !== null) { 95 | let _this = this 96 | ui.run(function () { 97 | _this.floatyLock.lock() 98 | _this.floatyWindow.content.setTextColor(colorInt) 99 | _this.floatyLock.unlock() 100 | }) 101 | } 102 | } else { 103 | console.error('颜色值字符串格式不正确: ' + colorStr) 104 | } 105 | } 106 | 107 | FloatyUtil.prototype.setFloatyText = function (text, option) { 108 | this.setFloatyInfo(null, text, option) 109 | } 110 | 111 | FloatyUtil.prototype.setFloatyPosition = function (x, y, option) { 112 | this.setFloatyInfo({ x: x, y: y }, null, option) 113 | } 114 | 115 | FloatyUtil.prototype.setTextSize = function (textSize) { 116 | this.setFloatyInfo(null, null, { textSize: textSize }) 117 | } 118 | 119 | FloatyUtil.prototype.setTouchable = function (touchable) { 120 | this.setFloatyInfo(null, null, { touchable: touchable }) 121 | } 122 | 123 | module.exports = new FloatyUtil() -------------------------------------------------------------------------------- /lib/prototype/LockableStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-23 23:13:31 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-25 15:01:49 6 | * @Description: 7 | */ 8 | importClass(android.content.Context) 9 | importClass(android.content.SharedPreferences) 10 | 11 | let lockableStorages = { 12 | requireCount: 0 13 | } 14 | lockableStorages.create = function (name) { 15 | return new LockableStorage(name) 16 | } 17 | 18 | lockableStorages.remove = function (name) { 19 | return this.create(name).clear() 20 | } 21 | 22 | module.exports = lockableStorages 23 | 24 | 25 | // 支持锁的同步操作storage 26 | function LockableStorage (name) { 27 | this.NAME_PREFIX = "autojs.localstorage.sync." 28 | this.mSharedPreferences = context.getSharedPreferences(this.NAME_PREFIX + name, Context.MODE_PRIVATE) 29 | 30 | this.put = function (key, stringValue) { 31 | return this.mSharedPreferences.edit() 32 | .putString(key, stringValue) 33 | .commit() 34 | } 35 | 36 | this.get = function (key, defaultValue) { 37 | defaultValue = defaultValue || null 38 | return this.mSharedPreferences.getString(key, defaultValue) 39 | } 40 | 41 | this.clear = function () { 42 | return this.mSharedPreferences.edit().clear().commit() 43 | } 44 | } -------------------------------------------------------------------------------- /lib/prototype/LogUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Last Modified by: TonyJiangWJ 4 | * @Last Modified time: 2020-10-09 19:31:04 5 | * @Description: 日志工具 6 | */ 7 | importClass(java.lang.Thread) 8 | importClass(java.util.concurrent.LinkedBlockingQueue) 9 | importClass(java.util.concurrent.ThreadPoolExecutor) 10 | importClass(java.util.concurrent.TimeUnit) 11 | importClass(java.util.concurrent.ThreadFactory) 12 | importClass(java.util.concurrent.Executors) 13 | let { config: _config, storage_name: _storage_name } = require('../../config.js')(runtime, this) 14 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 15 | let formatDate = require('../DateUtil.js') 16 | let FileUtils = singletonRequire('FileUtils') 17 | const MAIN_PATH = FileUtils.getRealMainScriptPath(true) 18 | _config.isRunning = true 19 | // -------------初始化------------- 20 | // 确保目录存在 21 | files.ensureDir(MAIN_PATH + '/logs/') 22 | files.ensureDir(MAIN_PATH + '/logs/logback/') 23 | const LOG_TYPES = { 24 | VERBOSE: 'VERBOSE', 25 | DEBUG: 'DEBUG', 26 | LOG: 'LOG', 27 | INFO: 'INFO', 28 | WARN: 'WARN', 29 | ERROR: 'ERROR', 30 | DEVELOP: 'DEVELOP' 31 | } 32 | const PATH_CONFIG = { 33 | 'VERBOSE': MAIN_PATH + '/logs/log-verboses.log', 34 | 'LOG': MAIN_PATH + '/logs/log.log', 35 | 'INFO': MAIN_PATH + '/logs/info.log', 36 | 'WARN': MAIN_PATH + '/logs/warn.log', 37 | 'ERROR': MAIN_PATH + '/logs/error.log', 38 | 'DEVELOP': MAIN_PATH + '/logs/develop.log' 39 | } 40 | const ENGINE_ID = engines.myEngine().id 41 | // 移除过期的日志 42 | removeOutdateBacklogFiles() 43 | // -------------初始化结束------------- 44 | 45 | /** 46 | * Logger 日志基类 47 | */ 48 | function Logger () { 49 | 50 | this.fileWriteCostCounter = 0 51 | this.backupCostCounter = 0 52 | this.enqueueCostCounter = 0 53 | 54 | /** 55 | * 刷新日志缓冲区,仅异步日志用到 56 | * 切换读写缓冲区,将缓冲区中的日志全部写入到日志文件 57 | */ 58 | this.flushAllLogs = () => { } 59 | /** 60 | * 异步日志:将日志内容写入写缓冲区 61 | * 同步日志:将日志内容写入日志文件中 62 | * @param {*} logData 日志内容对象:包含logType,dataTime,content,threadId等信息 63 | */ 64 | this.enqueueLog = (logData) => { } 65 | 66 | this.showCostingInfo = () => { 67 | console.verbose(ENGINE_ID + ' 日志入队总耗时:' + this.enqueueCostCounter + 'ms 写入文件总耗时:' + this.fileWriteCostCounter + 'ms 备份日志文件总耗时:' + this.backupCostCounter + 'ms') 68 | } 69 | } 70 | 71 | /** 72 | * 异步日志 73 | */ 74 | function AsyncLogger () { 75 | Logger.call(this) 76 | 77 | this.executeThreadPool = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(10), new ThreadFactory({ 78 | newThread: function (runnable) { 79 | let thread = Executors.defaultThreadFactory().newThread(runnable) 80 | thread.setName(_config.thread_name_prefix + ENGINE_ID + '-logging-' + thread.getName()) 81 | return thread 82 | } 83 | })) 84 | this.writingList = [] 85 | this.readingList = [] 86 | this.MAX_QUEUE_SIZE = 256 87 | this.writingLock = threads.lock() 88 | this.queueChangeLock = threads.lock() 89 | this.queueChangeCondition = this.queueChangeLock.newCondition() 90 | let self = this 91 | // 将日志写入文件 92 | this.executeThreadPool.execute(function () { 93 | let loggerRunning = true 94 | while (true && _config.save_log_file && (_config.isRunning || self.readingList.length !== 0 || self.writingList.length !== 0)) { 95 | let start = new Date().getTime() 96 | try { 97 | self.queueChangeLock.lock() 98 | while (self.readingList.length === 0 && _config.isRunning) { 99 | if (_config.develop_mode) { 100 | console.verbose(ENGINE_ID + ' 等待日志刷新') 101 | } 102 | if (!self.queueChangeCondition.await(5, java.util.concurrent.TimeUnit.SECONDS)) { 103 | let currentEngine = engines.all().filter(engine => engine.id === ENGINE_ID) 104 | _config.isRunning = currentEngine && currentEngine.length > 0 105 | } 106 | } 107 | if (self.readingList.length === 0) { 108 | // console.warn(ENGINE_ID + ' 脚本可能已终止执行') 109 | if (self.writingList.length !== 0) { 110 | // 切换缓冲区,将缓冲区内容全部写入日志 111 | console.verbose(ENGINE_ID + ' 切换缓冲区,将缓冲区内容全部写入日志') 112 | self.readingList = self.writingList 113 | self.writingList = [] 114 | } else { 115 | loggerRunning = false 116 | // 双队列为空 直接退出 117 | break 118 | } 119 | } 120 | start = new Date().getTime() 121 | let writerHolder = {} 122 | for (let key in PATH_CONFIG) { 123 | writerHolder[key] = files.open(PATH_CONFIG[key], 'a') 124 | } 125 | self.readingList.forEach(logData => { 126 | let { logType, content, dateTime, threadId } = logData 127 | let logWriter = writerHolder[logType] 128 | if (logWriter) { 129 | logWriter.writeline(dateTime + ' ' + content) 130 | } 131 | writerHolder[LOG_TYPES.VERBOSE].writeline(dateTime + ' ' + logType + ' [E:' + ENGINE_ID + ' T:' + threadId + '] - ' + content) 132 | }) 133 | for (let key in PATH_CONFIG) { 134 | let writer = writerHolder[key] 135 | if (writer) { 136 | writer.flush() 137 | writer.close() 138 | } 139 | } 140 | } catch (e) { 141 | console.error(ENGINE_ID + ' 写入日志异常:' + e) 142 | } finally { 143 | if (loggerRunning) { 144 | let cost = new Date().getTime() - start 145 | self.fileWriteCostCounter += cost 146 | start = new Date().getTime() 147 | checkFileSizeAndBackup() 148 | self.backupCostCounter += new Date().getTime() - start 149 | console.verbose(ENGINE_ID + ' 写入日志到文件耗时:' + cost + 'ms') 150 | } 151 | // 置空 152 | self.readingList = [] 153 | self.queueChangeLock.unlock() 154 | } 155 | } 156 | console.warn(ENGINE_ID + ' 脚本执行结束,日志文件写入线程关闭') 157 | self.showCostingInfo() 158 | // 新建线程 关闭线程池 159 | let thread = new Thread(new java.lang.Runnable({ 160 | run: function () { 161 | try { 162 | self.executeThreadPool.shutdown() 163 | let flag = self.executeThreadPool.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS) 164 | console.verbose(ENGINE_ID + ' 日志线程池关闭:' + flag) 165 | } catch (e) { 166 | console.error(ENGINE_ID + ' 关闭日志线程池异常:' + e) 167 | } finally { 168 | self.executeThreadPool = null 169 | } 170 | } 171 | })) 172 | thread.setName(_config.thread_name_prefix + ENGINE_ID + '_shutdown_logging_thread') 173 | thread.start() 174 | }) 175 | 176 | this.flushAllLogs = function () { 177 | if (_config.save_log_file) { 178 | try { 179 | this.queueChangeLock.lock() 180 | this.readingList = this.writingList 181 | this.writingList = [] 182 | this.queueChangeCondition.signal() 183 | } finally { 184 | this.queueChangeLock.unlock() 185 | } 186 | } 187 | } 188 | 189 | this.enqueueLog = function (logData) { 190 | if (_config.save_log_file) { 191 | let enqueueStart = new Date().getTime() 192 | // 异步方式 将日志内容入队列 193 | try { 194 | this.writingLock.lock() 195 | this.writingList.push(logData) 196 | if (this.writingList.length >= this.MAX_QUEUE_SIZE) { 197 | try { 198 | this.queueChangeLock.lock() 199 | this.readingList = this.writingList 200 | this.writingList = [] 201 | this.queueChangeCondition.signal() 202 | } finally { 203 | this.queueChangeLock.unlock() 204 | } 205 | } 206 | } finally { 207 | this.enqueueCostCounter += new Date().getTime() - enqueueStart 208 | this.writingLock.unlock() 209 | } 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * 同步日志 216 | */ 217 | function SyncLogger () { 218 | Logger.call(this) 219 | this.storage = storages.create(_storage_name + 'run_log_file') 220 | 221 | this.enqueueLog = function (logData) { 222 | if (_config.save_log_file) { 223 | let enqueueStart = new Date().getTime() 224 | // 同步方式写入文件 225 | let { logType, content, dateTime, threadId } = logData 226 | content += '\n' 227 | let verboseLog = dateTime + ' ' + logType + ' [E:' + ENGINE_ID + ' T:' + threadId + '] - ' + content 228 | let log = dateTime + ' ' + content 229 | let start = new Date().getTime() 230 | if (PATH_CONFIG[logType]) { 231 | files.append(PATH_CONFIG[logType], log) 232 | } 233 | files.append(PATH_CONFIG[LOG_TYPES.VERBOSE], verboseLog) 234 | this.fileWriteCostCounter += new Date().getTime() - start 235 | 236 | let target = 'checkFileSizeAndBackup' 237 | let clearFlag = this.storage.get(target) 238 | if (!clearFlag || parseInt(clearFlag) < new Date().getTime()) { 239 | // 十秒钟进行一次 240 | clearFlag = new Date().getTime() + 10000 241 | this.storage.put(target, clearFlag) 242 | start = new Date().getTime() 243 | checkFileSizeAndBackup() 244 | this.backupCostCounter += new Date().getTime() - start 245 | } 246 | this.enqueueCostCounter += new Date().getTime() - enqueueStart 247 | } 248 | } 249 | } 250 | 251 | AsyncLogger.prototype = Object.create(Logger.prototype) 252 | AsyncLogger.prototype.constructor = AsyncLogger 253 | 254 | SyncLogger.prototype = Object.create(Logger.prototype) 255 | SyncLogger.prototype.constructor = SyncLogger 256 | 257 | const LOGGER = _config.async_save_log_file ? new AsyncLogger() : new SyncLogger() 258 | if (_config.async_save_log_file) { 259 | console.verbose(ENGINE_ID + ' 使用异步日志') 260 | } else { 261 | console.verbose(ENGINE_ID + ' 使用同步日志') 262 | } 263 | /** 264 | * @param {string} content 265 | * @param {function} logFunc 执行控制台日志打印的方法 266 | * @param {boolean} isToast 267 | * @param {string} logType 日志类型 268 | */ 269 | const showToast = function (content, logFunc, isToast, logType) { 270 | content = convertObjectContent(content) 271 | if (isToast) { 272 | toast(content) 273 | } 274 | let logData = { 275 | logType: logType, 276 | content: content, 277 | dateTime: formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss.S'), 278 | threadId: Thread.currentThread().getId() 279 | } 280 | if (_config.show_engine_id) { 281 | content = '[E:' + ENGINE_ID + ' T:' + logData.threadId + '] - ' + content 282 | } 283 | logFunc(content) 284 | LOGGER.enqueueLog(logData) 285 | } 286 | 287 | /** 288 | * 移除过期的日志 默认清除三天前的 289 | */ 290 | function removeOutdateBacklogFiles () { 291 | let logbackDirPath = MAIN_PATH + '/logs/logback' 292 | if (files.exists(logbackDirPath)) { 293 | // 日志保留天数 294 | let saveDays = _config.logSavedDays || 3 295 | let threeDayAgo = formatDate(new Date(new Date().getTime() - saveDays * 24 * 3600000), 'yyyyMMddHHmm') 296 | let timeRegex = /.*(\d{12})\.log/ 297 | let outdateLogs = files.listDir(logbackDirPath, function (fileName) { 298 | let checkResult = timeRegex.exec(fileName) 299 | if (checkResult) { 300 | let timestr = checkResult[1] 301 | return timestr < threeDayAgo 302 | } else { 303 | return true 304 | } 305 | }) 306 | if (outdateLogs && outdateLogs.length > 0) { 307 | outdateLogs.forEach(logFile => { 308 | console.verbose(ENGINE_ID + ' 日志文件过期,删除掉:' + logFile) 309 | files.remove(logbackDirPath + '/' + logFile) 310 | }) 311 | } 312 | } 313 | } 314 | 315 | /** 316 | * 清除日志到备份文件夹,当不传递日志类型时清除所有日志 317 | * @param {string} target 日志类型 318 | */ 319 | function innerClearLogFile (target) { 320 | let path = PATH_CONFIG[target] 321 | if (!target) { 322 | // 全部清除 323 | for (let k in PATH_CONFIG) { 324 | clearTarget(PATH_CONFIG[k]) 325 | } 326 | } else if (path) { 327 | clearTarget(path) 328 | } 329 | } 330 | 331 | /** 332 | * 根据日志路径备份,并清空内容 333 | * @param {string} originLogPath 目标日志全路径 334 | */ 335 | const clearTarget = function (originLogPath) { 336 | let nameRegex = /.*\/([-\w]+)\.log$/ 337 | if (nameRegex.test(originLogPath)) { 338 | fileName = nameRegex.exec(originLogPath)[1] 339 | if (files.exists(originLogPath)) { 340 | let timestampLastHour = new Date().getTime() 341 | let backLogPath = MAIN_PATH + '/logs/logback/' + fileName + '.' + formatDate(new Date(timestampLastHour), 'yyyyMMddHHmm') + '.log' 342 | console.info(ENGINE_ID + ' 备份日志文件[' + backLogPath + ']' + (files.move(originLogPath, backLogPath) ? '成功' : '失败')) 343 | } else { 344 | console.info(ENGINE_ID + ' ' + originLogPath + '不存在,不执行备份') 345 | } 346 | try { 347 | files.write(originLogPath, fileName + ' logs for [' + formatDate(new Date()) + ']\n') 348 | } catch (e) { 349 | console.error(ENGINE_ID + ' 初始化写入日志文件失败' + e) 350 | } 351 | } else { 352 | console.error(ENGINE_ID + ' 解析文件名失败:' + originLogPath) 353 | } 354 | } 355 | 356 | /** 357 | * 校验文件大小并执行备份 358 | */ 359 | function checkFileSizeAndBackup () { 360 | let start = new Date() 361 | let hadBackup = false 362 | for (let key in PATH_CONFIG) { 363 | if (key === LOG_TYPES.DEVELOP) { 364 | // 开发用的develop日志不做备份 365 | continue 366 | } 367 | let filePath = PATH_CONFIG[key] 368 | let length = new java.io.File(filePath).length() 369 | if (files.exists(filePath) && length > 1024 * (_config.back_size || 100)) { 370 | hadBackup = true 371 | console.verbose(ENGINE_ID + ' ' + key + '文件大小:' + length + ' 大于' + (_config.back_size || 100) + 'kb,执行备份') 372 | innerClearLogFile(key) 373 | } 374 | } 375 | if (hadBackup) { 376 | console.verbose(ENGINE_ID + ' 备份文件耗时:' + (new Date().getTime() - start) + 'ms') 377 | } 378 | } 379 | 380 | /** 381 | * 格式化输入参数 eg. `['args: {} {} {}', 'arg1', 'arg2', 'arg3']` => `'args: arg1 arg2 arg3'` 382 | * @param {array} originContent 输入参数 383 | */ 384 | function convertObjectContent (originContent) { 385 | if (typeof originContent === 'string') { 386 | return originContent 387 | } else if (Array.isArray(originContent)) { 388 | let marker = originContent[0] 389 | let args = originContent.slice(1) 390 | if (Array.isArray(args) && args.length > 0) { 391 | args = args.map(r => { 392 | if (typeof r === 'function') { 393 | return r() 394 | } else { 395 | return r 396 | } 397 | }) 398 | } 399 | let regex = /(\{\})/g 400 | let matchResult = marker.match(regex) 401 | if (matchResult && args && matchResult.length > 0 && matchResult.length === args.length) { 402 | args.forEach((item, idx) => { 403 | marker = marker.replace('{}', item) 404 | }) 405 | return marker 406 | } else if (matchResult === null) { 407 | return marker 408 | } 409 | } 410 | console.error(ENGINE_ID + ' 参数不匹配[' + JSON.stringify(originContent) + ']') 411 | return originContent 412 | } 413 | 414 | module.exports = { 415 | debugInfo: function (content, isToast) { 416 | isToast = isToast && _config.show_debug_log 417 | if (_config.show_debug_log || _config.save_log_file) { 418 | showToast(content, _config.show_debug_log ? (c) => console.verbose(c) : () => { }, isToast, LOG_TYPES.DEBUG) 419 | } 420 | }, 421 | debugForDev: function (content, isToast, fileOnly) { 422 | if (_config.develop_mode) { 423 | if (Array.isArray(content) && content.length > 0) { 424 | content = content.map(r => { 425 | if (typeof r === 'function') { 426 | return r() 427 | } else { 428 | return r 429 | } 430 | }) 431 | } 432 | showToast( 433 | content, 434 | (c) => { 435 | if (!fileOnly) { 436 | console.verbose(c) 437 | } 438 | }, 439 | isToast, 440 | LOG_TYPES.DEVELOP 441 | ) 442 | } 443 | }, 444 | logInfo: function (content, isToast) { 445 | showToast(content, (c) => console.log(c), isToast, LOG_TYPES.LOG) 446 | }, 447 | infoLog: function (content, isToast) { 448 | showToast(content, (c) => console.info(c), isToast, LOG_TYPES.INFO) 449 | }, 450 | warnInfo: function (content, isToast) { 451 | showToast(content, (c) => console.warn(c), isToast, LOG_TYPES.WARN) 452 | }, 453 | errorInfo: function (content, isToast) { 454 | showToast(content, (c) => console.error(c), isToast, LOG_TYPES.ERROR) 455 | }, 456 | appendLog: function (content) { 457 | showToast(content, () => { }, false, LOG_TYPES.DEBUG) 458 | }, 459 | developSaving: function (content, fileName) { 460 | if (_config.develop_saving_mode) { 461 | content = convertObjectContent(content) 462 | content = formatDate(new Date()) + ' ' + content 463 | console.verbose(content) 464 | files.append(MAIN_PATH + '/logs/' + fileName + '.log', content) 465 | } 466 | }, 467 | clearLogFile: innerClearLogFile, 468 | removeOldLogFiles: removeOutdateBacklogFiles, 469 | flushAllLogs: function () { 470 | LOGGER.flushAllLogs() 471 | }, 472 | showCostingInfo: () => { 473 | LOGGER.showCostingInfo() 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /lib/prototype/RunningQueueDispatcher.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | let STORAGE_KEY = "autojs_dispatch_queue_storage" 5 | let RUNNING_KEY = "qunningTask" 6 | let WAITING_QUEUE_KEY = "waitingQueue" 7 | let WRITE_LOCK_KEY = "writeLock" 8 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 9 | let lockableStorages = singletonRequire('LockableStorage') 10 | let Timers = singletonRequire('Timers') 11 | let _logUtils = singletonRequire('LogUtils') 12 | let crashCatcher = singletonRequire('CrashCatcher') 13 | 14 | function RunningQueueDispatcher () { 15 | 16 | this.checkDuplicateRunning = function () { 17 | let currentEngine = engines.myEngine() 18 | let runningEngines = this.tryGetRunningEngines() 19 | if (runningEngines === null) { 20 | // 获取运行中脚本引擎失败 21 | _logUtils.errorInfo('校验重复运行异常,直接退出') 22 | exit() 23 | } 24 | let runningSize = runningEngines.length 25 | let currentSource = currentEngine.getSource() + '' 26 | _logUtils.debugInfo('Dispatcher:当前脚本信息 id:' + currentEngine.id + ' source:' + currentSource + ' 运行中脚本数量:' + runningSize) 27 | if (runningSize > 1) { 28 | runningEngines.forEach(engine => { 29 | let compareEngine = engine 30 | let compareSource = compareEngine.getSource() + '' 31 | _logUtils.debugInfo('Dispatcher:对比脚本信息 id:' + compareEngine.id + ' source:' + compareSource) 32 | if (currentEngine.id !== compareEngine.id && compareSource === currentSource) { 33 | _logUtils.warnInfo(['Dispatcher:脚本正在运行中 退出当前脚本:{} - {}', currentEngine.id, currentSource], true) 34 | this.removeRunningTask(true, true) 35 | engines.myEngine().forceStop() 36 | exit() 37 | } 38 | }) 39 | } 40 | } 41 | 42 | /** 43 | * 设置自动启动 44 | * 45 | * @param {string} source 脚本path路径 46 | * @param {number} seconds 延迟时间 秒 47 | */ 48 | this.setUpAutoStart = function (source, seconds) { 49 | let waitTime = seconds || 5 50 | _logUtils.debugInfo("定时[" + waitTime + "]秒后启动脚本: " + source) 51 | let task = Timers.addDisposableTask({ 52 | path: source, 53 | date: new Date().getTime() + waitTime * 1000 54 | }) 55 | _logUtils.debugInfo("定时任务预定成功: " + task.id) 56 | } 57 | 58 | /** 59 | * 立即启动目标脚本 60 | * 61 | * @param {string} source 脚本path路径 62 | */ 63 | this.executeTargetScript = function (source) { 64 | _logUtils.logInfo(['启动目标脚本:{}', source]) 65 | engines.execScriptFile(source, { path: source.substring(0, source.lastIndexOf('/')) }) 66 | } 67 | 68 | this.getCurrentTaskInfo = function () { 69 | currentEngine = engines.myEngine().getSource() + '' 70 | return { 71 | source: currentEngine, 72 | engineId: engines.myEngine().id 73 | } 74 | } 75 | 76 | 77 | this.clearAll = function () { 78 | lockableStorages.remove(STORAGE_KEY) 79 | _logUtils.logInfo('清除数据成功') 80 | } 81 | 82 | this.showDispatchStatus = function () { 83 | let runningTaskStr = this.getStorage().get(RUNNING_KEY) 84 | let waitingQueueStr = this.getStorage().get(WAITING_QUEUE_KEY) 85 | let lockKeyStr = this.getStorage().get(WRITE_LOCK_KEY) 86 | if (runningTaskStr) { 87 | let runningTask = JSON.parse(runningTaskStr) 88 | let timeout = new Date().getTime() - parseInt(runningTask.timeout) 89 | _logUtils.logInfo('当前运行中的任务:' + runningTaskStr + (timeout > 0 ? ' 已超时' + (timeout / 1000.0).toFixed(2) + '秒' : ' 超时剩余时间' + (-timeout / 1000.0).toFixed(0) + '秒')) 90 | } else { 91 | _logUtils.logInfo('当前无运行中的任务') 92 | } 93 | if (waitingQueueStr && waitingQueueStr !== '[]') { 94 | _logUtils.logInfo('当前等待中的队列:' + waitingQueueStr) 95 | } else { 96 | _logUtils.logInfo('当前无等待中的队列') 97 | } 98 | if (lockKeyStr) { 99 | let key = JSON.parse(lockKeyStr) 100 | _logUtils.logInfo('当前存在的锁:' + lockKeyStr + " 超时时间剩余:" + ((parseInt(key.timeout) - new Date().getTime()) / 1000.0).toFixed(2) + '秒') 101 | } else { 102 | _logUtils.logInfo('当前无存在的锁') 103 | } 104 | } 105 | 106 | this.getStorage = function () { 107 | return lockableStorages.create(STORAGE_KEY) 108 | } 109 | 110 | this.clearLock = function () { 111 | let taskInfo = this.getCurrentTaskInfo() 112 | let storedLockStr = this.getStorage().get(WRITE_LOCK_KEY) 113 | if (storedLockStr) { 114 | let storedLock = JSON.parse(storedLockStr) 115 | if (storedLock.source === taskInfo.source) { 116 | _logUtils.debugInfo('移除任务锁:' + JSON.stringify(taskInfo)) 117 | this.getStorage().put(WRITE_LOCK_KEY, '') 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * 尝试获取锁 124 | * @param {number} tryTime 尝试次数,默认一次 125 | */ 126 | this.lock = function (tryTime) { 127 | tryTime = tryTime || 1 128 | let lockSuccess = this.storageLock() 129 | while (--tryTime > 0 && !lockSuccess) { 130 | sleep(200) 131 | lockSuccess = this.storageLock() 132 | } 133 | return lockSuccess 134 | } 135 | 136 | this.putLockInfo = function (taskInfo) { 137 | return this.getStorage().put(WRITE_LOCK_KEY, 138 | JSON.stringify({ 139 | source: taskInfo.source, 140 | engineId: taskInfo.engineId, 141 | count: 1, 142 | timeout: new Date().getTime() + 30000 143 | })) 144 | } 145 | 146 | this.storageLock = function () { 147 | let taskInfo = this.getCurrentTaskInfo() 148 | let storedLockStr = this.getStorage().get(WRITE_LOCK_KEY) 149 | if (storedLockStr) { 150 | let storedLock = JSON.parse(storedLockStr) 151 | if (storedLock.source === taskInfo.source) { 152 | if (storedLock.engineId === taskInfo.engineId) { 153 | storedLock.count = parseInt(storedLock.count) + 1 154 | // 锁超时时间30秒 155 | storedLock.timeout = new Date().getTime() + 30000 156 | return this.getStorage().put(WRITE_LOCK_KEY, JSON.stringify(storedLock)) 157 | } else { 158 | // 校验加锁引擎是否挂了 159 | let runningEngines = this.tryGetRunningEngines() 160 | // null 说明获取运行中engines失败,作为操作异常,加锁失败 161 | if (runningEngines === null || runningEngines.find((engine) => engine.id === storedLock.engineId)) { 162 | return false 163 | } else { 164 | _logUtils.warnInfo('加锁脚本引擎 engineId「' + storedLock.engineId + '」已停止,直接覆盖:' + JSON.stringify(storedLock)) 165 | return this.putLockInfo(taskInfo) 166 | } 167 | } 168 | } else { 169 | if (parseInt(storedLock.timeout) < new Date().getTime()) { 170 | _logUtils.warnInfo('已有锁已超时,直接覆盖:' + JSON.stringify(storedLock)) 171 | return this.putLockInfo(taskInfo) 172 | } 173 | return false 174 | } 175 | } else { 176 | return this.putLockInfo(taskInfo) 177 | } 178 | } 179 | 180 | this.unlock = function () { 181 | let taskInfo = this.getCurrentTaskInfo() 182 | let storedLockStr = this.getStorage().get(WRITE_LOCK_KEY) 183 | if (storedLockStr) { 184 | let storedLock = JSON.parse(storedLockStr) 185 | if (storedLock.source === taskInfo.source && storedLock.engineId === taskInfo.engineId) { 186 | if (parseInt(storedLock.count) > 1) { 187 | storedLock.count = parseInt(storedLock.count) - 1 188 | return this.getStorage().put(WRITE_LOCK_KEY, JSON.stringify(storedLock)) 189 | } else { 190 | return this.getStorage().put(WRITE_LOCK_KEY, '') 191 | } 192 | } else { 193 | return false 194 | } 195 | } else { 196 | return false 197 | } 198 | } 199 | 200 | this.getRunningStatus = function () { 201 | let storedRunningTask = this.getStorage().get(RUNNING_KEY) 202 | if (storedRunningTask) { 203 | let runningTask = JSON.parse(storedRunningTask) 204 | let currentTimestamp = new Date().getTime() 205 | if (currentTimestamp > runningTask.timeout) { 206 | _logUtils.debugInfo('运行中任务已超时:' + storedRunningTask + ' 超时时间:' + ((currentTimestamp - runningTask.timeout) / 1000).toFixed(0) + '秒') 207 | // 直接移除已超时运行中的任务 208 | this.getStorage().put(RUNNING_KEY, '') 209 | return null 210 | } else { 211 | _logUtils.debugInfo('获取运行中任务信息:' + storedRunningTask + ' 超时剩余时间:' + ((runningTask.timeout - currentTimestamp) / 1000).toFixed(0) + '秒') 212 | return runningTask 213 | } 214 | } else { 215 | return null 216 | } 217 | } 218 | 219 | this.getWaitingStatus = function () { 220 | // 任务队列去重 221 | this.distinctAwaitTasks() 222 | let waitingArrayStr = this.getStorage().get(WAITING_QUEUE_KEY) 223 | let waitingArray = null 224 | if (waitingArrayStr) { 225 | waitingArray = JSON.parse(waitingArrayStr) 226 | } 227 | if (waitingArray && waitingArray.length > 0) { 228 | return waitingArray[0] 229 | } 230 | return null 231 | } 232 | 233 | /** 234 | * 从等待队列队首移除任务,当外层已包裹lock 可忽略返回值 否则后续操作需要判断是否为null 235 | */ 236 | this.popWaitingTask = function () { 237 | let waitingArrayStr = this.getStorage().get(WAITING_QUEUE_KEY) 238 | let waitingArray = null 239 | if (waitingArrayStr) { 240 | waitingArray = JSON.parse(waitingArrayStr) 241 | } 242 | if (waitingArray && waitingArray.length > 0) { 243 | let waitingTask = waitingArray.splice(0, 1) 244 | if (this.lock()) { 245 | this.getStorage().put(WAITING_QUEUE_KEY, JSON.stringify(waitingArray)) 246 | this.unlock() 247 | return waitingTask 248 | } 249 | } 250 | return null 251 | } 252 | 253 | /** 254 | * @param {boolean} checkOwner 判断当前运行中的任务信息是否是当前脚本引擎施加的 255 | * @param {boolean} notRemoveCrashFlag 重复执行的任务不移除运行中标记 256 | * @param {function} callbackOnSuccess 移除成功后执行 257 | */ 258 | this.removeRunningTask = function (checkOwner, notRemoveCrashFlag, callbackOnSuccess) { 259 | callbackOnSuccess = callbackOnSuccess || function () {} 260 | let taskInfo = this.getCurrentTaskInfo() 261 | let runningTask = this.getRunningStatus() 262 | if (runningTask !== null) { 263 | // engineId判断所有权 264 | if (runningTask.source === taskInfo.source && (!checkOwner || runningTask.engineId === taskInfo.engineId)) { 265 | _logUtils.debugInfo('准备移除运行中任务') 266 | if (this.lock(3)) { 267 | this.getStorage().put(RUNNING_KEY, '') 268 | callbackOnSuccess() 269 | let waitingTask = this.getWaitingStatus() 270 | if (waitingTask !== null && this.lock()) { 271 | _logUtils.debugInfo('有任务在等待,执行它') 272 | this.popWaitingTask() 273 | _logUtils.debugInfo('执行等待队列首个任务:' + JSON.stringify(waitingTask)) 274 | 275 | // 将队列中任务放入执行中 276 | this.doAddRunningTask(waitingTask) 277 | sleep(1000) 278 | this.unlock() 279 | // 将队列中的任务执行掉 280 | this.executeTargetScript(waitingTask.source) 281 | } else { 282 | _logUtils.debugInfo('无任务等待中') 283 | } 284 | this.unlock() 285 | } 286 | 287 | } else { 288 | _logUtils.warnInfo('运行中任务:' + JSON.stringify(runningTask) + '和当前任务:' + JSON.stringify(taskInfo) + '不同,不可移除') 289 | } 290 | } else { 291 | _logUtils.warnInfo('无任务在运行中,不可移除') 292 | callbackOnSuccess() 293 | } 294 | // 重复执行的任务 不移除运行中的标记 295 | if (!notRemoveCrashFlag) { 296 | crashCatcher.setDone() 297 | } 298 | // 清空当前任务施加的锁 299 | this.clearLock() 300 | } 301 | 302 | this.doAddRunningTask = function (taskInfo) { 303 | // 默认超时时间15分钟 304 | taskInfo.timeout = new Date().getTime() + 15 * 60 * 1000 305 | this.getStorage().put(RUNNING_KEY, JSON.stringify(taskInfo)) 306 | if (taskInfo.source === this.getCurrentTaskInfo().source) { 307 | // 当前脚本正常开始执行后 标记为运行中 308 | crashCatcher.setOnRunning() 309 | } 310 | // 杀死运行中但是未加入队列的任务 311 | } 312 | 313 | this.addRunningTask = function () { 314 | let taskInfo = this.getCurrentTaskInfo() 315 | let runningTask = this.getRunningStatus() 316 | if (runningTask !== null) { 317 | _logUtils.debugInfo('当前有任务正在运行:' + JSON.stringify(runningTask)) 318 | if (runningTask.source === taskInfo.source) { 319 | _logUtils.debugInfo('运行中脚本任务和当前任务相同,继续判断同源脚本是否正在运行') 320 | // 如果判断当前运行中和存储任务状态是同一个则不去校验是否重复运行 321 | if (runningTask.engineId !== taskInfo.engineId) { 322 | // 避免重复运行,如果挂了则继续 323 | this.checkDuplicateRunning() 324 | if (this.lock(3)) { 325 | // 更新运行中任务信息 326 | this.doAddRunningTask(taskInfo) 327 | this.unlock() 328 | } else { 329 | _logUtils.warnInfo('更新运行中任务信息失败') 330 | } 331 | } 332 | _logUtils.debugInfo('运行状态校验成功,执行后续功能') 333 | return 334 | } else { 335 | // 判断执行状态 336 | let timeoutMillis = runningTask.timeout 337 | // 超过一分钟 338 | if (timeoutMillis - new Date().getTime() < 14 * 60 * 1000) { 339 | let runningEngines = this.tryGetRunningEngines() 340 | if (runningEngines === null || runningEngines.find(v => v.id === runningTask.engineId || runningTask.source === v.getSource() + '' )) { 341 | _logUtils.debugInfo('运行中任务执行正常') 342 | } else { 343 | if (this.lock(3)) { 344 | _logUtils.warnInfo('运行中任务已经异常关闭,直接删除运行中标记') 345 | // 清空运行中数据 346 | this.getStorage().put(RUNNING_KEY, '') 347 | this.unlock() 348 | // 然后重新加入运行中任务 349 | return this.addRunningTask() 350 | } else { 351 | _logUtils.warnInfo('运行中任务已经异常关闭,删除运行中标记失败') 352 | } 353 | } 354 | } 355 | _logUtils.debugInfo('将当前task放入等待队列:' + JSON.stringify(taskInfo)) 356 | this.addAwaitTask(taskInfo) 357 | exit() 358 | } 359 | } else { 360 | let waitingTask = this.getWaitingStatus() 361 | if (waitingTask !== null) { 362 | _logUtils.debugInfo('等待队列中已有任务待运行:' + JSON.stringify(waitingTask)) 363 | if (waitingTask.source === taskInfo.source) { 364 | _logUtils.debugInfo('等待中任务和当前任务相同,可直接执行,将任务信息放入running') 365 | if (this.lock(3)) { 366 | this.doAddRunningTask(taskInfo) 367 | this.popWaitingTask() 368 | this.unlock() 369 | } else { 370 | _logUtils.errorInfo('获取锁失败,无法继续执行任务:' + JSON.stringify(taskInfo)) 371 | _logUtils.warnInfo('尝试将任务加入等待队列中') 372 | if (this.lock(3)) { 373 | this.addAwaitTask(taskInfo) 374 | this.unlock() 375 | } else { 376 | if (!this.isTaskInQueue(taskInfo)) { 377 | _logUtils.warnInfo('尝试将任务加入等待队列失败,定时十秒后启动') 378 | this.setUpAutoStart(taskInfo.source, 10) 379 | } 380 | } 381 | exit() 382 | } 383 | } else { 384 | _logUtils.debugInfo('等待中任务和当前任务不同,将任务信息放入等待队列:' + JSON.stringify(taskInfo)) 385 | if (this.lock(3)) { 386 | this.addAwaitTask(taskInfo) 387 | this.popWaitingTask() 388 | _logUtils.debugInfo('执行等待队列首个任务:' + JSON.stringify(waitingTask)) 389 | // 将队列中任务放入执行中 390 | this.doAddRunningTask(waitingTask) 391 | this.unlock() 392 | // 将队列中的任务执行掉 393 | this.executeTargetScript(waitingTask.source) 394 | exit() 395 | } else { 396 | if (!this.isTaskInQueue(taskInfo)) { 397 | _logUtils.errorInfo('获取锁失败,无法执行等待中任务,当前任务也未成功入队列,设定10秒后启动:' + JSON.stringify(taskInfo)) 398 | this.setUpAutoStart(taskInfo.source, 10) 399 | } 400 | exit() 401 | } 402 | } 403 | } else { 404 | if (this.lock()) { 405 | _logUtils.debugInfo('当前无任务等待,直接执行:' + JSON.stringify(taskInfo)) 406 | this.doAddRunningTask(taskInfo) 407 | this.unlock() 408 | } else { 409 | _logUtils.errorInfo('获取锁失败,无法继续执行任务:' + JSON.stringify(taskInfo)) 410 | this.setUpAutoStart(taskInfo.source, 10) 411 | exit() 412 | } 413 | } 414 | } 415 | } 416 | 417 | this.addAwaitTask = function (taskInfo) { 418 | if (this.isTaskInQueue(taskInfo)) { 419 | _logUtils.debugInfo(['任务:{} 已经在队列中,不再加入任务队列', JSON.stringify(taskInfo)]) 420 | return 421 | } 422 | let storedArrayStr = this.getStorage().get(WAITING_QUEUE_KEY) 423 | let storedArray = null 424 | if (storedArrayStr) { 425 | storedArray = JSON.parse(storedArrayStr) 426 | } else { 427 | storedArray = [] 428 | } 429 | storedArray.push(taskInfo) 430 | if (this.lock(3)) { 431 | this.getStorage().put(WAITING_QUEUE_KEY, JSON.stringify(storedArray)) 432 | this.distinctAwaitTasks() 433 | this.unlock() 434 | } else { 435 | _logUtils.errorInfo('添加等待任务队列失败,获取写锁失败,任务信息:' + JSON.stringify(taskInfo)) 436 | this.setUpAutoStart(taskInfo.source, 10) 437 | } 438 | } 439 | 440 | this.distinctAwaitTasks = function () { 441 | if (this.lock()) { 442 | let storedArrayStr = this.getStorage().get(WAITING_QUEUE_KEY) 443 | let storedArray = null 444 | if (storedArrayStr) { 445 | storedArray = JSON.parse(storedArrayStr) 446 | } else { 447 | storedArray = [] 448 | } 449 | if (storedArray && storedArray.length > 0) { 450 | _logUtils.debugInfo('去重复前的任务队列:' + storedArrayStr) 451 | let distinctArray = [] 452 | storedArray.forEach(task => { 453 | if (distinctArray.map(r => r.source).indexOf(task.source) < 0) { 454 | distinctArray.push(task) 455 | } 456 | }) 457 | let distinctArrayStr = JSON.stringify(distinctArray) 458 | _logUtils.debugInfo('去重复后的任务队列:' + distinctArrayStr) 459 | this.getStorage().put(WAITING_QUEUE_KEY, distinctArrayStr) 460 | } else { 461 | _logUtils.debugInfo('队列小于等于1 不需要去重:' + storedArrayStr) 462 | } 463 | this.unlock() 464 | } 465 | } 466 | 467 | /** 468 | * 判断任务是否已经加入到了等待队列 469 | */ 470 | this.isTaskInQueue = function (taskInfo) { 471 | this.distinctAwaitTasks() 472 | let storedArrayStr = this.getStorage().get(WAITING_QUEUE_KEY) 473 | let storedArray = null 474 | if (storedArrayStr) { 475 | storedArray = JSON.parse(storedArrayStr) 476 | } else { 477 | storedArray = [] 478 | } 479 | taskInfo = taskInfo || this.getCurrentTaskInfo() 480 | if (storedArray.length > 0 && storedArray.find(task => task.source === taskInfo.source)) { 481 | return true 482 | } else { 483 | return false 484 | } 485 | } 486 | 487 | /** 488 | * 尝试获取运行中的脚本引擎 489 | * 每200~300毫秒获取一次 490 | */ 491 | this.tryGetRunningEngines = function (tryCount) { 492 | let runningEngines = null 493 | tryCount = tryCount || 5 494 | while (runningEngines === null && tryCount-- > 0) { 495 | // engines.all()有并发问题,尝试多次获取 496 | try { 497 | runningEngines = engines.all() 498 | } catch (e) { 499 | // 延迟随机时间200~300毫秒 500 | sleep(200 + parseInt(Math.random() * 100 % 100)) 501 | } 502 | } 503 | if (runningEngines === null) { 504 | _logUtils.warnInfo('获取运行中脚本引擎失败') 505 | } 506 | return runningEngines 507 | } 508 | } 509 | 510 | 511 | module.exports = new RunningQueueDispatcher() -------------------------------------------------------------------------------- /lib/prototype/Timers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: SilvMonFr.00 https://github.com/SuperMonster003 3 | * @Last Modified by: TonyJiangWJ 4 | * @Last Modified time: 2020-08-03 14:00:31 5 | * @Description: 定时任务桥接,由大佬SilvMonFr.00提供 6 | */ 7 | importPackage(org.joda.time); 8 | 9 | let waitForAction = _waitForAction 10 | 11 | module.exports = (function (_runtime_, scope) { 12 | 13 | let is_pro = Object.prototype.toString.call(com.stardust.autojs.core.timing.TimedTask.Companion).match(/Java(Class|Object)/) 14 | let is_modify = Object.prototype.toString.call(org.autojs.autojsm.timing.TimedTask).match(/Java(Class|Object)/) 15 | let timing = is_pro ? com.stardust.autojs.core.timing : (is_modify ? org.autojs.autojsm.timing : org.autojs.autojs.timing) 16 | var timers = Object.create(_runtime_.timers); 17 | var TimedTask = is_pro ? timing.TimedTask.Companion : timing.TimedTask; 18 | var IntentTask = timing.IntentTask; 19 | var TimedTaskManager = is_pro ? timing.TimedTaskManager.Companion.getInstance() : timing.TimedTaskManager.getInstance(); 20 | var bridges = require("__bridges__"); 21 | let days_ident = [ 22 | 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 23 | 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', 24 | '一', '二', '三', '四', '五', '六', '日', 25 | 1, 2, 3, 4, 5, 6, 0, 26 | 1, 2, 3, 4, 5, 6, 7, 27 | ].map(value => value.toString()); 28 | 29 | scope.__asGlobal__(timers, ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate']); 30 | 31 | scope.loop = function () { 32 | console.warn("loop() has been deprecated and has no effect. Remove it from your code."); 33 | }; 34 | 35 | timers.addDailyTask = function (task) { 36 | let localTime = parseDateTime("LocalTime", task.time); 37 | let timedTask = TimedTask.dailyTask(localTime, files.path(task.path), parseConfig(task)); 38 | 39 | addTask(timedTask); 40 | return timedTask; 41 | }; 42 | 43 | timers.addWeeklyTask = function (task) { 44 | let localTime = parseDateTime("LocalTime", task.time); 45 | let timeFlag = 0; 46 | for (let i = 0; i < task.daysOfWeek.length; i++) { 47 | let dayString = task.daysOfWeek[i].toString(); 48 | let dayIndex = days_ident.indexOf(dayString.toLowerCase()) % 7; 49 | if (!~dayIndex) throw new Error('unknown day: ' + dayString); 50 | timeFlag |= TimedTask.getDayOfWeekTimeFlag(dayIndex + 1); 51 | } 52 | let timedTask = TimedTask.weeklyTask(localTime, new java.lang.Long(timeFlag), files.path(task.path), parseConfig(task)); 53 | addTask(timedTask); 54 | return timedTask; 55 | }; 56 | 57 | timers.addDisposableTask = function (task) { 58 | let localDateTime = parseDateTime("LocalDateTime", task.date); 59 | let timedTask = TimedTask.disposableTask(localDateTime, files.path(task.path), parseConfig(task)); 60 | addTask(timedTask); 61 | return timedTask; 62 | }; 63 | 64 | timers.addIntentTask = function (task) { 65 | let intentTask = new IntentTask(); 66 | intentTask.setLocal(task.isLocal) 67 | intentTask.setScriptPath(files.path(task.path)); 68 | task.action && intentTask.setAction(task.action); 69 | addTask(intentTask); 70 | return intentTask; 71 | }; 72 | 73 | timers.getTimedTask = function (id) { 74 | return TimedTaskManager.getTimedTask(id); 75 | }; 76 | 77 | timers.getIntentTask = function (id) { 78 | return TimedTaskManager.getIntentTask(id); 79 | }; 80 | 81 | timers.removeIntentTask = function (id) { 82 | if (!id && isNaN(+id)) return; 83 | let task = timers.getIntentTask(id); 84 | return task && removeTask(task); 85 | }; 86 | 87 | timers.removeTimedTask = function (id) { 88 | if (!id && isNaN(+id)) return; 89 | let task = timers.getTimedTask(id); 90 | return task && removeTask(task); 91 | }; 92 | 93 | timers.queryTimedTasks = function (options, callback) { 94 | options = options || {}; 95 | var sql = ''; 96 | var args = []; 97 | 98 | function sqlAppend (str) { 99 | if (sql.length === 0) { 100 | sql += str; 101 | } else { 102 | sql += ' AND ' + str; 103 | } 104 | return true; 105 | } 106 | 107 | let path = options.path; 108 | path && sqlAppend('script_path = ?') && args.push(path); 109 | return is_pro ? bridges.toArray(TimedTaskManager.queryTimedTasks(sql || null, args)) : (() => { 110 | let list = TimedTaskManager.getAllTasksAsList().toArray(); 111 | if (options.path) list = list.filter(task => task.getScriptPath() === path); 112 | return list; 113 | })(); 114 | }; 115 | 116 | timers.queryIntentTasks = function (options, callback) { 117 | let allIntentTasks = TimedTaskManager.getAllIntentTasksAsList() 118 | return bridges.toArray(allIntentTasks).filter(intentTask => { 119 | return (options.action ? intentTask.getAction() === options.action : true) 120 | && (options.path ? intentTask.getScriptPath() === options.path : true) 121 | }); 122 | }; 123 | 124 | return timers; 125 | 126 | // tool function(s) // 127 | 128 | function parseConfig (c) { 129 | let config = new com.stardust.autojs.execution.ExecutionConfig(); 130 | config.delay = c.delay || 0; 131 | config.interval = c.interval || 0; 132 | config.loopTimes = (c.loopTimes === undefined) ? 1 : c.loopTimes; 133 | return config; 134 | } 135 | 136 | function parseDateTime (clazz, dateTime) { 137 | clazz = is_pro ? clazz : org.joda.time[clazz]; 138 | if (typeof (dateTime) == 'string') { 139 | return is_pro ? TimedTask.parseDateTime(clazz, dateTime) : clazz.parse(dateTime); 140 | } else if (typeof (dateTime) == 'object' && dateTime.constructor === Date) { 141 | return is_pro ? TimedTask.parseDateTime(clazz, dateTime.getTime()) : new clazz(dateTime.getTime()); 142 | } else if (typeof (dateTime) == 'number' && isFinite(dateTime)) { 143 | return is_pro ? TimedTask.parseDateTime(clazz, dateTime) : new clazz(dateTime); 144 | } else { 145 | throw new Error("cannot parse date time: " + dateTime); 146 | } 147 | } 148 | 149 | function addTask (task) { 150 | TimedTaskManager[is_pro ? "addTaskSync" : "addTask"](task); 151 | waitForAction(() => task.id !== 0, 500, 80); 152 | } 153 | 154 | function removeTask (task) { 155 | let id = task.id; 156 | TimedTaskManager[is_pro ? "removeTaskSync" : "removeTask"](task); 157 | return waitForAction(() => !timers.getTimedTask(id), 500, 80); 158 | } 159 | })(runtime, this) 160 | 161 | // monster function(s) // 162 | 163 | function _waitForAction (f, timeout_or_times, interval) { 164 | let _timeout = timeout_or_times || 10000; 165 | let _interval = interval || 200; 166 | let _times = _timeout < 100 ? _timeout : ~~(_timeout / _interval) + 1; 167 | 168 | let _messageAction = typeof messageAction === "undefined" ? messageActionRaw : messageAction; 169 | 170 | while (!_checkF(f) && _times--) sleep(_interval); 171 | return _times >= 0; 172 | 173 | // tool function(s) // 174 | 175 | function _checkF (f) { 176 | let _classof = o => Object.prototype.toString.call(o).slice(8, -1); 177 | if (_classof(f) === "JavaObject") return _checkF(() => f.exists()); 178 | if (_classof(f) === "Array") { 179 | let _arr = f; 180 | let _logic_flag = "all"; 181 | if (typeof _arr[_arr.length - 1] === "string") _logic_flag = _arr.pop(); 182 | if (_logic_flag.match(/^(or|one)$/)) _logic_flag = "one"; 183 | for (let i = 0, len = _arr.length; i < len; i += 1) { 184 | if (!(typeof _arr[i]).match(/function|object/)) _messageAction("数组参数中含不合法元素", 8, 1, 0, 1); 185 | if (_logic_flag === "all" && !_checkF(_arr[i])) return false; 186 | if (_logic_flag === "one" && _checkF(_arr[i])) return true; 187 | } 188 | return _logic_flag === "all"; 189 | } else if (typeof f === "function") return f(); 190 | else _messageAction("\"waitForAction\"传入f参数不合法\n\n" + f.toString() + "\n", 8, 1, 1, 1); 191 | } 192 | 193 | // raw function(s) // 194 | 195 | function messageActionRaw (msg, msg_level, toast_flag) { 196 | let _msg = msg || " "; 197 | if (msg_level && msg_level.toString().match(/^t(itle)?$/)) { 198 | return messageAction("[ " + msg + " ]", 1, toast_flag); 199 | } 200 | let _msg_level = typeof +msg_level === "number" ? +msg_level : -1; 201 | toast_flag && toast(_msg); 202 | _msg_level === 1 && log(_msg) || _msg_level === 2 && console.info(_msg) || 203 | _msg_level === 3 && console.warn(_msg) || _msg_level >= 4 && console.error(_msg); 204 | _msg_level >= 8 && exit(); 205 | return !(_msg_level in { 3: 1, 4: 1 }); 206 | } 207 | } -------------------------------------------------------------------------------- /lib/prototype/TryRequestScreenCapture.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: SilvMonFr.00 https://github.com/SuperMonster003 3 | * Just an insurance way of images.requestScreenCapture() to avoid infinite stuck or stalled without any hint or log 4 | * During this operation, permission prompt window will be confirmed (with checkbox checked if possible) automatically with effort 5 | * @param [params] {object} 6 | * @param [params.debug_info_flag] {boolean} 7 | * @param [params.restart_this_engine_flag=false] {boolean} 8 | * @param [params.restart_this_engine_params] {object} 9 | * @param [params.restart_this_engine_params.new_file] {string} - new engine task name with or without path or file extension name 10 | *
11 | * -- *DEFAULT* - old engine task
12 | * -- new file - like "hello.js", "../hello.js" or "hello" 13 | * @param [params.restart_this_engine_params.debug_info_flag] {boolean} 14 | * @param [params.restart_this_engine_params.max_restart_engine_times=3] {number} - max restart times for avoiding infinite recursion 15 | * @return {boolean} 16 | */ 17 | function tryRequestScreenCapture(params) { 18 | __global__ = typeof __global__ === "undefined" ? {} : __global__; 19 | if (__global__._monster_$_request_screen_capture_flag) return true; 20 | 21 | sleep(200); // why are you always a naughty boy... how can i get along well with you... 22 | 23 | let _params = params || {}; 24 | 25 | let _debugInfo = _msg => (typeof debugInfo === "undefined" ? debugInfoRaw : debugInfo)(_msg, "", _params.debug_info_flag); 26 | let _waitForAction = typeof waitForAction === "undefined" ? waitForActionRaw : waitForAction; 27 | let _messageAction = typeof messageAction === "undefined" ? messageActionRaw : messageAction; 28 | let _clickAction = typeof clickAction === "undefined" ? clickActionRaw : clickAction; 29 | let _restartThisEngine = typeof restartThisEngine === "undefined" ? restartThisEngineRaw : restartThisEngine; 30 | let _getSelector = typeof getSelector === "undefined" ? getSelectorRaw : getSelector; 31 | 32 | let sel = _getSelector(); 33 | 34 | _params.restart_this_engine_flag = typeof _params.restart_this_engine_flag === "undefined" ? true : !!_params.restart_this_engine_flag; 35 | _params.restart_this_engine_params = _params.restart_this_engine_params || {}; 36 | _params.restart_this_engine_params.max_restart_engine_times = _params.restart_this_engine_params.max_restart_engine_times || 3; 37 | 38 | _debugInfo("开始申请截图权限"); 39 | 40 | __global__._monster_$_request_screen_capture_flag = true; 41 | _debugInfo("已存储截图权限申请标记"); 42 | 43 | _debugInfo("已开启弹窗监测线程"); 44 | let _thread_prompt = threads.start(function () { 45 | let _kw_no_longer_prompt = type => sel.pickup(id("com.android.systemui:id/remember"), "kw_req_capt_no_longer_prompt", type); 46 | let _kw_sure_btn = type => sel.pickup(/START NOW|立即开始|允许/, "", type); 47 | 48 | if (_waitForAction(_kw_sure_btn, 5000)) { 49 | if (_waitForAction(_kw_no_longer_prompt, 1000)) { 50 | _debugInfo("勾选\"不再提示\"复选框"); 51 | _clickAction(_kw_no_longer_prompt(), "widget"); 52 | } 53 | if (_waitForAction(_kw_sure_btn, 2000)) { 54 | let _node = _kw_sure_btn(); 55 | let _btn_click_action_str = "点击\"" + _kw_sure_btn("txt") + "\"按钮"; 56 | 57 | _debugInfo(_btn_click_action_str); 58 | _clickAction(_node, "widget"); 59 | 60 | if (!_waitForAction(() => !_kw_sure_btn(), 1000)) { 61 | _debugInfo("尝试click()方法再次" + _btn_click_action_str); 62 | _clickAction(_node, "click"); 63 | } 64 | } 65 | } 66 | }); 67 | 68 | let _thread_monitor = threads.start(function () { 69 | if (_waitForAction(() => !!_req_result, 2000, 500)) { 70 | _thread_prompt.interrupt(); 71 | return _debugInfo("截图权限申请结果: " + _req_result); 72 | } 73 | if (!__global__._monster_$_debug_info_flag) { 74 | __global__._monster_$_debug_info_flag = true; 75 | _debugInfo("开发者测试模式已自动开启", 3); 76 | } 77 | if (_params.restart_this_engine_flag) { 78 | _debugInfo("截图权限申请结果: 失败", 3); 79 | if (_restartThisEngine(_params.restart_this_engine_params)) return; 80 | } 81 | _messageAction("截图权限申请失败", 9, 1, 0, 1); 82 | }); 83 | 84 | let _req_result = images.requestScreenCapture(false); 85 | sleep(300); 86 | 87 | _thread_monitor.join(2400); 88 | _thread_monitor.interrupt(); 89 | return _req_result; 90 | 91 | // raw function(s) // 92 | 93 | function getSelectorRaw() { 94 | let classof = o => Object.prototype.toString.call(o).slice(8, -1); 95 | let sel = selector(); 96 | sel.__proto__ = { 97 | pickup: (filter) => { 98 | if (classof(filter) === "JavaObject") { 99 | if (filter.toString().match(/UiObject/)) return filter; 100 | return filter.findOnce() || null; 101 | } 102 | if (typeof filter === "string") return desc(filter).findOnce() || text(filter).findOnce() || null; 103 | if (classof(filter) === "RegExp") return descMatches(filter).findOnce() || textMatches(filter).findOnce() || null; 104 | return null; 105 | }, 106 | }; 107 | return sel; 108 | } 109 | 110 | function debugInfoRaw(msg, info_flag) { 111 | if (info_flag) console.verbose((msg || "").replace(/^(>*)( *)/, ">>" + "$1 ")); 112 | } 113 | 114 | function waitForActionRaw(cond_func, time_params) { 115 | let _cond_func = cond_func; 116 | if (!cond_func) return true; 117 | let classof = o => Object.prototype.toString.call(o).slice(8, -1); 118 | if (classof(cond_func) === "JavaObject") _cond_func = () => cond_func.exists(); 119 | let _check_time = typeof time_params === "object" && time_params[0] || time_params || 10000; 120 | let _check_interval = typeof time_params === "object" && time_params[1] || 200; 121 | while (!_cond_func() && _check_time >= 0) { 122 | sleep(_check_interval); 123 | _check_time -= _check_interval; 124 | } 125 | return _check_time >= 0; 126 | } 127 | 128 | function clickActionRaw(kw) { 129 | let classof = o => Object.prototype.toString.call(o).slice(8, -1); 130 | let _kw = classof(_kw) === "Array" ? kw[0] : kw; 131 | let _key_node = classof(_kw) === "JavaObject" && _kw.toString().match(/UiObject/) ? _kw : _kw.findOnce(); 132 | if (!_key_node) return; 133 | let _bounds = _key_node.bounds(); 134 | click(_bounds.centerX(), _bounds.centerY()); 135 | return true; 136 | } 137 | 138 | function messageActionRaw(msg, msg_level, toast_flag) { 139 | let _msg = msg || " "; 140 | if (msg_level && msg_level.toString().match(/^t(itle)?$/)) { 141 | return messageActionRaw("[ " + msg + " ]", 1, toast_flag); 142 | } 143 | let _msg_level = typeof +msg_level === "number" ? +msg_level : -1; 144 | toast_flag && toast(_msg); 145 | if (_msg_level === 0) return console.verbose(_msg) || true; 146 | if (_msg_level === 1) return console.log(_msg) || true; 147 | if (_msg_level === 2) return console.info(_msg) || true; 148 | if (_msg_level === 3) return console.warn(_msg) || false; 149 | if (_msg_level >= 4) { 150 | console.error(_msg); 151 | _msg_level >= 8 && exit(); 152 | } 153 | } 154 | 155 | function restartThisEngineRaw(params) { 156 | let _params = params || {}; 157 | let _my_engine = engines.myEngine(); 158 | 159 | let _max_restart_engine_times_argv = _my_engine.execArgv.max_restart_engine_times; 160 | let _max_restart_engine_times_params = _params.max_restart_engine_times; 161 | let _max_restart_engine_times; 162 | let _instant_run_flag = !!_params.instant_run_flag; 163 | if (typeof _max_restart_engine_times_argv === "undefined") { 164 | if (typeof _max_restart_engine_times_params === "undefined") _max_restart_engine_times = 1; 165 | else _max_restart_engine_times = +_max_restart_engine_times_params; 166 | } else _max_restart_engine_times = +_max_restart_engine_times_argv; 167 | 168 | if (!_max_restart_engine_times) return; 169 | 170 | let _file_name = _params.new_file || _my_engine.source.toString(); 171 | if (_file_name.match(/^\[remote]/)) return ~console.error("远程任务不支持重启引擎") && exit(); 172 | let _file_path = files.path(_file_name.match(/\.js$/) ? _file_name : (_file_name + ".js")); 173 | engines.execScriptFile(_file_path, { 174 | arguments: { 175 | max_restart_engine_times: _max_restart_engine_times - 1, 176 | instant_run_flag: _instant_run_flag, 177 | }, 178 | }); 179 | _my_engine.forceStop(); 180 | } 181 | } 182 | 183 | module.exports = tryRequestScreenCapture -------------------------------------------------------------------------------- /lib/prototype/WidgetUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-11-05 09:12:00 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-05-07 20:52:15 6 | * @Description: 7 | */ 8 | let { config: _config } = require('../../config.js')(runtime, this) 9 | let singletonRequire = require('../SingletonRequirer.js')(runtime, this) 10 | let { 11 | debugInfo, debugForDev, logInfo, infoLog, warnInfo, errorInfo 12 | } = singletonRequire('LogUtils') 13 | 14 | const changeMode = function (newMode) { 15 | try { 16 | let clz = runtime.accessibilityBridge.getClass() 17 | clz = clz.getSuperclass() 18 | let field = clz.getDeclaredField('mMode') 19 | field.setAccessible(true) 20 | let mode = field.get(runtime.accessibilityBridge) 21 | debugInfo(['current mode: {}', mode === 0 ? 'NORMAL' : 'FAST']) 22 | runtime.accessibilityBridge.setMode(newMode) 23 | mode = field.get(runtime.accessibilityBridge) 24 | debugInfo(['mode after set: {}', mode === 0 ? 'NORMAL' : 'FAST']) 25 | } catch (e) { 26 | console.error('执行异常' + e) 27 | } 28 | } 29 | 30 | const enableFastMode = function () { 31 | changeMode(1) 32 | } 33 | 34 | const enableNormalMode = function () { 35 | changeMode(0) 36 | } 37 | 38 | /** 39 | * 判断控件A或者控件B是否存在;超时返回0 找到A返回1 否则返回2 40 | * 41 | * @param {string|regex} contentA 控件A的内容 42 | * @param {string|regex} contentB 控件B的内容 43 | * @param {number} timeout 超时时间 44 | */ 45 | const alternativeWidget = function (contentA, contentB, timeout) { 46 | timeout = timeout || _config.timeout_existing 47 | let timeoutFlag = true 48 | let countDown = new java.util.concurrent.CountDownLatch(1) 49 | let matchRegexA = new RegExp(contentA) 50 | let matchRegexB = new RegExp(contentB) 51 | let isDesc = false, findA = false 52 | let descThreadA = threads.start(function () { 53 | descMatches(matchRegexA).waitFor() 54 | let res = descMatches(matchRegexA).findOne().desc() 55 | debugInfo('find desc ' + contentA + " " + res) 56 | timeoutFlag = false 57 | isDesc = true 58 | findA = true 59 | countDown.countDown() 60 | }) 61 | 62 | let textThreadA = threads.start(function () { 63 | textMatches(matchRegexA).waitFor() 64 | let res = textMatches(matchRegexA).findOne().text() 65 | debugInfo('find text ' + contentA + " " + res) 66 | timeoutFlag = false 67 | findA = true 68 | countDown.countDown() 69 | }) 70 | let descThreadB = threads.start(function () { 71 | descMatches(matchRegexB).waitFor() 72 | let res = descMatches(matchRegexB).findOne().desc() 73 | debugInfo('find desc ' + contentB + " " + res) 74 | timeoutFlag = false 75 | isDesc = true 76 | countDown.countDown() 77 | }) 78 | 79 | let textThreadB = threads.start(function () { 80 | textMatches(matchRegexB).waitFor() 81 | let res = textMatches(matchRegexB).findOne().text() 82 | debugInfo('find text ' + contentB + " " + res) 83 | timeoutFlag = false 84 | countDown.countDown() 85 | }) 86 | 87 | let timeoutThread = threads.start(function () { 88 | sleep(timeout) 89 | countDown.countDown() 90 | }) 91 | countDown.await() 92 | descThreadA.interrupt() 93 | textThreadA.interrupt() 94 | descThreadB.interrupt() 95 | textThreadB.interrupt() 96 | timeoutThread.interrupt() 97 | if (timeoutFlag) { 98 | debugInfo(['cannot find any matches {} or {}', contentA, contentB]) 99 | } 100 | // 超时返回0 找到A返回1 否则返回2 101 | return timeoutFlag ? 0 : (findA ? 1 : 2) 102 | } 103 | 104 | /** 105 | * 校验控件是否存在,并打印相应日志 106 | * @param {String} contentVal 控件文本 107 | * @param {String} position 日志内容 当前所在位置是否成功进入 108 | * @param {Number} timeoutSetting 超时时间 单位毫秒 默认为_config.timeout_existing 109 | */ 110 | const widgetWaiting = function (contentVal, position, timeoutSetting) { 111 | let waitingSuccess = widgetCheck(contentVal, timeoutSetting) 112 | position = position || contentVal 113 | if (waitingSuccess) { 114 | debugInfo('等待控件成功:' + position) 115 | return true 116 | } else { 117 | errorInfo('等待控件[' + position + ']失败, 查找内容:' + contentVal) 118 | return false 119 | } 120 | } 121 | 122 | /** 123 | * 校验控件是否存在 124 | * @param {String} contentVal 控件文本 125 | * @param {Number} timeoutSetting 超时时间 单位毫秒 不设置则为_config.timeout_existing 126 | * @param {Boolean} containType 返回结果附带文本是desc还是text 127 | * 超时返回false 128 | */ 129 | const widgetCheck = function (contentVal, timeoutSetting, containType) { 130 | let timeout = timeoutSetting || _config.timeout_existing 131 | let timeoutFlag = true 132 | let countDown = new java.util.concurrent.CountDownLatch(1) 133 | let matchRegex = new RegExp(contentVal) 134 | let isDesc = false 135 | let descThread = threads.start(function () { 136 | descMatches(matchRegex).waitFor() 137 | let res = descMatches(matchRegex).findOne().desc() 138 | debugInfo('find desc ' + contentVal + " " + res) 139 | timeoutFlag = false 140 | isDesc = true 141 | countDown.countDown() 142 | }) 143 | 144 | let textThread = threads.start(function () { 145 | textMatches(matchRegex).waitFor() 146 | let res = textMatches(matchRegex).findOne().text() 147 | debugInfo('find text ' + contentVal + " " + res) 148 | timeoutFlag = false 149 | countDown.countDown() 150 | }) 151 | 152 | let timeoutThread = threads.start(function () { 153 | sleep(timeout) 154 | countDown.countDown() 155 | }) 156 | countDown.await() 157 | descThread.interrupt() 158 | textThread.interrupt() 159 | timeoutThread.interrupt() 160 | if (containType) { 161 | return { 162 | timeout: timeoutFlag, 163 | isDesc: isDesc 164 | } 165 | } 166 | return !timeoutFlag 167 | } 168 | 169 | /** 170 | * id检测 171 | * @param {string|RegExp} idRegex 172 | * @param {number} timeoutSetting 173 | */ 174 | const idCheck = function (idRegex, timeoutSetting) { 175 | let timeout = timeoutSetting || _config.timeout_existing 176 | let timeoutFlag = true 177 | let countDown = new java.util.concurrent.CountDownLatch(1) 178 | let idCheckThread = threads.start(function () { 179 | idMatches(idRegex).waitFor() 180 | debugInfo('find id ' + idRegex) 181 | timeoutFlag = false 182 | countDown.countDown() 183 | }) 184 | 185 | let timeoutThread = threads.start(function () { 186 | sleep(timeout) 187 | countDown.countDown() 188 | }) 189 | countDown.await() 190 | idCheckThread.interrupt() 191 | timeoutThread.interrupt() 192 | if (timeoutFlag) { 193 | warnInfo(['未能找到id:{}对应的控件', idRegex]) 194 | } 195 | return !timeoutFlag 196 | } 197 | 198 | /** 199 | * 校验控件是否存在,并打印相应日志 200 | * @param {String} idRegex 控件文本 201 | * @param {String} position 日志内容 当前所在位置是否成功进入 202 | * @param {Number} timeoutSetting 超时时间 默认为_config.timeout_existing 203 | */ 204 | const idWaiting = function (idRegex, position, timeoutSetting) { 205 | let waitingSuccess = idCheck(idRegex, timeoutSetting) 206 | position = position || idRegex 207 | if (waitingSuccess) { 208 | debugInfo('等待控件成功:' + position) 209 | return true 210 | } else { 211 | errorInfo('等待控件' + position + '失败, id:' + idRegex) 212 | return false 213 | } 214 | } 215 | 216 | /** 217 | * 根据id获取控件信息 218 | * @param {String|RegExp} idRegex id 219 | * @param {number} timeout 超时时间 220 | * @return 返回找到的控件,否则null 221 | */ 222 | const widgetGetById = function (idRegex, timeout) { 223 | timeout = timeout || _config.timeout_findOne 224 | let target = null 225 | if (idCheck(idRegex, timeout)) { 226 | idRegex = new RegExp(idRegex) 227 | target = idMatches(idRegex).findOne(timeout) 228 | } 229 | return target 230 | } 231 | 232 | /** 233 | * 根据内容获取一个对象 234 | * 235 | * @param {string} contentVal 236 | * @param {number} timeout 237 | * @param {boolean} containType 是否带回类型 238 | * @param {boolean} suspendWarning 是否隐藏warning信息 239 | */ 240 | const widgetGetOne = function (contentVal, timeout, containType, suspendWarning) { 241 | let target = null 242 | let isDesc = false 243 | let waitTime = timeout || _config.timeout_existing 244 | let timeoutFlag = true 245 | debugInfo(['try to find one: {} timeout: {}ms', contentVal.toString(), waitTime]) 246 | let checkResult = widgetCheck(contentVal, waitTime, true) 247 | if (!checkResult.timeout) { 248 | timeoutFlag = false 249 | let matchRegex = new RegExp(contentVal) 250 | if (!checkResult.isDesc) { 251 | target = textMatches(matchRegex).findOne(_config.timeout_findOne) 252 | } else { 253 | isDesc = true 254 | target = descMatches(matchRegex).findOne(_config.timeout_findOne) 255 | } 256 | } 257 | // 当需要带回类型时返回对象 传递target以及是否是desc 258 | if (target && containType) { 259 | let result = { 260 | target: target, 261 | isDesc: isDesc, 262 | content: isDesc ? target.desc() : target.text() 263 | } 264 | return result 265 | } 266 | if (timeoutFlag) { 267 | if (suspendWarning) { 268 | debugInfo('timeout for finding ' + contentVal) 269 | } else { 270 | warnInfo('timeout for finding ' + contentVal) 271 | } 272 | } 273 | return target 274 | } 275 | 276 | /** 277 | * 根据内容获取所有对象的列表 278 | * 279 | * @param {string} contentVal 280 | * @param {number} timeout 281 | * @param {boolean} containType 是否传递类型 282 | */ 283 | const widgetGetAll = function (contentVal, timeout, containType) { 284 | let target = null 285 | let isDesc = false 286 | let timeoutFlag = true 287 | let waitTime = timeout || _config.timeout_existing 288 | debugInfo(['try to find all: {} timeout: {}ms', contentVal.toString(), waitTime]) 289 | let checkResult = widgetCheck(contentVal, waitTime, true) 290 | if (!checkResult.timeout) { 291 | timeoutFlag = false 292 | let matchRegex = new RegExp(contentVal) 293 | if (!checkResult.isDesc) { 294 | target = textMatches(matchRegex).untilFind() 295 | } else { 296 | isDesc = true 297 | target = descMatches(matchRegex).untilFind() 298 | } 299 | } 300 | if (timeoutFlag && !target) { 301 | return null 302 | } else if (target && containType) { 303 | let result = { 304 | target: target, 305 | isDesc: isDesc 306 | } 307 | return result 308 | } 309 | return target 310 | } 311 | 312 | module.exports = { 313 | enableFastMode: enableFastMode, 314 | enableNormalMode: enableNormalMode, 315 | alternativeWidget: alternativeWidget, 316 | widgetWaiting: widgetWaiting, 317 | widgetCheck: widgetCheck, 318 | idWaiting: idWaiting, 319 | idCheck: idCheck, 320 | widgetGetOne: widgetGetOne, 321 | widgetGetAll: widgetGetAll, 322 | widgetGetById: widgetGetById 323 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Last Modified by: TonyJiangWJ 4 | * @Last Modified time: 2020-11-12 20:36:41 5 | * @Description: 6 | */ 7 | let { config } = require('./config.js')(runtime, this) 8 | let singletonRequire = require('./lib/SingletonRequirer.js')(runtime, this) 9 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 10 | let { logInfo, errorInfo, warnInfo, debugInfo, infoLog } = singletonRequire('LogUtils') 11 | let tryRequestScreenCapture = singletonRequire('TryRequestScreenCapture') 12 | let commonFunctions = singletonRequire('CommonFunction') 13 | let automator = singletonRequire('Automator') 14 | let unlocker = require('./lib/Unlock.js') 15 | let creditRunner = require('./core/CreditRunner.js') 16 | // 不管其他脚本是否在运行 清除任务队列 适合只使用蚂蚁森林的用户 17 | if (config.single_script) { 18 | logInfo('======单脚本运行直接清空任务队列=======') 19 | runningQueueDispatcher.clearAll() 20 | } 21 | logInfo('======加入任务队列,并关闭重复运行的脚本=======') 22 | runningQueueDispatcher.addRunningTask() 23 | /*********************** 24 | * 初始化 25 | ***********************/ 26 | logInfo('======校验无障碍功能======') 27 | // 检查手机是否开启无障碍服务 28 | // 当无障碍经常莫名消失时 可以传递true 强制开启无障碍 29 | // if (!commonFunctions.checkAccessibilityService(true)) { 30 | if (!commonFunctions.checkAccessibilityService()) { 31 | try { 32 | auto.waitFor() 33 | } catch (e) { 34 | warnInfo('auto.waitFor()不可用') 35 | auto() 36 | } 37 | } 38 | logInfo('---前置校验完成;启动系统--->>>>') 39 | // 打印运行环境信息 40 | if (files.exists('version.json')) { 41 | let content = JSON.parse(files.read('version.json')) 42 | logInfo(['版本信息:{} nodeId:{}', content.version, content.nodeId]) 43 | } else if (files.exists('project.json')) { 44 | let content = JSON.parse(files.read('project.json')) 45 | logInfo(['版本信息:{}', content.versionName]) 46 | } else { 47 | logInfo('无法获取脚本版本信息') 48 | } 49 | logInfo(['AutoJS version: {}', app.autojs.versionName]) 50 | logInfo(['device info: {} {} {}', device.brand, device.product, device.release]) 51 | 52 | logInfo(['设备分辨率:[{}, {}]', config.device_width, config.device_height]) 53 | logInfo('======解锁并校验截图权限======') 54 | try { 55 | unlocker.exec() 56 | } catch (e) { 57 | errorInfo('解锁发生异常, 三分钟后重新开始' + e) 58 | commonFunctions.setUpAutoStart(3) 59 | runningQueueDispatcher.removeRunningTask() 60 | exit() 61 | } 62 | logInfo('解锁成功') 63 | if (config.auto_set_bang_offset || config.updated_temp_flag_1325) { 64 | // 首次执行 需要识别刘海高度 65 | // 请求截图权限 66 | let screenPermission = false 67 | let actionSuccess = commonFunctions.waitFor(function () { 68 | if (config.request_capture_permission) { 69 | screenPermission = tryRequestScreenCapture() 70 | } else { 71 | screenPermission = requestScreenCapture(false) 72 | } 73 | }, 15000) 74 | if (!actionSuccess || !screenPermission) { 75 | errorInfo('请求截图失败, 设置6秒后重启') 76 | runningQueueDispatcher.removeRunningTask() 77 | sleep(6000) 78 | runningQueueDispatcher.executeTargetScript(FileUtils.getRealMainScriptPath()) 79 | exit() 80 | } else { 81 | logInfo('请求截屏权限成功') 82 | } 83 | } 84 | // 自动设置刘海偏移量 85 | commonFunctions.autoSetUpBangOffset() 86 | /************************ 87 | * 主程序 88 | ***********************/ 89 | commonFunctions.showDialogAndWait(true) 90 | commonFunctions.listenDelayStart() 91 | if (config.develop_mode) { 92 | creditRunner.exec() 93 | } else { 94 | try { 95 | creditRunner.exec() 96 | } catch (e) { 97 | commonFunctions.setUpAutoStart(1) 98 | errorInfo('执行异常, 1分钟后重新开始' + e) 99 | } 100 | } 101 | 102 | if (config.auto_lock === true && unlocker.needRelock() === true) { 103 | debugInfo('重新锁定屏幕') 104 | automator.lockScreen() 105 | } 106 | events.removeAllListeners() 107 | events.recycle() 108 | runningQueueDispatcher.removeRunningTask(true) 109 | exit() -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "蚂蚁积分签到", 3 | "main": "main.js", 4 | "ignore": [ 5 | "build" 6 | ], 7 | "packageName": "com.alipay.credit", 8 | "versionName": "1.0.4.1", 9 | "versionCode": 1 10 | } 11 | -------------------------------------------------------------------------------- /resources/for_update/download.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyJiangWJ/Alipay-Credits/1b51e49ab2565af5931195471701eafc7fd72be1/resources/for_update/download.dex -------------------------------------------------------------------------------- /test/AES_Test.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../../lib/SingletonRequirer.js')(runtime, this) 2 | let AesUtil = require('../lib/AesUtil.js') 3 | let Base64 = require('../lib/Base64.js') 4 | let FileUtils = singletonRequire('FileUtils') 5 | let CryptoJS = require('../lib/crypto-js.js') 6 | var key = device.getAndroidId() //秘钥必须为:8/16/32位 7 | var message = "123456"; 8 | console.show() 9 | console.log('秘钥:' + key) 10 | // 加密 11 | let encrypt = AesUtil.encrypt(message, key) 12 | console.log("encrypted: " + encrypt); 13 | 14 | // 解密 15 | console.log("decrypted: " + AesUtil.decrypt(encrypt, key)); 16 | 17 | key = key + key 18 | encrypt = CryptoJS.AES.encrypt(message, CryptoJS.enc.Utf8.parse(key), { 19 | mode: CryptoJS.mode.ECB, 20 | padding: CryptoJS.pad.Pkcs7 21 | }) 22 | console.log('加密内容:' + encrypt) 23 | 24 | let decrypt = CryptoJS.AES.decrypt(encrypt, CryptoJS.enc.Utf8.parse(key), { 25 | mode: CryptoJS.mode.ECB, 26 | padding: CryptoJS.pad.Pkcs7 27 | }).toString(CryptoJS.enc.Utf8) 28 | 29 | console.log('解密内容:' + decrypt) -------------------------------------------------------------------------------- /test/GestureLockTest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-23 09:09:08 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2019-12-23 09:14:57 6 | * @Description: 7 | */ 8 | let config = { 9 | alipay_lock_password: '' 10 | } 11 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 12 | let alipayUnlocker = singletonRequire('AlipayUnlocker') 13 | 14 | alipayUnlocker.drawGestureByPassword({ 15 | left: 270, 16 | top: 400, 17 | right: 870, 18 | bottom: 1000 19 | }) -------------------------------------------------------------------------------- /test/TestFamilyCredits.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 2 | let widgetUtils = singletonRequire('WidgetUtils') 3 | let logUtils = singletonRequire('LogUtils') 4 | let widgets = widgetUtils.widgetGetAll(/^\+(\d+)$/, null, true) 5 | if (widgets) { 6 | let targets = widgets.target 7 | let isDesc = widgets.isDesc 8 | targets.forEach(val => { 9 | let contentInfo = isDesc ? val.desc() : val.text() 10 | toastLog(contentInfo + " bounds: " + JSON.stringify(val.bounds())) 11 | let bounds = val.bounds() 12 | let flag = Math.abs(bounds.width() - bounds.height()) <= 10 && bounds.width() > 30 13 | logUtils.debugInfo(['校验控件形状是否符合:[{}, {}] result: {}', bounds.width(), bounds.height(), flag]) 14 | }) 15 | } else { 16 | toastLog('未找到对象') 17 | } -------------------------------------------------------------------------------- /test/TestLockScreen.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-27 23:41:15 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-27 23:51:48 6 | * @Description: 测试锁屏功能 7 | */ 8 | 9 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 10 | let automator = singletonRequire('Automator') 11 | automator.lockScreen() -------------------------------------------------------------------------------- /test/TestLockStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-23 23:33:09 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-25 15:28:23 6 | * @Description: 7 | */ 8 | 9 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 10 | let lockableStorages = singletonRequire('LockableStorage') 11 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 12 | 13 | let storge = lockableStorages.create('test_storage') 14 | log(storge.put('yes', 'yesyes')) 15 | log(storge.get('yes')) 16 | runningQueueDispatcher.addRunningTask() 17 | runningQueueDispatcher.removeRunningTask() 18 | log('调用次数:' + singletonRequire('LockableStorage', true)) 19 | -------------------------------------------------------------------------------- /test/queueTest/Task1.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | let commonFunctions = singletonRequire('CommonFunction') 4 | 5 | runningQueueDispatcher.addRunningTask() 6 | runningQueueDispatcher.showDispatchStatus() 7 | log('task1 start') 8 | let count = 15 9 | while (count-- > 0) { 10 | let content = 'Task1 Running count:' + count 11 | commonFunctions.showMiniFloaty(content, 700 - count * 10, 700 - count * 10, '#00FF00') 12 | sleep(1000) 13 | } 14 | log('task1 end') 15 | runningQueueDispatcher.showDispatchStatus() 16 | runningQueueDispatcher.removeRunningTask() -------------------------------------------------------------------------------- /test/queueTest/Task2.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | let commonFunctions = singletonRequire('CommonFunction') 4 | 5 | runningQueueDispatcher.addRunningTask() 6 | runningQueueDispatcher.showDispatchStatus() 7 | log('task2 start') 8 | let count = 15 9 | while (count-- > 0) { 10 | let content = 'Task2 Running count:' + count 11 | commonFunctions.showMiniFloaty(content, 500 - count * 10, 600 - count * 10, '#ff0000') 12 | sleep(1000) 13 | } 14 | log('task2 end') 15 | runningQueueDispatcher.showDispatchStatus() 16 | runningQueueDispatcher.removeRunningTask() -------------------------------------------------------------------------------- /test/queueTest/Task3.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | let commonFunctions = singletonRequire('CommonFunction') 4 | 5 | runningQueueDispatcher.addRunningTask() 6 | runningQueueDispatcher.showDispatchStatus() 7 | log('task3 start') 8 | let count = 15 9 | while (count-- > 0) { 10 | let content = 'Task3 Running count:' + count 11 | commonFunctions.showMiniFloaty(content, 400 - count * 10, 500 - count * 10, '#0000ff') 12 | sleep(1000) 13 | } 14 | log('task3 end') 15 | runningQueueDispatcher.showDispatchStatus() 16 | runningQueueDispatcher.removeRunningTask() -------------------------------------------------------------------------------- /test/queueTest/TaskTestManager.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | 4 | let pwd = files.cwd() 5 | runningQueueDispatcher.showDispatchStatus() 6 | runningQueueDispatcher.clearAll() 7 | let task1 = pwd + '/Task1.js' 8 | let task2 = pwd + '/Task2.js' 9 | let task3 = pwd + '/Task3.js' 10 | 11 | let count = 10 12 | while (count-- >0) { 13 | runningQueueDispatcher.executeTargetScript(task1, 1) 14 | runningQueueDispatcher.executeTargetScript(task2, 1) 15 | runningQueueDispatcher.executeTargetScript(task3, 1) 16 | } 17 | 18 | runningQueueDispatcher.showDispatchStatus() 19 | -------------------------------------------------------------------------------- /unit/功能测试-任务队列测试.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | 4 | runningQueueDispatcher.showDispatchStatus() 5 | runningQueueDispatcher.addRunningTask() 6 | sleep(500) 7 | runningQueueDispatcher.showDispatchStatus() 8 | runningQueueDispatcher.addRunningTask() 9 | sleep(500) 10 | runningQueueDispatcher.showDispatchStatus() 11 | runningQueueDispatcher.removeRunningTask() 12 | sleep(500) 13 | runningQueueDispatcher.showDispatchStatus() 14 | // runningQueueDispatcher.clearAll() 15 | 16 | -------------------------------------------------------------------------------- /unit/功能测试-查看运行队列.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | 4 | runningQueueDispatcher.showDispatchStatus() 5 | 6 | -------------------------------------------------------------------------------- /unit/功能测试-清空任务队列.js: -------------------------------------------------------------------------------- 1 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 2 | let runningQueueDispatcher = singletonRequire('RunningQueueDispatcher') 3 | 4 | runningQueueDispatcher.showDispatchStatus() 5 | runningQueueDispatcher.clearAll() 6 | 7 | -------------------------------------------------------------------------------- /unit/功能测试-重置默认配置.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-12 11:27:11 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-25 16:14:36 6 | * @Description: 7 | */ 8 | let { config, default_config, storage_name: _storage_name } = require('../config.js')(runtime, this) 9 | let storageConfig = storages.create(_storage_name) 10 | let allSame = true 11 | 12 | 13 | function objectEquals(x, y) { 14 | 'use strict'; 15 | 16 | if (x === null || x === undefined || y === null || y === undefined) { return x === y; } 17 | // after this just checking type of one would be enough 18 | if (x.constructor !== y.constructor) { return false; } 19 | // if they are functions, they should exactly refer to same one (because of closures) 20 | if (x instanceof Function) { return x === y; } 21 | // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES) 22 | if (x instanceof RegExp) { return x === y; } 23 | if (x === y || x.valueOf() === y.valueOf()) { return true; } 24 | if (Array.isArray(x) && x.length !== y.length) { return false; } 25 | 26 | // if they are dates, they must had equal valueOf 27 | if (x instanceof Date) { return false; } 28 | 29 | // if they are strictly equal, they both need to be object at least 30 | if (!(x instanceof Object)) { return false; } 31 | if (!(y instanceof Object)) { return false; } 32 | 33 | // recursive object equality check 34 | var p = Object.keys(x); 35 | return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) && 36 | p.every(function (i) { return objectEquals(x[i], y[i]); }); 37 | } 38 | 39 | Object.keys(default_config).forEach(key => { 40 | let currentVal = config[key] 41 | let defaultVal = default_config[key] 42 | config[key] = defaultVal 43 | storageConfig.put(key, defaultVal) 44 | if (!objectEquals(currentVal, defaultVal)) { 45 | allSame = false 46 | log('[' + key + ']当前配置:' + currentVal + ' 重置为默认:' + defaultVal) 47 | } 48 | }) 49 | toastLog('完成!' + (allSame ? '全部配置和默认值相同' : '重置了默认值,详情见日志')) 50 | -------------------------------------------------------------------------------- /unit/获取当前页面的布局信息.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2020-04-29 14:44:49 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-04-30 13:44:56 6 | * @Description: 7 | */ 8 | let singletonRequire = require('../lib/SingletonRequirer.js')(runtime, this) 9 | let widgetUtils = singletonRequire('WidgetUtils') 10 | let logUtils = singletonRequire('LogUtils') 11 | let floatyInstance = singletonRequire('FloatyUtil') 12 | let commonFunctions = singletonRequire('CommonFunction') 13 | let { config } = require('../config.js')(runtime, this) 14 | 15 | if (!floatyInstance.init()) { 16 | toast('创建悬浮窗失败') 17 | exit() 18 | } 19 | 20 | // 适配老代码 21 | if (!floatyInstance.hasOwnProperty('setFloatyInfo')) { 22 | floatyInstance.setFloatyText = function (text) { 23 | this.setFloatyInfo(null, text, null) 24 | } 25 | 26 | floatyInstance.setPosition = function (x, y) { 27 | this.setFloatyInfo({ x: x, y: y }, null, null) 28 | } 29 | } 30 | 31 | 32 | floatyInstance.setFloatyInfo({ x: parseInt(config.device_width / 2.7), y: parseInt(config.device_height / 2) }, '即将开始分析', { textSize: 20 }) 33 | sleep(1000) 34 | let limit = 3 35 | while (limit > 0) { 36 | floatyInstance.setFloatyText('倒计时' + limit-- + '秒') 37 | sleep(1000) 38 | } 39 | floatyInstance.setFloatyText('正在分析中...') 40 | 41 | 42 | let uiObjectInfoList = null 43 | let start = new Date().getTime() 44 | 45 | let any = widgetUtils.widgetGetOne(/.+/) 46 | if (any) { 47 | let root = getRootContainer(any) 48 | let boundsInfo = root.bounds() 49 | let content = root.text() || root.desc() 50 | let id = root.id() 51 | logUtils.logInfo([ 52 | 'rootInfo id:{} content: {} bounds:[{}, {}, {}, {}]', 53 | id, content, 54 | boundsInfo.left, boundsInfo.top, boundsInfo.width(), boundsInfo.height() 55 | ]) 56 | let resultList = iterateAll(root, 1) 57 | uiObjectInfoList = flatMap(flatArrayList, resultList) 58 | 59 | floatyInstance.setPosition(parseInt(config.device_width / 5), parseInt(config.device_height / 2)) 60 | floatyInstance.setFloatyText('分析完成,请查看日志页面') 61 | } else { 62 | floatyInstance.setPosition(parseInt(config.device_width / 5), parseInt(config.device_height / 2)) 63 | floatyInstance.setFloatyText('无法获取任何控件信息') 64 | } 65 | sleep(1000) 66 | floatyInstance.close() 67 | if (uiObjectInfoList) { 68 | let timeCost = new Date().getTime() - start 69 | let total = uiObjectInfoList.length 70 | let logInfoList = uiObjectInfoList.filter(v => v.hasUsableInfo()).map(v => v.toString()) 71 | let content = removeMinPrefix(logInfoList).join('\n') 72 | logUtils.debugInfo(content) 73 | dialogs.build({ 74 | title: '布局分析结果', 75 | content: commonFunctions.formatString("总分析耗时:{}ms 总控件数:{}\n{}", timeCost, total, content), 76 | negative: '关闭', 77 | negativeColor: 'red', 78 | cancelable: false 79 | }) 80 | .on('negative', () => { 81 | exit() 82 | }) 83 | .show() 84 | } 85 | 86 | 87 | function getRootContainer (target) { 88 | if (target === null) { 89 | logUtils.errorInfo("target为null 无法获取root节点", true) 90 | } 91 | if (target.parent() !== null) { 92 | return getRootContainer(target.parent()) 93 | } else { 94 | return target 95 | } 96 | } 97 | 98 | 99 | function iterateAll (root, depth) { 100 | depth = depth || 1 101 | let uiObjectInfo = new UiObjectInfo(root, depth) 102 | logUtils.logInfo(uiObjectInfo.toString()) 103 | if (root.getChildCount() > 0) { 104 | return [uiObjectInfo].concat(root.children().map(child => iterateAll(child, depth + 1))) 105 | } else { 106 | return uiObjectInfo 107 | } 108 | } 109 | 110 | function UiObjectInfo (uiObject, depth) { 111 | this.content = uiObject.text() || uiObject.desc() || '' 112 | this.isDesc = typeof uiObject.desc() !== 'undefined' && uiObject.desc() !== '' 113 | this.id = uiObject.id() 114 | this.boundsInfo = uiObject.bounds() 115 | this.depth = depth 116 | 117 | 118 | this.toString = function () { 119 | return commonFunctions.formatString( 120 | // ---- id:[] [text/desc]content:[] bounds:[] 121 | '{}{}{}{}', 122 | new Array(this.depth).join('-'), 123 | this.isEmpty(this.id) ? '' : 'id:[' + this.id + ']', 124 | this.isEmpty(this.content) ? '' : 125 | commonFunctions.formatString( 126 | '[{}]content:[{}]', 127 | (this.isDesc ? 'desc' : 'text'), this.content 128 | ), 129 | this.hasUsableInfo() ? commonFunctions.formatString( 130 | 'bounds:[{}, {}, {}, {}]', 131 | this.boundsInfo.left, this.boundsInfo.top, 132 | this.boundsInfo.width(), this.boundsInfo.height() 133 | ) : '' 134 | ) 135 | } 136 | 137 | this.isEmpty = function (v) { 138 | return typeof v === 'undefined' || v === null || v === '' 139 | } 140 | 141 | this.hasUsableInfo = function () { 142 | return !(this.isEmpty(this.content) && this.isEmpty(this.id)) 143 | } 144 | } 145 | 146 | function flatMap (f, list) { 147 | return list.map(f).reduce((x, y) => x.concat(y), []) 148 | } 149 | 150 | function Queue (size) { 151 | this.size = size || 10 152 | this.pushTime = 0 153 | this.queue = [] 154 | 155 | this.enqueue = function (val) { 156 | if (this.queue.length >= this.size) { 157 | this.queue.shift() 158 | } 159 | this.pushTime++ 160 | this.queue.push(val) 161 | } 162 | 163 | this.dequeue = function () { 164 | return this.queue.shift() 165 | } 166 | 167 | this.peek = function () { 168 | return this.queue[0] 169 | } 170 | } 171 | 172 | 173 | function flatArrayList (a) { 174 | if (a instanceof UiObjectInfo) { 175 | return a 176 | } else { 177 | return flatMap(flatArrayList, a) 178 | } 179 | } 180 | 181 | function removeMinPrefix (list) { 182 | let min = 10000000 183 | let minQueue = new Queue(3) 184 | const regex = /^(-+)[^-]+$/ 185 | let result = list.map(l => { 186 | if (regex.test(l)) { 187 | let prefixLength = regex.exec(l)[1].length 188 | if (prefixLength < min) { 189 | minQueue.enqueue(prefixLength) 190 | min = prefixLength 191 | } 192 | } 193 | return l 194 | }).map(l => l.substring(minQueue.peek())) 195 | return result 196 | } -------------------------------------------------------------------------------- /update/检测更新.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: TonyJiangWJ 3 | * @Date: 2019-12-23 22:54:22 4 | * @Last Modified by: TonyJiangWJ 5 | * @Last Modified time: 2020-07-01 09:21:36 6 | * @Description: 7 | */ 8 | 9 | runtime.loadDex('../lib/download.dex') 10 | let FileUtils = require('../lib/prototype/FileUtils.js') 11 | let loadingDialog = null 12 | let is_pro = Object.prototype.toString.call(com.stardust.autojs.core.timing.TimedTask.Companion).match(/Java(Class|Object)/) 13 | try { 14 | importClass(com.tony.listener.DownloaderListener) 15 | } catch (e) { 16 | let errorInfo = e + '' 17 | if (/importClass must be called/.test(errorInfo)) { 18 | toastLog('请强制关闭AutoJS并重新启动') 19 | exit() 20 | } 21 | } 22 | importClass(com.tony.listener.DownloaderListener) 23 | importClass(com.tony.resolver.JSONResolver) 24 | importClass(com.tony.downloader.GithubReleaseDownloader) 25 | importClass(com.tony.downloader.GiteeReleaseDownloader) 26 | 27 | let apiUrl = 'https://api.github.com/repos/TonyJiangWJ/Alipay-Credits/releases/latest' 28 | let downloader = new GithubReleaseDownloader() 29 | if (is_pro) { 30 | let origin = {} 31 | let new_object = {} 32 | downloader.setJsonResolver(new JSONResolver({ 33 | /** 34 | * 将对象转换成 JSON字符串 35 | * 36 | * @param obj 37 | * @return jsonString 38 | */ 39 | toJSONString (obj) { 40 | return JSON.stringify(obj) 41 | }, 42 | 43 | /** 44 | * 根据json字符串获取 指定json key内容 并转为String 45 | * 46 | * @param jsonString 47 | * @param name key 48 | * @return 49 | */ 50 | getString: function (jsonString, name) { 51 | let v = JSON.parse(jsonString)[name] 52 | return v ? v.toString() : '' 53 | }, 54 | 55 | /** 56 | * 可以嵌套调用 获取对象,不转为String 57 | * 58 | * @param jsonString 59 | * @param name 60 | * @return 61 | */ 62 | getObject (jsonString, name) { 63 | return JSON.parse(jsonString)[name] 64 | }, 65 | 66 | //--------------- 67 | 68 | /** 69 | * 设置原始JSONString 70 | * 71 | * @param jsonString 72 | * @return 73 | */ 74 | setOrigin: function (jsonString) { 75 | origin = JSON.parse(jsonString) 76 | return this 77 | }, 78 | 79 | getString: function (name) { 80 | let v = origin[name] 81 | return v ? v.toString() : '' 82 | }, 83 | 84 | getObject: function (name) { 85 | return origin[name] 86 | }, 87 | 88 | //--------------- 89 | 90 | /** 91 | * 创建新的封装 内部new一个Map 创建JSON对象 92 | * 93 | * @return 94 | */ 95 | newObject: function () { 96 | new_object = {} 97 | return this 98 | }, 99 | put: function (name, value) { 100 | new_object[name] = value 101 | return this 102 | }, 103 | 104 | /** 105 | * 将创建的JSON对象转换成字符串 106 | * 107 | * @return 108 | */ 109 | toJSONString: function () { 110 | return JSON.stringify(new_object) 111 | } 112 | }) 113 | ) 114 | } 115 | let targetOutputDir = FileUtils.getRealMainScriptPath(true) 116 | 117 | downloader.setListener(new DownloaderListener({ 118 | updateGui: function (string) { 119 | log(string) 120 | }, 121 | updateError: function (string) { 122 | console.error(string) 123 | }, 124 | updateProgress: function (progressInfo) { } 125 | })) 126 | log('下载并解压文件到目录:' + targetOutputDir) 127 | // 设置尝试获取总大小的次数,默认5次,github的content-length偶尔会给 偶尔不会给,主要原因是服务端用了分块传输的缘故 128 | downloader.setTryCount(2) 129 | downloader.setTargetReleasesApiUrl(apiUrl) 130 | downloader.setOutputDir(targetOutputDir) 131 | // 设置不需要解压覆盖的文件 132 | // 请勿移除'lib/autojs-tools.dex' 否则引起报错 133 | downloader.setUnzipSkipFiles(['.gitignore', 'lib/autojs-tools.dex', 'lib/download.dex']) 134 | // 设置不需要备份的文件 135 | downloader.setBackupIgnoreFiles([]) 136 | 137 | loadingDialog = dialogs.build({ 138 | cancelable: false, 139 | negative: '取消', 140 | title: '正在从Github获取更新信息', 141 | content: '加载中,请稍等...' 142 | }) 143 | .on('negative', () => { 144 | exit() 145 | }) 146 | .show() 147 | let summary = downloader.getUpdateSummary() 148 | if (summary === null) { 149 | loadingDialog.setContent('无法获取release版本信息') 150 | sleep(1000) 151 | loadingDialog.dismiss() 152 | exit() 153 | } 154 | summary = JSON.parse(summary) 155 | let localVersion = downloader.getLocalVersion() 156 | let content = '线上版本:' + summary.tagName + '\n' 157 | content += '本地版本:' + (localVersion === null ? '无法获取本地版本信息' : localVersion) + '\n' 158 | content += '更新内容:\n' + summary.body 159 | 160 | loadingDialog.dismiss() 161 | 162 | 163 | let downloadDialog = dialogs.build({ 164 | title: '更新中...', 165 | content: '更新中', 166 | cancelable: false, 167 | negative: '取消', 168 | progress: { 169 | max: 100, 170 | horizontal: true, 171 | showMinMax: false 172 | } 173 | }) 174 | .on('negative', () => { 175 | exit() 176 | }) 177 | 178 | downloader.setListener(new DownloaderListener({ 179 | updateGui: function (string) { 180 | log(string) 181 | downloadDialog.setContent(string) 182 | }, 183 | updateError: function (string) { 184 | console.error(string) 185 | }, 186 | updateProgress: function (progressInfo) { 187 | downloadDialog.setProgress(progressInfo.getProgress() * 100) 188 | } 189 | })) 190 | let downloadingExecutor = function (backup) { 191 | if (backup) { 192 | downloader.backup() 193 | sleep(1000) 194 | } 195 | downloader.downloadZip() 196 | 197 | // 覆盖新的dex到lib下 198 | let copy_result = files.copy(targetOutputDir + '/resources/for_update/download.dex', targetOutputDir + '/lib/download.dex') 199 | toastLog('复制新的dex文件' + (copy_result ? '成功' : '失败')) 200 | log('清理过时lib文件') 201 | let outdate_file_path = targetOutputDir + '/resources/for_update/OutdateFiles.js' 202 | if (files.exists(outdate_file_path)) { 203 | downloadDialog.setContent('清理过期文件...') 204 | let outdateFiles = require(outdate_file_path) 205 | outdateFiles && outdateFiles.length > 0 && outdateFiles.forEach(fileName => { 206 | let fullPath = targetOutputDir + '/' + fileName 207 | if (files.exists(fullPath)) { 208 | let deleteResult = false 209 | if (files.isDir(fullPath) && !files.isEmptyDir(fullPath)) { 210 | deleteResult = files.removeDir(fullPath) 211 | } else { 212 | deleteResult = files.remove(fullPath) 213 | } 214 | console.verbose('删除过期文件:' + fullPath + ' ' + (deleteResult ? '成功' : '失败')) 215 | } 216 | }) 217 | } 218 | downloadDialog.setContent('更新完成') 219 | sleep(2000) 220 | downloadDialog.dismiss() 221 | } 222 | dialogs.build({ 223 | title: '是否下载更新', 224 | content: content, 225 | cancelable: false, 226 | neutral: '备份后更新', 227 | negative: '取消', 228 | positive: '覆盖更新', 229 | 230 | negativeColor: 'red', 231 | positiveColor: '#f9a01c', 232 | }) 233 | .on('negative', () => { 234 | exit() 235 | }) 236 | .on('neutral', () => { 237 | downloadDialog.show() 238 | threads.start(function () { downloadingExecutor(true) }) 239 | }) 240 | .on('positive', () => { 241 | downloadDialog.show() 242 | threads.start(function () { downloadingExecutor(false) }) 243 | }) 244 | .show() 245 | -------------------------------------------------------------------------------- /update/说明-重要.txt: -------------------------------------------------------------------------------- 1 | “自动下载更新脚本”更新之后需要手动关闭AutoJS 2 | 否则新的dex没法覆盖加载。 3 | 具体操作以MIUI为例,上划打开后台 长按AutoJS, 4 | 设置 选择结束运行,而不仅仅是滑动关闭。 5 | 理论上Pro版已经支持覆盖加载,免费版不支持。 6 | 但是为了保险起见都进行一遍强制停止。 7 | 8 | 可以手动修改第23行 9 | 将解压路径修改为你喜欢的文件夹 10 | let targetOutputDir = files.cwd() 11 | 比如修改为如下 12 | let targetOutputDir = '/storage/0/脚本/你喜欢的路径' 13 | 下载完成后新代码就会到 14 | '/storage/0/脚本/你喜欢的路径'里面 15 | 16 | 执行完毕后需要在 targetOutputDir中下拉刷新,否则看不到 17 | 18 | 19 | 本 “自动下载更新脚本” 同样适用于 20 | 所有github/releases发布的最新脚本 21 | 仅仅需要修改21行 22 | let apiUrl = "目标脚本更新API" 23 | "目标脚本更新API" 需要自己去获取 24 | 格式如下: 25 | 'https://api.github.com/repos/${github用户名}/${github仓库名}/releases/latest' 26 | 当然如果没有发布releases 下载也是无效的 --------------------------------------------------------------------------------