From d39db21d9be3771a1feffbff6b282c739c2b6645 Mon Sep 17 00:00:00 2001 From: SuperMonster003 Date: Mon, 1 Nov 2021 18:41:23 +0800 Subject: [PATCH] =?UTF-8?q?2.2.2=20-=20=E6=96=B0=E5=A2=9E=E6=81=AF?= =?UTF-8?q?=E5=B1=8F=E6=97=B6=E6=8E=A8=E8=BF=9F=E9=80=89=E9=A1=B9=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=80=9B=E4=B8=80=E9=80=9B=E7=BB=93=E6=9D=9F?= =?UTF-8?q?=E5=88=A4=E6=96=AD/=E5=B1=8F=E5=B9=95=E6=B0=B4=E5=B9=B3?= =?UTF-8?q?=E6=96=B9=E5=90=91=E5=90=AF=E5=8A=A8=20=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=9B=BE=E7=89=87=E8=B5=84=E6=BA=90=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/.nomedia | 0 modules/ext-plugins.js | 39 + modules/mod-alipay.js | 88 ++ modules/mod-ant-forest.js | 5 + modules/mod-autojs.js | 356 +++++++ modules/mod-project.js | 974 ++++++++++++++++++ modules/plugin-ant-forest-biodiversity.js | 14 + modules/plugin-ant-forest-district-protect.js | 14 + ...lugin-ant-forest-on-screen-off-launcher.js | 98 ++ modules/plugin-ant-forest.js | 2 + modules/plugin-device-unlock.js | 2 + 11 files changed, 1592 insertions(+) create mode 100644 assets/images/.nomedia create mode 100644 modules/ext-plugins.js create mode 100644 modules/mod-alipay.js create mode 100644 modules/mod-ant-forest.js create mode 100644 modules/mod-autojs.js create mode 100644 modules/mod-project.js create mode 100644 modules/plugin-ant-forest-biodiversity.js create mode 100644 modules/plugin-ant-forest-district-protect.js create mode 100644 modules/plugin-ant-forest-on-screen-off-launcher.js create mode 100644 modules/plugin-ant-forest.js create mode 100644 modules/plugin-device-unlock.js diff --git a/assets/images/.nomedia b/assets/images/.nomedia new file mode 100644 index 0000000..e69de29 diff --git a/modules/ext-plugins.js b/modules/ext-plugins.js new file mode 100644 index 0000000..92b7d98 --- /dev/null +++ b/modules/ext-plugins.js @@ -0,0 +1,39 @@ +module.exports = { + pluginsx: { + af: { + get biodiversity() { + return require('./plugin-ant-forest-biodiversity'); + }, + get deployment() { + return require('./plugin-ant-forest-deployment'); + }, + get district_protect() { + return require('./plugin-ant-forest-district-protect'); + }, + get energy_rain() { + return require('./plugin-ant-forest-energy-rain'); + }, + get on_screen_off_launcher() { + return require('./plugin-ant-forest-on-screen-off-launcher'); + }, + }, + alipay: { + get account() { + return require('./plugin-alipay-account'); + }, + }, + autojs: { + get memory_info() { + return require('./plugin-autojs-memory-info'); + }, + }, + device: { + get next_alarm_clock() { + return require('./plugin-device-next-alarm-clock'); + }, + get unlock() { + return require('./plugin-device-unlock'); + }, + }, + }, +}; \ No newline at end of file diff --git a/modules/mod-alipay.js b/modules/mod-alipay.js new file mode 100644 index 0000000..b27d33b --- /dev/null +++ b/modules/mod-alipay.js @@ -0,0 +1,88 @@ +let { + $$num, $$str, isPlainObject, +} = require('./mod-global'); +let {appx} = require('./ext-app'); + +let exp = { + package_name: 'com.eg.android.AlipayGphone', + app_name: String.unEsc('652F4ED85B9D'), + /** + * @type {Mod.Alipay['app_preset']} + */ + app_preset: { + af_homepage: ['https://60000002.h5app.alipay.com/www/home.html', { + transparentTitle: 'auto', + backgroundColor: -1, + }], + af_rank_list: [['https://60000002.h5app.alipay.com/www/listRank.html', { + conf: '["totalRank"]', + }], { + transparentTitle: 'none', + backgroundColor: -1, + canPullDown: 'NO', + backBehavior: 'back', + showOptionMenu: 'YES', + readTitle: 'NO', + defaultTitle: String.unEsc('2615FE0F0020597D53CB6392884C699C'), + }], + af_energy_rain: 68687791, + settings_general: 20000724, + account_manager: 20000027, + account_login: 20000008, + account_nickname: 20000141, + }, + /** + * @param {Alipay.App.Id|[string,object]} app_id + * @param {?Alipay.App.URL|Alipay.JSBridge.WebviewOptions} [url] + * @param {Alipay.JSBridge.WebviewOptions} [webview_options] + * @return {boolean} + */ + startApp(app_id, url, webview_options) { + let _preset_args = this.app_preset[app_id]; + if (typeof _preset_args !== 'undefined') { + return Array.isArray(_preset_args) + ? this.startApp.apply(this, _preset_args) + : this.startApp.call(this, _preset_args); + } + // @Overload + if (!($$num(app_id) || $$str(app_id) && app_id.match(/^\d+$/))) { + return this.startApp(20000067, app_id, url); + } + // @Overload + if (isPlainObject(url)) { + return this.startApp(app_id, null, url); + } + return appx.startActivity({ + url: { + src: 'alipays://platformapi/startapp', + query: function $iiFe() { + let _o = {appId: app_id}; + if (typeof url === 'string') { + _o.url = url; + } else if (Array.isArray(url)) { + _o.url = {src: url[0], query: url[1]}; + } + /** + * @type {Alipay.JSBridge.WebviewOptions} + */ + _o.__webview_options__ = Object.assign({ + appClearTop: 'YES', + startMultApp: 'YES', + enableCubeView: 'NO', + enableScrollBar: 'NO', + closeCurrentWindow: 'YES', + }, webview_options); + return _o; + }(), + exclude: 'defaultTitle', + }, + package: 'alipay', + monitor: 'ensure_open', + }); + }, +}; + +/** + * @type {Mod.alipay} + */ +module.exports = {alipay: exp}; \ No newline at end of file diff --git a/modules/mod-ant-forest.js b/modules/mod-ant-forest.js new file mode 100644 index 0000000..c95ace1 --- /dev/null +++ b/modules/mod-ant-forest.js @@ -0,0 +1,5 @@ +let exp = { + +}; + +module.exports = {af: exp}; \ No newline at end of file diff --git a/modules/mod-autojs.js b/modules/mod-autojs.js new file mode 100644 index 0000000..f957625 --- /dev/null +++ b/modules/mod-autojs.js @@ -0,0 +1,356 @@ +let {uix} = require('./ext-ui'); +let {appx} = require('./ext-app'); +let {a11yx} = require('./ext-a11y'); +let {consolex} = require('./ext-console'); + +/* Here, importClass() is not recommended for intelligent code completion in IDE like WebStorm. */ +/* The same is true of destructuring assignment syntax (like `let {Uri} = android.net`). */ + +let exp = { + /** + * @return {boolean} + */ + isPro() { + return (this.isPro = () => /Pro\b/i.test(this.getPkgName()))(); + }, + /** + * @example + * console.log(autojs.getName()); // like: 'Auto.js' + * @return {string} + */ + getAppName() { + return 'Auto.js' + (this.isPro() ? '\x20Pro' : ''); + }, + /** + * @example + * console.log(appx.getAutoJsPkg()); // like: 'org.autojs.autojs' + * @return {string} + */ + getPkgName() { + return context.getPackageName(); + }, + /** + * @example + * console.log(appx.getAutoJsVer()); // e.g. '4.1.1 Alpha2' + * @return {?string} + */ + getVerName() { + // Pro version(s) (e.g. 8.8.16-0) returns abnormal value like '${xxx.xxx}' + let _ver = app.autojs.versionName; + return String(_ver).match(/^\d/) ? _ver : appx.getVerName('Auto.js'); + }, + getVerCode() { + return app.autojs.versionCode; + }, + /** + * @param {string|number} ver + * @example + * console.log(autojs.isVerNewer(7)); // e.g. false + * console.log(autojs.isVerNewer('4.1.0')); // e.g. true + * console.log(autojs.isVerNewer('4.1.1 alpha')); // e.g. true + * console.log(autojs.isVerNewer('4.1.1 a2')); // e.g. false + * console.log(autojs.isVerNewer('8.9.6 beta')); // e.g. false + * @return {boolean} + */ + isVerNewer(ver) { + return appx.version.isNewer(this.getVerName(), ver); + }, + /** + * Check if device is running compatible Auto.js version + */ + ensureVersion() { + let _ = { + VERSION_UNKNOWN: -1, + VERSION_NORMAL: 0, + VERSION_FREE_FINAL: 'v4.1.1 Alpha2', + bug_map: { + ab_cwd: 'cwd()方法功能异常', + ab_engines_set_arguments: 'engines.setArguments()功能异常', + ab_find_for_each: 'UiSelector.find().forEach()方法功能异常', + ab_floaty: 'Floaty模块异常', + ab_floaty_raw_window: 'floaty.rawWindow()功能异常', + ab_inflate: 'ui.inflate()方法功能异常', + ab_login: '无法登陆Auto.js账户', + ab_relative_path: '相对路径功能异常', + ab_set_global_log_config: 'console.setGlobalLogConfig()功能异常', + ab_simp_act_auto: 'SimpleActionAutomator模块异常', + ab_ui_selector: 'UiSelector模块功能异常', + ab_ui_layout: 'UI页面布局异常', + crash_autojs: '脚本运行后导致Auto.js崩溃', + crash_ui_call_ui: 'ui脚本调用ui脚本会崩溃', + crash_ui_settings: '图形配置页面崩溃', + dialogs_event: 'Dialogs模块事件失效', + dialogs_not_responded: '无法响应对话框点击事件', + dislocation_floaty: 'Floaty模块绘制存在错位现象', + forcibly_update: '强制更新', + not_full_function: '此版本未包含所需全部功能', + press_block: 'press()方法时间过短时可能出现阻塞现象', + un_cwd: '不支持cwd()方法及相对路径', + un_engines: '不支持Engines模块', + un_exec_argv: '不支持Engines模块的execArgv对象', + un_inflate: '不支持ui.inflate()方法', + un_relative_path: '不支持相对路径', + un_runtime: '不支持runtime参数', + un_view_bind: '不支持view对象绑定自定义方法', + }, + /** + * @param {string} ver + * @return {string[]|number} + */ + diagnose(ver) { + // version ∈ 4.1.1 + // version <= Pro 8.3.16-0 + // version === Pro 7.0.0-(4|6) || version === Pro 7.0.2-4 + // version === Pro 7.0.3-7 || version === Pro 7.0.4-1 + if (ver.match(/^(4\.1\.1.+)$/) || + ver.match(/^Pro 8\.([0-2]\.\d{1,2}-\d|3\.(\d|1[0-6])-0)$/) || + ver.match(/^Pro 7\.0\.(0-[46]|2-4|3-7|4-1)$/) + ) { + return this.VERSION_NORMAL; + } + + // version > Pro 8.3.16 + if (ver.match(/^Pro ([89]|[1-9]\d)\./)) { + a11yx.bridge.resetWindowFilter(); + return this.VERSION_NORMAL; + } + + // 4.1.0 Alpha3 <= version <= 4.1.0 Alpha4 + if (ver.match(/^4\.1\.0 Alpha[34]$/)) { + return ['ab_simp_act_auto', 'dialogs_not_responded']; + } + + // version === 4.1.0 Alpha(2|5)? + if (ver.match(/^4\.1\.0 Alpha[25]$/)) { + return ['dialogs_not_responded']; + } + + // 4.0.x versions + if (ver.match(/^4\.0\./)) { + return ['dialogs_not_responded', 'not_full_function']; + } + + // version === Pro 7.0.0-(1|2) + if (ver.match(/^Pro 7\.0\.0-[12]$/)) { + return ['ab_relative_path']; + } + + // version === Pro 7.0.0-7 || version === Pro 7.0.1-0 || version === Pro 7.0.2-(0|3) + if (ver.match(/^Pro 7\.0\.((0-7)|(1-0)|(2-[03]))$/)) { + return ['crash_autojs']; + } + + // version >= 4.0.2 Alpha7 || version === 4.0.3 Alpha([1-5]|7)? + if (ver.match(/^((4\.0\.2 Alpha([7-9]|\d{2}))|(4\.0\.3 Alpha([1-5]|7)?))$/)) { + return ['dislocation_floaty', 'ab_inflate', 'not_full_function']; + } + + // version >= 3.1.1 Alpha5 || version -> 4.0.0/4.0.1 || version <= 4.0.2 Alpha6 + if (ver.match(/^((3\.1\.1 Alpha[5-9])|(4\.0\.[01].+)|(4\.0\.2 Alpha[1-6]?))$/)) { + return ['un_exec_argv', 'ab_inflate', 'not_full_function']; + } + + // 3.1.1 Alpha3 <= version <= 3.1.1 Alpha4: + if (ver.match(/^3\.1\.1 Alpha[34]$/)) { + return ['ab_inflate', 'un_engines', 'not_full_function']; + } + + // version >= 3.1.0 Alpha6 || version <= 3.1.1 Alpha2 + if (ver.match(/^((3\.1\.0 (Alpha[6-9]|Beta))|(3\.1\.1 Alpha[1-2]?))$/)) { + return ['un_inflate', 'un_engines', 'not_full_function']; + } + + // version >= 3.0.0 Alpha42 || version ∈ 3.0.0 Beta[s] || version <= 3.1.0 Alpha5 + if (ver.match(/^((3\.0\.0 ((Alpha(4[2-9]|[5-9]\d))|(Beta\d?)))|(3\.1\.0 Alpha[1-5]?))$/)) { + return ['un_inflate', 'un_runtime', 'un_engines', 'not_full_function']; + } + + // 3.0.0 Alpha37 <= version <= 3.0.0 Alpha41 + if (ver.match(/^3\.0\.0 Alpha(3[7-9]|4[0-1])$/)) { + return ['ab_cwd', 'un_relative_path', 'un_inflate', 'un_runtime', 'un_engines', 'not_full_function']; + } + + // 3.0.0 Alpha21 <= version <= 3.0.0 Alpha36 + if (ver.match(/^3\.0\.0 Alpha(2[1-9]|3[0-6])$/)) { + return ['un_cwd', 'un_inflate', 'un_runtime', 'un_engines', 'not_full_function']; + } + + // version <= 3.0.0 Alpha20 + if (ver.match(/^3\.0\.0 Alpha([1-9]|1\d|20)?$/)) { + return ['un_cwd', 'un_inflate', 'un_runtime', 'un_engines', 'crash_ui_settings', 'not_full_function']; + } + + switch (ver) { + case '4.0.3 Alpha6': + return ['ab_floaty', 'ab_inflate', 'not_full_function']; + case '4.0.4 Alpha': + return ['dislocation_floaty', 'un_view_bind', 'not_full_function']; + case '4.0.4 Alpha3': + return ['dislocation_floaty', 'ab_ui_layout', 'not_full_function']; + case '4.0.4 Alpha4': + return ['ab_find_for_each', 'not_full_function']; + case '4.0.4 Alpha12': + return ['un_exec_argv', 'not_full_function']; + case '4.0.5 Alpha': + return ['ab_ui_selector', 'not_full_function']; + case 'Pro 7.0.0-0': + return ['ab_login']; + case 'Pro 7.0.0-3': + return ['crash_ui_call_ui']; + case 'Pro 7.0.0-5': + return ['forcibly_update']; + case 'Pro 7.0.3-1': + return ['dialogs_event']; + case 'Pro 7.0.3-4': + return ['ab_set_global_log_config']; + case 'Pro 7.0.3-5': + return ['ab_floaty_raw_window']; + case 'Pro 7.0.3-6': + return ['ab_engines_set_arguments', 'press_block']; + case 'Pro 7.0.4-0': + return ['crash_ui_settings']; + default: + return this.VERSION_UNKNOWN; + } + }, + alert(msg) { + alert(msg); + exit(); + }, + dialog(msg) { + let $ = { + message: msg || null, + is_continued: false, + exitNow() { + this.dialog.dismiss(); + exit(); + }, + getView() { + let _view = ui.inflate( + + + + + + + + + ); + _view['btn'].on('click', () => { + toast('已退出'); + this.exitNow(); + }); + _view['btn'].on('long_click', (e) => { + e.consumed = this.is_continued = true; + if (uix.isUiMode()) { + return this.exitNow(); + } + this.dialog.dismiss(); + consolex.e('仍然尝试继续', 2, 0, 2); + }); + ui.run(() => { + _view['text'].attr('text', this.message); + _view['img'].attr('tint_color', '#FF9100'); + }); + + return _view; + }, + getDialog() { + let _diag = dialogs.build({ + customView: this.view, + autoDismiss: false, + canceledOnTouchOutside: false, + }); + + _diag.setOnKeyListener({ + onKey(diag, key_code) { + if (key_code === KeyEvent.KEYCODE_BACK) { + $.exitNow(); + return true; + } + return false; + }, + }); + + let _win = _diag.getWindow(); + _win.setBackgroundDrawableResource(android.R.color.transparent); + _win.setWindowAnimations(android.R.style.Animation_InputMethod); + _win.setDimAmount(0.85); + + return _diag; + }, + parseArgs() { + this.view = this.getView(); + this.dialog = this.getDialog(); + }, + monitor() { + if (!uix.isUiMode()) { + let _start = Date.now(); + while (!this.is_continued) { + sleep(200); + if (Date.now() - _start > 60e3) { + consolex.e('等待用户操作超时', 2, 0, 2); + return this.exitNow(); + } + } + } + }, + show() { + this.parseArgs(); + this.dialog.show(); + this.monitor(); + }, + }; + + $.show(); + }, + alertAndExit(ver, diagnosis) { + let _msg = '脚本可能无法正常运行\n' + '需更换 Auto.js 版本\n\n' + + '软件版本:' + '\n' + ver + '\n\n' + + '异常详情:' + diagnosis.map((code) => { + return '\n' + (this.bug_map[code] || '无效的Bug描述'); + }).join('') + '\n\n' + + '在项目简介中查看支持版本\n' + '或直接尝试 ' + this.VERSION_FREE_FINAL; + + exp.isVerNewer('v4.1.0') ? this.dialog(_msg) : this.alert(_msg); + }, + }; + + let $ = { + parseArgs() { + this.version = exp.getVerName() || '未知版本'; + this.diagnosis = _.diagnose(this.version); + }, + bugTrigger() { + if (typeof this.diagnosis !== 'number') { + consolex._('Bug版本检查: 确诊', 3); + return true; + } + if (this.diagnosis === _.VERSION_NORMAL) { + consolex._('Bug版本检查: 正常'); + return false; + } + if (this.diagnosis === _.VERSION_UNKNOWN) { + consolex._('Bug版本检查: 未知'); + return false; + } + }, + alertAndExit() { + _.alertAndExit(this.version, this.diagnosis); + }, + ensure() { + this.parseArgs(); + this.bugTrigger() && this.alertAndExit(); + }, + }; + + $.ensure(); + }, +}; + +module.exports = {autojs: exp}; \ No newline at end of file diff --git a/modules/mod-project.js b/modules/mod-project.js new file mode 100644 index 0000000..f6bc90c --- /dev/null +++ b/modules/mod-project.js @@ -0,0 +1,974 @@ +let { + $$cvt, isNullish, +} = require('./mod-global'); +let {appx} = require('./ext-app'); +let {httpx} = require('./ext-http'); +let {filesx} = require('./ext-files'); +let {threadsx} = require('./ext-threads'); +let {dialogsx} = require('./ext-dialogs'); +let {storagesx} = require('./ext-storages'); + +/* Here, importClass() is not recommended for intelligent code completion in IDE like WebStorm. */ +/* The same is true of destructuring assignment syntax (like `let {Uri} = android.net`). */ + +let File = java.io.File; + +let _ = { + step: { + download: '下载数据包', + decompress: '解压缩', + backup: '备份本地项目', + files_check: '检查文件', + files_update: '项目文件替换', + files_ready: '项目文件就绪', + finish_deploy: '清理并完成部署', + finish_restore: '清理并完成项目恢复', + }, + structure: [ + {name: '/assets'}, + {name: '/modules', necessary: true}, + {name: '/tools'}, + {name: 'ant-forest-launcher.js', necessary: true}, + {name: 'ant-forest-settings.js'}, + {name: '.gitignore'}, + {name: 'jsconfig.json'}, + {name: 'project.json', necessary: true}, + {name: 'LICENSE'}, + {name: 'README.md'}, + ], + get ignore_list() { + return this._ignore_list = this._ignore_list + || storagesx.af.get('update_ignore_list') + || storagesx['@default'].af.update_ignore_list; + }, +}; + +let exp = { + version: { + /** + * Returns if a version is ignored in the given list + * @param {string|{version_name:string}} ver + * @param {string[]} [list] + * @return {boolean} + */ + isIgnored(ver, list) { + let _ls = list || _.ignore_list; + if (!_ls) { + throw Error('No available list for project.version.isIgnored()'); + } + return typeof ver === 'string' ? _ls.includes(ver) : _ls.includes(ver.version_name); + }, + /** + * Returns if a version is not ignored in the given list + * @param {string|{version_name:string}} ver + * @param {string[]} [list] + * @return {boolean} + */ + isCared(ver, list) { + let _ls = list || _.ignore_list; + if (!_ls) { + throw Error('No available list for project.version.isCared()'); + } + return !this.isIgnored(ver, _ls); + }, + }, + /** + * @param {...string[]} [children] + * @return {string} + * @see filesx.\.local + */ + '.local'(children) { + return filesx['.local'].apply(filesx, arguments); + }, + /** + * @example + * console.log(project.getLocal().version_name); // like: 'v2.0.2 Alpha2' + * @return {{ + * version_name: string, + * version_code: number, + * main: {name: string, path: string}, + * path: string, + * }} + */ + getLocal() { + let _ver_name = ''; + let _ver_code = -1; + let _path = this.getLocalPath(); + if (!_path) { + throw Error('Cannot locate project path for project.getLocal()'); + } + let _json_name = 'project.json'; + let _json_path = files.join(_path, _json_name); + let _main_name = 'ant-forest-launcher.js'; + let _main_path = files.join(_path, _main_name); + let _res = { + version_name: _ver_name, + version_code: _ver_code, + main: {name: _main_name, path: _main_path}, + path: _path, + }; + if (files.exists(_json_path)) { + try { + let _o = JSON.parse(filesx.read(_json_path, '{}')); + return Object.assign(_res, { + version_name: 'v' + _o.versionName, + version_code: Number(_o.versionCode), + main: {name: _o.main, path: files.join(_path, _o.main)}, + }); + } catch (e) { + console.warn(e.message); + console.warn(e.stack); + } + } + if (files.exists(_main_path)) { + try { + return Object.assign(_res, { + version_name: 'v' + filesx.read(_main_path) + .match(/version (\d+\.?)+( ?(Alpha|Beta)(\d+)?)?/)[0].slice(8), + }); + } catch (e) { + console.warn(e.message); + console.warn(e.stack); + } + } + console.warn('Both\x20' + _json_name + '\x20and\x20' + _main_name + '\x20do not exist'); + return _res; + }, + /** + * @param {boolean} [is_with_creation=false] + * @return {string} + */ + getLocalPath(is_with_creation) { + return _.local_path || (_.local_path = (() => { + let _cwd = files.cwd(); + if (this.isAlike(_cwd)) { + return _cwd; + } + _cwd = new File(_cwd).getParent(); + if (this.isAlike(_cwd)) { + return _cwd; + } + let _proj_def_n = 'Ant-Forest-003'; + let _root_proj_path = files.join(filesx.getScriptDirPath(), _proj_def_n); + if (files.isDir(_root_proj_path)) { + return _root_proj_path; + } + if (is_with_creation) { + files.createWithDirs(_root_proj_path + filesx.sep); + return _root_proj_path; + } + return String(); + })()); + }, + /** + * @return {string} + */ + getLocalVerName() { + return this.getLocal().version_name; + }, + /** + * @param {...string[]} [children] + * @example + * // like: '/storage/emulated/0/Scripts/Ant-Forest-003/assets' + * console.log(project.getAssetPath()); + * // like: '/storage/emulated/0/Scripts/Ant-Forest-003/assets/images' + * console.log(project.getAssetPath('images')); + * @return {string} + */ + getAssetPath(children) { + return files.join.apply(files, [this.getLocalPath(), 'assets'].concat([].slice.call(arguments))); + }, + /** + * @param {Object} [options] + * @param {number} [options.max_items=Infinity] + * @param {number} [options.per_page=30] + * @param {string} [options.min_version_name='v0.0.0'] + * @param {boolean} [options.no_extend=false] + * @param {boolean} [options.show_progress_dialog=false] + * @param {function(items:GitHub.Releases.List|GitHub.Releases.ExtendedList)} [callback] + * @return {GitHub.Releases.List|GitHub.Releases.ExtendedList} + */ + getReleases(options, callback) { + if (typeof callback !== 'function') { + return _getReleases(); + } + threadsx.start(() => callback(_getReleases())); + + // tool function(s) // + + function _getReleases() { + /** @type {GitHub.Releases.List} */ + let _releases = []; + let _opt = options || {}; + + /** @type {JsDialog$} */ + let _p_diag = null; + delete global._$_get_proj_releases_interrupted; + if (_opt.show_progress_dialog) { + dialogsx.setProgressColorTheme(_p_diag = dialogsx.builds([ + null, '正在获取版本信息...', 0, 0, 'I', 1, + ], { + progress: {max: -1, showMinMax: false, horizontal: true}, + keycode_back: 'disabled', + }).on('positive', (d) => { + d.dismiss(); + global._$_get_proj_releases_interrupted = true; + }).show(), 'indeterminate'); + } + + let _max_items = _opt.max_items || Infinity; + let _cur_page = 1; + let _per_page = _opt.per_page || 30; // 100 + let _min_ver = _opt.min_version_name || 'v0.0.0'; // 'v2.0.1' + let _max = 3; + while (_max--) { + try { + let _items = http.get('https://api.github.com/repos/' + + 'SuperMonster003/Ant-Forest/releases' + + '?per_page=' + _per_page + '&page=' + _cur_page++) + .body.json().filter(o => o.tag_name >= _min_ver); + if (global._$_get_proj_releases_interrupted) { + return []; + } + _releases = _releases.concat(_items); + if (_items.length < _per_page || _releases.length >= _max_items) { + break; + } + } catch (e) { + sleep(120 + Math.random() * 240); + } + } + if (_max < 0) { + if (_p_diag) { + _p_diag.dismiss(); + dialogsx.builds([ + '失败', '版本信息获取失败', 0, 0, 'X', 1, + ]).on('positive', d => d.dismiss()).show(); + } + return []; + } + _releases.splice(_max_items); + if (_p_diag) { + _p_diag.dismiss(); + _p_diag = null; + } + return _opt.no_extend ? _releases : _releases.map(_extend); + + // tool function(s) // + + /** + * @param {GitHub.Releases.ExtendedListItem} o + * @return {GitHub.Releases.ExtendedListItem} + */ + function _extend(o) { + o.version_name = o.tag_name; + + o.brief_info_str = [ + {key: 'name', desc: '标题'}, + {key: 'tag_name', desc: '标签'}, + { + key: 'published_at', desc: '发布', + cvt: typeof $$cvt !== 'undefined' && $$cvt.date, + }, + {key: 'body', desc: '内容描述'}, + ].map((info) => { + let _k = info.key; + let _v = o[_k]; + if (_v) { + if (_k === 'body') { + _v = '\n' + _v; + } + if (typeof info.cvt === 'function') { + _v = info.cvt.call(null, _v); + } + return info.desc + ':\x20' + _v; + } + }).filter(s => !!s).join('\n\n'); + + return o; + } + } + }, + /** + * @param {Object} [options] + * @param {string} [options.min_version_name='v0.0.0'] + * @param {boolean} [options.no_extend=false] + * @param {boolean} [options.show_progress_dialog=false] + * @param {function(item:GitHub.Releases.ExtendedListItem|void)} [callback] + * @return {GitHub.Releases.ExtendedListItem|void} + */ + getNewestRelease(options, callback) { + /** + * @return {GitHub.Releases.ExtendedListItem} + */ + let _getRelease = () => this.getReleases(Object.assign(options || {}, { + max_items: 1, per_page: 1, + }))[0]; + if (typeof callback !== 'function') { + return _getRelease(); + } + threadsx.start(() => callback(_getRelease())); + }, + /** + * @param {Object} [options] + * @param {string} [options.min_version_name='v0.0.0'] + * @param {boolean} [options.no_extend=false] + * @param {boolean} [options.show_progress_dialog=false] + * @param {string[]} [options.ignore_list] + * @param {function(item:GitHub.Releases.ExtendedListItem|void)} [callback] + * @return {GitHub.Releases.ExtendedListItem|void} + */ + getNewestReleaseCared(options, callback) { + let _opt = options || {}; + // noinspection JSValidateTypes + /** + * @return {GitHub.Releases.ExtendedListItem} + */ + let _getRelease = () => this.getReleases(Object.assign(_opt, { + max_items: 100, per_page: 100, + })).filter(o => this.version.isCared(o, _opt.ignore_list))[0]; + if (typeof callback !== 'function') { + return _getRelease(); + } + threadsx.start(() => callback(_getRelease())); + }, + /** + * @param {'1.x'|'2.x'|string|number} ver + * @param {Object} [options] + * @param {boolean} [options.is_show_dialog=false] + * @param {boolean} [options.is_joint=false] + * @param {boolean} [options.no_earlier=false] + * @param {Object} [callback] + * @param {function():*} [callback.onStart] + * @param {function(value:{ver:string,log:string}[]|string):*} [callback.onSuccess] + * @param {function(value:*=):*} [callback.onFailure] + * @return {{ver:string,log:string}[]|string|void} + */ + getChangelog(ver, options, callback) { + let _ver_num = Math.trunc(Number(ver.toString().match(/\d+/)[0])); + let _opt = options || {}; + + let _cbk = callback || {}; + let _onStart = _cbk.onStart || (r => r); + let _onSuccess = _cbk.onSuccess || (r => r); + let _onFailure = _cbk.onFailure || console.error; + + /** @type {JsDialog$} */ + let _diag = null; + + if (!_opt.is_show_dialog) { + return _getLog(); + } + + let _neu_act = null; + let _neu_cbk = (r => r); + if (!_opt.no_earlier && _ver_num > 1) { + _neu_act = '更早期的历史'; + _neu_cbk = _showEarlier; + } + _diag = dialogsx + .builds(['历史更新', '处理中...', [_neu_act, 'hint'], '\xa0', 'B', 1]) + .on('neutral', _neu_cbk) + .on('positive', dialogsx.dismiss) + .show(); + + threadsx.start(_getLog); + + // tool function(s) // + + function _getLog() { + _onStart(); + + let _cont = _getContentByBlob(); + if (!_cont) { + let _msg = '获取历史更新信息失败'; + _diag ? _diag.setContent(_msg) : _onFailure(_msg); + return _opt.is_joint ? '' : []; + } + + let _rex_ver_name = /# v\d+\.\d+\.\d+.*/g; + let _rex_remove = new RegExp( + /^(\s*\n\s*)+/.source // starts with multi blank lines + + '|' + /(# *){3,}/.source // over three hash symbols + + '|' + / +(?=\s+)/.source // ends with blank spaces in a single line + + '|' + /.*~~.*/.source // markdown strikethrough + + '|' + /.*`灵感`.*/.source // lines with inspiration label + + '|' + /\(http.+?\)/.source // URL content (not the whole line) + + '|' + /\[\/\/]:.+\(\n*.+?\n*\)/.source // markdown comments + + '|' + /\s*
/.source // line breaks + , 'g'); + let _names = _cont.match(_rex_ver_name); + let _infos = _cont.split(_rex_ver_name); + let _res = _names.map((n, i) => ({ + ver: 'v' + n.split('v')[1], + log: _infos[i + 1] + .replace(/ ?_\[`(issue |pr )?#(\d+)`](\(http.+?\))?_ ?/g, '[$2]') + .replace(_rex_remove, '') + .replace(/(\[\d+])+/g, ($) => ( + '\x20' + $.split(/\[]/).join(',').replace(/\d+/g, '#$&') + )) + .replace(/(\s*\n\s*){2,}/g, '\n'), + })); + + _onSuccess(_res); + + if (!_diag && !_opt.is_joint) { + return _res; + } + let _res_str = _res.map(o => o.ver + '\n' + o.log).join('\n').slice(0, -1); + if (_diag) { + _diag.setContent(_res_str); + } + if (_opt.is_joint) { + return _res_str; + } + + // tool function(s) // + + function _getContentByBlob() { + let _max = 5; + let _url = 'https://cdn.jsdelivr.net/gh/SuperMonster003/Ant-Forest@master' + + '/assets/docs/CHANGELOG-' + _ver_num + '.md'; + while (_max--) { + try { + return httpx.okhttp3Request(_url, { + onDownloadFailure(e) { + _max || _onFailure('请求失败: ' + e); + }, + }, {is_async: false}); + } catch (e) { + _onFailure(e.message); + } + } + } + } + + function _showEarlier() { + dialogsx + .builds(['选择一个历史版本记录', '', 0, 0, 'B', 1], { + items: (function $iiFe() { + let _items = []; + for (let i = 1; i < _ver_num; i += 1) { + _items.push('v' + i + '.x'); + } + return _items; + })(), + }) + .on('positive', dialogsx.dismiss) + .on('item_select', (idx) => { + exp.getChangelog(idx + 1, { + is_show_dialog: true, no_earlier: true, + }); + }) + .show(); + } + }, + /** + * Returns if a directory is an Ant-Forest project with a considerable possibility + * @param {string} dir + * @param {boolean} [is_throw_allowed=false] + * @return {boolean} + */ + isAlike(dir, is_throw_allowed) { + let _path = files.path(dir); + + if (!files.exists(_path)) { + if (is_throw_allowed) { + throw Error('Passed "dir" is not exist'); + } + return false; + } + if (!files.isDir(_path)) { + if (is_throw_allowed) { + throw Error('Passed "dir" is not a directory'); + } + return false; + } + + let _files = files.listDir(_path = new File(_path).getAbsolutePath()); + + return _.structure + .filter(o => o.necessary) + .map((o) => o.name[0] === filesx.sep + ? {name: o.name.slice(1), is_dir: true} + : {name: o.name}) + .every((o) => { + if (_files.indexOf(o.name) > -1) { + let _cA = o.is_dir; + let _cB = files.isDir(files.join(_path, o.name)); + return _cA && _cB || !_cA && !_cB; + } + }); + }, + /** + * @async + * @param { + * GitHub.Releases.ListItem| + * GitHub.Releases.ExtendedListItem| + * string|'newest'|'newest_cared'|'latest'|'latest_cared' + * } version + * @param {Object} [callback] + * @param {function():*} [callback.onStart] + * @param {function():*} [callback.onDeployStart] + * @param {function(value:{target_path:string}=,d:Dialogsx.BuildFlow.Extension=):*} [callback.onSuccess] + * @param {function(value:{target_path:string}=,d:Dialogsx.BuildFlow.Extension=):*} [callback.onDeploySuccess] + * @param {function(value:*=,d:Dialogsx.BuildFlow.Extension=):*} [callback.onFailure] + * @param {function(value:*=,d:Dialogsx.BuildFlow.Extension=):*} [callback.onDeployFailure] + * @param {Object} [options] + * @param {boolean} [options.is_hide_title_version] + * @param {Dialogsx.Button.Text} [options.on_interrupt_btn_text='B'] + * @param {boolean} [options.local_project_path] + * @param {string} [options.success_title] + * @param {string[]} [options.ignore_list] + * @example + * project.deploy('latest'); + */ + deploy(version, callback, options) { + let _opt = options || {}; + let _cbk = callback || {}; + let _onStart = _cbk.onDeployStart || _cbk.onStart || (r => r); + let _onSuccess = _cbk.onDeploySuccess || _cbk.onSuccess || (r => r); + let _onFailure = _cbk.onDeployFailure || _cbk.onFailure || console.error; + + let _getVersionByTag = (tag) => { + if (tag.match(/^(newest|latest)$/)) { + return this.getNewestRelease({show_progress_dialog: true}); + } + if (tag.match(/^(newest|latest)_cared$/)) { + return this.getNewestReleaseCared({ + show_progress_dialog: true, + ignore_list: _opt.ignore_list, + }); + } + let _ver = null; + this.getReleases({ + per_page: 100, show_progress_dialog: true, + }).some(o => (_ver = o).version_name === tag); + return _ver; + }; + + if (isNullish(version)) { + throw Error('A version for project.deploy() must be defined'); + } + if (typeof version === 'string') { + version = _getVersionByTag(version); + if (!version) { + if (global._$_get_proj_releases_interrupted) { + delete global._$_get_proj_releases_interrupted; + return; + } else { + throw Error('Cannot parse version tag for project.deploy()'); + } + } + } + if (typeof version !== 'object') { + throw Error('Cannot parse version for project.deploy()'); + } + + // maybe 'tar' will be supported some day + let _file_ext = 'zip'; + // like: 'https://api.github.com/.../zipball/v2.0.4' + let _url = version[_file_ext + 'ball_url']; + // like: 'v2.0.4' + let _file_name = _url.slice(_url.lastIndexOf(filesx.sep) + 1); + // like: 'v2.0.4.zip' + let _file_full_name = _file_name + '.' + _file_ext; + // like: '/sdcard/.local/bak/ant-forest/v2.0.4.zip' + let _full_path = files.join(filesx['.local']('bak', 'ant-forest', _file_full_name)); + + let _cont_len = -1; + let _tt_suff = _opt.is_hide_title_version ? '' : '\x20' + version.version_name; + + let _steps = _.step; + let _diag_dn = dialogsx.buildFlow({ + title: '正在部署项目' + _tt_suff, + success_title: _opt.success_title || '部署完成', + on_interrupt_btn_text: _opt.on_interrupt_btn_text || 'B', + show_min_max: true, + onStart(v, d) { + _onStart(); + dialogsx.setProgressColorTheme(d, 'download'); + }, + onSuccess(o, d) { + _onSuccess(o, d); + }, + onFailure(o, d) { + _onFailure(o, d); + d.setFailureData(o); + }, + steps: [{ + desc: _steps.download, + action: (v, d) => new Promise((resolve, reject) => { + httpx.okhttp3Request(_url, { + onStart() { + let _l = _cont_len / 1024; + let _p = _l < 0 ? '' : '0KB/' + _l.toFixed(1) + 'KB'; + dialogsx.setProgressNumberFormat(d, _p); + }, + onDownloadProgress(o) { + o.total = Math.max(o.total, _cont_len); + let _t = o.total / 1024; + let _p = o.processed / 1024; + dialogsx.setProgressNumberFormat(d, '%.1fKB/%.1fKB', [_p, _t]); + d.setProgressData(o); + }, + onDownloadSuccess(r) { + resolve(r); + dialogsx.clearProgressNumberFormat(d); + }, + onDownloadFailure(e) { + reject(e); + }, + }, {is_async: true, path: _full_path}); + }), + }, { + desc: _steps.decompress, + action: (v, d) => new Promise((resolve, reject) => { + filesx.unzip(v.downloaded_path, null, { + onUnzipProgress: o => d.setProgressData(o), + onUnzipSuccess(r) { + let _path = r.unzipped_path; + if (!exp.isAlike(_path)) { + _path = files.join(_path, files.listDir(_path)[0]); + } + if (!exp.isAlike(_path)) { + reject('Cannot locate project path in unzipped files'); + } + resolve(Object.assign(v, { + unzipped_files_path: r.unzipped_path, + unzipped_proj_path: _path, + })); + }, + onUnzipFailure: e => reject(e), + }, {to_archive_name_folder: true, is_delete_source: true}); + }), + }, { + desc: _steps.backup, + action: (v, d) => new Promise((resolve, reject) => { + if (!this.getLocalPath() || !this.getLocalVerName()) { + d.setStepDesc(3, ' [ 跳过 ]', true); + return resolve(v); + } + this.backup({ + onBackupProgress: o => d.setProgressData(o), + onBackupSuccess: r => resolve(Object.assign(v, {backup: r})), + onBackupFailure: e => reject(e), + }, {remark: '版本升级前的自动备份', is_save_storage: true}); + }), + }, { + desc: _steps.files_update, + action: (v, d) => new Promise((resolve, reject) => { + let _tar = this.getLocalPath(true); + filesx.copy(v.unzipped_proj_path, _tar, {is_unbundled: true}, { + onCopyProgress: o => d.setProgressData(o), + onCopySuccess: () => resolve(Object.assign(v, {tar_proj_path: _tar})), + onCopyFailure: e => reject(e), + }); + }), + }, { + desc: _steps.finish_deploy, + action: (v, d) => new Promise((resolve, reject) => { + filesx.deleteByList(v.unzipped_files_path, {is_async: true}, { + onDeleteProgress: o => d.setProgressData(o), + onDeleteSuccess: () => resolve({target_path: v.tar_proj_path}), + onDeleteFailure: e => reject(e), + }); + }), + }], + }).act(); + + httpx.getContentLength(_url, function (value) { + _diag_dn.setStepDesc(1, ' [ ' + $$cvt.bytes(_cont_len = value, 'B', { + fixed: 1, space: true, + }) + ' ]', true); + }, {timeout: 15e3, concurrence: 15}); + }, + /** + * @param {Object} [callback] + * @param {function():*} [callback.onStart] + * @param {function():*} [callback.onBackupStart] + * @param {function(data:{processed:number,total:number},diag:JsDialog$):*} [callback.onProgress] + * @param {function(data:{processed:number,total:number},diag:JsDialog$):*} [callback.onBackupProgress] + * @param {Appx.Project.Backup.OnSuccess} [callback.onSuccess] + * @param {Appx.Project.Backup.OnSuccess} [callback.onBackupSuccess] + * @param {function(value:*=):*} [callback.onFailure] + * @param {function(value:*=):*} [callback.onBackupFailure] + * @param {Object} [options] + * @param {'cwd'|'current'|string} [options.source_path='cwd'] + * @param {boolean} [options.is_show_dialog=false] + * @param {boolean} [options.is_save_storage=false] + * @param {string} [options.remark='手动备份'] + * @return {boolean|Dialogsx.BuildProgress.Extension} + */ + backup(callback, options) { + let _cbk = callback || {}; + let _opt = options || {}; + + let _now_ts = Date.now(); + let _local_ver_name = this.getLocalVerName(); + let _local_ver_hex = appx.version.getHex(_local_ver_name); + + let _def_bak_path = filesx['.local']('bak', 'ant-forest'); + let _bak_file_name = $$cvt.date(_now_ts, 'yyMMddhhmmss') + '-' + _local_ver_hex + '.zip'; + let _bak_dest_path = files.join(_def_bak_path, _bak_file_name); + + let _locateProject = () => { + let _cwd = _opt.source_path; + if (!_cwd || _cwd === 'cwd' || _cwd === 'current') { + _cwd = files.cwd(); + } + if (!files.isDir(_cwd)) { + throw Error('source_path for project.backup must be a directory'); + } + if (this.isAlike(_cwd)) { + return _cwd; + } + _cwd = new File(_cwd).getParent(); + if (this.isAlike(_cwd)) { + return _cwd; + } + let _aj_wd = filesx.getScriptDirPath(); + let _proj_def_n = 'Ant-Forest-003'; + _cwd = files.join(_aj_wd, _proj_def_n); + if (this.isAlike(_cwd)) { + return _cwd; + } + throw Error('Unable to locate Ant-Forest project folder'); + }; + + let _handleSourcePath = () => { + let _tmp_path = files.join(_def_bak_path, '.' + _now_ts) + filesx.sep; + files.createWithDirs(_tmp_path); + + let _proj_path = _locateProject(); + filesx.copy(_proj_path, _tmp_path, { + is_unbundled: true, + filter: name => _.structure + .map(o => o.name.replace(/^\//, '')).includes(name), + }); + + return _tmp_path; + }; + + let _backup = (internal_dialog_callback) => { + let _d_cbk = internal_dialog_callback || {}; + + return filesx.zip(_handleSourcePath(), _bak_dest_path, { + onZipStart() { + if (typeof _d_cbk.onStart === 'function') { + _d_cbk.onStart(); + } + let _f = _cbk.onBackupStart || _cbk.onStart; + typeof _f === 'function' && _f.call(_cbk); + }, + onZipProgress(o) { + if (typeof _d_cbk.onProgress === 'function') { + _d_cbk.onProgress(o); + } + let _f = _cbk.onBackupProgress || _cbk.onProgress; + typeof _f === 'function' && _f.call(_cbk, o); + }, + onZipSuccess() { + let _data = { + path: _bak_dest_path, + timestamp: _now_ts, + version_name: _local_ver_name, + remark: _opt.remark || '手动备份', + }; + if (typeof _d_cbk.onSuccess === 'function') { + _d_cbk.onSuccess(_data); + } + let _f = _cbk.onBackupSuccess || _cbk.onSuccess; + if (typeof _f === 'function') { + _f.call(_cbk, _data); + } + if (_opt.is_save_storage) { + let _af_bak = storagesx.af_bak; + let _sto_data = _af_bak.get('project', []); + _af_bak.put('project', _sto_data.concat(_data)); + } + }, + onZipFailure(e) { + if (typeof _d_cbk.onFailure === 'function') { + _d_cbk.onFailure(e); + } + let _f = _cbk.onBackupFailure || _cbk.onFailure; + typeof _f === 'function' && _f.call(_cbk, e); + }, + }, {is_exclude_root_folder: true, is_delete_source: true}); + }; + + return !_opt.is_show_dialog ? _backup() : dialogsx.buildProgress({ + show_min_max: true, + title: '正在备份', + content: '此过程可能需要一些时间', + success_title: '备份完成', + onStart: (v, d) => dialogsx.setProgressColorTheme(d, 'backup'), + action(value, d) { + return _backup({ + onProgress(o) { + let _p = o.processed / 1024; + let _t = o.total / 1024; + dialogsx.setProgressNumberFormat(d, '%.1fKB/%.1fKB', [_p, _t]); + d.setProgressData(o); + }, + onSuccess(o) { + dialogsx.setContentText(d, '' + + '版本: ' + o.version_name + '\n' + + '路径: ' + o.path + '\n' + + '备注: ' + o.remark); + }, + }); + }, + }).act(); + }, + /** + * @async + * @param {*} [source] - local zip path or url + * @param {Object} [callback] + * @param {function():*} [callback.onStart] + * @param {function():*} [callback.onRestoreStart] + * @param {function(value:{target_path:string}=):*} [callback.onSuccess] + * @param {function(value:{target_path:string}=):*} [callback.onRestoreSuccess] + * @param {function(value:*=):*} [callback.onFailure] + * @param {function(value:*=):*} [callback.onRestoreFailure] + * @return {boolean|Dialogsx.BuildProgress.Extension} + */ + restore(source, callback) { + let _steps = _.step; + let _cbk = callback || {}; + let _onStart = _cbk.onRestoreStart || _cbk.onStart || (r => r); + + let _preset = { + local: { + title: '正在从本地恢复', + success_title: '本地恢复完成', + '1st_step': { + desc: _steps.files_check, + action: () => { + let _src = files.path(source); + if (!files.exists(_src)) { + throw Error('Source file of project.restore() doesn\'t exist'); + } + if (!filesx.isValidZip(_src)) { + throw Error('Source file of project.restore() is corrupted'); + } + return {zip_src_file: _src}; + }, + }, + }, + server: { + title: '正在从服务器恢复', + success_title: '服务器恢复完成', + '1st_step': { + desc: _steps.download, + action: (v, d) => new Promise((resolve, reject) => { + let _cont_len = -1; + httpx.getContentLength(source, function (value) { + d.setStepDesc(1, ' [ ' + $$cvt.bytes(_cont_len = value, 'B', { + fixed: 1, space: true, + }) + ' ]', true); + }, {timeout: 15e3, concurrence: 15}); + + let _file_name = source.slice(source.lastIndexOf(filesx.sep) + 1); + let _bak_path = filesx['.local']('bak', 'ant-forest'); + let _full_path = files.join(_bak_path, _file_name + '.zip'); + + httpx.okhttp3Request(source, { + onStart() { + let _l = _cont_len / 1024; + let _p = _l < 0 ? '' : '0KB/' + _l.toFixed(1) + 'KB'; + dialogsx.setProgressNumberFormat(d, _p); + }, + onDownloadProgress(o) { + let _p = o.processed / 1024; + o.total = Math.max(o.total, _cont_len); + let _t = o.total / 1024; + dialogsx.setProgressNumberFormat(d, '%.1fKB/%.1fKB', [_p, _t]); + d.setProgressData(o); + }, + onDownloadSuccess(r) { + resolve({zip_src_file: r.downloaded_path}); + dialogsx.clearProgressNumberFormat(d); + }, + onDownloadFailure(e) { + reject(e); + }, + }, {is_async: true, path: _full_path}); + }), + }, + }, + }; + /** @type {'local'|'server'} */ + let _mode = 'local'; + if (!files.exists(source)) { + _mode = 'server'; + if (!source.match(/^https?:\/\//)) { + // noinspection HttpUrlsUsage + source = 'http://' + source; + } + } + let _cfg = _preset[_mode]; + + dialogsx.buildFlow({ + title: _cfg.title, + success_title: _cfg.success_title, + show_min_max: true, + onStart(v, d) { + _onStart(); + dialogsx.setProgressColorTheme(d, 'restore'); + dialogsx.clearProgressNumberFormat(d); + }, + onSuccess: _cbk.onRestoreSuccess || _cbk.onSuccess, + onFailure: _cbk.onRestoreFailure || _cbk.onFailure, + steps: [_cfg['1st_step'], { + desc: _steps.decompress, + action: (v, d) => new Promise((resolve, reject) => { + filesx.unzip(v.zip_src_file, null, { + onProgress: o => d.setProgressData(o), + onSuccess(r) { + let _path = r.unzipped_path; + if (!exp.isAlike(_path)) { + _path = files.join(_path, files.listDir(_path)[0]); + } + if (exp.isAlike(_path)) { + resolve(Object.assign(v, { + unzipped_files_path: r.unzipped_path, + unzipped_proj_path: _path, + })); + } else { + reject('Cannot locate project path in unzipped files'); + } + }, + onFailure: e => reject(e), + }, {to_archive_name_folder: true, is_delete_source: false}); + }), + }, { + desc: _steps.files_update, + action: (v, d) => new Promise((resolve, reject) => { + let _tar = this.getLocalPath(true); + filesx.copy(v.unzipped_proj_path, _tar, {is_unbundled: true}, { + onProgress: o => d.setProgressData(o), + onSuccess: () => resolve(Object.assign(v, {tar_proj_path: _tar})), + onFailure: e => reject(e), + }); + }), + }, { + desc: _steps.finish_restore, + action: (v, d) => new Promise((resolve, reject) => { + filesx.deleteByList(v.unzipped_files_path, {is_async: true}, { + onStart: () => _mode === 'server' && files.remove(v.zip_src_file), + onProgress: o => d.setProgressData(o), + onSuccess: () => resolve({target_path: v.tar_proj_path}), + onFailure: e => reject(e), + }); + }), + }], + }).act(); + }, +}; + +/** + * @type {Mod.project} + */ +module.exports = {project: exp}; \ No newline at end of file diff --git a/modules/plugin-ant-forest-biodiversity.js b/modules/plugin-ant-forest-biodiversity.js new file mode 100644 index 0000000..858034f --- /dev/null +++ b/modules/plugin-ant-forest-biodiversity.js @@ -0,0 +1,14 @@ +/** @type {Plugin$Exportation} */ +let exp = { + dialog: null, + view: null, + run() {}, + config() {}, + deploy() {}, +}; + +if (typeof module === 'object') { + module.exports = exp; +} else { + exp.run(); +} \ No newline at end of file diff --git a/modules/plugin-ant-forest-district-protect.js b/modules/plugin-ant-forest-district-protect.js new file mode 100644 index 0000000..858034f --- /dev/null +++ b/modules/plugin-ant-forest-district-protect.js @@ -0,0 +1,14 @@ +/** @type {Plugin$Exportation} */ +let exp = { + dialog: null, + view: null, + run() {}, + config() {}, + deploy() {}, +}; + +if (typeof module === 'object') { + module.exports = exp; +} else { + exp.run(); +} \ No newline at end of file diff --git a/modules/plugin-ant-forest-on-screen-off-launcher.js b/modules/plugin-ant-forest-on-screen-off-launcher.js new file mode 100644 index 0000000..c01bba5 --- /dev/null +++ b/modules/plugin-ant-forest-on-screen-off-launcher.js @@ -0,0 +1,98 @@ +let {timersx} = require('./ext-timers'); +let {project} = require('./mod-project'); +let {consolex} = require('./ext-console'); + +/* Here, importClass() is not recommended for intelligent code completion in IDE like WebStorm. */ +/* The same is true of destructuring assignment syntax (like `let {Uri} = android.net`). */ + +let App = org.autojs.autojs.App; + +let cfg = { + action: 'android.intent.action.SCREEN_OFF', + path: project.getLocal().main.path, + delay: 0, // milliseconds +}; + +let $ = { + file_name: 'plugin-ant-forest-on-screen-off-launcher.js', + /** + * @param {(typeof cfg) & any} [config] + */ + addIntentTask(config) { + Object.assign(cfg, config); + + timersx.addIntentTask({ + action: cfg.action, + path: files.join(project.getLocalPath(), 'modules', this.file_name), + is_async: true, + callback(task) { + consolex._([ + 'Intent task added successfully', + 'action: ' + task.getAction(), + 'path: ' + task.getScriptPath(), + ], 0, 0, -2); + + if (typeof config.callback === 'function') { + config.callback(task); + } + }, + }); + }, + removeAllTasks() { + let _tasks = timersx.queryIntentTasks({ + action: cfg.action, + }); + _tasks.forEach(task => timersx.removeTask(task)); + + consolex._([ + 'All intent tasks removed', + 'length: ' + _tasks.length, + 'action: ' + cfg.action, + ], 0, 0, -2); + }, + unregisterAllDynamicBroadcastReceivers() { + try { + App.Companion.getApp().getDynamicBroadcastReceivers().unregisterAll(); + } catch (e) { + // Wrapped java.lang.IllegalArgumentException: Receiver not registered... + // Error above could be ignored as the functionality + + // App.Companion.getApp().getDynamicBroadcastReceivers().unregister(action: string) + // Code above does not work as expected, unfortunately. + } + }, + addDisposableTask() { + timersx.addDisposableTask({ + date: Date.now() + cfg.delay, + path: cfg.path, + }); + }, +}; + +/** @type {Plugin$Exportation} */ +let exp = { + dialog: null, + view: null, + run() { + $.removeAllTasks(); + $.unregisterAllDynamicBroadcastReceivers(); + $.addDisposableTask(); + }, + config() { + // Maybe... Select an action like Auto.js 4.1.1 Alpha2 ? + }, + /** + * @param {typeof cfg} [config] + */ + deploy(config) { + $.removeAllTasks(); + $.unregisterAllDynamicBroadcastReceivers(); + $.addIntentTask(config); + }, +}; + +if (typeof module === 'object') { + module.exports = exp; +} else { + exp.run(); +} \ No newline at end of file diff --git a/modules/plugin-ant-forest.js b/modules/plugin-ant-forest.js new file mode 100644 index 0000000..5394bcd --- /dev/null +++ b/modules/plugin-ant-forest.js @@ -0,0 +1,2 @@ +//// -=-= PENDING =-=- //// +module.exports = {}; \ No newline at end of file diff --git a/modules/plugin-device-unlock.js b/modules/plugin-device-unlock.js new file mode 100644 index 0000000..5394bcd --- /dev/null +++ b/modules/plugin-device-unlock.js @@ -0,0 +1,2 @@ +//// -=-= PENDING =-=- //// +module.exports = {}; \ No newline at end of file