[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = tab\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[**/*.{cmd,bat}]\nend_of_line = crlf\n\n[**/*.{yml,yaml}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n\tenv: {\n\t\tbrowser: true,\n\t\tcommonjs: true,\n\t\tes6: true,\n\t\tnode: true,\n\t},\n\textends: [\n\t\t\"standard\",\n\t],\n\tparserOptions: {\n\t\tecmaFeatures: {\n\t\t\tjsx: true,\n\t\t},\n\t},\n\tglobals: {\n\t\tcolors: true,\n\t\tcom: true,\n\t\torg: true,\n\t\timportClass: true,\n\t\tstorages: true,\n\t\tdevice: true,\n\t\tlog: true,\n\t\tthreads: true,\n\t\texit: true,\n\t\truntime: true,\n\t\tjava: true,\n\t\timportPackage: true,\n\t\tui: true,\n\t\tactivity: true,\n\t\tcontext: true,\n\t\tsleep: true,\n\t\tandroid: true,\n\t\ttoastLog: true,\n\t\tfiles: true,\n\t\trequestScreenCapture: true,\n\t\thttp: true,\n\t\ttoast: true,\n\t\tengines: true,\n\t\trandom: true,\n\t\tevents: true,\n\t\tpress: true,\n\t\tgesture: true,\n\t\tgetPackageName: true,\n\t\tshell: true,\n\t\tfloaty: true,\n\t\tcurrentPackage: true,\n\t\tlaunch: true,\n\t\tapp: true,\n\t\timages: true,\n\t\tlaunchApp: true,\n\t\tidEndsWith: true,\n\t\ttextEndsWith: true,\n\t\tdescEndsWith: true,\n\t\tback: true,\n\t\tdialogs: true,\n\t\tauto: true,\n\t\tsetClip: true,\n\t\tgetClip: true,\n\t\tjavax: true,\n\t\tmedia: true,\n\t\tcaptureScreen: true,\n\t\ttimers: true,\n\t\tselector: true,\n\t\trecents: true,\n\t\tswipe: true,\n\t\twaitForActivity: true,\n\t\twaitForPackage: true,\n\t\tcurrentActivity: true,\n\t\tJavaAdapter: true,\n\t\t__non_webpack_require__: true,\n\t\tDEBUG: true,\n\t},\n\trules: {\n\t\t\"indent\": [\"error\", \"tab\", { SwitchCase: 1 }],\n\t\t\"quotes\": [\"error\", \"double\"],\n\t\t\"semi\": [\"error\", \"always\"],\n\t\t\"block-spacing\": [\"error\", \"always\"],\n\t\t\"array-bracket-spacing\": [\"error\", \"never\"],\n\t\t\"quote-props\": [\"error\", \"consistent-as-needed\"],\n\t\t\"comma-dangle\": [\"error\", \"always-multiline\"],\n\t\t\"no-tabs\": [\"off\"],\n\t},\n};\n"
  },
  {
    "path": ".github/workflows/webpack.yml",
    "content": "name: NodeJS with Webpack\n\non:\n  push:\n    branches: [ \"*\" ]\n  pull_request:\n    branches: [ \"*\" ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v3\n\n    - name: Use Node.js current\n      uses: actions/setup-node@v3\n      with:\n        node-version: current\n\n    - name: Build\n      run: |\n        npm install\n        npm run build\n\n    - name: Create Release\n      id: create_release\n      uses: actions/create-release@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        tag_name: ${{ github.ref }}\n        release_name: ${{ github.ref }}\n        draft: true\n        prerelease: true\n\n    - name: Upload Batch\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps\n        asset_path: ./dist/miui_cleaner_cmd/MiuiCleaner.cmd\n        asset_name: MiuiCleaner.cmd\n        asset_content_type: text/plain\n\n    - name: Upload JavaScript Main\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps\n        asset_path: ./dist/miui_cleaner_app/main.js\n        asset_name: main.js\n        asset_content_type: text/javascript\n\n    - name: Upload JavaScript Service\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps\n        asset_path: ./dist/miui_cleaner_app/services.js\n        asset_name: services.js\n        asset_content_type: text/javascript\n"
  },
  {
    "path": ".gitignore",
    "content": "# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig\n# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,node,linux,androidstudio,android\n# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,node,linux,androidstudio,android\n\n### Android ###\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n*.log\n\n# Android Studio generated files and folders\ncaptures/\n.externalNativeBuild/\n.cxx/\n*.apk\noutput.json\n\n# IntelliJ\n*.iml\n.idea/\nmisc.xml\ndeploymentTargetDropDown.xml\nrender.experimental.xml\n\n# Keystore files\n*.jks\n*.keystore\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Android Profiling\n*.hprof\n\n### Android Patch ###\ngen-external-apklibs\n\n# Replacement of .externalNativeBuild directories introduced\n# with Android Studio 3.5.\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### Node ###\n# Logs\nlogs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n### VisualStudioCode ###\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n!.vscode/*.code-snippets\n\n# Local History for Visual Studio Code\n.history/\n\n# Built Visual Studio Code Extensions\n*.vsix\n\n### VisualStudioCode Patch ###\n# Ignore all local history of files\n.history\n.ionide\n\n# Support for Project snippet scope\n.vscode/*.code-snippets\n\n# Ignore code-workspaces\n*.code-workspace\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n### AndroidStudio ###\n# Covers files to be ignored for android development using Android Studio.\n\n# Built application files\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradle\n\n# Signing files\n.signing/\n\n# Local configuration file (sdk path, etc)\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n\n# Android Studio\n/*/build/\n/*/local.properties\n/*/out\n/*/*/build\n/*/*/production\n.navigation/\n*.ipr\n*.swp\n\n# Keystore files\n\n# Google Services (e.g. APIs or Firebase)\n# google-services.json\n\n# Android Patch\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# NDK\nobj/\n\n# IntelliJ IDEA\n*.iws\n/out/\n\n# User-specific configurations\n.idea/caches/\n.idea/libraries/\n.idea/shelf/\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/.name\n.idea/compiler.xml\n.idea/copyright/profiles_settings.xml\n.idea/encodings.xml\n.idea/misc.xml\n.idea/modules.xml\n.idea/scopes/scope_settings.xml\n.idea/dictionaries\n.idea/vcs.xml\n.idea/jsLibraryMappings.xml\n.idea/datasources.xml\n.idea/dataSources.ids\n.idea/sqlDataSources.xml\n.idea/dynamic.xml\n.idea/uiDesigner.xml\n.idea/assetWizardSettings.xml\n.idea/gradle.xml\n.idea/jarRepositories.xml\n.idea/navEditor.xml\n\n# Legacy Eclipse project files\n.classpath\n.project\n.cproject\n.settings/\n\n# Mobile Tools for Java (J2ME)\n.mtj.tmp/\n\n# Package Files #\n*.war\n*.ear\n\n# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)\nhs_err_pid*\n\n## Plugin-specific files:\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Mongo Explorer plugin\n.idea/mongoSettings.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n### AndroidStudio Patch ###\n\n!/gradle/wrapper/gradle-wrapper.jar\n\n# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,node,linux,androidstudio,android\n\n# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)\n\nwindow_dump.xml\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Chaos\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# MiuiCleaner\n----\nMIUI广告清理工具\n\n## 使用方法\n\n- [点击下载最新版本MiuiCleaner](https://github.com/gucong3000/MiuiCleaner/releases/latest)，将`MiuiCleaner.apk`安装到手机即可\n- 使用“预装应用卸载”功能能时，需要root权限或者ADB权限（亦即“开发者选项”中“USB 调试”），未root用户在使用该功能时请在电脑上按以下步骤操作：\n\t- 在电脑上安装以下工具中任何一种，并确保其可以正常连接手机。\n\t\t- [小米手机助手](http://zhushou.xiaomi.com/)\n\t\t- [360手机助手](https://sj.360.cn/)\n\t\t- [豌豆荚](https://www.wandoujia.com/)\n\t\t- [Android SDK 平台工具](https://developer.android.google.cn/studio/releases/platform-tools?hl=zh-cn)\n\t- 在该工具的安装目录中搜索到`adb.exe`所在的子目录，将`MiuiCleaner.cmd`放入其中并运行（或者将这个目录加入环境变量`PATH`中）\n\n## 功能介绍\n### 预装应用卸载\n\n勾选你想要卸载的APP，点击确定就可以一键删除了。支持以下62款应用的卸载：\n<details>\n<summary>点击查看详细名单</summary>\n\n- APP 外置开屏广告\n\t- 广告分析\n\t- 小米系统广告解决方案（智能服务）\n- 桌面广告 APP\n\t- 智能助理（负一屏）\n\t- 信息助手（负一屏）\n\t- 智能出行\n\t- 内容中心（趣看看）\n\t- 百度搜索框\n\t- 桌面搜索框（搜索/全局搜索）\n\t- 桌面搜索框（Google）\n- 过时的 APP\n\t- 悬浮球\n\t- 小米闻声\n\t- 智慧生活\n- 影音类 APP\n\t- 音乐\n\t- Mi Video\n\t- 小米视频\n\t- 腾讯视频小米版\n\t- 爱奇艺播放器\n- 天气\n\t- 小米天气\n- 支付、电商、理财类 APP\n\t- 小米商城\n\t- 小米商城系统组件（电商助手）\n\t- 小米钱包\n\t- 米币支付\n\t- 小米支付\n\t- 小米卡包\n\t- 小米金融（天星金融）\n\t- 小米金融（天星金融）- 安全组件\n\t- 小米金服安全组件\n\t- 银联可信服务安全组件小米版\n- 低使用频率 APP\n\t- 小米换机\n\t- 小米社区\n\t- 用户反馈\n\t- KLO bug反馈\n\t- 服务与反馈\n\t- 我的服务\n\t- 小米画报\n\t- 动态壁纸\n\t- 动态壁纸获取\n\t- 壁纸备份\n\t- 壁纸编辑器\n\t- 收音机（蜻蜓FM）\n\t- WPS Office Lite\n\t- 阅读（番茄免费小说）\n\t- 阅读（多看阅读器）\n\t- 小米运动健康\n- 浏览器\n\t- 小米浏览器\n\t- 小米浏览器（国际版）\n\t- Chrome\n- 内置输入法\n\t- 百度输入法-小米版\n\t- 搜狗输入法-小米版\n\t- 讯飞输入法-小米版\n\t- 小米安全键盘\n- 小米游戏中心\n\t- 游戏中心（旧版）\n\t- 游戏中心\n\t- 游戏服务\n\t- 游戏中心 - pad 版\n\t- Joyose\n- SIM 卡应用\n\t- 小米移动\n\t- 全球上网\n\t- 小米云流量\n\t- 全球上网工具插件\n\t- SIM卡应用\n- 快应用\n\t- 快应用中心\n\t- 快应用服务框架\n- 语音助手\n\t- 语音唤醒\n\t- 小爱语音(小爱同学)\n\t- 小爱视觉（扫一扫）\n\t- 小爱翻译\n\t- 小爱通话（AI虚拟助手）\n</details>\n\n### 去广告应用\n\n内置多款去广告应用的下载链接：\n<details>\n<summary>点击查看详细名单</summary>\n\n- [李跳跳](https://www.123pan.com/s/A6cA-edAJh)\n\t> 广告自动跳过工具\n- [Edge](https://www.coolapk.com/apk/com.microsoft.emmx)\n\t> 广告可关，可与Windows的Edge互动，有网页广告屏蔽功能\n- [小米浏览器](https://com-globalbrowser.cn.aptoide.com/app)\n\t> 国际版，广告可关，有网页广告屏蔽功能\n- [讯飞输入法](https://423down.lanzouv.com/b0f24av5i)\n\t> Google Play版，无广告\n- 软件包安装程序\n\t> Google版，代替MIUI的“应用包管理程序”，无广告和审查功能\n- [应用包管理组件](https://zisu.lanzoum.com/iI7LGwn5xjc)\n\t> MIUI软件包安装程序v3.8.0，不含“纯净模式”\n- [QQ音乐简洁版](https://www.coolapk.com/apk/com.tencent.qqmusiclite)\n\t> MIUI 音乐APP套壳的产品\n- [Holi 天气](https://www.coolapk.com/apk/com.joe.holi)\n\t> 无广告，体较小，更漂亮，替代“小米天气”\n- [ES文件浏览器](https://423down.lanzouv.com/b0f1d7s2h)\n\t> 修改版，去广告，代替“小米视频”和“小米音乐”\n- [WPS Office Lite](https://www.32r.com/app/109976.html)\n\t> 国际版，无广告，替代“文档查看器”\n- [知乎](https://423down.lanzouo.com/b0f2lkafe)\n\t> 集成“知了”，可在“知了”中关闭所有广告\n- [哔哩哔哩](https://423down.lanzouv.com/b0f1gksne)\n\t> 集成“哔哩漫游”，可在“哔哩漫游”中关闭所有广告（需点击其版本号7次）\n- [优酷视频](https://423down.lanzouv.com/b0f1avpib)\n\t> 修改版，去广告\n- [百度贴吧](https://423down.lanzouv.com/b0f1b6q8d)\n\t> 修改版，去广告\n- [酷安](https://423down.lanzouv.com/b0f2uzq2b)\n\t> 应用商店，修改版，去广告\n- [AppShare](https://appshare.muge.info/)\n\t> 应用商店，可下载MIUI国际版中提取的APP\n\n</details>\n\n### 关闭各应用广告\n\n支持在以下17款应用中，自动搜索50多个广告开关的具体位置，并自动给予处置。\n<details>\n<summary>点击查看详细名单</summary>\n\n- 小米帐号\n\t- 关于小米帐号\n\t\t- 系统广告\n\t\t\t- 系统工具广告：`关闭`\n- 系统安全\n\t- 加入“用户体验改进计划”：`关闭`\n\t- 自动发送诊断数据：`关闭`\n\t- 广告服务\n\t\t- 个性化广告推荐：`关闭`\n\t- 网页链接调用服务\n\t\t- 网页链接调用服务：`关闭`\n- 手机管家\n\t- 在通知栏显示：`关闭`\n\t- 在线服务：`关闭`\n\t- 隐私设置\n\t\t- 仅在WLAN下推荐：`打开`\n\t\t- 个性化推荐：`关闭`\n- 应用管理\n\t- 资源推荐：`关闭`\n- 垃圾清理\n\t- 扫描内存：`关闭`\n\t- 推荐内容：`关闭`\n\t- 仅在WLAN下推荐：`打开`\n- 应用商店\n\t- 通知设置\n\t\t- 新手帮助：`关闭`\n\t\t- 应用更新通知：`关闭`\n\t\t- 点赞消息：`关闭`\n\t\t- 评论消息：`关闭`\n\t- 通知栏快捷入口：`关闭`\n\t- 隐私设置\n\t\t- 个性化服务\n\t\t\t- 个性化服务：`关闭`\n\t- 功能设置\n\t\t- 显示福利活动：`关闭`\n- 下载管理\n\t- 信息流设置\n\t\t- 仅在WLAN下加载：`打开`\n\t\t- 资源推荐：`关闭`\n\t\t- 热榜推荐：`关闭`\n- 日历\n\t- 功能设置\n\t\t- 显示天气服务：`关闭`\n\t- 用户体验计划\n\t\t- 内容推广：`关闭`\n- 时钟\n\t- 更多闹钟设置\n\t\t- 显示生活早报：`关闭`\n- 小米社区\n\t- 隐私管理\n\t\t- 详情页相似推荐：`关闭`\n\t\t- 个性化广告：`关闭`\n\t\t- 信息流推荐：`关闭`\n\t- 关闭私信消息提醒：`打开`\n- 小米天气\n\t- 用户体验计划\n\t\t- 天气视频卡片：`关闭`\n\t\t- 内容推广：`关闭`\n- 小米视频\n\t- 隐私设置\n\t\t- 个性化内容推荐：`关闭`\n\t\t- 个性化广告推荐：`关闭`\n\t- 消息与推送\n\t\t- 未读消息提醒：`关闭`\n\t\t- 接收小米推送：`关闭`\n\t- 其他\n\t\t- 在线服务：`关闭`\n- 音乐\n\t- 在线内容服务：`关闭`\n- 小爱语音\n\t- 隐私管理\n\t\t- 隐私设置\n\t\t\t- 加入用户体验改进计划：`关闭`\n\t\t\t- 小爱技巧推送服务：`关闭`\n\t\t\t- 个性化推荐：`关闭`\n\t\t\t- 个性化广告推荐：`关闭`\n- 搜索\n\t- 搜索快捷方式\n\t\t- 桌面搜索框：`关闭`\n\t- 首页展示模块\n\t\t- 热搜榜单\n\t\t\t- 热搜榜s：`关闭`\n\t\t- 搜索提示词：`关闭`\n\t- 搜索项\n\t\t- 搜索精选：`关闭`\n\t- 网站广告过滤：`打开`\n- 浏览器\n\t- 主页设置\n\t\t- 简洁版：`打开`\n\t\t- 宫格位推送：`关闭`\n\t- 隐私防护\n\t\t- 广告过滤\n\t\t\t- 广告过滤：`打开`\n\t- 消息通知管理\n\t\t- 接收消息通知：`关闭`\n- 小米浏览器\n\t- 首页设置\n\t\t- 简洁版：`打开`\n\t- 隐私保护\n\t\t- 广告过滤\n\t\t\t- 广告过滤：`打开`\n\t- 高级\n\t\t- 浏览器广告：`关闭`\n\t- 通知栏快捷入口：`关闭`\n\t- Facebook快捷通知：`关闭`\n\n</details>\n\n[演示视频：MiuiCleaner新功能演示-广告全自动关闭](https://www.zhihu.com/zvideo/1555993019102552064)\n### 应用管家\n\n- 自启动管理\n\t> 自启动及后台运行权限管理\n- 通知管理\n\t> 通知栏、悬浮提示、图标角标的管理\n- APP卸载\n\t> APP的批量卸载\n- APP管理\n\t> 手机管家的应用管理功能\n- APP信息\n\t> 权限管理模块\n\n### 回收站\n\n你可以在这里重新安装已卸载的预装应用\n\n## 常见问题\n- 电脑连不上手机，咋办？\n\t> 确保数据线正常，确保驱动安装正常，可以用“360手机助手”等工具自动安装\n- 删错了“XXX”，咋恢复？\n\t> 进入“回收站”或者“应用商店”，重新安装。\n\n## CHANGELOG\n- v2023.4.23.8\n\t- 预装应用卸载\n\t\t- 去除卸载“纯净模式”功能\n\t- 去广告APP\n\t\t- 李跳跳更新至v2.2\n\t- 关闭各应用广告\n\t\t- 新增替换软件包安装器功能\n\t- 回收站\n\t\t- 使用原生圆形进度条，不再卡UI\n\t- 修正MIUI 13+ 报错，无法找到设置项\n\t- 新增下载管理\n\t- 新增在线升级功能\n\t- 新增帮助与反馈功能\n- v2022.10.20.7\n\t- 重构UI，减少对弹出框权限的依赖，菜单项加入描述信息、图标\n\t- 权限获取功能重构，修复不能正确请求权限的bug\n\t- 增加若干可卸载APP\n\t- 增加若干去广告APP\n- v2022.9.25.5\n\t- PC端修复bug #1\n\t- 预装应用卸载\n\t\t- 修正：卸载多个应用时报“超时”的bug\n\t\t- 修正：单独卸载系统应用时流程卡死的bug\n\t\t- 新增：无`USB调试`权限时，自动获取授权的功能\n\t- 去广告应用\n\t\t- 新增：应用包管理组件\n\t\t- 新增：QQ音乐简洁版\n\t- 关闭各应用广告\n\t\t- 增强：小米帐号\n\t\t- 新增：系统安全\n\t\t- 新增：广告服务\n\t\t- 新增：小米帐号、系统安全、广告服务三个选项按需显示功能、相关广告已关，或者已通过“修改系统”权限自动关闭后，不显示\n\t\t- 删除：音乐APP的广告关闭功能\n\t- 应用管家\n\t\t- 新增：自启动管理\n\t\t- 新增：应用卸载\n\t\t- 删除：应用管理\n- v2022.9.21.4\n\t- 新增手机端 GUI\n\t- 新增广告全自动关闭功能\n\t- 新增应用管家入口\n\t\t- 通知管理\n\t\t- 应用管理\n\t\t- 应用信息\n\t- 增加若干可卸载APP\n\t\t- 动态壁纸\n\t\t- 动态壁纸获取\n\t\t- 用户反馈\n\t\t- 服务与反馈\n\t\t- 小米运动健康\n\t\t- 小米云流量\n\t\t- Chrome\n\t- 大部分功能从电脑端实现转为用手机端实现\n\t- 删除卸载米家功能\n- v2022.9.10.2\n\t- 修正文案丢字\n\t- “⋮”无法在Windows默认终端显示，改为“三个点”来迁就\n- v2022.9.9.1\n\t- 新增内置 APP 广告关闭引导功能\n\t- 部分文案修改\n\t- APP卸载功能新增9款 APP 的支持\n\t- 软件推送向手机前，增加了判断是否已装的逻辑\n\t- 删除MIUI内置应用前，增加了是否MIUI环境的判断\n- v2022.9.7.0\n\t- 首个版本\n\t- 提供若干内置APP卸载和恢复功能\n\t- 提供第三方APP替换功能\n"
  },
  {
    "path": "autojs-deploy.js",
    "content": "const { Adb } = require(\"@devicefarmer/adbkit\");\nconst { readFile } = require(\"fs/promises\");\nconst EventEmitter = require(\"events\");\nconst path = require(\"path\");\nconst {\n\tSourceMapConsumer,\n} = require(\"source-map\");\n\nclass AutojsDeployPlugin {\n\tconstructor (options = {}) {\n\t\tthis.options = {\n\t\t\t...AutojsDeployPlugin.defaultOptions,\n\t\t\t...options,\n\t\t};\n\t\tif (options.remoteDir) {\n\t\t\toptions.remoteDir = path.posix.resolve(options.remoteDir);\n\t\t}\n\t\tthis.adb = Adb.createClient(this.options.adb);\n\t}\n\n\t// 缺省配置\n\tstatic defaultOptions = {\n\t\tpackageName: {},\n\t\tproject: {},\n\t\tbuild: {\n\t\t\tui: true,\n\t\t},\n\t\tdeploy: {\n\t\t\trun: true,\n\t\t\tskipSourceMap: true,\n\t\t},\n\t\tlogcat: {\n\t\t\tstdout: process.stdout,\n\t\t\tsourceMap: true,\n\t\t},\n\t};\n\n\t// 读取AutoJS项目的`project.json`文件\n\tasync getProjectConfig () {\n\t\tlet projectConfig = await readFile(\n\t\t\tthis.options.configFile,\n\t\t\t\"utf-8\",\n\t\t);\n\t\tprojectConfig = JSON.parse(projectConfig);\n\t\tthis.options.project = projectConfig;\n\t\tif (!projectConfig.projectDirectory) {\n\t\t\tprojectConfig.projectDirectory = projectConfig.packageName || projectConfig.name || require(path.join(process.cwd(), \"package.json\")).name;\n\t\t}\n\t\tprojectConfig.projectDirectory = path.posix.resolve(\"/storage/emulated/0/脚本/\", projectConfig.projectDirectory);\n\t\treturn projectConfig;\n\t}\n\n\t// 获取手机中安装的AutoJS Pro 或者AutoX的包名\n\tasync getPackageName (device) {\n\t\tlet packageName = this.options.packageName[device.serial];\n\t\tif (!packageName) {\n\t\t\tlet packages = await this.shell(device, \"pm list package org.autojs.\");\n\t\t\tpackages = packages.trim().split(/\\r?\\n/g);\n\t\t\tif (packages.length) {\n\t\t\t\tpackageName = packages[0].replace(/^package\\s*:\\s*/, \"\").trim();\n\t\t\t\tthis.options.packageName[device.serial] = packageName;\n\t\t\t}\n\t\t}\n\t\tdevice.packageName = packageName;\n\t\treturn device;\n\t}\n\n\t// 日志显示功能，AutoJS Pro可直接使用，AutoX需要在项目代码中配置日志文件路径：`console.setGlobalLogConfig({file: files.join(context.getExternalFilesDir(\"logs\"),\"log.txt\")});`\n\tasync logcat (compilation) {\n\t\tif (!this.options.logcat) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst project = await this.getProjectConfig();\n\t\tconst logcat = async (device, packageName) => {\n\t\t\tif (!packageName) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet cmd = compilation.compiler.options.watch ? \"tail -f\" : \"cat\";\n\t\t\tcmd += ` /storage/emulated/0/Android/data/${packageName}/files/logs/log.txt`;\n\t\t\tconst log = await device.shell(cmd);\n\t\t\tconst transform = new LogTransform();\n\t\t\tconst stdout = this.options.logcat.stdout || process.stdout;\n\t\t\ttransform.colors = stdout.hasColors && stdout.hasColors() && (this.options.logcat.colors || transform.colors);\n\t\t\ttransform.sourceMap = this.options.logcat.sourceMap;\n\t\t\tconst startOptput = () => {\n\t\t\t\tconsole.log(\"> adb shell\", cmd);\n\t\t\t\tlog.pipe(\n\t\t\t\t\ttransform,\n\t\t\t\t).pipe(\n\t\t\t\t\tstdout,\n\t\t\t\t);\n\t\t\t};\n\t\t\tif (compilation.compiler.options.watch) {\n\t\t\t\tlog.once(\"data\", startOptput);\n\t\t\t} else {\n\t\t\t\tstartOptput();\n\t\t\t}\n\t\t\tthis.options.logcat.manager.once(\"close\", () => log.end());\n\t\t\treturn log;\n\t\t};\n\t\tconst start = async (device) => {\n\t\t\treturn await Promise.all(\n\t\t\t\t[\n\t\t\t\t\tproject.packageName,\n\t\t\t\t\tdevice.packageName,\n\t\t\t\t].map(packageName => logcat(device, packageName)),\n\t\t\t);\n\t\t};\n\t\tif (this.options.logcat.manager) {\n\t\t\tthis.options.logcat.manager.emit(\"close\");\n\t\t} else {\n\t\t\tthis.options.logcat.manager = new EventEmitter();\n\t\t\tif (compilation.compiler.options.watch) {\n\t\t\t\tprocess.nextTick(async () => {\n\t\t\t\t\tconst tracker = await this.adb.trackDevices();\n\t\t\t\t\ttracker.on(\"add\", async (device) => {\n\t\t\t\t\t\tdevice = this.adb.getDevice(device.id);\n\t\t\t\t\t\tawait device.waitForDevice();\n\t\t\t\t\t\tawait this.getPackageName(device);\n\t\t\t\t\t\tawait start(device);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tawait this.eachDevice(start);\n\t}\n\n\t// 通过ADB在手机的shell中运行命令\n\tshell (device, ...args) {\n\t\treturn device.shell(...args)\n\t\t\t.then(Adb.util.readAll)\n\t\t\t.then((output) => {\n\t\t\t\treturn output.toString();\n\t\t\t});\n\t}\n\n\t// 遍历所有通过ADB连接到PC的手机\n\tasync eachDevice (...args) {\n\t\tlet devices = await this.adb.listDevices();\n\t\tdevices = devices.map(device => this.adb.getDevice(device.id));\n\t\tdevices = (await Promise.all(\n\t\t\tdevices.map(async (device) => {\n\t\t\t\tawait this.getPackageName(device);\n\t\t\t\treturn device.packageName && device;\n\t\t\t}),\n\t\t)).filter(Boolean);\n\t\treturn await Promise.all(devices.map(...args));\n\t}\n\n\t// 通过ADB将webpack输出的文件部署文件到手机上,会跳过sourceMap文件，手机目录在`options.remoteDir`中配置，未声明会自动选择`/storage/emulated/0/脚本/{project.json中的packageName、name或者package.json中的name}`\n\tdeploy (compilation) {\n\t\tif (!this.options.deploy) {\n\t\t\treturn;\n\t\t}\n\t\tconst remoteDir = this.options.project.projectDirectory;\n\t\tlet assets = compilation.getAssets();\n\t\tif (this.options.deploy.skipSourceMap) {\n\t\t\tassets = assets.filter(asset =>\n\t\t\t\t!(\"development\" in asset.info) && !(\"extractedComments\" in asset.info),\n\t\t\t);\n\t\t}\n\t\tconst localDir = compilation.compiler.outputPath;\n\t\treturn this.eachDevice(async (device) => {\n\t\t\tconsole.log(`> adb push ${localDir} ${remoteDir}`);\n\t\t\tawait Promise.all(\n\t\t\t\tassets.map(asset => (\n\t\t\t\t\tdevice.push(\n\t\t\t\t\t\tpath.join(localDir, asset.name),\n\t\t\t\t\t\tpath.posix.join(remoteDir, asset.name),\n\t\t\t\t\t)\n\t\t\t\t)),\n\t\t\t);\n\t\t\tif (this.options.deploy.run) {\n\t\t\t\tconst jsFileList = assets.filter(asset =>\n\t\t\t\t\t\"javascriptModule\" in asset.info,\n\t\t\t\t).map(asset => asset.name);\n\t\t\t\tlet jsFile = jsFileList[0];\n\t\t\t\tif (jsFileList.length > 1) {\n\t\t\t\t\tconst { main } = await this.getProjectConfig();\n\t\t\t\t\tif (main && jsFileList.includes(main)) {\n\t\t\t\t\t\tjsFile = main;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!jsFile) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconsole.log(`> adb shell am start -a android.intent.action.MAIN -n ${device.packageName}/org.autojs.autojs.external.shortcut.ShortcutActivity -e path ${path.posix.join(remoteDir, jsFile)}`);\n\t\t\t\tawait device.startActivity({\n\t\t\t\t\tdebug: true,\n\t\t\t\t\taction: \"android.intent.action.MAIN\",\n\t\t\t\t\tcomponent: device.packageName + \"/org.autojs.autojs.external.shortcut.ShortcutActivity\",\n\t\t\t\t\textras: {\n\t\t\t\t\t\tpath: path.posix.join(remoteDir, jsFile),\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\t// 在webpack变异文件的队列中加入“project.json”、js文件中添加`\"ui\";指令头`、收集webpack生成的sourceMap文件，供logcat相关功能调用\n\tasync updateAsset (compilation) {\n\t\tconst project = await this.getProjectConfig();\n\n\t\tif (compilation.compiler.options.watch && this.options.logcat?.sourceMap) {\n\t\t\tthis.options.logcat.sourceMap = {};\n\t\t\tconst remoteDir = project.projectDirectory;\n\t\t\tconst posixPath = (sPath) => (path.isAbsolute(sPath) ? path.relative(process.cwd(), sPath) : sPath).replaceAll(path.win32.sep, path.posix.sep);\n\t\t\tconst contextPath = posixPath(compilation.options.context);\n\t\t\tconst outputPath = posixPath(compilation.compiler.outputPath);\n\n\t\t\tcompilation.getAssets().forEach((asset) => {\n\t\t\t\tif (\"development\" in asset.info) {\n\t\t\t\t\tconst sourceMap = JSON.parse(asset.source.source());\n\t\t\t\t\tsourceMap.sources = sourceMap.sources.map(file => {\n\t\t\t\t\t\tconst uri = file.match(/^webpack:\\/\\/([^/]+\\/)?\\.\\/(.*)$/);\n\t\t\t\t\t\tif (uri) {\n\t\t\t\t\t\t\treturn path.posix.join(contextPath, uri[2]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn file;\n\t\t\t\t\t});\n\t\t\t\t\tconst file = sourceMap.file;\n\t\t\t\t\tsourceMap.file = path.posix.join(outputPath, file);\n\t\t\t\t\tthis.options.logcat.sourceMap[path.posix.join(remoteDir, file)] = new SourceMapConsumer(sourceMap);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (!this.options.build) {\n\t\t\treturn;\n\t\t}\n\t\tconst RawSource = compilation.compiler.webpack.sources.RawSource;\n\t\tcompilation.emitAsset(\n\t\t\t\"project.json\",\n\t\t\tnew RawSource(JSON.stringify(project, 0, compilation.options.mode === \"development\" ? 4 : 0)),\n\t\t);\n\t\tlet ui = this.options.build.ui;\n\t\tif (ui) {\n\t\t\tif (!Array.isArray(ui)) {\n\t\t\t\tui = [ui];\n\t\t\t}\n\t\t\tui.forEach(fileName => {\n\t\t\t\tif (typeof fileName !== \"string\") {\n\t\t\t\t\tfileName = project.main;\n\t\t\t\t}\n\t\t\t\tcompilation.updateAsset(\n\t\t\t\t\tfileName,\n\t\t\t\t\t(source) => {\n\t\t\t\t\t\treturn new RawSource(\"\\\"ui\\\";\" + source.source());\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\t}\n\n\tapply (compiler) {\n\t\tcompiler.hooks.thisCompilation.tap(AutojsDeployPlugin.name, (compilation) => {\n\t\t\tcompilation.hooks.processAssets.tapPromise(\n\t\t\t\t{\n\t\t\t\t\tname: AutojsDeployPlugin.name,\n\t\t\t\t\tstage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\treturn this.updateAsset(compilation);\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\t\tcompiler.hooks.done.tapPromise(AutojsDeployPlugin.name, async (stats) => {\n\t\t\tconst compilation = stats.compilation;\n\t\t\ttry {\n\t\t\t\tawait this.deploy(compilation);\n\t\t\t} catch (ex) {\n\t\t\t\tif (ex.cause?.code === \"ENOENT\") {\n\t\t\t\t\tconsole.error(\"Could not find 'adb' in PATH. Please set options.adb of \" + AutojsDeployPlugin.name);\n\t\t\t\t} else {\n\t\t\t\t\tthrow ex;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (compilation.compiler.options.watch) {\n\t\t\t\tawait this.logcat(compilation);\n\t\t\t}\n\t\t});\n\t}\n}\nmodule.exports = AutojsDeployPlugin;\n\nconst { Transform } = require(\"stream\");\nconst styles = require(\"ansi-styles\");\nconst json5 = require(\"json5\");\nclass LogTransform extends Transform {\n\tconstructor () {\n\t\tsuper();\n\t\tthis.colors = {\n\t\t\tV: styles.gray,\n\t\t\tI: styles.green,\n\t\t\tW: styles.yellow,\n\t\t\tE: styles.red,\n\t\t};\n\t\t// \"https://cdn.jsdelivr.net/gh/kkevsekk1/AutoX/autojs/src/main/assets\",\n\t\t// \"https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets\"\n\t\tthis.assets = \"https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets\";\n\t}\n\n\t// 将文件路径转换为source map映射的文件路径\n\ttoSourcePath (options) {\n\t\tconst consumer = this.sourceMap && this.sourceMap[options.file];\n\t\tif (consumer) {\n\t\t\tconst originalPos = consumer.originalPositionFor && consumer.originalPositionFor(options);\n\t\t\tif (originalPos?.source) {\n\t\t\t\toptions.file = originalPos.source;\n\t\t\t\tif (originalPos.name && (!options.name || !(/\\b\\w*E(rror|xception):/.test(options.name) || options.name.includes(originalPos.name)))) {\n\t\t\t\t\toptions.name = originalPos.name;\n\t\t\t\t}\n\t\t\t\toptions.line = originalPos.line;\n\t\t\t\toptions.column = originalPos.column;\n\t\t\t} else {\n\t\t\t\toptions.file = consumer.file || consumer;\n\t\t\t}\n\t\t}\n\t\treturn options;\n\t}\n\n\t// 将日志中的错误信息中的trace统一格式并转换文件路径\n\ttraceFormat (options) {\n\t\tif (options.pos) {\n\t\t\tconst arrPos = options.pos.match(/\\d+/g);\n\t\t\toptions.line = +arrPos[0] || 1;\n\t\t\toptions.column = +arrPos[1] || 0;\n\t\t}\n\t\tlet {\n\t\t\tfile,\n\t\t\tprefix,\n\t\t\tname,\n\t\t\tline,\n\t\t\tcolumn,\n\t\t} = this.toSourcePath(options);\n\t\tif (file.startsWith(\"file:///android_asset/modules/\")) {\n\t\t\tfile = this.assets + file.slice(21) + \"#L\" + line;\n\t\t} else {\n\t\t\tfile = [file, line || \"1\", column || \"0\"].join(\":\");\n\t\t}\n\t\tprefix = prefix || \"\";\n\t\tif (name) {\n\t\t\treturn `${prefix}${name} (${file})`;\n\t\t} else {\n\t\t\treturn `${prefix}${file}`;\n\t\t}\n\t}\n\n\t_transform (string, encoding, callback) {\n\t\tstring = string.toString();\n\t\tif (this.sourceMap) {\n\t\t\tstring = string.replaceAll(\n\t\t\t\t/\\bfile:\\/\\/\\/android_asset(\\/.*?)#(\\d+)/g,\n\t\t\t\t// \"https://cdn.jsdelivr.net/gh/kkevsekk1/AutoX/autojs/src/main/assets/modules/__json2__.js#L493\",\n\t\t\t\t// \"https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets\" + file + \"#L\" + line,\n\t\t\t\t(s, file, line) => this.assets + file + \"#L\" + line,\n\t\t\t).replaceAll(\n\t\t\t\t// 替换以下两种错误日志格式中的文件路径和行号(文件路径和行号带括号)：\n\t\t\t\t// XxxError: error_messarg (/some/path/to/file:69:54)\n\t\t\t\t//     at function_name (/some/path/to/file:69:54)\n\t\t\t\t/^((?:[\\d:.]+\\/[A-Z]:|\\s*at)\\s+)?(.*?)\\s+\\((.*?)((?:[:#]\\d+)+)\\)$/gm,\n\t\t\t\t(s, prefix, name, file, pos) => this.traceFormat({\n\t\t\t\t\tprefix,\n\t\t\t\t\tname,\n\t\t\t\t\tfile,\n\t\t\t\t\tpos,\n\t\t\t\t}),\n\t\t\t).replaceAll(\n\t\t\t\t// 替换以下两种错误日志格式中的文件路径和行号(文件路径和行号不带括号，函数名如果存在、带括号)：\n\t\t\t\t//     at /some/path/to/file:69:54 (function_name)\n\t\t\t\t//     at /some/path/to/file:69:54\n\t\t\t\t/^(\\s*at\\s+)(.*?)((?:[:#]\\d+)+)(?:\\s+\\((.*)\\))?$/gm,\n\t\t\t\t(s, prefix, file, pos, name) => this.traceFormat({\n\t\t\t\t\tprefix,\n\t\t\t\t\tfile,\n\t\t\t\t\tpos,\n\t\t\t\t\tname,\n\t\t\t\t}),\n\t\t\t).replaceAll(\n\t\t\t\t// 替换类似JSON格式的报错\n\t\t\t\t// { [JavaException: message ]\n\t\t\t\t//   fileName: 'file:///android_asset/modules/filename.js',\n\t\t\t\t//   lineNumber: 8848 }`;\n\t\t\t\t/\\{\\s+\\[(.+)\\]([\\s\\S]*?)\\}/gm,\n\t\t\t\t(s, message, jsonBody) => {\n\t\t\t\t\tlet errInfo;\n\t\t\t\t\ttry {\n\t\t\t\t\t/* eslint no-new-func: \"off\" */\n\t\t\t\t\t\terrInfo = json5.parse(`{${jsonBody}}`);\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// return s;\n\t\t\t\t\t}\n\t\t\t\t\tif (!errInfo || !errInfo.fileName || !/\\b\\w*E(rror|xception)/.test(message)) {\n\t\t\t\t\t\treturn s;\n\t\t\t\t\t}\n\t\t\t\t\treturn `${message}\\n${this.traceFormat({\n\t\t\t\t\t\tprefix: \"\\tat \",\n\t\t\t\t\t\tfile: errInfo.fileName,\n\t\t\t\t\t\tline: +errInfo.lineNumber,\n\t\t\t\t\t\tcolumn: +errInfo.columnNumber,\n\t\t\t\t\t})}`;\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (this.colors) {\n\t\t\tlet currColor = null;\n\t\t\tstring = string.replaceAll(/^([\\d:.]+)\\/([A-Z]):\\s/gm, (s, timestamp, level) => {\n\t\t\t\ts = \"\";\n\t\t\t\tconst newColor = this.colors[level] || null;\n\t\t\t\tif (newColor !== currColor) {\n\t\t\t\t\tif (currColor) {\n\t\t\t\t\t\ts += currColor.close;\n\t\t\t\t\t}\n\t\t\t\t\tif (newColor) {\n\t\t\t\t\t\ts += newColor.open;\n\t\t\t\t\t}\n\t\t\t\t\tcurrColor = newColor;\n\t\t\t\t}\n\t\t\t\t// if (this.timestamp) {\n\t\t\t\t// \ts += `${timestamp}/${level}: `;\n\t\t\t\t// }\n\t\t\t\treturn s;\n\t\t\t});\n\t\t\tif (currColor) {\n\t\t\t\tstring += currColor.close;\n\t\t\t\tcurrColor = null;\n\t\t\t}\n\t\t}\n\t\tcallback(null, string);\n\t}\n\n\t_flush (callback) {\n\t\tcallback();\n\t}\n}\n\n// require(\"fs\").createReadStream(\"lot.txt\").pipe(process.output);\n// const fs = require(\"fs\");\n// const logCat = new LogCat();\n// const sourceMap = JSON.parse(\n// \tfs.readFileSync(\"dist/miui_cleaner_app/main.js.map\", \"utf-8\"),\n// );\n\n// let context = process.cwd();\n// let output = path.resolve(\"dist/miui_cleaner_app\");\n// context = path.relative(process.cwd(), context);\n// output = path.relative(process.cwd(), output).replaceAll(path.win32.sep, path.posix.sep);\n// sourceMap.sources = sourceMap.sources.map(file => {\n// \tconst uri = file.match(/^webpack:\\/\\/([^/]+\\/)?(\\.\\/.*)$/);\n// \tif (uri) {\n// \t\treturn path.posix.join(context, uri[2]);\n// \t}\n// \treturn file;\n// });\n// sourceMap.file = path.posix.join(output, \"main.js\");\n// logCat.sourceMap = {\n// \t\"/storage/emulated/0/脚本/com.github.gucong3000.miui.cleaner/main.js\": \"dist/miui_cleaner_app/main.js\" || new SourceMapConsumer(sourceMap),\n// };\n// fs.createReadStream(\"log.txt\").pipe(logCat).pipe(process.stdout);\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n\tsourceType: \"script\",\n\ttargets: {\n\t\trhino: \"1.7.13\",\n\t},\n\tpresets: [\n\t\t\"@babel/preset-env\",\n\t],\n\tplugins: [\n\t\t\"@babel/plugin-transform-runtime\",\n\t\t\"@babel/plugin-syntax-jsx\",\n\t],\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"miui_cleaner\",\n\t\"version\": \"2023.4.23.8\",\n\t\"private\": true,\n\t\"description\": \"MIUI广告清理工具\",\n\t\"scripts\": {\n\t\t\"test\": \"npx eslint *.js src/**/*.js\",\n\t\t\"test:fix\": \"npm run test -- --fix\",\n\t\t\"start\": \"npx webpack --config webpack.config.js --watch --mode=development\",\n\t\t\"build:pack\": \"npx webpack --config webpack.config.js --mode=production\",\n\t\t\"build:pull\": \"adb pull sdcard/脚本/com.github.gucong3000.miui.cleaner/build ./dist/\",\n\t\t\"build:init\": \"sh -c \\\"mkdir -p dist/miui_cleaner_cmd\\\" && node project.js\",\n\t\t\"build\": \"npm run build:init && npm run build:pack\",\n\t\t\"deploy:res\": \"adb push ./res /sdcard/脚本/com.github.gucong3000.miui.cleaner/\",\n\t\t\"deploy\": \"npm run build & npm run deploy:res\",\n\t\t\"dump:ui\": \"adb shell uiautomator dump && (adb shell cat /sdcard/window_dump.xml | xmllint --format - > window_dump.xml) && code window_dump.xml\",\n\t\t\"dump:act\": \"adb shell dumpsys activity activities | grep Hist\",\n\t\t\"dump\": \"npm run dump:ui && npm run dump:act\"\n\t},\n\t\"author\": \"GuCong\",\n\t\"license\": \"MIT\",\n\t\"devDependencies\": {\n\t\t\"@auto.pro/webpack-plugin\": \"^8.13.3\",\n\t\t\"@babel/core\": \"^7.19.1\",\n\t\t\"@babel/plugin-syntax-jsx\": \"^7.18.6\",\n\t\t\"@babel/plugin-transform-runtime\": \"^7.19.1\",\n\t\t\"@babel/preset-env\": \"^7.19.1\",\n\t\t\"@devicefarmer/adbkit\": \"^3.2.3\",\n\t\t\"ansi-styles\": \"^5.2.0\",\n\t\t\"babel-loader\": \"^8.2.5\",\n\t\t\"eslint\": \"^8.24.0\",\n\t\t\"eslint-config-standard\": \"^17.0.0\",\n\t\t\"webpack\": \"^5.74.0\",\n\t\t\"webpack-cli\": \"^4.10.0\",\n\t\t\"wrapper-webpack-plugin\": \"^2.2.2\"\n\t},\n\t\"dependencies\": {\n\t\t\"blob-polyfill\": \"^7.0.20220408\",\n\t\t\"core-js\": \"^3.26.0\",\n\t\t\"debounce\": \"^1.2.1\",\n\t\t\"headers-polyfill\": \"^3.1.2\",\n\t\t\"json5\": \"^2.2.1\",\n\t\t\"pretty-bytes\": \"^5.6.0\"\n\t}\n}\n"
  },
  {
    "path": "project.js",
    "content": "const fs = require(\"fs/promises\");\nconst { spawnSync } = require(\"node:child_process\");\n\n(async () => {\n\tconst [\n\t\tpackageConfig,\n\t\tappConfig,\n\t\tcmd,\n\t\treadme,\n\t] = await Promise.all([\n\t\treadFile(\"package.json\"),\n\t\treadFile(\"src/miui_cleaner_app/project.json\"),\n\t\treadFile(\"src/miui_cleaner_cmd/main.cmd\"),\n\t\treadFile(\"README.md\"),\n\t]);\n\tconst pkgInfo = packageConfig.json;\n\tconst appInfo = appConfig.json;\n\n\tconst date = new Date();\n\tlet versionCode;\n\n\tif (process.env.GITHUB_RUN_NUMBER) {\n\t\tversionCode = process.env.GITHUB_RUN_NUMBER - 0;\n\t} else {\n\t\tconst controller = new AbortController();\n\t\tversionCode = await Promise.any(\n\t\t\t[\n\t\t\t\t\"https://cdn.jsdelivr.net/gh/gucong3000/MiuiCleaner/src/miui_cleaner_app/project.json\",\n\t\t\t\t\"https://raw.fastgit.org/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json\",\n\t\t\t\t\"https://raw.githubusercontent.com/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json\",\n\t\t\t].map(async url => {\n\t\t\t\tconst res = await fetch(url, { signal: controller.signal });\n\t\t\t\tconst data = await res.json();\n\t\t\t\treturn data.versionCode + 1;\n\t\t\t}),\n\t\t);\n\t\tcontroller.abort();\n\t}\n\n\tconst versionName = [\n\t\tdate.getUTCFullYear(),\n\t\tdate.getUTCMonth() + 1,\n\t\tdate.getUTCDate(),\n\t\tversionCode,\n\t].join(\".\");\n\tpkgInfo.version = versionName;\n\tappInfo.versionCode = versionCode;\n\tappInfo.versionName = versionName;\n\tappInfo.launchConfig.splashText = pkgInfo.description;\n\tcmd.constents = cmd.constents.replace(\n\t\t/^title\\s+.*$/im,\n\t\t`title ${appInfo.name} - ${pkgInfo.description}`,\n\t);\n\n\tconst distCmd = fs.writeFile(\n\t\t\"dist/miui_cleaner_cmd/MiuiCleaner.cmd\",\n\t\tspawnSync(\n\t\t\t\"iconv\",\n\t\t\t[\n\t\t\t\t\"--from-code=utf-8\",\n\t\t\t\t\"--to-code=gb18030\",\n\t\t\t],\n\t\t\t{\n\t\t\t\tinput: cmd.constents.replace(\n\t\t\t\t\t/^chcp\\s+\\d+/im, \"chcp 936\",\n\t\t\t\t).replace(\n\t\t\t\t\t/^title\\s+.*$/im,\n\t\t\t\t\t`title ${appInfo.name} - ${pkgInfo.description} -v${pkgInfo.version}`,\n\t\t\t\t).replace(/\\r?\\n/g, \"\\r\\n\"),\n\t\t\t},\n\t\t).stdout,\n\t);\n\tupdateDoc(readme);\n\n\tawait Promise.all([\n\t\tappConfig.update(),\n\t\tpackageConfig.update(),\n\t\tcmd.update(),\n\t\treadme.update(),\n\t\tdistCmd,\n\t]);\n})(\n);\n\nasync function readFile (path) {\n\tlet constents = await fs.readFile(path);\n\tconstents = constents.toString(\"utf-8\");\n\tconst json = /\\.json$/.test(path) && JSON.parse(constents);\n\n\tconst file = {\n\t\tjson,\n\t\tconstents,\n\t\tupdate: (...args) => {\n\t\t\tlet newContents;\n\t\t\tif (file.json) {\n\t\t\t\tnewContents = JSON.stringify(file.json, 0, \"\\t\");\n\t\t\t} else {\n\t\t\t\tnewContents = file.constents;\n\t\t\t}\n\t\t\tconst finalNewline = /\\.(cmd|bat)$/i.test(path) ? \"\\r\\n\" : \"\\n\";\n\t\t\tnewContents = newContents.replace(/\\r?\\n/g, finalNewline);\n\t\t\tif (constents.trim() !== newContents.trim()) {\n\t\t\t\treturn fs.writeFile(path, newContents.trim() + finalNewline, ...args);\n\t\t\t}\n\t\t},\n\t};\n\treturn file;\n}\n\n// 保持文档的描述和关闭广告的单元测试数据一致\nfunction updateDoc (readme) {\n\tconst docResult = [];\n\tconst testCase = require(\"./src/miui_cleaner_app/test/services\").testCase;\n\tdelete testCase[\"关于手机\"];\n\tdelete testCase[\"开发者选项\"];\n\n\tprintTestCase(testCase);\n\n\treadme.constents = readme.constents.replace(/(#+\\s*关闭各应用广告[\\s\\S]*?<\\/summary>[\\s\\S]*?)-[\\s\\S]*(<\\/details>)/, (s, prefix, suffix) => {\n\t\treturn prefix + docResult.join(\"\\n\") + \"\\n\\n\" + suffix;\n\t});\n\n\tfunction printTestCase (data, deep = 0) {\n\t\tfor (const caseName in data) {\n\t\t\tif (data[caseName] === true) {\n\t\t\t\tdocResult.push(\"\\t\".repeat(deep) + \"- \" + caseName + \"：`打开`\");\n\t\t\t} else if (data[caseName] === false) {\n\t\t\t\tdocResult.push(\"\\t\".repeat(deep) + \"- \" + caseName + \"：`关闭`\");\n\t\t\t} else if (\n\t\t\t\ttypeof data[caseName] !== \"object\" ||\n\t\t\tdata[caseName] === null ||\n\t\t\t(caseName === \"广告服务\" && !deep)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tdocResult.push(\"\\t\".repeat(deep) + \"- \" + caseName);\n\t\t\t\tprintTestCase(data[caseName], deep + 1);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/miui_cleaner_app/123pan.js",
    "content": "const jsonParse = require(\"json5/lib/parse\");\nconst fetch = global.fetch || require(\"./fetch\");\nconst atob = global.atob || global.$base64.decode;\n\n// const webView = global.ui && require(\"./webView\");\nlet userAgent;\ntry {\n\tuserAgent = android.webkit.WebSettings.getDefaultUserAgent(context);\n} catch (ex) {\n\tuserAgent = \"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1\";\n}\n\nasync function getFileInfo (url) {\n\turl = parseUrl(url);\n\tconst res = await fetch(\n\t\turl.href,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"accept\": \"text/html\",\n\t\t\t\t\"user-agent\": userAgent,\n\t\t\t},\n\t\t},\n\t);\n\tawait checkResponse(res);\n\tconst html = await res.text();\n\tlet initialProps = html.match(/\\b(window\\.)?g_initialProps\\s*=\\s*(.*);/m);\n\tinitialProps = initialProps && jsonParse(initialProps[2]);\n\treturn parseFileInfo(initialProps.reslist.data.InfoList, url, initialProps);\n}\n\nasync function parseFileInfo (fileInfo, url, initialProps) {\n\tif (Array.isArray(fileInfo)) {\n\t\t// 解析多个结果\n\t\tfileInfo = await Promise.all(fileInfo.map(fileInfo => parseFileInfo(fileInfo, url, initialProps)));\n\t\tif (fileInfo.length === 1) {\n\t\t\tfileInfo = fileInfo[0];\n\t\t}\n\t} else if (fileInfo.Etag || fileInfo.S3KeyFlag) {\n\t\t// 解析单个文件\n\t\tfileInfo.publicPath = initialProps.publicPath;\n\t\tfileInfo.shareKey = initialProps.res.data.ShareKey;\n\t\tfileInfo = new FileInfo(fileInfo);\n\t} else {\n\t\t// 解析文件夹\n\t\tconst api = new URL(`share/get?limit=999&next=1&orderBy=share_id&orderDirection=desc&shareKey=${initialProps.res.data.ShareKey}&ParentFileId=${fileInfo.FileId}&Page=1`, initialProps.publicPath);\n\t\tlet res = await fetch(\n\t\t\tapi.href,\n\t\t\t{\n\t\t\t\theaders: {\n\t\t\t\t\t\"accept\": \"application/json\",\n\t\t\t\t\t\"user-agent\": userAgent,\n\t\t\t\t},\n\t\t\t\treferrerPolicy: \"no-referrer\",\n\t\t\t},\n\t\t);\n\t\tawait checkResponse(res);\n\t\tres = await res.json();\n\t\tfileInfo = await parseFileInfo(res.data.InfoList, url, initialProps);\n\t}\n\treturn fileInfo;\n}\n\nasync function checkResponse (res) {\n\tif (!res.ok) {\n\t\tthrow new Error(`status: ${res.status}\\nmessage: ${res.message || JSON.stringify(await res.text())}\\n    at ${res.url}`);\n\t}\n}\n\nfunction parseUrl (url) {\n\tif (!url.href) {\n\t\turl = new URL(url);\n\t}\n\treturn url;\n}\n\nclass FileInfo {\n\tconstructor (data) {\n\t\tObject.assign(this, data);\n\t}\n\n\tget fileName () {\n\t\treturn this.FileName;\n\t}\n\n\tget size () {\n\t\treturn this.Size;\n\t}\n\n\tget lastModified () {\n\t\treturn Date.parse(this.UpdateAt);\n\t}\n\n\tget id () {\n\t\treturn this.FileId;\n\t}\n\n\tasync getLocation (redirect) {\n\t\tconst data = await getRealFile(this, redirect);\n\t\tObject.assign(this, data);\n\t\treturn this;\n\t}\n}\n\nasync function parse (url, options) {\n\tconst data = await getFileInfo(url, options || {});\n\tif (Array.isArray(data)) {\n\t\treturn data.map(data => new FileInfo(data));\n\t}\n\treturn new FileInfo(data);\n}\n\nasync function getRealFile (fileInfo, redirect) {\n\tlet res = await fetch(\"https://www.123pan.com/a/api/share/download/info\", {\n\t\theaders: {\n\t\t\t\"accept\": \"application/json\",\n\t\t\t\"content-type\": \"application/json;charset=UTF-8\",\n\t\t\t\"user-agent\": userAgent,\n\t\t},\n\t\treferrerPolicy: \"no-referrer\",\n\t\tbody: JSON.stringify({\n\t\t\tShareKey: fileInfo.shareKey,\n\t\t\tFileID: fileInfo.FileId,\n\t\t\tS3keyFlag: fileInfo.S3KeyFlag,\n\t\t\tSize: fileInfo.Size,\n\t\t\tEtag: fileInfo.Etag,\n\t\t}),\n\t\tmethod: \"POST\",\n\t});\n\tawait checkResponse(res);\n\tres = await res.json();\n\tfileInfo.location = decodeURI(atob(new URL(res.data.DownloadURL).searchParams.get(\"params\")));\n\t// if (redirect) {\n\t// \t//\n\t// }\n\treturn fileInfo;\n}\nmodule.exports = parse;\n// (async () => {\n// \tlet file = await parse(\"https://423down.lanzouv.com/tp/iKBGf0hcsq5e\");\n// \tconsole.log(file.url);\n// \tfile = await parse(\"https://423down.lanzouv.com/tp/iKBGf0hcsq5e\");\n// \tconsole.log(file.url);\n// })();\n//\n// getFileInfoFromUrl(\"https://www.123pan.com/s/A6cA-gT9Jh\").then(async file => console.log(await file.getLocation(true)));\n// getFileInfoFromUrl(\"https://www.123pan.com/s/A6cA-dJAJh\").then(file => console.log(file));\n\n// getFileInfoFromUrl(\"https://www.123pan.com/s/ZYAZVv-TBYjd.html\").then(file => console.log(file));\n"
  },
  {
    "path": "src/miui_cleaner_app/appDesc.js",
    "content": "\n// https://gist.github.com/mcxiaoke/0a4c639d04e94c45eb6c787c0f98940a\n// https://fengooge.blogspot.com/2019/03/taking-ADB-to-uninstall-system-applications-in-MIUI-without-root.html\n\nmodule.exports = {\n\t// APP 外置开屏广告\n\t\"com.miui.analytics\": \"广告分析\",\n\t\"com.miui.systemAdSolution\": \"小米广告联盟∑的开屏广告\",\n\t// 桌面广告 APP\n\t\"com.miui.personalassistant\": \"负一屏\",\n\t\"com.mi.android.globalminusscreen\": \"负一屏\",\n\t\"com.miui.smarttravel\": \"智能出行\",\n\t\"com.miui.newhome\": \"趣看看\",\n\t\"com.android.quicksearchbox\": \"桌面搜索框/搜索/全局搜索\",\n\t\"com.google.android.googlequicksearchbox\": \"桌面搜索框（Google）\",\n\t\"com.baidu.searchbox\": \"桌面搜索框（百度）\",\n\t// 过时的 APP\n\t\"com.miui.touchassistant\": \"悬浮球/Quickball\",\n\t\"com.miui.accessibility\": \"听障辅助工具\",\n\t\"com.miui.hybrid.accessory\": \"古早版智能家居\",\n\t// 影音类 APP\n\t\"com.miui.player\": \"QQ音乐简洁版，应替换成ES文件浏览器\",\n\t\"com.miui.videoplayer\": \"Mi Video，应替换成ES文件浏览器\",\n\t\"com.miui.video\": \"小米视频，应替换成ES文件浏览器\",\n\t\"com.tencent.qqlivexiaomi\": \"小米视频插件-腾讯视频小米版\",\n\t\"com.qiyi.video.sdkplayer\": \"小米视频插件-爱奇艺播放器\",\n\t// 天气\n\t\"com.miui.weather2\": \"小米天气，应替换成Holi天气\",\n\t// 支付、电商、理财类 APP\n\t\"com.xiaomi.shop\": \"小米商城\",\n\t\"com.xiaomi.ab\": \"小米商城系统组件/电商助手\",\n\t\"com.mipay.wallet\": \"小米钱包\",\n\t\"com.xiaomi.payment\": \"米币支付\",\n\t\"com.miui.nextpay\": \"小米支付\",\n\t\"com.xiaomi.pass\": \"小米卡包\",\n\t\"com.xiaomi.jr\": \"小米金融/天星金融\",\n\t\"com.xiaomi.jr.security\": \"小米金融/天星金融-安全组件\",\n\t\"com.xiaomi.mifisecurity\": \"小米金服安全组件\",\n\t\"com.unionpay.tsmservice.mi\": \"银联可信服务安全组件小米版\",\n\t// 低使用频率 APP\n\t\"com.miui.huanji\": \"小米换机\",\n\t\"com.xiaomi.vipaccount\": \"小米社区\",\n\t\"com.miui.bugreport\": \"bug反馈\",\n\t\"com.miui.klo.bugreport\": \"KLO bug反馈\",\n\t\"com.miui.miservice\": \"服务与反馈\",\n\t\"com.miui.vipservice\": \"我的服务\",\n\t\"com.mfashiongallery.emag\": \"小米画报\",\n\t\"com.android.wallpaper\": \"动态壁纸\",\n\t\"com.android.wallpaper.livepicker\": \"动态壁纸获取\",\n\t\"com.android.wallpaperbackup\": \"壁纸备份\",\n\t\"com.android.wallpapercropper\": \"壁纸编辑器\",\n\t\"com.miui.fm\": \"收音机/蜻蜓FM\",\n\t\"cn.wps.moffice_eng.xiaomi.lite\": \"WPS Office Lite，应替换成ES文件浏览器\",\n\t\"com.dragon.read\": \"阅读/番茄免费小说\",\n\t\"com.duokan.reader\": \"阅读/多看阅读器\",\n\t\"com.mi.health\": \"小米健康/小米运动健康\",\n\t// 浏览器\n\t\"com.android.browser\": \"小米浏览器\",\n\t\"com.mi.globalbrowser\": \"小米浏览器（国际版）\",\n\t\"com.android.chrome\": \"Chrome\",\n\t// 内置输入法\n\t\"com.baidu.input_mi\": \"百度输入法-小米版\",\n\t\"com.sohu.inputmethod.sogou.xiaomi\": \"搜狗输入法-小米版\",\n\t\"com.iflytek.inputmethod.miui\": \"讯飞输入法-小米版\",\n\t\"com.miui.securityinputmethod\": \"小米安全键盘\",\n\t// 小米游戏中心\n\t\"com.xiaomi.migameservice\": \"// 游戏中心（旧版）\",\n\t\"com.xiaomi.gamecenter\": \"游戏中心\",\n\t\"com.xiaomi.gamecenter.sdk.service\": \"游戏中心-SDK服务\",\n\t\"com.xiaomi.gamecenter.pad\": \"游戏中心-pad版\",\n\t\"com.xiaomi.joyose\": \"云控/温控/记步\",\n\t// SIM 卡应用\n\t\"com.miui.virtualsim\": \"全球上网\",\n\t\"com.xiaomi.mimobile\": \"小米移动\",\n\t\"com.xiaomi.mimobile.cloudsim\": \"小米移动-小米云流量\",\n\t\"com.xiaomi.mimobile.noti\": \"小米移动-全球上网-插件\",\n\t\"com.android.stk\": \"SIM卡应用\",\n\t// 快应用\n\t\"com.miui.quickappCenter.miAppStore\": \"快应用中心/快应用商店\",\n\t\"com.miui.hybrid\": \"快应用服务框架\",\n\t// 语音助手\n\t\"com.miui.voiceassist\": \"小爱语音/小爱同学\",\n\t\"com.miui.voicetrigger\": \"语音唤醒语音助手\",\n\t\"com.xiaomi.scanner\": \"小爱视觉/扫一扫\",\n\t\"com.xiaomi.aiasst.vision\": \"小爱翻译\",\n\t\"com.xiaomi.aiasst.service\": \"小爱通话（AI虚拟助手）\",\n\t// 翻译\n\t\"com.miui.translationservice\": \"MIUI翻译服务\",\n\t\"com.miui.translation.kingsoft\": \"MIUI翻译-金山\",\n\t\"com.miui.translation.xmcloud\": \"MIUI翻译-小米云\",\n\t\"com.miui.translation.youdao\": \"MIUI翻译-有道\",\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/appManager.js",
    "content": "const startActivity = require(\"./startActivity\");\nconst singleChoice = require(\"./singleChoice\");\n\n// https://blog.unidevel.cn/xiao-mi-dian-zi-shu-shang-yi-xie-yin-cang-de-she-zhi/\n\nconst actions = [\n\t{\n\t\tname: \"自启动管理\",\n\t\tsummary: \"自启动及后台运行权限管理\",\n\t\ticon: \"./res/drawable/ic_check_list.png\",\n\t\tpackageName: \"com.miui.securitycenter\",\n\t\tclassName: \"com.miui.permcenter.autostart.AutoStartManagementActivity\",\n\t},\n\t{\n\t\tname: \"通知管理\",\n\t\tsummary: \"通知栏、悬浮提示、图标角标的管理\",\n\t\ticon: \"./res/drawable/ic_item_list.png\",\n\t\tpackageName: \"com.miui.notification\",\n\t\tclassName: \"miui.notification.management.activity.NotificationAppListActivity\",\n\t},\n\t{\n\t\tname: \"APP卸载\",\n\t\tsummary: \"APP的批量卸载\",\n\t\ticon: \"./res/drawable/ic_recovery.png\",\n\t\tpackageName: \"com.miui.cleanmaster\",\n\t\tclassName: \"com.miui.optimizecenter.deepclean.installedapp.InstalledAppsActivity\",\n\t},\n\t{\n\t\tname: \"APP管理\",\n\t\tsummary: \"手机管家的应用管理功能\",\n\t\ticon: \"./res/drawable/ic_settings.png\",\n\t\tpackageName: \"com.miui.securitycenter\",\n\t\tclassName: \"com.miui.appmanager.AppManagerMainActivity\",\n\t},\n\t// {\n\t// \tname: \"应用升级\",\n\t// \tpackageName: \"com.xiaomi.market\",\n\t// \tclassName: \".ui.UpdateListActivity\",\n\t// \tsummary: \"APP的更新管理\",\n\t// },\n\t{\n\t\tname: \"APP信息\",\n\t\tsummary: \"权限管理模块\",\n\t\ticon: \"./res/drawable/ic_key.png\",\n\t\tpackageName: \"com.android.settings\",\n\t\tclassName: \".applications.ManageApplications\",\n\t},\n\t// {\n\t// \tname: \"甜品盒\",\n\t// \tsummary: \"彩蛋\",\n\t// \ticon: \"./res/drawable/ic_android.png\",\n\t// \tpackageName: \"com.android.systemui\",\n\t// \tclassName: \".DessertCase\",\n\t// },\n\t// {\n\t// \tname: \"Marshmallow Land\",\n\t// \tsummary: \"彩蛋\",\n\t// \ticon: \"./res/drawable/ic_android.png\",\n\t// \tpackageName: \"com.android.systemui\",\n\t// \tclassName: \".egg.MLandActivity\",\n\t// },\n].filter(action => (\n\tapp.getAppName(action.packageName)\n));\n\nconst name = \"APP管家\";\nconst icon = \"./res/drawable/ic_phone_settings.png\";\n\nfunction appManager () {\n\tsingleChoice({\n\t\ttitle: name,\n\t\ticon,\n\t\titemList: actions,\n\t\tfn: startActivity,\n\t});\n\trequire(\"./index\")();\n}\n\nmodule.exports = {\n\tname,\n\ticon,\n\tsummary: \"广告相关权限管理\",\n\tfn: appManager,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/dialogs.js",
    "content": "const resString = com.stardust.autojs.R.string;\nconst AlertDialog = android.app.AlertDialog;\n\nconst btnLabelMap = {\n\tpositive: resString.ok,\n\tnegative: resString.cancel,\n\tneutral: \"在浏览器中打开\",\n};\n\nfunction alertDialog (\n\tmessage,\n\toptions,\n) {\n\toptions = {\n\t\tpositive: true,\n\t\tnegative: true,\n\t\tneutral: false,\n\t\tcancelable: false,\n\t\tmessage,\n\t\t...options,\n\t};\n\tconst builder = new AlertDialog.Builder(activity);\n\tconst emitter = options.emitter || events.emitter();\n\n\tfunction createListener (eventKeyName, eventName) {\n\t\tconst listener = {};\n\t\tlistener[`on${eventKeyName}`] = (...args) => {\n\t\t\tconsole.log(\"对话框事件：\", eventName);\n\t\t\temitter.emit(eventName, ...args);\n\t\t};\n\t\treturn listener;\n\t}\n\n\tfunction setAttr (dialog, filter) {\n\t\tlet keys = Object.keys(dialog);\n\t\tif (filter) {\n\t\t\tkeys = keys.filter(filter);\n\t\t}\n\t\tkeys.forEach(key => {\n\t\t\tkey = key.match(/^set?(On)?(\\w+?)(Button|Listener)?$/);\n\t\t\tif (!key) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst setName = key[0];\n\t\t\tconst attrKeyName = key[2];\n\t\t\tconst attrName = attrKeyName.replace(/^\\w/, w => w.toLowerCase());\n\t\t\tconst type = key[3] || attrKeyName;\n\t\t\tlet attrValue;\n\t\t\tif (type === \"Listener\") {\n\t\t\t\tattrValue = createListener(attrKeyName, attrName.replace(/[A-Z]/, w => \"_\" + w.toLowerCase()));\n\t\t\t} else if (attrName in options) {\n\t\t\t\tattrValue = options[attrName];\n\t\t\t\tif (type === \"Button\") {\n\t\t\t\t\tif (attrValue) {\n\t\t\t\t\t\tattrValue = [\n\t\t\t\t\t\t\ttypeof attrValue === \"string\" ? attrValue : btnLabelMap[attrName],\n\t\t\t\t\t\t\tcreateListener(\"Click\", attrName),\n\t\t\t\t\t\t];\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t} else if (type === \"MultiChoiceItems\") {\n\t\t\t\t\tattrValue = [\n\t\t\t\t\t\tattrValue.map(String),\n\t\t\t\t\t\tattrValue.map(Boolean),\n\t\t\t\t\t\tcreateListener(\"Click\", \"multi_choice\"),\n\t\t\t\t\t];\n\t\t\t\t} else if (type === \"Items\") {\n\t\t\t\t\tattrValue = [\n\t\t\t\t\t\tattrValue.map(String),\n\t\t\t\t\t\tcreateListener(\"Click\", \"single_choice\"),\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdialog[setName].apply(dialog, Array.isArray(attrValue) ? attrValue : [attrValue]);\n\t\t});\n\t}\n\n\tif (options.view) {\n\t\tconst frame = ui.inflate(\"<frame padding=\\\"22 0\\\"></frame >\");\n\t\tlet viewList = options.view;\n\t\tviewList = Array.isArray(options.view) ? viewList : [viewList];\n\t\tviewList.forEach(view => {\n\t\t\tif (typeof view === \"string\") {\n\t\t\t\tview = ui.inflate(view, frame);\n\t\t\t}\n\t\t\tframe.addView(view);\n\t\t});\n\t\toptions.view = frame;\n\t}\n\n\tsetAttr(builder);\n\tui.post(() => {\n\t\tconst dialog = builder.create();\n\t\tsetAttr(dialog, attrName => !builder[attrName]);\n\t\tdialog.show();\n\t\tconsole.log(`对话框：“${options.message || options.title}”`);\n\t\treturn dialog;\n\t}, 1);\n\treturn {\n\t\temitter,\n\t\tthen: (...args) => {\n\t\t\treturn new Promise(resolve => {\n\t\t\t\tfunction call (...args) {\n\t\t\t\t\tui.post(() => {\n\t\t\t\t\t\tresolve(...args);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\temitter.once(\"positive\", () => { call(true); });\n\t\t\t\temitter.once(\"negative\", () => { call(false); });\n\t\t\t\temitter.once(\"neutral\", () => { call(null); });\n\t\t\t\temitter.once(\"cancel\", () => { call(); });\n\t\t\t\temitter.once(\"single_choice\", (dialog, index) => {\n\t\t\t\t\tcall(options.items[index]);\n\t\t\t\t});\n\t\t\t}).then(...args);\n\t\t},\n\t};\n}\n\nfunction confirm (\n\tmessage,\n\toptions,\n) {\n\treturn alertDialog(\n\t\tmessage,\n\t\t{\n\t\t\t...options,\n\t\t},\n\t);\n}\n\nfunction alert (\n\tmessage,\n\toptions,\n) {\n\treturn alertDialog(\n\t\tmessage,\n\t\t{\n\t\t\tnegative: false,\n\t\t\t...options,\n\t\t},\n\t).then(() => {});\n}\n\nfunction prompt (\n\tmessage,\n\tvalue,\n\toptions,\n) {\n\tconst view = options.view || ui.inflate(`<input text=\"${value || \"\"}\" />`);\n\treturn alertDialog(\n\t\tmessage,\n\t\t{\n\t\t\tview,\n\t\t\t...options,\n\t\t},\n\t).then(result => {\n\t\tif (result) {\n\t\t\treturn view.getText().toString();\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t});\n}\n\nfunction singleChoice (\n\titems,\n\toptions,\n) {\n\treturn alertDialog(\n\t\tnull,\n\t\t{\n\t\t\titems,\n\t\t\tpositive: false,\n\t\t\t// negative: false,\n\t\t\t...options,\n\t\t},\n\t);\n}\n\nmodule.exports = Object.assign(alertDialog, {\n\tconfirm,\n\talert,\n\tprompt,\n\tsingleChoice,\n});\n"
  },
  {
    "path": "src/miui_cleaner_app/downApp.js",
    "content": "const getApplicationInfo = require(\"./getApplicationInfo\");\nconst getRemoteFileInfo = require(\"./getRemoteFileInfo\");\nconst singleChoice = require(\"./singleChoice\");\nconst prettyBytes = require(\"pretty-bytes\");\nconst downFile = require(\"./downFile\");\nconst dialogs = require(\"./dialogs\");\n\n// https://github.abskoop.workers.dev/\n// http://fastgit.org/\n// https://download.fastgit.org/skylot/jadx/releases/download/v1.4.4/jadx-gui-1.4.4-no-jre-win.exe\n// https://download.fastgit.org/MrIkso/ArscEditor/releases/download/1.0.2/ArscEditor-1.0.2.zip\n\nconst appList = [\n\t{\n\t\tname: \"李跳跳\",\n\t\tsummary: \"干净小巧的广告自动跳过工具\",\n\t\ticon: \"https://litiaotiao.cn/apple-touch-icon.png\",\n\t\tpackageName: \"hello.litiaotiao.app\",\n\t\turl: \"https://www.123pan.com/s/ZYAZVv-TBYjd\",\n\t\tfilter: function (files) {\n\t\t\treturn files.filter(file => {\n\t\t\t\treturn /李跳跳|MissLee/.test(file.fileName) && !file.fileName.includes(\"真实好友\");\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"QQ音乐简洁版\",\n\t\tsummary: \"MIUI音乐APP套壳的产品\",\n\t\ticon: \"https://m.32r.com/logo/210807/202108070906595774.png\",\n\t\tpackageName: \"com.tencent.qqmusiclite\",\n\t\turl: \"https://www.coolapk.com/apk/com.tencent.qqmusiclite\",\n\t},\n\t{\n\t\tname: \"Edge\",\n\t\tsummary: \"浏览器，微软出品，带广告屏蔽功能\",\n\t\ticon: \"https://edgefrecdn.azureedge.net/welcome/static/favicon.png\",\n\t\tpackageName: \"com.microsoft.emmx\",\n\t\turl: \"https://app.mi.com/details?id=com.microsoft.emmx\",\n\t},\n\t{\n\t\tname: \"小米浏览器\",\n\t\tsummary: \"国际版\",\n\t\ticon: \"https://m.32r.com/logo/210519/202105191427372351.png\",\n\t\tpackageName: \"com.mi.globalbrowser\",\n\t\turl: \"https://wwm.lanzoul.com/tp/idzsf0bh062h\",\n\t},\n\t{\n\t\tname: \"讯飞输入法\",\n\t\tsummary: \"定制版、Google Play版\",\n\t\ticon: \"https://srf.xunfei.cn/favicon.ico\",\n\t\tpackageName: \"com.iflytek.inputmethod\",\n\t\t// url: \"https://app.meizu.com/apps/public/detail?package_name=com.iflytek.inputmethod\",\n\t\t// url: \"https://m.32r.com/app/7401.html\",\n\t\turl: \"https://firepx.lanzoul.com/b00vf92jc#pwd=647w\",\n\t},\n\t// {\n\t// \tname: \"软件包安装程序\",\n\t// \tsummary: \"Google版\",\n\t// \tpackageName: \"com.google.android.packageinstaller\",\n\t// \ticon: \"https://file.1xiazai.net/d/file/android/20220728/202266164286724.png\",\n\t// \turl: {\n\t// \t\t// 3.01 MB 版本号 未知 适用于安卓 13 SDK 33\n\t// \t\t33: \"https://www.123pan.com/s/OZe0Vv-iOKl3\",\n\t// \t\t// 3.14 MB 版本号 12-7567768 适用于安卓 12 SDK 31\n\t// \t\t31: \"https://www.123pan.com/s/OZe0Vv-LOKl3\",\n\t// \t\t// 3.13 MB 版本号 11-7532981 适用于安卓 11 SDK 30\n\t// \t\t30: \"https://www.123pan.com/s/OZe0Vv-zOKl3\",\n\t// \t\t// 1.83 MB 版本号 10-7029319 适用于安卓 10 SDK 29\n\t// \t\t29: \"https://www.123pan.com/s/OZe0Vv-tOKl3\",\n\t// \t\t// 8.55 MB 版本号 9-7126274 适用于安卓 9 SDK 28\n\t// \t\t28: \"https://www.123pan.com/s/OZe0Vv-qOKl3\",\n\t// \t}[device.sdkInt],\n\t// },\n\t{\n\t\tname: \"应用包管理组件\",\n\t\tsummary: \"MIUI软件包安装程序v3.8.0，不含“纯净模式”\",\n\t\ticon: \"http://pic.danji100.com/upload/2022-4/20224261118377118.png\",\n\t\tpackageName: \"com.miui.packageinstaller\",\n\t\turl: \"https://zisu.lanzoum.com/tp/iI7LGwn5xjc\",\n\t\tfilter: function (files) {\n\t\t\tfiles = files.map(file => {\n\t\t\t\tconst miuiInst = file.fileName.match(/(应用包管理组件).*?([\\d.]+)-(\\d+).*?(\\.\\w+)$/);\n\t\t\t\tif (miuiInst) {\n\t\t\t\t\tconst appName = miuiInst[1];\n\t\t\t\t\tconst versionCode = Number.parseInt(miuiInst[2].replace(/\\./g, \"\"), 10);\n\t\t\t\t\tconst versionName = `${Array.from(String(versionCode)).join(\".\")}-${miuiInst[3]}`;\n\t\t\t\t\tfile.fileName = `${appName}_v${versionName}${miuiInst[4]}`;\n\t\t\t\t\tfile.versionName = versionName;\n\t\t\t\t\tfile.versionCode = versionCode;\n\t\t\t\t\tconsole.log(file);\n\t\t\t\t}\n\t\t\t\treturn file;\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"几何天气\",\n\t\tsummary: \"干净、小巧、漂亮、功能多\",\n\t\ticon: \"https://raw.fastgit.org/WangDaYeeeeee/GeometricWeather/master/app/src/main/res/drawable/ic_launcher.png\",\n\t\tpackageName: \"wangdaye.com.geometricweather\",\n\t\turl: \"https://github.com/WangDaYeeeeee/GeometricWeather/releases/latest\",\n\t\tfilter: function (files) {\n\t\t\tconst appInfo = this;\n\t\t\tfiles = files.filter(file => {\n\t\t\t\tconst verInfo = file.url.match(/\\/(.+?)\\/.*?\\.\\1_(\\w+)\\.\\w+$/);\n\t\t\t\tif (verInfo) {\n\t\t\t\t\tconst verName = verInfo[1];\n\t\t\t\t\tconst verType = verInfo[2];\n\t\t\t\t\tfile.versionName = `${verName}_${verType}`;\n\t\t\t\t\tfile.versionCode = Number.parseInt(verName.replace(/\\./, \"\"), 10);\n\t\t\t\t}\n\t\t\t\treturn verInfo;\n\t\t\t});\n\n\t\t\tif (appInfo.appName) {\n\t\t\t\tlet subVer = appInfo.getVersionName().match(/_\\w+$/);\n\t\t\t\tif (subVer) {\n\t\t\t\t\tsubVer = subVer[0] + \".apk\";\n\t\t\t\t\treturn files.filter(file => file.fileName.endsWith(subVer));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn [files[files.length - 1]];\n\t\t},\n\t},\n\t{\n\t\tname: \"ES文件浏览器\",\n\t\tsummary: \"去广告版，替代MIUI视频、音乐、文档查看器\",\n\t\ticon: \"https://m.32r.com/logo/220311/202203111728435421.png\",\n\t\tpackageName: \"com.estrongs.android.pop\",\n\t\turl: \"https://423down.lanzouv.com/b0f1d7s2h\",\n\t},\n\t{\n\t\tname: \"WPS Office Lite\",\n\t\tsummary: \"国际版，无广告，替代“文档查看器”\",\n\t\ticon: \"https://m.32r.com/logo/220908/202209081617517363.png\",\n\t\tpackageName: \"cn.wps.moffice_i18n\",\n\t\turl: \"https://m.32r.com/app/109976.html\",\n\t},\n\t{\n\t\tname: \"知乎\",\n\t\tsummary: \"集成“知了”，“设置→知了”中有去广告开关\",\n\t\ticon: \"https://static.zhihu.com/heifetz/assets/apple-touch-icon-60.8f6c52aa.png\",\n\t\tpackageName: \"com.zhihu.android\",\n\t\turl: \"https://www.123pan.com/s/A6cA-dJAJh\",\n\t\t// url: \"https://423down.lanzouo.com/b0f2lkafe\",\n\t\t// url: \"https://m.32r.com/app/80966.html\",\n\t\t// https://www.423down.com/11775.html\n\t\tfilter: function (files) {\n\t\t\treturn files.filter(file => {\n\t\t\t\treturn /知乎.*知了/.test(file.fileName);\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"哔哩哔哩\",\n\t\tsummary: \"“设置→哔哩漫游→关于版本”点五下有惊喜\",\n\t\ticon: \"https://m.32r.com/logo/221114/202211141125334046.png\",\n\t\tpackageName: \"tv.danmaku.bili\",\n\t\t// url: \"https://www.123pan.com/s/A6cA-gT9Jh\",\n\t\turl: \"https://423down.lanzouv.com/b0f1gksne\",\n\t\t// https://www.423down.com/12235.html\n\t\tfilter: function (files) {\n\t\t\treturn files.filter(file => {\n\t\t\t\treturn /哔哩哔哩.*漫游/.test(file.fileName);\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"优酷视频\",\n\t\tsummary: \"去广告版\",\n\t\ticon: \"https://img.alicdn.com/tfs/TB1WeJ9Xrj1gK0jSZFuXXcrHpXa-195-195.png\",\n\t\tpackageName: \"com.youku.phone\",\n\t\turl: \"https://423down.lanzouv.com/b0f1avpib\",\n\t\tfilter: function (files) {\n\t\t\treturn files.filter(file => {\n\t\t\t\tfile.fileName = file.fileName.replace(/忧(?=酷)/g, \"优\");\n\t\t\t\treturn file.fileName.includes(\"优酷视频\");\n\t\t\t});\n\t\t},\n\t},\n\t{\n\t\tname: \"高德地图\",\n\t\tsummary: \"Google版、纯净版\",\n\t\ticon: \"https://m.amap.com/img/screenLogo.png\",\n\t\tpackageName: \"com.autonavi.minimap\",\n\t\turl: \"https://423down.lanzouv.com/b0f29j15c\",\n\t},\n\t{\n\t\tname: \"百度贴吧\",\n\t\tsummary: \"去广告版\",\n\t\ticon: \"https://m.32r.com/logo/210810/202108101711331977.png\",\n\t\tpackageName: \"com.baidu.tieba\",\n\t\turl: \"https://423down.lanzouv.com/b0f1b6q8d\",\n\t},\n\t{\n\t\tname: \"酷安\",\n\t\tsummary: \"应用商店，去广告版\",\n\t\ticon: \"https://static.coolapk.com/static/web/v8/images/header-logo.png\",\n\t\tpackageName: \"com.coolapk.market\",\n\t\turl: \"https://423down.lanzouv.com/b0f2uzq2b\",\n\t},\n\t{\n\t\tname: \"App分享\",\n\t\tsummary: \"应用商店，刷机包，国际版提取的APP\",\n\t\ticon: \"http://pic.xfdown.com/uploads/2022-5/2022551511344265.png\",\n\t\tpackageName: \"info.muge.appshare\",\n\t\turl: \"https://423down.lanzouv.com/tp/iHmmD06tw9xa\",\n\t},\n];\n\nfunction formatSize (number, options) {\n\tif (!number || !Number.isSafeInteger(number)) {\n\t\treturn number;\n\t}\n\treturn prettyBytes(number, {\n\t\tbinary: true,\n\t\t...options,\n\t});\n}\n\nfunction formatDate (number) {\n\tif (!number || !Number.isSafeInteger(number)) {\n\t\treturn number;\n\t}\n\tconst dateFormat = android.text.format.DateFormat.getDateFormat(activity);\n\treturn dateFormat.format(number) || number;\n}\n\nasync function download (appInfo, item) {\n\tif (typeof appInfo === \"string\") {\n\t\tappInfo = appList.find(info => info.packageName === appInfo);\n\t}\n\tif (/^\\w+:\\/\\/app.mi.com\\//i.test(appInfo.url)) {\n\t\tapp.startActivity({\n\t\t\taction: \"android.intent.action.VIEW\",\n\t\t\tdata: \"market://details?id=\" + appInfo.packageName,\n\t\t});\n\t\treturn;\n\t}\n\tconst View = android.view.View;\n\tlet progress = item.progress;\n\tif (progress) {\n\t\tprogress.setVisibility(View.VISIBLE);\n\t\tprogress.indeterminate = true;\n\t} else {\n\t\tprogress = ui.inflate(`\n\t\t\t<progressbar id=\"progress\" indeterminate=\"true\" layout_centerHorizontal=\"true\" layout_alignParentBottom=\"true\" w=\"*\" h=\"auto\"style=\"@style/Base.Widget.AppCompat.ProgressBar.Horizontal\" />\n\t\t`, item, true);\n\t}\n\tfunction hideProgress () {\n\t\t// console.log(progress);\n\t\tprogress.setVisibility(View.GONE);\n\t\t// item.removeView(progress);\n\t\t// item.invalidate();\n\t\t// progress.invalidate();\n\t}\n\tlet file;\n\tlet getLocationTask;\n\tfunction getLocation () {\n\t\tif (file && file.getLocation) {\n\t\t\tgetLocationTask = file.getLocation(true);\n\t\t}\n\t};\n\ttry {\n\t\tfile = await getRemoteFiles(appInfo);\n\t\tif (file.length > 1) {\n\t\t\tconst choice = await dialogs.singleChoice(file.map(file => ({\n\t\t\t\ttoString: () => file.fileName + \"\\n\" + [\n\t\t\t\t\tfile.versionName,\n\t\t\t\t\tformatSize(file.size),\n\t\t\t\t\tformatDate(file.lastModified),\n\t\t\t\t].filter(Boolean).join(\" | \"),\n\t\t\t\tfile,\n\t\t\t})), {\n\t\t\t\ttitle: `请选择要下载的“${appInfo.appName || appInfo.name}”版本`,\n\t\t\t\tneutral: true,\n\t\t\t});\n\t\t\tfile = choice && choice.file;\n\t\t\tgetLocation();\n\t\t} else {\n\t\t\tfile = file[0];\n\t\t\tgetLocation();\n\t\t\tconst localVer = appInfo.appName && appInfo.getVersionName();\n\t\t\tconst confirm = await dialogs.confirm([\n\t\t\t\tfile.versionName && `版本：${(localVer ? `${localVer} → ` : \"\") + file.versionName}`,\n\t\t\t\tfile.size && `大小：${formatSize(file.size)}`,\n\t\t\t\tfile.lastModified && `日期：${formatDate(file.lastModified)}`,\n\t\t\t].filter(Boolean).join(\"\\n\"), {\n\t\t\t\ttitle: `是否${appInfo.appName ? \"更新\" : \"下载\"}“${appInfo.appName || appInfo.name}”？`,\n\t\t\t\tneutral: true,\n\t\t\t});\n\t\t\tfile = confirm && file;\n\t\t}\n\t} catch (ex) {\n\t\tconsole.error(ex);\n\t\tfile = null;\n\t}\n\n\tif (file) {\n\t\tawait getLocationTask;\n\t\tconst downTask = downFile(file);\n\t\tdownTask.on(\"progress\", (e) => {\n\t\t\tprogress.indeterminate = false;\n\t\t\tprogress.max = e.size;\n\t\t\tprogress.progress = e.progress;\n\t\t});\n\t\tconst intent = await downTask;\n\t\tconst confirm = intent.getPackage() || (await dialogs.confirm(`“${file.fileName}”下载完毕，立即安装？`, {\n\t\t\ttitle: \"确认安装\",\n\t\t}));\n\t\tif (confirm) {\n\t\t\tapp.startActivity(intent);\n\t\t}\n\t} else {\n\t\tif (file === null && appInfo.url) {\n\t\t\tapp.openUrl(appInfo.url);\n\t\t}\n\t}\n\thideProgress();\n}\n\nfunction verCompare (verA, verB) {\n\tfunction splitVer (versionName) {\n\t\treturn versionName.replace(/^\\D+|\\D+$/g, \"\").split(/\\./g);\n\t}\n\tfunction parseNum (str) {\n\t\treturn Number.parseInt(str, 10) || 0;\n\t}\n\tverA = splitVer(verA);\n\tverB = splitVer(verB);\n\tconst length = Math.max(verA.length, verB.length);\n\tlet result;\n\tfor (let i = 0; i < length && !result; i++) {\n\t\tresult = parseNum(verA[i]) - parseNum(verB[i]);\n\t}\n\treturn result;\n}\n\nfunction fileCompare (b, a) {\n\tlet result;\n\tif (a.versionCode && b.versionCode) {\n\t\tresult = a.versionCode - b.versionCode;\n\t}\n\tif (a.versionName && b.versionName) {\n\t\tresult = result || verCompare(a.versionName, b.versionName);\n\t}\n\tif (a.lastModified && b.lastModified) {\n\t\tresult = result || a.lastModified - b.lastModified;\n\t}\n\treturn result;\n}\n\nfunction getRemoteFiles (appInfo) {\n\treturn getRemoteFileInfo(appInfo.url).then(fileList => {\n\t\tif (!fileList) {\n\t\t\treturn;\n\t\t}\n\t\tif (!Array.isArray(fileList)) {\n\t\t\tfileList = [fileList];\n\t\t}\n\t\tif (appInfo.filter) {\n\t\t\tfileList = appInfo.filter(fileList) || fileList;\n\t\t} else if (fileList.length > 1) {\n\t\t\tfileList = fileList.filter(file => file.fileName.includes(appInfo.name));\n\t\t}\n\t\tfileList = fileList.sort(fileCompare);\n\t\tif (fileList.length > 1 && fileList[0].versionName) {\n\t\t\tfileList = fileList.filter(file => file.versionName === fileList[0].versionName);\n\t\t}\n\t\tif (fileList.length > 1) {\n\t\t\tconst mouse = fileList.find(file => /耗/.test(file.fileName));\n\t\t\tif (mouse) {\n\t\t\t\tfileList = [mouse];\n\t\t\t}\n\t\t}\n\t\treturn fileList;\n\t});\n}\n\nfunction downApp () {\n\tappList.forEach((appInfo) => {\n\t\tgetApplicationInfo(appInfo);\n\t\tif (appInfo.appName) {\n\t\t\tappInfo.displayName = appInfo.appName + \" v\" + appInfo.getVersionName();\n\t\t\t// if (!/^\\w+:\\/\\/app.mi.com\\//i.test(appInfo.url)) {\n\t\t\t// \tgetRemoteFiles(appInfo);\n\t\t\t// }\n\t\t} else {\n\t\t\tdelete appInfo.displayName;\n\t\t}\n\t});\n\tsingleChoice({\n\t\ttitle: \"请选择要下载的APP\",\n\t\titemList: appList,\n\t\tfn: download,\n\t});\n\trequire(\"./index\")();\n}\n// downApp.download = downApp;\nmodule.exports = {\n\tname: \"去广告APP\",\n\tsummary: \"各APP的去广告版和广告自动跳过工具\",\n\ticon: \"./res/drawable/ic_download.png\",\n\tfn: downApp,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/downFile.js",
    "content": "const DownloadManager = android.app.DownloadManager;\nconst Cursor = android.database.Cursor;\nconst Intent = android.content.Intent;\n\nconst downloadManager = context.getSystemService(context.DOWNLOAD_SERVICE);\nconst mimeTypeMap = android.webkit.MimeTypeMap.getSingleton();\nconst emitter = events.emitter();\n\nfunction getValOfCursor (cursor, columnName, columnType) {\n\tlet columnIndex = cursor.getColumnIndex(columnName);\n\tif (columnIndex < 0) {\n\t\tcolumnName = DownloadManager[\"COLUMN_\" + columnName] || DownloadManager[columnName];\n\t\tcolumnIndex = cursor.getColumnIndex(columnName);\n\t}\n\tif (columnIndex < 0) {\n\t\treturn;\n\t}\n\tif (!columnType) {\n\t\tswitch (cursor.getType(columnIndex)) {\n\t\t\tcase Cursor.FIELD_TYPE_INTEGER:\n\t\t\t\tcolumnType = \"Long\";\n\t\t\t\tbreak;\n\t\t\tcase Cursor.FIELD_TYPE_FLOAT:\n\t\t\t\tcolumnType = \"Float\";\n\t\t\t\tbreak;\n\t\t\tcase Cursor.FIELD_TYPE_STRING:\n\t\t\t\tcolumnType = \"String\";\n\t\t\t\tbreak;\n\t\t\tcase Cursor.FIELD_TYPE_BLOB:\n\t\t\t\tcolumnType = \"Blob\";\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn cursor[`get${columnType}`](columnIndex);\n}\n\nfunction queryDownList (callback, query) {\n\tconst cursor = downloadManager.query(query || new DownloadManager.Query());\n\tconst valueOf = getValOfCursor.bind(cursor, cursor);\n\tlet result;\n\tif (cursor) {\n\t\tif (cursor.moveToFirst()) {\n\t\t\tdo {\n\t\t\t\tif ((result = callback(valueOf))) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} while (cursor.moveToNext());\n\t\t}\n\t\tcursor.close();\n\t}\n\treturn result;\n}\n\nfunction guessFileName (disposition) {\n\tif (disposition) {\n\t\tconst fileName = disposition.match(/(^|;)\\s*filename\\*?\\s*=\\s*(UTF-8(''|\\/))?(.*?)(;|\\s|$)/i);\n\t\treturn fileName && decodeURI(fileName[4]);\n\t}\n}\n\nfunction readConfig (options) {\n\t// let disposition;\n\t// if (options.headers) {\n\t// \tObject.keys(options.headers).forEach(key => {\n\t// \t\tswitch (key.toLowerCase()) {\n\t// \t\t\tcase \"content-disposition\": {\n\t// \t\t\t\tdisposition = options.headers[key];\n\t// \t\t\t\tbreak;\n\t// \t\t\t}\n\t// \t\t\tcase \"content-length\": {\n\t// \t\t\t\toptions.size = +options.headers[key];\n\t// \t\t\t\tbreak;\n\t// \t\t\t}\n\t// \t\t\tcase \"content-type\": {\n\t// \t\t\t\tif (options.headers[key] !== \"application/octet-stream\") {\n\t// \t\t\t\t\toptions.mimeType = options.headers[key];\n\t// \t\t\t\t}\n\t// \t\t\t\tbreak;\n\t// \t\t\t}\n\t// \t\t}\n\t// \t});\n\t// }\n\toptions.location = decodeURI(options.location || options.url);\n\tif (!options.fileName) {\n\t\toptions.fileName = (guessFileName(options.disposition) || android.webkit.URLUtil.guessFileName(options.location, null, null)).replace(/_(Coolapk|\\d+)(?=\\.\\w+$)/i, \"\");\n\t}\n\tif (!options.mimeType || /^application\\/octet-stream$/.test(options.mimeType)) {\n\t\toptions.mimeType = mimeTypeMap.getMimeTypeFromExtension(files.getExtension(options.fileName));\n\t}\n\treturn options;\n}\n\nfunction downFile (options) {\n\toptions = readConfig(options);\n\tlet downId;\n\tlet complete;\n\tconst downEmitter = Object.create(events.emitter());\n\n\tconst promise = new Promise((resolve, reject) => {\n\t\tdownEmitter.on(\"complete\", resolve);\n\t\t// downEmitter.on(\"cancel\", reject);\n\t\t// downEmitter.on(\"error\", reject);\n\t});\n\n\tfunction emitProgressEvent (progressEvent) {\n\t\tif (!progressEvent.size) {\n\t\t\tprogressEvent.size = options.size;\n\t\t}\n\t\tdownEmitter.emit(\"progress\", progressEvent);\n\t}\n\n\tfunction emitCompleteEvent (intent) {\n\t\tif (!intent) {\n\t\t\tintent = new Intent();\n\t\t\tintent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downId);\n\t\t}\n\t\tintent.setDataAndType(\n\t\t\tdownloadManager.getUriForDownloadedFile(downId),\n\t\t\toptions.mimeType,\n\t\t);\n\t\tintent.setAction(Intent.ACTION_VIEW);\n\t\tintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n\t\tintent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n\t\tconst googleInstaller = \"com.google.android.packageinstaller\";\n\n\t\tintent.setPackage(app.getAppName(googleInstaller) ? googleInstaller : null);\n\n\t\tdownEmitter.emit(\"complete\", intent);\n\t\tdownEmitter.removeAllListeners();\n\t\tthrottle[downId] = null;\n\t\tcomplete = true;\n\t}\n\n\tfunction startTask () {\n\t\tqueryDownList((valueOf) => {\n\t\t\tif (options.location === valueOf(\"URI\")) {\n\t\t\t\tdownId = valueOf(\"ID\");\n\t\t\t} else {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (valueOf(\"STATUS\")) {\n\t\t\t\tcase DownloadManager.STATUS_SUCCESSFUL: {\n\t\t\t\t\temitCompleteEvent();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase DownloadManager.STATUS_PAUSED:\n\t\t\t\tcase DownloadManager.STATUS_FAILED: {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tapp.launchPackage(\"com.android.providers.downloads.ui\");\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tapp.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));\n\t\t\t\t\t}\n\t\t\t\t\t// falls through\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\tdownEmitter.emit(\"start\", downId);\n\t\t\t\t\temitProgressEvent(createProgressEvent(valueOf));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\tif (!downId) {\n\t\t\tconst request = new DownloadManager.Request(android.net.Uri.parse(options.location));\n\t\t\trequest.addRequestHeader(\"User-Agent\", options.userAgent || android.webkit.WebSettings.getDefaultUserAgent(context));\n\t\t\tif (options.referer) {\n\t\t\t\trequest.addRequestHeader(\"Referer\", options.referer);\n\t\t\t}\n\t\t\t// request.setDestinationInExternalPublicDir(android.os.Environment.DIRECTORY_DOWNLOADS, options.fileName);\n\t\t\trequest.setDestinationInExternalFilesDir(context, android.os.Environment.DIRECTORY_DOWNLOADS, options.fileName);\n\t\t\trequest.setMimeType(options.mimeType);\n\t\t\tconsole.log(\"开始下载：\", options);\n\t\t\tdownId = downloadManager.enqueue(request);\n\t\t\tdownEmitter.emit(\"start\", downId);\n\t\t}\n\n\t\temitter.on(`${downId}.click`, (...args) => downEmitter.emit(\"click\", ...args));\n\n\t\tif (!complete) {\n\t\t\temitter.on(`${downId}.progress`, emitProgressEvent);\n\t\t\temitter.once(`${downId}.complete`, (...args) => {\n\t\t\t\tconsole.log(\"下载完毕：\", options);\n\t\t\t\temitter.removeAllListeners(`${downId}.complete`);\n\t\t\t\temitter.removeAllListeners(`${downId}.progress`);\n\t\t\t\temitCompleteEvent(...args);\n\t\t\t\t// emitter.removeAllListeners(`${downId}.click`);\n\t\t\t});\n\t\t\tstartDownReceiver();\n\t\t}\n\t}\n\tdownEmitter.then = (...args) => promise.then(...args);\n\tsetTimeout(startTask, 0);\n\treturn downEmitter;\n}\n\nfunction registerReceiver (sysActionName, onReceive) {\n\tcontext.registerReceiver(\n\t\tnew JavaAdapter(android.content.BroadcastReceiver, {\n\t\t\tonReceive,\n\t\t}),\n\t\tnew android.content.IntentFilter(sysActionName),\n\t);\n}\n\nregisterReceiver(DownloadManager.ACTION_DOWNLOAD_COMPLETE, (context, intent) => {\n\temitter.emit(`${intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)}.complete`, intent);\n});\n\nregisterReceiver(DownloadManager.ACTION_NOTIFICATION_CLICKED, (context, intent) => {\n\tintent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS).forEach(downId => {\n\t\temitter.emit(`${downId}.click`, intent);\n\t});\n});\n\nfunction createProgressEvent (valueOf) {\n\t// 已经下载文件大小\n\tconst progress = valueOf(\"BYTES_DOWNLOADED_SO_FAR\");\n\tconst speed = valueOf(\"downloading_current_speed\");\n\t// 下载文件的总大小\n\tconst size = valueOf(\"TOTAL_SIZE_BYTES\");\n\treturn {\n\t\tprogress,\n\t\tspeed: speed >= 0 ? speed : null,\n\t\tsize,\n\t};\n}\n\nlet downStatus = false;\nlet throttle = {};\n\nfunction downReceiver () {\n\tlet running;\n\tqueryDownList(valueOf => {\n\t\tif (valueOf(\"STATUS\") === DownloadManager.ACTION_DOWNLOAD_COMPLETE) {\n\t\t\treturn;\n\t\t}\n\t\tconst downId = valueOf(\"ID\");\n\t\tconst progressEvent = createProgressEvent(valueOf);\n\t\tif (progressEvent.progress > 0 || progressEvent.size > 0) {\n\t\t\tconst key = JSON.stringify(progressEvent);\n\t\t\tif (throttle[downId] !== key) {\n\t\t\t\temitter.emit(`${downId}.progress`, progressEvent);\n\t\t\t\tthrottle[downId] = key;\n\t\t\t}\n\t\t}\n\t\trunning = true;\n\t});\n\tif (running) {\n\t\tsetTimeout(downReceiver, 0x200);\n\t} else {\n\t\tthrottle = {};\n\t}\n\tdownStatus = running || false;\n}\nfunction startDownReceiver () {\n\tif (!downStatus) {\n\t\tdownReceiver();\n\t}\n}\n\ndownFile.queryDownList = queryDownList;\nmodule.exports = downFile;\n"
  },
  {
    "path": "src/miui_cleaner_app/emitItemShowEvent.js",
    "content": "const debounce = require(\"debounce\");\nconst Rect = android.graphics.Rect;\nconst inNightMode = Boolean(activity.getApplicationContext().getResources().getConfiguration().uiMode & android.content.res.Configuration.UI_MODE_NIGHT_YES);\nfunction emitItemShowEvent (listView, defaultIcon) {\n\tconst itemList = new Map();\n\tlistView.on(\"item_bind\", function (itemView, itemHolder) {\n\t\titemList.set(itemView, itemHolder);\n\t\tsetTimeout(() => {\n\t\t\tlistView.emit(\"item_show\", itemHolder.item, itemView, listView);\n\t\t\tif (itemHolder.item.loadIcon || (itemHolder.item.icon && /^https?:/i.test(itemHolder.item.icon))) {\n\t\t\t\titemView.icon.clearColorFilter();\n\t\t\t}\n\t\t}, 0);\n\t});\n\tlistView.on(\"scroll_change\", debounce(() => {\n\t\tconst parentRect = new Rect();\n\t\tlistView.getGlobalVisibleRect(parentRect);\n\t\tfunction isVisible (target) {\n\t\t\tconst rect = new Rect();\n\t\t\ttarget.getGlobalVisibleRect(rect);\n\t\t\treturn parentRect.contains(rect) || parentRect.intersect(rect);\n\t\t}\n\t\titemList.forEach((itemHolder, itemView) => {\n\t\t\tif (isVisible(itemView)) {\n\t\t\t\tlistView.emit(\"item_show\", itemHolder.item, itemView, listView);\n\t\t\t}\n\t\t});\n\t}, 80));\n\tlistView.on(\"item_show\", function (item, itemView, listView) {\n\t\tconst imageView = itemView.icon;\n\t\tif (item.loadIcon) {\n\t\t\timageView.setImageDrawable(item.loadIcon());\n\t\t} else if (!(item.icon && /^https?:/i.test(item.icon))) {\n\t\t\timageView.setColorFilter(android.graphics.Color.parseColor(inNightMode ? \"#FFCCCCCC\" : \"#FF333333\"));\n\t\t\treturn;\n\t\t}\n\t\timageView.clearColorFilter();\n\t});\n}\nmodule.exports = emitItemShowEvent;\n"
  },
  {
    "path": "src/miui_cleaner_app/fetch.js",
    "content": "const okhttp3 = global.Packages?.okhttp3;\nconst Headers = global.Headers || require(\"headers-polyfill\").Headers;\n// const ReadableStream = global.ReadableStream || require(\"web-streams-ponyfill\").ReadableStream;\nconst Blob = global.Blob || require(\"blob-polyfill\").Blob;\n// const FormData = global.FormData || require(\"formdata-polyfill\").Headers;\n\nfunction fetchAny (url, options = {}) {\n\tif (Array.isArray(url)) {\n\t\tlet controller;\n\t\tif (!options.signal) {\n\t\t\tif (global.AbortController) {\n\t\t\t\tcontroller = new AbortController();\n\t\t\t\toptions.signal = controller.signal;\n\t\t\t} else {\n\t\t\t\toptions.signal = events.emitter(threads.currentThread());\n\t\t\t}\n\t\t}\n\t\treturn Promise.any(url.map(url => fetch(url, options))).then(res => {\n\t\t\tif (controller) {\n\t\t\t\tcontroller.abort();\n\t\t\t} else if (options.signal?.emit) {\n\t\t\t\toptions.signal.emit(\"abort\");\n\t\t\t}\n\t\t\treturn res;\n\t\t});\n\t} else {\n\t\treturn fetch(url, options);\n\t}\n}\n\nfunction fetch (url, options = {}) {\n\treturn new Promise((resolve, reject) => {\n\t\toptions = {\n\t\t\tredirect: \"follow\",\n\t\t\tmethod: \"GET\",\n\t\t\t...options,\n\t\t};\n\t\toptions.method = options.method.toUpperCase();\n\t\t// console.time(url);\n\t\tconst client = http.client().newBuilder()\n\t\t\t.followRedirects(/^follow$/i.test(options.redirect))\n\t\t\t.build();\n\t\tconst call = client.newCall(http.buildRequest(url, options));\n\t\tconst work = events.emitter(threads.currentThread());\n\t\twork.once(\"response\", resolve);\n\t\twork.once(\"error\", reject);\n\t\tcall.enqueue(new okhttp3.Callback({\n\t\t\tonResponse: function (call, res) {\n\t\t\t\ttry {\n\t\t\t\t\tres = wrapResponse(res, options);\n\t\t\t\t} catch (ex) {\n\t\t\t\t\twork.emit(\"error\", ex);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twork.emit(\"response\", res);\n\t\t\t\t// console.timeEnd(url);\n\t\t\t},\n\t\t\tonFailure: function (call, err) {\n\t\t\t\twork.emit(\"error\", err);\n\t\t\t},\n\t\t}));\n\n\t\tif (options.signal) {\n\t\t\tconst abort = () => {\n\t\t\t\tcall.isCanceled() || call.cancel();\n\t\t\t\twork.emit(\"error\", new Error(options.signal.reason || \"The user aborted a request.\"));\n\t\t\t};\n\t\t\tif (options.signal.aborted) {\n\t\t\t\treturn abort();\n\t\t\t}\n\t\t\tif (options.signal.addEventListener) {\n\t\t\t\toptions.signal.addEventListener(\"abort\", abort);\n\t\t\t} else if (options.signal.on) {\n\t\t\t\toptions.signal.on(\"abort\", abort);\n\t\t\t}\n\t\t}\n\t});\n}\n\nconst _response = new Map();\nclass Response {\n\tget status () {\n\t\treturn _response.get(this).code();\n\t}\n\n\tget ok () {\n\t\treturn this.status >= 200 && this.status < 300;\n\t}\n\n\tget url () {\n\t\treturn _response.get(this).request().url().toString();\n\t}\n\n\tget redirected () {\n\t\treturn _response.get(this).isRedirect();\n\t}\n\n\tget statusText () {\n\t\treturn _response.get(this).message();\n\t}\n\n\tget headers () {\n\t\treturn _response.get(_response.get(this)).getHeaders();\n\t}\n\n\tget body () {\n\t\treturn _response.get(_response.get(this)).getBody();\n\t}\n\n\tblob () {\n\t\treturn _response.get(_response.get(this)).getBlob();\n\t}\n\n\tarrayBuffer () {\n\t\tthis.blob().arrayBuffer();\n\t}\n\n\ttext () {\n\t\treturn _response.get(_response.get(this)).getBodyText();\n\t}\n\n\tjson () {\n\t\treturn this.text().then(text => JSON.parse(text));\n\t}\n\n\tclone () {\n\t\tconst response = Object.create(Response.prototype);\n\t\t_response.set(response, _response.get(this));\n\t\treturn response;\n\t}\n}\nfunction hexToArrayUint8Array (input) {\n\tconst view = new Uint8Array(input.length / 2);\n\tfor (let i = 0; i < input.length; i += 2) {\n\t\tview[i / 2] = parseInt(input.substring(i, i + 2), 16);\n\t}\n\treturn view;\n}\n// https://square.github.io/okhttp/4.x/okhttp/okhttp3/-response/\nfunction wrapResponse (res, options) {\n\tif (/^error$/i.test(options.redirect) && res.isRedirect()) {\n\t\tthrow new Error(\"unexpected redirect\");\n\t}\n\tconst response = Object.create(Response.prototype);\n\t_response.set(response, res);\n\tlet headers;\n\tconst body = res.body();\n\tconst bodyByteString = body.byteString();\n\tbody.close();\n\tconst contentType = body.contentType();\n\tlet text;\n\tconst bodyToText = () => {\n\t\tif (text === undefined) {\n\t\t\ttext = new java.lang.String(bodyByteString.toByteArray(), contentType.charset() || \"UTF-8\");\n\t\t}\n\t\treturn text;\n\t};\n\tlet blob;\n\tconst bodyToBlob = () => {\n\t\tif (!blob) {\n\t\t\tblob = blob = new Blob([\n\t\t\t\thexToArrayUint8Array(bodyByteString.hex()),\n\t\t\t], {\n\t\t\t\ttype: `${contentType.type()}/${contentType.subtype()}`,\n\t\t\t});\n\t\t}\n\t\treturn blob;\n\t};\n\tconst resProps = {\n\t\tgetHeaders: () => {\n\t\t\tif (!headers) {\n\t\t\t\theaders = new Headers();\n\t\t\t\tres.headers().forEach(entry => {\n\t\t\t\t\theaders.append(entry.first, entry.second);\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn headers;\n\t\t},\n\t\tgetBlob: () => {\n\t\t\treturn Promise.resolve().then(bodyToBlob);\n\t\t},\n\t\tgetBody: () => {\n\t\t\treturn bodyToBlob().stream();\n\t\t},\n\t\tgetBodyText: (fnName) => {\n\t\t\treturn Promise.resolve().then(bodyToText);\n\t\t},\n\t};\n\t_response.set(res, resProps);\n\treturn response;\n}\n\nmodule.exports = fetchAny;\n\n// fetch(\n// \t\"https://developer.lanzoug.com/file/?UjRbZQw9UGEHDgY+AzYGalJtAjpe5FPCA5BVtlaZU/sG41CUAchX5lOWB/gAsweqUKYC41ewUNtSs1fHAOUEUVIEW+sM2FCMB3wGYQN5BjFSJgIxXixTtAOLVfxW7VPOBo1Q4gHgV71T4AfoAMEH4FCeAqhXJlAzUiVXOgB6BGNSO1tgDDRQWwc4BjQDagY1UjkCPl42U2ADPVVjVjhTdAZgUHQBYVc0UzwHYABkBz9QPwIxVy5QIlIlV2wAbgQ1UmBbPAx+UDQHZQZ/A2UGP1InAjNeNFNoAz1ValY6U2EGM1A3AThXNVNgBzcAMAcyUDoCN1c+UGFSYldnAD8EMlJiWzwMM1A2B2gGYwNkBjJSOgIpXmBTIQNvVXVWf1MhBmNQdQE1V2dTOAdoAGMHMFA6AjBXLlAmUjxXPAA5BGNSb1s9DGdQMgdoBmgDZgYzUjoCMl4yU3cDYlU/Vn1TbwY3UDEBalc6Uz0HYABnBzBQOwIzVy5QJ1IlVyYAYQQ0UmdbNAxpUDQHaQZoA2MGMVIwAiFedFM4A3RVblY4U2IGMlApAW1XOlM8B38AZgc0UD8CKVc7UGNSc1c1ADAEOFJi\",\n// \t{\n// \t\tredirect: \"follow\",\n// \t\t// method: \"HEAD\",\n// \t\theaders: {\n// \t\t\t\"accept\": \"*/*\",\n// \t\t\t\"user-agent\": \"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1\",\n// \t\t\t\"referer\": \"https://423down.lanzouv.com/tp/ic1wllc\",\n// \t\t\t\"x-forwarded-for\": \"202.247.192.146\",\n// \t\t\t\"client-ip\": \"59.142.129.197\",\n// \t\t},\n// \t},\n// );\n"
  },
  {
    "path": "src/miui_cleaner_app/findClickableParent.js",
    "content": "/**\n * 向上查找 UiObject 的父节点，找到可点击的祖先节点\n * @param {UiObject} node 节点\n * @returns\n */\nfunction findClickableParent (node) {\n\treturn !node || node.clickable() ? node : findClickableParent(node.parent());\n}\nmodule.exports = findClickableParent;\n"
  },
  {
    "path": "src/miui_cleaner_app/getApplicationInfo.js",
    "content": "// const PackageManager = android.content.pm.PackageManager;\nconst pm = context.getPackageManager();\n// https://developer.android.google.cn/reference/kotlin/android/content/pm/ApplicationInfo\n// https://developer.android.google.cn/reference/kotlin/android/content/pm/PackageInfo\nfunction getApplicationInfo (options) {\n\tlet appInfo;\n\tlet packageInfo;\n\t// const getPackageInfo = () => packageInfo || (packageInfo = pm.getPackageInfo(options.packageName, PackageManager.GET_SIGNING_CERTIFICATES));\n\tconst getPackageInfo = () => packageInfo || (packageInfo = pm.getPackageInfo(options.packageName, 0));\n\n\ttry {\n\t\tappInfo = pm.getApplicationInfo(options.packageName, 0);\n\t} catch (ex) {\n\t\treturn null;\n\t}\n\tif (!options.appName) {\n\t\tconst appName = pm.getApplicationLabel(appInfo).toString();\n\t\tif (appName === options.packageName) {\n\t\t\tif (!options.name && options.summary) {\n\t\t\t\toptions.name = options.summary;\n\t\t\t\toptions.summary = options.packageName;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.appName = appName;\n\t\t}\n\t}\n\n\t// if (!options.getSignature) {\n\t// \toptions.getSignature = () => getPackageInfo().signingInfo.getApkContentsSigners();\n\t// }\n\tif (!options.loadIcon && appInfo.icon) {\n\t\toptions.loadIcon = () => appInfo.loadIcon(pm);\n\t}\n\toptions.getVersionName = () => {\n\t\tlet versionName = getPackageInfo().versionName;\n\t\tif (options.packageName === \"com.miui.packageinstaller\") {\n\t\t\tversionName = versionName.replace(/^\\d+(?=-)/, () => Array.from(String(packageInfo.getLongVersionCode())).join(\".\"));\n\t\t}\n\t\treturn versionName;\n\t};\n\toptions.getVersionCode = () => getPackageInfo().getLongVersionCode();\n\toptions.getUpdateTime = () => getPackageInfo().lastUpdateTime;\n\treturn options;\n}\nmodule.exports = getApplicationInfo;\n\n// console.log(\n// \tgetApplicationInfo({\n// \t\tpackageName: \"org.autojs.autoxjs.v6\",\n// \t}),\n// );\n\n// const apkPath = \"/data/app/org.autojs.autoxjs.v6-XPge4R-XoervO0iNge1BhQ==/base.apk\";\n\n// const apkPath = \"/storage/emulated/0/Android/data/org.autojs.autoxjs.v6/files/Download/GeometricWeather.3.013_pub-2.apk\";\n// const info = pm.getPackageArchiveInfo(\n// \tapkPath,\n// \tPackageManager.GET_SIGNING_CERTIFICATES,\n// );\n// const appInfo = info.applicationInfo;\n\n// appInfo.sourceDir = apkPath;\n// appInfo.publicSourceDir = apkPath;\n// console.log(\n// \tappInfo.loadLabel(pm).toString(),\n// );\n// console.log(\n// \tappInfo.packageName,\n// );\n// console.log(\n// \tappInfo.loadLabel(pm).toString(),\n// );\n\n// const zipFile = $zip.open(apkPath);\n// log(zipFile.getPath());\n// log();\n// zipFile.getFileHeaders().forEach(file => {\n// \tconst fileName = file.getFileName();\n// \tif (/^META-INF\\/.+?\\..*SA$/i.test(fileName)) {\n// \t\tconsole.log(fileName);\n// \t\tconsole.log(file.getSignature());\n// \t}\n// });\n"
  },
  {
    "path": "src/miui_cleaner_app/getRemoteFileInfo.js",
    "content": "const fetch = require(\"./fetch\");\nconst lanzou = require(\"./lanzou\");\nconst _123pan = require(\"./123pan\");\n\nclass Asset {\n\tconstructor (data) {\n\t\tObject.assign(this, data);\n\t}\n\n\tasync getLocation () {\n\t\tconst location = await parseGithubRelease(new URL(this.url), false);\n\t\tif (location) {\n\t\t\tthis.location = location;\n\t\t}\n\t\treturn this;\n\t}\n}\n\nfunction parseGithubRelease (url, redirect) {\n\tconst pathInfo = url.pathname.match(/\\/releases\\/(.+)$/);\n\tif (!pathInfo) {\n\t\treturn;\n\t}\n\tif (pathInfo[1].includes(\"/\")) {\n\t\treturn fetch(\n\t\t\t[\n\t\t\t\turl.href,\n\t\t\t\turl.protocol + \"//gh.api.99988866.xyz/\" + url.href,\n\t\t\t\turl.protocol + \"//download.fastgit.org\" + url.pathname + url.search,\n\t\t\t],\n\t\t\t{\n\t\t\t\tredirect: redirect ? \"follow\" : \"manual\",\n\t\t\t\tmethod: \"HEAD\",\n\t\t\t},\n\t\t).then(res => {\n\t\t\tconst location = res.headers.get(\"location\");\n\t\t\tif (location) {\n\t\t\t\treturn location;\n\t\t\t} else if (res.ok) {\n\t\t\t\treturn res.url;\n\t\t\t}\n\t\t});\n\t} else {\n\t\turl.hostname = \"api.\" + url.hostname;\n\t\turl.pathname = \"/repos\" + url.pathname;\n\t\treturn fetch(url.href).then(res => res.json()).then(release => {\n\t\t\tconst referer = release.html_url;\n\t\t\tconst versionName = release.tag_name.replace(/^v/, \"\");\n\t\t\treturn release.assets.map(\n\t\t\t\tasset => new Asset({\n\t\t\t\t\tfileName: asset.name,\n\t\t\t\t\ttype: asset.content_type,\n\t\t\t\t\tsize: asset.size,\n\t\t\t\t\tlastModified: Date.parse(asset.updated_at),\n\t\t\t\t\tid: asset.node_id,\n\t\t\t\t\turl: asset.browser_download_url,\n\t\t\t\t\treferer,\n\t\t\t\t\tversionName,\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction parse32r (url) {\n\tlet id = url.pathname.match(/^\\/\\w+\\/(\\w+?)(\\.\\w+)?$/i);\n\tif (!id) {\n\t\treturn;\n\t}\n\tid = id[1];\n\tconst htmlUrl = `https://m.32r.com/app/${id}.html`;\n\tconst appUrl = `https://m.32r.com/downapp/${id}`;\n\tlet html;\n\tlet res = {};\n\treturn Promise.all([\n\t\tfetch(\n\t\t\tappUrl,\n\t\t\t{\n\t\t\t\tmethod: \"HEAD\",\n\t\t\t\theaders: {\n\t\t\t\t\tReferer: htmlUrl,\n\t\t\t\t},\n\t\t\t},\n\t\t).then(data => {\n\t\t\tres = data;\n\t\t}, console.error),\n\t\tfetch(\n\t\t\thtmlUrl,\n\t\t).then(res => res.text()).then(data => {\n\t\t\thtml = data;\n\t\t}, console.error),\n\t]).then(() => {\n\t\tlet json = html && html.match(/<script type=\"application\\/ld\\+json\">\\s*([\\s\\S]+?)\\s*<\\/script>/);\n\t\tif (json) {\n\t\t\tjson = JSON.parse(json[1]);\n\t\t} else {\n\t\t\tjson = {};\n\t\t}\n\t\tlet versionName = json.title?.match(/\\d+(\\.+\\d+)+/);\n\t\tversionName = versionName && versionName[0];\n\t\treturn {\n\t\t\tfileName: res.url && files.getName(new URL(res.url).pathname).replace(/_\\d+(?=\\.\\w+$)/i, \"\"),\n\t\t\tsize: +res.headers?.get(\"content-length\"),\n\t\t\tlastModified: Date.parse(res.headers?.get(\"last-modified\") || json.upDate || json.pubDate),\n\t\t\tid,\n\t\t\turl: res.url || appUrl,\n\t\t\treferer: htmlUrl,\n\t\t\tversionName,\n\t\t};\n\t});\n}\n\nfunction getVersionForFile (fileInfo) {\n\tif (Array.isArray(fileInfo)) {\n\t\tfileInfo.forEach(getVersion);\n\t} else {\n\t\tgetVersion(fileInfo);\n\t}\n\treturn fileInfo;\n}\n\nfunction getVersion (fileInfo) {\n\tconst versionName = fileInfo.fileName.match(/\\d+(\\.+\\d+)+/);\n\tif (versionName) {\n\t\tfileInfo.versionName = versionName[0];\n\t}\n\tconst versionCode = fileInfo.fileName.match(/\\(\\s*(\\d+)\\s*\\)/);\n\tif (versionCode) {\n\t\tfileInfo.versionCode = +versionCode[1];\n\t}\n}\n\nfunction getOptsFromUrl (url) {\n\tconst options = {};\n\turl.hash.replace(/^#+/, \"\").split(/\\s*&\\s*/g).forEach(\n\t\tvalue => {\n\t\t\tvalue = value.split(/\\s*=\\s*/g);\n\t\t\toptions[value[0]] = value[1];\n\t\t},\n\t);\n\treturn options;\n}\n\nfunction getRemoteFileInfo (url) {\n\tif (!url.href) {\n\t\turl = new URL(url);\n\t}\n\t// if (/(^|\\.)firepx\\.com$/i.test(url.hostname)) {\n\t// \treturn request(url.href).then(res => {\n\t// \t\tconst body = res.body.string();\n\t// \t\tconst link = body.match(/<a\\s*\\bhref=\"(.*?)\".*密码\\s*[:：]\\s*(\\w+)/);\n\t// \t\tif (link) {\n\t// \t\t\tconst newUrl = new URL(link[1], url.href);\n\t// \t\t\tif (newUrl.hostname !== url.hostname) {\n\t// \t\t\t\tnewUrl.hash = \"#pwd=\" + link[2];\n\t// \t\t\t\treturn toDownOpts(newUrl);\n\t// \t\t\t}\n\t// \t\t}\n\t// \t\treturn openWeb(url);\n\t// \t});\n\t// } else\n\tif (url.hostname === \"github.com\") {\n\t\tconsole.log(\"正在解析Github API\", url.href);\n\t\treturn parseGithubRelease(url);\n\t} else if (/^(\\w+\\.)*32r(\\.\\w+)*$/i.test(url.hostname)) {\n\t\tconsole.log(\"正在解析网页\", url.href);\n\t\treturn parse32r(url);\n\t} else if (/^(\\w+\\.)*lanzou\\w*(\\.\\w+)*$/.test(url.hostname)) {\n\t\tconsole.log(\"正在解析网盘\", url.href);\n\t\treturn lanzou(url, getOptsFromUrl(url)).then(getVersionForFile);\n\t} else if (/^(\\w+\\.)*123pan(\\.\\w+)*$/.test(url.hostname)) {\n\t\tconsole.log(\"正在解析网盘\", url.href);\n\t\treturn _123pan(url, getOptsFromUrl(url)).then(getVersionForFile);\n\t}\n}\n\n// function createFnCache (fn, cache = {}) {\n// \treturn (url) => {\n// \t\tconst key = encodeURI(url);\n// \t\tconst result = cache[key];\n// \t\tif (result) {\n// \t\t\treturn Promise.resolve(result);\n// \t\t}\n// \t\treturn fn(url).then(result => {\n// \t\t\tcache[key] = result;\n// \t\t\treturn result;\n// \t\t});\n// \t};\n// }\n\n// module.exports = createFnCache(getRemoteFileInfo);\nmodule.exports = getRemoteFileInfo;\n\nif (DEBUG) {\n\trequire(\"./test/getRemoteFileInfo\")(module.exports);\n}\n"
  },
  {
    "path": "src/miui_cleaner_app/index.js",
    "content": "console.setGlobalLogConfig({\n\tfile: files.join(\n\t\tcontext.getExternalFilesDir(\"logs\"),\n\t\t\"log.txt\",\n\t),\n});\n\ndelete global.Promise;\nrequire(\"core-js/modules/web.url.js\");\nrequire(\"core-js/modules/web.url-search-params\");\nrequire(\"core-js/modules/es.promise\");\nrequire(\"core-js/modules/es.promise.any\");\nrequire(\"core-js/modules/es.promise.finally\");\n\nconst singleChoice = require(\"./singleChoice\");\n\nconst mainActions = [\n\trequire(\"./sysAppRm\"),\n\trequire(\"./downApp\"),\n\trequire(\"./offAppAd\"),\n\trequire(\"./appManager\"),\n\trequire(\"./recycle\"),\n\trequire(\"./support\"),\n\t{\n\t\tname: \"控制台\",\n\t\tsummary: \"查看运行日志\",\n\t\tfn: () => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tconst settings = require(\"./settings\");\n\t\t\t\tconsole.log(\n\t\t\t\t\t[\n\t\t\t\t\t\t`SDK: ${device.sdkInt}`,\n\t\t\t\t\t\t`Android: ${device.release}`,\n\t\t\t\t\t\t`MIUI: ${device.incremental}`,\n\t\t\t\t\t\t\"settings:\",\n\t\t\t\t\t].concat(\n\t\t\t\t\t\tsettings.keys().map(key => `\\t${key}: ${JSON.stringify(settings[key])}`),\n\t\t\t\t\t).join(\"\\n\"),\n\t\t\t\t);\n\t\t\t}, 0);\n\t\t\treturn app.startActivity(\"console\");\n\t\t},\n\t\ticon: \"./res/drawable/ic_log.png\",\n\t},\n\t{\n\t\tname: \"退出\",\n\t\tsummary: \"再见\",\n\t\ticon: \"./res/drawable/ic_exit.png\",\n\t\tfn: () => {\n\t\t\tif (DEBUG) {\n\t\t\t\tui.finish();\n\t\t\t} else {\n\t\t\t\tjava.lang.System.exit(0);\n\t\t\t}\n\t\t},\n\t},\n];\n\nfunction mainMenu () {\n\tsingleChoice({\n\t\ttitle: \"请选择你的操作\",\n\t\titemList: mainActions,\n\t});\n}\n\nfunction regBack () {\n\tui.emitter.removeAllListeners(\"back_pressed\");\n\tui.emitter.once(\"back_pressed\", (e) => {\n\t\te.consumed = true;\n\t\tmainMenu();\n\t});\n}\n\n(() => {\n\tif (DEBUG) {\n\t\tconst thisPackageName = context.getPackageName();\n\t\tconsole.log(\"DEBUG in\", thisPackageName);\n\t\tif (thisPackageName === \"com.github.gucong3000.miui.cleaner\") {\n\t\t\tif (!engines.myEngine().source.toString().startsWith(\"/\")) {\n\t\t\t\tengines.execScriptFile(`/storage/emulated/0/脚本/${thisPackageName}/main.js`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else {\n\t\t\t// app.launch(\"com.github.gucong3000.miui.cleaner\");\n\t\t}\n\t}\n\tmainMenu();\n\trequire(\"./update\");\n})();\nmodule.exports = regBack;\nmodule.exports.mainMenu = mainMenu;\n"
  },
  {
    "path": "src/miui_cleaner_app/instApk.js",
    "content": "/**\n * 调用 APK 安装界面\n */\nfunction instApk (apk) {\n\ttry {\n\t\tapp.viewFile(apk);\n\t} catch (ex) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nmodule.exports = instApk;\n"
  },
  {
    "path": "src/miui_cleaner_app/lanzou.js",
    "content": "const jsonParse = require(\"json5/lib/parse\");\nconst fetch = global.fetch || require(\"./fetch\");\n// const webView = global.ui && require(\"./webView\");\nlet userAgent;\ntry {\n\tuserAgent = android.webkit.WebSettings.getDefaultUserAgent(context);\n} catch (ex) {\n\tuserAgent = \"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1\";\n}\nconst storage = {};\nconst realFileCache = {};\n\nasync function getFileInfoFromUrl (url, options) {\n\turl = parseUrl(url);\n\tconst res = await fetch(\n\t\turl.href,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"accept\": \"text/html\",\n\t\t\t\t\"user-agent\": userAgent,\n\t\t\t\t\"x-forwarded-for\": getRandomIP(),\n\t\t\t\t\"client-ip\": getRandomIP(),\n\t\t\t},\n\t\t},\n\t);\n\tawait checkResponse(res);\n\tconst html = await res.text();\n\tlet fileName = html.match(/\\bclass=\"(md|appname)\"[^<>]*>\\s*(.+?)\\s*<\\/?\\w+/);\n\tfileName = fileName && fileName[2];\n\tlet size = html.match(/\\b(id|class)=\"(submit|mtt)\"[^<>]*>.*?\\(\\s*(\\d+.*?)\\s*\\)\\s*<\\/?\\w+/);\n\tsize = size && size[3];\n\tlet lastModified = html.match(/\\bclass=\"appinfotime\"[^<>]*>\\s*(.+?)\\s*<\\/?\\w+/);\n\tif (lastModified) {\n\t\tlastModified = lastModified[1];\n\t} else {\n\t\tlastModified = html.match(/\\bclass=\"mt2\"[^<>]*>\\s*(时间.*?)?\\s*<\\/\\w+>\\s*(.*?)\\s*</);\n\t\tlastModified = lastModified && lastModified[2];\n\t}\n\tconst fileInfo = {\n\t\tfileName,\n\t\tsize,\n\t\tlastModified,\n\t};\n\n\tconst that = Object.assign({}, options);\n\tfunction getVal (code) {\n\t\tif (code in that) {\n\t\t\treturn that[code];\n\t\t}\n\t\ttry {\n\t\t\treturn jsonParse(code);\n\t\t} catch (ex) {\n\t\t\t// console.error(ex);\n\t\t}\n\t}\n\tfor await (const script of html.match(/<script\\s+type=\"text\\/javascript\">[\\s\\S]+?<\\/script>/ig).map(\n\t\tscript => script.slice(31, -9).trim(),\n\t)) {\n\t\tconst hostname = script.match(/(['\"])(https?:\\/\\/(\\w+\\.)*lanzoug\\w*(\\.\\w+)+\\/file\\/?)\\1/i);\n\t\tconst pathname = script.match(/(['\"])(\\?\\S{256,})\\1/);\n\t\tif (hostname && pathname) {\n\t\t\tconsole.log(\"发现无密码的单文件：\", url.href);\n\t\t\tfileInfo.url = new URL(pathname[2], hostname[2]).href;\n\t\t\tbreak;\n\t\t}\n\t\tconst ajaxCode = script.match(/\\$.ajax\\({([\\s\\S]+?)}\\);?/);\n\t\tif (!ajaxCode) {\n\t\t\tcontinue;\n\t\t}\n\t\tscript.slice(0, ajaxCode.index).split(/\\r?\\n/).forEach(line => {\n\t\t\tline = line.trim();\n\t\t\tif (line.startsWith(\"//\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tline = line.match(/^((var|let|const)\\s+)?(\\w+)\\s*=\\s*(.*?)(;|$)/);\n\t\t\tif (line) {\n\t\t\t\tthat[line[3]] = getVal(line[4]) || that[line[3]];\n\t\t\t}\n\t\t});\n\t\tconst ajaxConfig = {};\n\t\tlet inData;\n\t\tconst data = {};\n\t\tajaxCode[1].replace(/\\s*(}\\s*,?)\\s*/g, \"\\n$1\\n\").replace(/(,|{)\\s*/g, \"$1\\n\").split(/\\r?\\n/).forEach(line => {\n\t\t\tline = line.trim();\n\t\t\tif (line.startsWith(\"//\")) {\n\t\t\t\treturn false;\n\t\t\t} else if (/^}\\s*,?$/.test(line)) {\n\t\t\t\tinData = false;\n\t\t\t} else if ((line = line.match(/^(['\"])?(\\S+)\\1\\s*:\\s*(.+?)\\s*,?$/))) {\n\t\t\t\tconst key = line[2];\n\t\t\t\tconst value = line[3];\n\t\t\t\tif (key === \"data\") {\n\t\t\t\t\tinData = true;\n\t\t\t\t\tajaxConfig.data = data;\n\t\t\t\t} else if (inData) {\n\t\t\t\t\tdata[key] = getVal(value);\n\t\t\t\t} else {\n\t\t\t\t\tajaxConfig[key] = getVal(value);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tif (ajaxConfig.url && ajaxConfig.data) {\n\t\t\t// 有密码的单文件或者文件夹\n\t\t\treturn getFileInfoByAjax(url, fileInfo, ajaxConfig, options);\n\t\t}\n\t}\n\tfileInfo.options = options;\n\treturn parseFileInfo(fileInfo, url);\n}\n\nfunction parseFileInfo (fileInfo, url) {\n\tif (typeof fileInfo.size === \"string\") {\n\t\tconst size = fileInfo.size.match(/^([+-\\d.]+)\\s*(\\w+)?$/);\n\t\tconst BIBYTE_UNITS = \"BKMGTPEZY\";\n\t\tconst number = Number.parseFloat(size[1]);\n\t\tconst exponent = BIBYTE_UNITS.indexOf(size[2][0].toUpperCase());\n\t\tif (Number.isNaN(number) || exponent < 0) {\n\t\t\tdelete fileInfo.size;\n\t\t} else {\n\t\t\tfileInfo.size = Math.round(number * Math.pow(1024, exponent));\n\t\t}\n\t}\n\tif (typeof fileInfo.lastModified === \"string\") {\n\t\tlet lastTime = fileInfo.lastModified.match(/^(\\d+)\\s*(.*)前$/);\n\t\tif (lastTime) {\n\t\t\tfileInfo.lastModified = Date.now() - (+lastTime[1] * 1000 * ({\n\t\t\t\t天: 60 * 60 * 24,\n\t\t\t\t小时: 60 * 60,\n\t\t\t\t分钟: 60,\n\t\t\t\t分: 60,\n\t\t\t\t秒钟: 1,\n\t\t\t\t秒: 1,\n\t\t\t}[lastTime[2]]));\n\t\t} else if ((lastTime = fileInfo.lastModified.match(/^(.*)天\\s*((?:\\d+:+)*\\d+)/))) {\n\t\t\tlet time = lastTime[2].split(\":\");\n\t\t\ttime = Date.parse(new Date().toLocaleDateString() + ` ${time[0]}:${time[1]}:${time[2] || 0} GMT+0800`);\n\t\t\ttime -= ({\n\t\t\t\t昨: 1,\n\t\t\t\t前: 2,\n\t\t\t}[lastTime[1]] || 0) * 60 * 60 * 24 * 1000;\n\t\t\tfileInfo.lastModified = time;\n\t\t} else {\n\t\t\tfileInfo.lastModified = Date.parse(fileInfo.lastModified) || fileInfo.lastModified;\n\t\t}\n\t}\n\tif (fileInfo.id) {\n\t\tfileInfo.referer = new URL(\"/tp/\" + fileInfo.id, url.origin).href;\n\t} else {\n\t\tconst id = getIdByUrl(url);\n\t\tfileInfo.id = id;\n\t\tfileInfo.referer = `${url.origin}/tp/${id}`;\n\t}\n\n\tif (fileInfo.url) {\n\t\tfileInfo.expires = Date.now() + 1800000;\n\t}\n\tstorage[fileInfo.id] = fileInfo;\n\treturn fileInfo;\n}\n\nfunction getIdByUrl (url) {\n\treturn url.pathname.replace(/^(\\/+tp)*\\/+/, \"\") + url.search;\n}\n\nasync function getFileInfoByAjax (url, fileInfo, ajaxConfig, options) {\n\tconst res = await fetch(\n\t\tnew URL(ajaxConfig.url, url.origin).href,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"content-type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\"accept\": \"application/json\",\n\t\t\t\t\"x-requested-with\": \"XMLHttpRequest\",\n\t\t\t\t\"user-agent\": userAgent,\n\t\t\t\t\"referer\": url.href,\n\t\t\t\t\"x-forwarded-for\": getRandomIP(),\n\t\t\t\t\"client-ip\": getRandomIP(),\n\t\t\t},\n\t\t\tbody: new URLSearchParams(ajaxConfig.data).toString(),\n\t\t\tmethod: (ajaxConfig.type || \"POST\").toUpperCase(),\n\t\t},\n\t);\n\tawait checkResponse(res);\n\tlet data = await res.json();\n\tif (Array.isArray(data.text)) {\n\t\tconsole.log(\"发现文件夹:\", url.href);\n\t\tdata = data.text.map(file => parseFileInfo({\n\t\t\tfileName: file.name_all,\n\t\t\tsize: file.size,\n\t\t\tlastModified: file.time,\n\t\t\tid: file.id,\n\t\t}, url));\n\t\tstorage[getIdByUrl(url)] = data.map(dada => dada.id);\n\t} else if (data.url && data.inf && data.dom) {\n\t\tconsole.log(\"发现需要密码的单文件:\", url.href);\n\t\tfileInfo.fileName = data.inf;\n\t\tfileInfo.url = new URL(data.url, new URL(\"/file/\", data.dom)).href;\n\t\tfileInfo.options = options;\n\t\tdata = parseFileInfo(fileInfo, url);\n\t} else {\n\t\t// 除非网络异常，否则大概里是密码错了\n\t\tthrow new Error(data.info || data);\n\t}\n\treturn data;\n}\n\n// function reqFileInfoByWeb (url) {\n// \tconst web = webView({\n// \t\turl: url.href,\n// \t\tuserAgent: uaMobile,\n// \t});\n// \treturn web.ready(\n// \t\t() => document.documentElement.innerHTML,\n// \t).then(html => getFileInfo(url, html, (ajaxInfo) => web.evaluate(\n// \t\tajaxInfo => fetch(ajaxInfo.url, {\n// \t\t\theaders: {\n// \t\t\t\t\"accept\": \"application/json\",\n// \t\t\t\t\"content-type\": \"application/x-www-form-urlencoded\",\n// \t\t\t\t\"x-requested-with\": \"XMLHttpRequest\",\n// \t\t\t},\n// \t\t\tbody: new URLSearchParams(ajaxInfo.data).toString(),\n// \t\t\tmethod: ajaxInfo.type,\n// \t\t}).then(response => response.json()),\n// \t\t[ajaxInfo],\n// \t), \"WebView: \")).finally(() => web.cancel);\n// };\n\nfunction getFileInfo (url, options) {\n\turl = parseUrl(url);\n\tconst id = getIdByUrl(url);\n\tconst fileInfo = storage[id];\n\tif (fileInfo) {\n\t\tif (Array.isArray(fileInfo)) {\n\t\t\treturn getFileInfoFromUrl(url, options).catch(ex => {\n\t\t\t\treturn fileInfo.map(id => storage[id]);\n\t\t\t});\n\t\t} else {\n\t\t\tif (!options.reqUrl || (fileInfo.url && fileInfo.expires > Date.now())) {\n\t\t\t\treturn Promise.resolve(fileInfo);\n\t\t\t}\n\t\t\tif (fileInfo.options) {\n\t\t\t\toptions = Object.assign(fileInfo.options, options);\n\t\t\t}\n\t\t}\n\t}\n\treturn getFileInfoFromUrl(url, options);\n}\nasync function getRealFile (fileInfo, redirect) {\n\tconst cache = realFileCache[fileInfo.id];\n\tconst now = Date.now();\n\tif (cache) {\n\t\tif (cache.expires && now >= cache.expires) {\n\t\t\t// console.log(\"命中缓存，但已过期:\", fileInfo);\n\t\t\tdelete realFileCache[fileInfo.id];\n\t\t} else {\n\t\t\t// console.log(\"命中缓存:\", fileInfo);\n\t\t\treturn Promise.resolve(cache);\n\t\t}\n\t}\n\tif (fileInfo.url && fileInfo.expires > now) {\n\t\tconst res = await fetch(fileInfo.url, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tredirect: redirect ? \"follow\" : \"manual\",\n\t\t\t// redirect: \"error\",\n\t\t\theaders: {\n\t\t\t\t\"Accept\": \"text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8\",\n\t\t\t\t\"Accept-Encoding\": \"gzip, deflate, br\",\n\t\t\t\t\"Accept-Language\": \"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\",\n\t\t\t\t// \"Cache-Control\": \"no-cache\",\n\t\t\t\t// \"Connection\": \"keep-alive\",\n\t\t\t\t// \"Pragma\": \"no-cache\",\n\t\t\t\t\"Upgrade-Insecure-Requests\": 1,\n\t\t\t\t\"Cookie\": \"down_ip=1\",\n\t\t\t\t\"User-Agent\": userAgent,\n\t\t\t\t\"referer\": fileInfo.url,\n\t\t\t\t\"x-forwarded-for\": getRandomIP(),\n\t\t\t\t\"client-ip\": getRandomIP(),\n\t\t\t},\n\t\t\treferrer: fileInfo.url,\n\t\t});\n\t\tconst location = res.headers.get(\"location\");\n\t\tif (location) {\n\t\t\tfileInfo.location = location;\n\t\t} else {\n\t\t\tawait checkResponse(res);\n\t\t}\n\t\tlet disposition = res.headers.get(\"content-disposition\");\n\t\tif (disposition) {\n\t\t\tdisposition = disposition && disposition.match(/(^|;)\\s*filename\\*?\\s*=\\s*(UTF-8(''|\\/))?(.*?)(;|\\s|$)/i);\n\t\t\tdisposition = disposition && decodeURI(disposition[4]);\n\t\t\tfileInfo.fileName = disposition || fileInfo.fileName;\n\t\t\tfileInfo.location = location || res.url;\n\t\t\tconst expires = res.headers.get(\"expires\");\n\t\t\tif (expires) {\n\t\t\t\tfileInfo.expires = Date.parse(expires);\n\t\t\t}\n\t\t\tconst type = res.headers.get(\"content-type\");\n\t\t\tif (type && !/^application\\/octet-stream$/i.test(type)) {\n\t\t\t\tfileInfo.type = type;\n\t\t\t}\n\t\t\tconst size = +res.headers.get(\"content-length\");\n\t\t\tif (size) {\n\t\t\t\tfileInfo.size = size;\n\t\t\t}\n\t\t\tconst lastModified = res.headers.get(\"last-modified\");\n\t\t\tif (lastModified) {\n\t\t\t\tfileInfo.lastModified = Date.parse(lastModified);\n\t\t\t}\n\t\t}\n\t\tif (fileInfo.location) {\n\t\t\trealFileCache[fileInfo.id] = fileInfo;\n\t\t\treturn fileInfo;\n\t\t} else {\n\t\t\tthrow new Error(\"unknow error: \\nat \" + res.url);\n\t\t}\n\t}\n\tif (fileInfo.referer) {\n\t\treturn getRealFile(await getFileInfo(fileInfo.referer, {\n\t\t\treqUrl: true,\n\t\t}));\n\t}\n}\n\nasync function checkResponse (res) {\n\tif (!res.ok) {\n\t\tthrow new Error(`status: ${res.status}\\nmessage: ${res.message || JSON.stringify(await res.text())}\\n    at ${res.url}`);\n\t}\n}\n\nfunction parseUrl (url) {\n\tif (!url.href) {\n\t\turl = new URL(url);\n\t}\n\treturn url;\n}\n\nfunction getRandomIP () {\n\tlet ip = [218, 66, 60, 202, 204, 59, 61, 222, 221, 62, 63, 64, 122, 211];\n\tip = [\n\t\tip[Math.floor(Math.random() * ip.length)],\n\t];\n\tfor (let i = 0; i < 3; i++) {\n\t\tip.push(Math.floor(Math.random() * 256));\n\t}\n\treturn ip.join(\".\");\n}\n\nclass FileInfo {\n\tconstructor (data) {\n\t\tObject.assign(this, data);\n\t}\n\n\tasync getLocation (redirect) {\n\t\tconst data = await getRealFile(this, redirect);\n\t\tObject.assign(this, data);\n\t\treturn this;\n\t}\n}\n\nasync function parse (url, options) {\n\tconst data = await getFileInfo(url, options || {});\n\tif (Array.isArray(data)) {\n\t\treturn data.map(data => new FileInfo(data));\n\t}\n\treturn new FileInfo(data);\n}\nmodule.exports = parse;\n// (async () => {\n// \tlet file = await parse(\"https://423down.lanzouv.com/tp/iKBGf0hcsq5e\");\n// \tconsole.log(file.url);\n// \tfile = await parse(\"https://423down.lanzouv.com/tp/iKBGf0hcsq5e\");\n// \tconsole.log(file.url);\n// })();\n"
  },
  {
    "path": "src/miui_cleaner_app/multiChoice.js",
    "content": "const emitItemShowEvent = require(\"./emitItemShowEvent\");\nconst project = require(\"./project.json\");\n\nfunction multiChoice (\n\t{\n\t\titemList = [],\n\t\ttitle = \"多选\",\n\t\ticon = \"./res/drawable/ic_android.png\",\n\t\tchecked = true,\n\t},\n) {\n\ticon = /^\\w+:\\/\\//.test(icon) ? icon : (\"file://\" + files.path(icon));\n\n\tui.layout(`\n\t\t<frame>\n\t\t\t<vertical>\n\t\t\t\t<appbar>\n\t\t\t\t\t<toolbar id=\"toolbar\" title=\"${project.name}\" subtitle=\"${title}\" />\n\t\t\t\t</appbar>\n\t\t\t\t<list id=\"itemList\">\n\t\t\t\t\t<card w=\"*\" h=\"auto\" margin=\"0 0 0 10\" foreground=\"?selectableItemBackground\">\n\t\t\t\t\t\t<horizontal gravity=\"center_vertical\">\n\t\t\t\t\t\t\t<img id=\"icon\" h=\"48\" w=\"48\" src=\"{{this.icon || '${icon}'}}\" margin=\"10 10 0 10\" />\n\t\t\t\t\t\t\t<vertical h=\"auto\" layout_weight=\"1\" margin=\"10 0\">\n\t\t\t\t\t\t\t\t<text text=\"{{this.displayName || this.appName || this.name}}\" textColor=\"#333333\" textSize=\"16sp\" maxLines=\"1\" />\n\t\t\t\t\t\t\t\t<text text=\"{{this.summary}}\" textColor=\"#999999\" textSize=\"14sp\" maxLines=\"1\" />\n\t\t\t\t\t\t\t</vertical>\n\t\t\t\t\t\t\t<checkbox id=\"checkbox\" checked=\"{{this.checked == null ? ${checked} : this.checked}}\"  marginRight=\"10\" />\n\t\t\t\t\t\t</horizontal>\n\t\t\t\t\t</card>\n\t\t\t\t</list>\n\t\t\t</vertical>\n\t\t\t<fab id=\"done\" w=\"auto\" h=\"auto\" src=\"@drawable/ic_done_white_48dp\" margin=\"0 32\" layout_gravity=\"bottom|center\" tint=\"#ffffff\" />\n\t\t</frame>\n\t`);\n\n\temitItemShowEvent(ui.itemList, icon);\n\n\tui.itemList.on(\"item_bind\", function (itemView, itemHolder) {\n\t\titemView.checkbox.on(\"check\", function (checked) {\n\t\t\tconst item = itemHolder.item;\n\t\t\titem.checked = checked;\n\t\t\tbindDoneBtnVisibility(checked);\n\t\t});\n\t});\n\n\tui.itemList.on(\"item_click\", function (item, i, itemView, listView) {\n\t\titemView.checkbox.checked = !itemView.checkbox.checked;\n\t});\n\n\tfunction bindDoneBtnVisibility (checked) {\n\t\tif (checked || itemList.some(item => item.checked)) {\n\t\t\tui.done.show();\n\t\t} else {\n\t\t\tui.done.hide();\n\t\t}\n\t}\n\n\tglobal.activity.setSupportActionBar(ui.toolbar);\n\titemList.forEach((item) => {\n\t\tif (item.icon && !/^\\w+:\\/\\//.test(item.icon)) {\n\t\t\titem.icon = \"file://\" + files.path(item.icon);\n\t\t}\n\t});\n\tbindDoneBtnVisibility();\n\tui.itemList.setDataSource(itemList);\n\n\treturn new Promise((resolve) => {\n\t\tui.done.on(\"click\", () => {\n\t\t\tresolve(itemList.filter(item => item.checked));\n\t\t});\n\t});\n}\n\nmodule.exports = multiChoice;\n"
  },
  {
    "path": "src/miui_cleaner_app/offAppAd.js",
    "content": "const getApplicationInfo = require(\"./getApplicationInfo\");\nconst multiChoice = require(\"./multiChoice\");\nconst serviceMgr = require(\"./serviceMgr\");\nconst settings = require(\"./settings\");\nconst settingsPackageName = \"com.android.settings\";\n\nconst cleanerList = [\n\t{\n\t\tname: \"先清理后台应用\",\n\t\tsummary: \"推荐，增加操作成功率\",\n\t\taction: \"clearAnim\",\n\t},\n\t{\n\t\t// 设置→小米帐号→关于小米帐号→系统广告→“关闭系统工具广告”\n\t\tsummary: \"设置→小米帐号→关闭系统工具广告\",\n\t\tpackageName: \"com.xiaomi.account\",\n\t\taction: \".ui.AccountSettingsActivity\",\n\t\tsettings: [\"passportAD\"],\n\t},\n\t{\n\t\tdisplayName: \"系统安全\",\n\t\tsummary: \"系统安全→网页拉活应用、日志上传等\",\n\t\tpackageName: settingsPackageName,\n\t\taction: \"ACTION_SECURITY_SETTINGS\",\n\t\tsettings: [\n\t\t\t// 网页链接调用服务\n\t\t\t\"httpInvokeApp\",\n\t\t\t// 加入“用户体验改进计划”\n\t\t\t\"uploadLog\",\n\t\t\t// 自动发送诊断数据\n\t\t\t\"uploadDebugLog\",\n\t\t],\n\t},\n\t{\n\t\t// `广告服务` 位于 `安全` 的子页面\n\t\tdisplayName: \"广告服务\",\n\t\tsummary: \"系统安全→广告服务→个性化广告推荐\",\n\t\tpackageName: settingsPackageName,\n\t\taction: \".ad.AdServiceSettings\",\n\t\tsettings: [\"personalizedAD\"],\n\t},\n\t{\n\t\t// 应用商店\n\t\tpackageName: \"com.xiaomi.market\",\n\t\tsummary: \"新手帮助、个性化服务、福利活动等\",\n\t\t// activity: \".ui.MarketTabActivity\",\n\t},\n\t{\n\t\t// 应用包管理组件\n\t\tsummary: \"安装监控、纯净模式、安全检查\",\n\t\tpackageName: \"com.miui.packageinstaller\",\n\t\t// action: \"com.android.browser.BrowserActivity\",\n\t},\n\t{\n\t\t// 下载管理程序\n\t\tpackageName: \"com.android.providers.downloads.ui\",\n\t\tsummary: \"资源推荐、热榜推荐\",\n\t\taction: \".activity.DownloadSettingActivity\",\n\t},\n\t{\n\t\t// 手机管家→设置页\n\t\tsummary: \"在线服务、个性化推荐等\",\n\t\tpackageName: \"com.miui.securitycenter\",\n\t\taction: \"com.miui.securityscan.ui.settings.SettingsActivity\",\n\t},\n\t{\n\t\t// 手机管家→应用管理→设置页\n\t\tdisplayName: \"应用管理\",\n\t\tsummary: \"资源推荐\",\n\t\tpackageName: \"com.miui.securitycenter\",\n\t\taction: \"com.miui.appmanager.AppManagerMainActivity\",\n\t},\n\t{\n\t\t// 手机管家→垃圾清理→设置页\n\t\tpackageName: \"com.miui.cleanmaster\",\n\t\tsummary: \"推荐内容、扫描内存\",\n\t\taction: \"com.miui.optimizecenter.settings.SettingsActivity\",\n\t},\n\t{\n\t\t// 日历\n\t\tpackageName: \"com.android.calendar\",\n\t\tsummary: \"天气服务、内容推广\",\n\t\taction: \".settings.CalendarActionbarSettingsActivity\",\n\t},\n\t{\n\t\t// 时钟\n\t\tpackageName: \"com.android.deskclock\",\n\t\tsummary: \"生活早报\",\n\t\taction: \".settings.SettingsActivity\",\n\t},\n\t{\n\t\t// 小米社区\n\t\tpackageName: \"com.xiaomi.vipaccount\",\n\t\tsummary: \"详情页相似推荐、个性化广告、信息流推荐\",\n\t\taction: \".ui.home.page.HomeFrameActivity\",\n\t},\n\t{\n\t\t// 小米天气\n\t\tpackageName: \"com.miui.weather2\",\n\t\tsummary: \"天气视频卡片，内容推广\",\n\t\taction: \".ActivityWeatherMain\",\n\t},\n\t{\n\t\t// 小米视频\n\t\tpackageName: \"com.miui.video\",\n\t\tsummary: \"内容推荐、广告推荐、在线服务、消息\",\n\t\taction: \".feature.mine.setting.SettingActivity\",\n\t},\n\t{\n\t\t// 小爱语音\n\t\tpackageName: \"com.miui.voiceassist\",\n\t\tsummary: \"小爱技巧推送、个性化推荐、广告推荐等\",\n\t\taction: \"com.xiaomi.voiceassistant.settings.MiuiVoiceSettingActivity\",\n\t},\n\t{\n\t\t// 搜索\n\t\tpackageName: \"com.android.quicksearchbox\",\n\t\tsummary: \"热搜榜单、搜索精选等，开:广告过滤\",\n\t\taction: \".preferences.SearchSettingsPreferenceActivity\",\n\t},\n\t{\n\t\t// 小米浏览器国际版\n\t\tpackageName: \"com.mi.globalbrowser\",\n\t\tsummary: \"宫格位、消息等，开:广告过滤、简洁版首页\",\n\t\taction: \"com.android.browser.BrowserActivity\",\n\t},\n\t{\n\t\t// 小米浏览器国内版\n\t\tsummary: \"广告、消息等，开:广告过滤、简洁版首页\",\n\t\tpackageName: \"com.android.browser\",\n\t\taction: \"com.android.browser.BrowserActivity\",\n\t},\n];\n\nfunction getCleanerList () {\n\treturn cleanerList.filter((cleaner) => {\n\t\t// 通过系统设置判断是否已经关闭该板块的广告，如果已经全部关闭，则不显示该模块\n\t\tif (cleaner.settings && cleaner.settings.every(key => settings[key] === false)) {\n\t\t\tcleaner.checked = false;\n\t\t}\n\t\treturn !cleaner.packageName || getApplicationInfo(cleaner);\n\t});\n}\n\nfunction offAppAd () {\n\tmultiChoice({\n\t\ttitle: \"请选择要关闭广告的APP\",\n\t\titemList: getCleanerList(),\n\t\tchecked: true,\n\t}).then(cleanerList => (\n\t\tsettings.set(\n\t\t\t\"accessibilityServiceEnabled\",\n\t\t\ttrue,\n\t\t\t\"操作其他APP的广告开关\",\n\t\t).then(accessibilityServiceEnabled => {\n\t\t\tif (!accessibilityServiceEnabled) {\n\t\t\t\treturn accessibilityServiceEnabled;\n\t\t\t}\n\t\t\treturn settings.set(\n\t\t\t\t\"drawOverlays\",\n\t\t\t\ttrue,\n\t\t\t).then(\n\t\t\t\t() => serviceMgr(cleanerList),\n\t\t\t);\n\t\t})\n\t)).then(\n\t\toffAppAd,\n\t).catch(console.error);\n\trequire(\"./index\")();\n};\n\nmodule.exports = {\n\tname: \"关闭各APP广告\",\n\tsummary: \"自动查询并关闭各APP中的广告\",\n\ticon: \"./res/drawable/ic_no_ad.png\",\n\tfn: offAppAd,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/project.json",
    "content": "{\n\t\"icon\": \"res/drawable/ic_launcher.png\",\n\t\"launchConfig\": {\n\t\t\"displaySplash\": false,\n\t\t\"hideLogs\": false,\n\t\t\"permissions\": [],\n\t\t\"serviceDesc\": \"自动操作(关闭 MIUI 广告)所需，若关闭则只能执行其他功能。\",\n\t\t\"splashIcon\": \"res/drawable/ic_launcher.png\",\n\t\t\"splashText\": \"MIUI广告清理工具\",\n\t\t\"stableMode\": false,\n\t\t\"volumeUpcontrol\": true\n\t},\n\t\"main\": \"main.js\",\n\t\"name\": \"MiuiCleaner\",\n\t\"packageName\": \"com.github.gucong3000.miui.cleaner\",\n\t\"scripts\": {},\n\t\"useFeatures\": [],\n\t\"versionCode\": 8,\n\t\"versionName\": \"2023.4.23.8\"\n}\n"
  },
  {
    "path": "src/miui_cleaner_app/recycle.js",
    "content": "const singleChoice = require(\"./singleChoice\");\nconst serviceMgr = require(\"./serviceMgr\");\nconst settings = require(\"./settings\");\nconst instApk = require(\"./instApk\");\nconst appDesc = require(\"./appDesc\");\n\nconst marketPackageName = \"com.xiaomi.market\";\n\nfunction launchMarket () {\n\treturn settings.set(\n\t\t\"accessibilityServiceEnabled\",\n\t\ttrue,\n\t\t\"自动打开“应用商店”的“系统应用管理”\",\n\t).then(accessibilityServiceEnabled => {\n\t\tif (accessibilityServiceEnabled) {\n\t\t\treturn serviceMgr({\n\t\t\t\tpackageName: marketPackageName,\n\t\t\t\taction: \".ui.CommonWebActivity\",\n\t\t\t\tname: \"系统应用管理\",\n\t\t\t});\n\t\t} else {\n\t\t\treturn app.launchPackage(marketPackageName);\n\t\t}\n\t});\n}\n\nfunction getInstalledPackages () {\n\tconst pm = context.getPackageManager();\n\treturn Array.from(\n\t\tpm.getInstalledApplications(android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES),\n\t).map(appInfo => {\n\t\tif (app.getAppName(appInfo.packageName)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst summary = appDesc[appInfo.packageName] || \"\";\n\t\tlet appName = pm.getApplicationLabel(appInfo).toString();\n\t\tif (appName === appInfo.packageName) {\n\t\t\tappName = null;\n\t\t}\n\t\treturn {\n\t\t\tsummary: appName ? summary : appInfo.packageName,\n\t\t\tpackageName: appInfo.packageName,\n\t\t\tloadIcon: appInfo.icon && (() => appInfo.loadIcon(pm)),\n\t\t\tapk: appInfo.sourceDir,\n\t\t\tname: appName || summary,\n\t\t\tappName,\n\t\t};\n\t}).filter(Boolean).sort((app1, app2) => (\n\t\tapp1.packageName.localeCompare(app2.packageName)\n\t)).concat({\n\t\tname: \"其他\",\n\t\tloadIcon: () => pm.getApplicationInfo(marketPackageName, 0).loadIcon(pm),\n\t\tfn: launchMarket,\n\t\tsummary: \"去应用商店下载其他小米官方应用\",\n\t});\n}\nlet requestInstallPackages;\nfunction recycle () {\n\tsingleChoice({\n\t\ttitle: \"请选择要恢复的应用\",\n\t\titemList: {\n\t\t\tthen: (...args) => Promise.resolve(getInstalledPackages()).then(...args),\n\t\t},\n\t\tfn: function (appInfo) {\n\t\t\tif (!requestInstallPackages) {\n\t\t\t\trequestInstallPackages = settings.set(\"requestInstallPackages\", true, \"打开应用安装权限\");\n\t\t\t}\n\t\t\treturn requestInstallPackages.then(() => {\n\t\t\t\tinstApk(appInfo.apk);\n\t\t\t\tconsole.log(\"正在恢复：\", appInfo);\n\t\t\t});\n\t\t},\n\t});\n\trequire(\"./index\")();\n}\n\nmodule.exports = {\n\tname: \"回收站\",\n\tsummary: \"恢复已卸载的APP\",\n\ticon: \"./res/drawable/ic_recovery.png\",\n\tfn: recycle,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/serviceMgr.js",
    "content": "let scriptEngine;\nfunction sleep (time) {\n\treturn new Promise((resolve) => setTimeout(resolve, time));\n}\n\nfunction waitForEngineStart () {\n\treturn sleep(0x50).then(() => {\n\t\tif (scriptEngine.engine) {\n\t\t\treturn scriptEngine.engine;\n\t\t} else {\n\t\t\treturn waitForEngineStart();\n\t\t}\n\t});\n}\n\nfunction waitForEngineStop () {\n\treturn sleep(0x50).then(() => {\n\t\tif (scriptEngine && scriptEngine.engine && !scriptEngine.engine.destroyed) {\n\t\t\treturn waitForEngineStop();\n\t\t}\n\t});\n}\n\nfunction getEngine (task) {\n\treturn Promise.resolve().then(() => {\n\t\tif (!scriptEngine || !scriptEngine.engine || scriptEngine.engine.destroyed) {\n\t\t\tscriptEngine = engines.execScriptFile(\"./services.js\");\n\t\t}\n\t});\n}\n\n// function runAutoActions (cleanerList) {\n// \t(\n// \t\tcleanerList.length\n// \t\t\t? serviceMgr(cleanerList.shift())\n// \t\t\t: Promise.resolve()\n// \t).then(() => {\n// \t\treturn runAutoActions(cleanerList);\n// \t});\n// }\n\nfunction parseTaskInfo (taskInfo) {\n\treturn {\n\t\tpackageName: taskInfo.packageName,\n\t\taction: taskInfo.action,\n\t\tname: taskInfo.name || taskInfo.appName,\n\t\tchecked: taskInfo.checked,\n\t};\n}\n\nfunction start (taskList) {\n\tfiles.write(\n\t\tfiles.join(\n\t\t\tcontext.getExternalFilesDir(null),\n\t\t\t\"taskList.json\",\n\t\t),\n\t\tJSON.stringify(\n\t\t\tArray.isArray(taskList)\n\t\t\t\t? taskList.map(parseTaskInfo)\n\t\t\t\t: parseTaskInfo(taskList),\n\t\t),\n\t);\n\treturn getEngine().then(waitForEngineStart).then(waitForEngineStop);\n}\n\nmodule.exports = start;\n"
  },
  {
    "path": "src/miui_cleaner_app/services.js",
    "content": "const findClickableParent = require(\"./findClickableParent\");\nconst servicesTest = DEBUG && require(\"./test/services\");\n\nconst thisPackageName = context.getPackageName();\nconst securityCenterPackageName = \"com.miui.securitycenter\";\nconst settingsPackageName = \"com.android.settings\";\nconst marketPackageName = \"com.xiaomi.market\";\n\nfunction delay (time) {\n\tsleep(time || 0x200);\n}\n\nfunction findByClassName (reg) {\n\treturn selector().filter(\n\t\tuiObject => reg.test(uiObject.className()),\n\t);\n}\n\n/**\n * 向上查找 UiObject 的父节点，找到可滚动的祖先节点\n * @param {UiObject} node 节点\n * @returns\n */\nfunction findScrollableParent (node) {\n\treturn !node || node.scrollable() ? node : findScrollableParent(node.parent());\n}\n\nfunction isFrameLayout (linear) {\n\treturn /\\.FrameLayout$/.test(linear.className());\n}\n\nfunction getDefaultValue (value, defaultValu) {\n\treturn value == null\n\t\t? defaultValu\n\t\t: value\n\t;\n}\n\n// 打开USB调试、关闭下载管理程序资源推荐等，会弹出的二次确认，自动点击\nfunction skipConfirmPopup () {\n\tlet btn;\n\tlet dialog;\n\tdo {\n\t\tconst checkBox = selector().id(\"check_box\").packageName(\"com.miui.securitycenter\").findOnce();\n\t\tif (checkBox && checkBox.checkable() && checkBox.clickable() && !checkBox.checked()) {\n\t\t\tcheckBox.click();\n\t\t}\n\t\tbtn = selector().filter(btn =>\n\t\t\t/\\b(ok|accept|intercept_warn_allow)$/.test(btn.id()) ||\n\t\t\t/^(确[认定](关闭)?|删除)$/.test(btn.text()),\n\t\t).findOne(0x200);\n\t\tif (btn) {\n\t\t\tconst time = /\\((\\d+)\\)$/.exec(btn.text());\n\t\t\tif (time) {\n\t\t\t\tdelay(time[1] * 1000);\n\t\t\t} else {\n\t\t\t\tclickButton(btn);\n\t\t\t}\n\t\t}\n\t\tdialog = selector().filter(dialog =>\n\t\t\t/\\bdialog(_\\w+)*$/.test(dialog.id()),\n\t\t).findOnce();\n\t\tif (dialog) {\n\t\t\tdelay();\n\t\t}\n\t} while (btn || dialog);\n}\n\nfunction walkListView (options = {}) {\n\tconst adCheckBoxList = [];\n\tconst adLinearList = [];\n\tlet inNotifyMgr;\n\tconst listView = findByClassName(/\\.(RecyclerView|ListView)$/).filter(view => {\n\t\tconst packageName = view.packageName();\n\t\treturn options.packageName === packageName || (inNotifyMgr = packageName === settingsPackageName);\n\t}).findOne();\n\tdelay();\n\tconst regSwitchOn = inNotifyMgr\n\t\t? null\n\t\t: getDefaultValue(\n\t\t\toptions.regSwitchOn,\n\t\t\t/^(仅在(WLAN|Wi-?Fi)下.*?|.*?广告(拦截|过滤)|去.*?广告)$/i,\n\t\t);\n\tconst regSwitchOff = inNotifyMgr\n\t\t? /^允许通知$/\n\t\t: getDefaultValue(\n\t\t\toptions.regSwitchOff,\n\t\t\t/猜你喜欢|天气视频|桌面搜索|用户体验|在线(内容)?服务|个性化|消息|广告|热[榜门]|推[荐广]|宫格[栏位]|技巧|热点|新闻|[资快]讯|推送(服务|通知)|通知栏|扫描内存|福利活动|显示天气服务/,\n\t\t);\n\tconst regSubPage = inNotifyMgr\n\t\t? null\n\t\t: getDefaultValue(\n\t\t\toptions.regSubPage,\n\t\t\t/^(高级|其他|消息[与和]?推送|.*?广告(拦截|过滤)|去.*?广告|(.*?((消息)?通知|隐私|功能|个性化|信息流|用户体验|[主首]页|闹钟)(设置|管理|服务|计划|[防保]护)))$/,\n\t\t);\n\tif (!options.handle) {\n\t\toptions.handle = {};\n\t};\n\tconst result = {};\n\tif (options.walk) {\n\t\tObject.assign(\n\t\t\tresult,\n\t\t\toptions.walk(listView, options),\n\t\t);\n\t}\n\tlet linearList = listView.children();\n\tlinearList = Array.from(linearList).filter(linear => linear && linear.clickable() && !isFrameLayout(linear));\n\n\tlinearList.forEach(linear => {\n\t\tconst textViewList = Array.from(linear.find(findByClassName(/\\.TextView$/)));\n\t\tif (!textViewList.length) {\n\t\t\treturn;\n\t\t}\n\t\tconst textList = textViewList.map(textView => textView.text());\n\n\t\t// 判断按钮是否处置过了\n\t\tconst key = textList.join();\n\t\tif (key in options.handle) {\n\t\t\treturn;\n\t\t}\n\t\toptions.handle[key] = null;\n\t\tconst checkBox = linear.findOne(selector().checkable(true));\n\t\tif (checkBox && !checkBox.enabled()) {\n\t\t\treturn;\n\t\t}\n\t\ttextList.some((text) => {\n\t\t\t// 如果 linear 是复选框（CheckBox、Switch）\n\t\t\tif (checkBox) {\n\t\t\t\tlet expect;\n\t\t\t\tif (regSwitchOn && regSwitchOn.test(text)) {\n\t\t\t\t\texpect = true;\n\t\t\t\t} else if (regSwitchOff && regSwitchOff.test(text)) {\n\t\t\t\t\texpect = false;\n\t\t\t\t} else {\n\t\t\t\t\t// continue textList的循环体\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\t// 添加至 adCheckBoxList 中暂存\n\t\t\t\tadCheckBoxList.push({\n\t\t\t\t\tlinear,\n\t\t\t\t\tcheckBox,\n\t\t\t\t\texpect,\n\t\t\t\t\ttext: textList[0],\n\t\t\t\t});\n\t\t\t\t// break textList的循环体\n\t\t\t\treturn true;\n\t\t\t} else if (regSubPage && regSubPage.test(text)) {\n\t\t\t\t// 添加至 adLinearList 中暂存\n\t\t\t\tadLinearList.push({\n\t\t\t\t\tlinear,\n\t\t\t\t\ttext: textList[0],\n\t\t\t\t});\n\t\t\t\t// break textList的循环体\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// continue textList的循环体\n\t\t\treturn false;\n\t\t});\n\t});\n\n\t// 统一处置广告相关开关\n\tadCheckBoxList.forEach(adCheckBox => {\n\t\tif (!adCheckBox.checkBox.enabled()) {\n\t\t\treturn;\n\t\t}\n\t\t// CheckBox的checked属性与预期不符，\n\t\tif (adCheckBox.expect !== adCheckBox.checkBox.checked()) {\n\t\t\t// click一下\n\t\t\tadCheckBox.linear.click();\n\t\t\tskipConfirmPopup();\n\t\t\tconsole.log(`已${adCheckBox.expect ? \"打开\" : \"关闭\"}“${adCheckBox.text}”`);\n\t\t\tresult[adCheckBox.text] = adCheckBox.expect;\n\t\t} else {\n\t\t\tconsole.log(`“${adCheckBox.text}”已处于${adCheckBox.expect ? \"打开\" : \"关闭\"}状态`);\n\t\t}\n\t\toptions.handle[adCheckBox.text] = adCheckBox.expect;\n\t});\n\n\tadLinearList.forEach(adLinear => {\n\t\t// 统一处置广告相关子页面\n\t\tif (options.handle[adLinear.text]) {\n\t\t\treturn;\n\t\t}\n\t\tadLinear.linear.click();\n\t\tconsole.log(`已点击“${adLinear.text}”`);\n\t\tdelay();\n\t\tconst subResult = walkListView({\n\t\t\t...options,\n\t\t\thandle: {},\n\t\t});\n\t\toptions.handle[adLinear.text] = subResult.handle;\n\t\tdelete subResult.handle;\n\t\tresult[adLinear.text] = subResult;\n\t});\n\n\tif (\n\t\t// 判断页面是否需要下滚\n\t\tlistView.scrollable() &&\n\t\t!(\n\t\t\toptions.disableScroll ||\n\t\t\tinNotifyMgr ||\n\t\t\t(options.max && Object.keys(options.handle).filter(key => options.handle[key]).length >= options.max)\n\t\t) &&\n\t\tlistView.scrollForward()\n\t) {\n\t\tconsole.log(\"页面滑动\");\n\t\tdelay();\n\t\tObject.assign(result, walkListView(options));\n\t} else {\n\t\tconsole.log(\"返回\");\n\t\tback();\n\t\tdelay();\n\t}\n\tresult.handle = options.handle;\n\treturn result;\n}\n\nfunction getDelayTimeByPackageName (packageName) {\n\tif (!/\\bbrowser\\b/.test(packageName) && (/^com\\.(android|(xiao)?mi(ui)?)\\./.test(packageName) || /inputmethod/.test(packageName))) {\n\t\t// 系统应用等500毫秒\n\t\treturn 0x200;\n\t}\n\t// 第三方应用等2秒\n\t// console.log(\"任务两秒后开始\");\n\treturn 0x800;\n}\n\n/**\n * 启动应用\n * @param {string} packageName 包名\n * @param {string} className Activity 的类名\n * @returns {bool|null} 是否成功启动\n */\nfunction startPkg ({\n\tpackageName,\n\tactivity,\n\tname,\n}) {\n\tconst result = app.launchPackage(packageName);\n\tif (result) {\n\t\ttoastLog(`正在启动“${name}”，“${packageName}”`);\n\t\tconst delayTime = getDelayTimeByPackageName(packageName);\n\t\tif (activity && activity.startsWith(packageName)) {\n\t\t\twaitForActivity(activity);\n\t\t} else {\n\t\t\twaitForPackage(packageName);\n\t\t\tdelay(delayTime);\n\t\t}\n\t\tdelay(delayTime);\n\t} else {\n\t\tconsole.error(`未安装“${packageName}”`);\n\t}\n\treturn result;\n}\n\n/**\n * 启动 Activity\n * @param {string} packageName 包名\n * @param {string} className Activity 的类名\n * @returns {bool|null} 是否成功启动\n */\nfunction startAct ({\n\tpackageName,\n\tactivity,\n\tname,\n}) {\n\tconst className = activity.replace(/^(?=\\.)/, packageName);\n\tconst opts = {\n\t\tpackageName,\n\t\tclassName,\n\t};\n\tlet result;\n\ttry {\n\t\tresult = app.startActivity(opts);\n\t} catch (ex) {\n\t\tif (/Permission Denial/.test(ex.message)) {\n\t\t\tconsole.error(ex);\n\t\t\treturn startPkg({\n\t\t\t\tpackageName,\n\t\t\t\tactivity: className,\n\t\t\t});\n\t\t} else {\n\t\t\tconsole.error(ex);\n\t\t}\n\t\treturn false;\n\t}\n\n\ttoastLog(`正在启动“${name}”，“${packageName}/${activity}”`);\n\twaitForActivity(className);\n\tdelay(getDelayTimeByPackageName(packageName));\n\treturn result || true;\n}\n\nfunction startIntent ({\n\tpackageName,\n\tintent,\n\tname,\n}) {\n\tintent = android.provider.Settings[intent];\n\tapp.startActivity(\n\t\t/\\bMANAGE_/.test(intent)\n\t\t\t? {\n\t\t\t\taction: intent,\n\t\t\t\tdata: \"package:\" + thisPackageName,\n\t\t\t}\n\t\t\t: new android.content.Intent(intent),\n\t);\n\n\twaitForPackage(packageName);\n\ttoastLog(`正在启动“${name}”，“${intent}”`);\n\tdelay();\n}\n\nfunction clickButton (btnLabelList, text) {\n\tif (!Array.isArray(btnLabelList)) {\n\t\tbtnLabelList = [btnLabelList];\n\t}\n\tbtnLabelList = btnLabelList.filter(btnLabel => {\n\t\tconst button = findClickableParent(btnLabel);\n\t\tif (button) {\n\t\t\tbutton.click();\n\t\t\ttext = text || btnLabel.text();\n\t\t}\n\t\treturn button;\n\t});\n\tif (btnLabelList.length) {\n\t\tconsole.log(`已点击“${text}”`);\n\t\tdelay();\n\t\treturn btnLabelList;\n\t} else {\n\t\treturn null;\n\t}\n}\n\n/**\n * @returns 跳过隐私、用户协议、登录页面、授权页面等\n */\nfunction skipPopupPage () {\n\tconst currAct = currentActivity();\n\tlet btnText;\n\tif (/^com\\.xiaomi\\.market\\..*?privacy/i.test(currAct)) {\n\t\t// 应用商店用户协议页\n\t\tbtnText = \"同意\";\n\t} if (/\\bAlertDialog/i.test(currAct)) {\n\t\tbtnText = \"确定\";\n\t} if (/\\bConfirmStart/i.test(currAct)) {\n\t\t// MIUI 允许启动其他APP\n\t\tbtnText = \"允许\";\n\t} else {\n\t\tclickButton(\n\t\t\t// 知乎用户协议页\n\t\t\t// 知乎账号登录页\n\t\t\tselector().filter(\n\t\t\t\tbtn => /^com\\.zhihu\\.android\\b/.test(btn.packageName()) && /^(tv_agree_continue|ivBack)$/.test(btn.id()),\n\t\t\t).findOnce(),\n\t\t);\n\t\tclickButton(\n\t\t\t// 小米视频、音乐、时钟、小米浏览器国际版等MIUI APP的用户协议页\n\t\t\tselector().filter(\n\t\t\t\tbtn => /^com\\.(xiao)?mi(ui)?\\./.test(btn.packageName()) && (/\\b(v_enable|cta_agree|cta_positive|privacy_continue_btn)$/.test(btn.id()) || /^同意$/.test(btn.text())),\n\t\t\t).findOnce(),\n\t\t);\n\t\tclickButton(\n\t\t\t// 天气、日历、时钟、小米社区等MIUI APP的用户协议页\n\t\t\tselector().packageName(securityCenterPackageName).id(\"cta_positive\").findOnce(),\n\t\t);\n\t}\n\tbtnText && clickButton(selector().text(btnText).findOne());\n}\n\n/**\n * 用 UiSelector 查找之后，选取最右上角的那个\n **/\nfunction findOneByRightTopCorner (uiSelector) {\n\treturn Array.from(\n\t\tuiSelector.untilFind(),\n\t).filter(findClickableParent).sort((uiObjectA, uiObjectB) => {\n\t\tconst rectA = uiObjectA.bounds();\n\t\tconst rectB = uiObjectB.bounds();\n\t\treturn rectA.top - rectB.top || rectB.right - rectA.right;\n\t})[0];\n}\n\n/**\n * 用 UiSelector 查找之后，选取最右下角的那个\n **/\nfunction findOneByRightBottomCorner (uiObjList) {\n\treturn uiObjList.filter(findClickableParent).sort((uiObjectA, uiObjectB) => {\n\t\tconst rectA = uiObjectA.bounds();\n\t\tconst rectB = uiObjectB.bounds();\n\t\treturn rectB.bottom - rectA.bottom || rectB.right - rectA.right;\n\t})[0];\n}\n\n/**\n * 先点击“ ⋮ ” ，再在弹出菜单中点“设置”按钮\n * APP 右上角的“ ⋮ ” 各应用情况\n * 天气：ID：activity_main_more、desc：更多设置\n * 日历：ID：setting_button、desc：设置\n * 下载管理：ID：more、desc：设置\n */\nfunction openCfgPageByPopupMenu (options) {\n\treturn clickButton(\n\t\tfindOneByRightTopCorner(\n\t\t\tselector().filter(\n\t\t\t\tbtn => /\\b(\\w+_)*?(menu|more|settings?)(_\\w+)*?$/.test(btn.id()) || /^(更多)?设置$/.test(btn.desc()),\n\t\t\t),\n\t\t),\n\t\t\" ⋮ \",\n\t) &&\n\tclickButton(selector().text(\"设置\").packageName(options.packageName).untilFind());\n}\n\n/**\n * 先点击“ ≡ ”或者“我的”，再点“⚙️”按钮\n * “≡”按钮各应用情况\n * miui浏览器(简洁模式)：ID：无、desc：菜单\n**/\nfunction openCfgPageBySubPage (options) {\n\tconst btnMineList = [];\n\tconst btnMenuList = [];\n\tselector().packageName(options.packageName).filter(btn => {\n\t\tif (findScrollableParent(btn) || !findClickableParent(btn)) {\n\t\t\treturn false;\n\t\t} else if (/^(我的|未登录|个人中心)$/.test(btn.text() || btn.desc()) || /\\b(\\w+_)*?(my|mine)(_\\w+)*?$/.test(btn.id())) {\n\t\t\tbtnMineList.push(btn);\n\t\t} else if (btn.desc() === \"菜单\") {\n\t\t\tbtnMenuList.push(btn);\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}).untilFind();\n\tconst btnMine = findOneByRightBottomCorner(btnMineList);\n\tconst btnMenu = findOneByRightBottomCorner(btnMenuList);\n\n\tlet btn;\n\tlet btnText;\n\tif (btnMenu) {\n\t\tbtn = btnMenu;\n\t\tbtnText = \" ≡ \";\n\t} else if (btnMine) {\n\t\tbtn = btnMine;\n\t} else {\n\t\treturn null;\n\t}\n\n\treturn clickButton(\n\t\tbtn,\n\t\tbtnText || btn.text() || \"我的\",\n\t) && openCfgPage(options);\n}\n\n/**\n * 应用包安装程序：ID：setting_icon、desc：设置\n * APP “⚙️”按钮各应用情况\n * 应用商店：ID：setting_icon、desc：设置\n * 知乎：ID：setting_btn、desc：无\n * miui浏览器(默认模式)：ID：无、desc：无\n * 应用包安装程序：ID：setting_icon、desc：设置、注：没有“我的”\n */\nfunction openCfgPage (options) {\n\treturn clickButton(\n\t\tfindOneByRightTopCorner(\n\t\t\tselector().packageName(options.packageName).filter(\n\t\t\t\tbtn => /^(更多)?设置$/.test(btn.text() || btn.desc()) || /\\b(\\w+_)*?settings?(_\\w+)*?$/.test(btn.id()),\n\t\t\t),\n\t\t),\n\t\t\"⚙️\",\n\t);\n}\n\n/**\n * 调用MIUI后台清理功能\n */\nfunction clearAnim () {\n\ttry {\n\t\trecents();\n\t} catch (ex) {\n\t\tconsole.error(ex);\n\t\treturn;\n\t}\n\ttoastLog(\"正在清理后台应用\");\n\tdelay();\n\tclickButton(selector().id(\"clearAnimView\").findOne(0x500), \"X\");\n\tdelay(0x400);\n}\n\nfunction startTask (options) {\n\toptions = { ...options };\n\tif (options.intent) {\n\t\tstartIntent(options);\n\t} else if (options.activity) {\n\t\tstartAct(options);\n\t} else {\n\t\tstartPkg(options);\n\t}\n\tconst entry = options.entry;\n\tif (entry) {\n\t\tentry(options);\n\t}\n\tconst result = walkListView(options);\n\n\t// if (entry && entry === openCfgPageBySubPage) {\n\t// \tback();\n\t// }\n\twhile (currentPackage() !== thisPackageName) {\n\t\tback();\n\t\tdelay();\n\t}\n\n\tif (options.done) {\n\t\toptions.done(result);\n\t}\n\tif (DEBUG && servicesTest) {\n\t\tservicesTest(options, result);\n\t}\n\treturn result;\n}\n\nfunction switchBroHomePage (listView, options) {\n\t// 小米浏览器及国际版的特别控件————版式切换\n\tArray.from(listView.children()).some(linear => {\n\t\tif (!linear) {\n\t\t\treturn false;\n\t\t}\n\t\treturn Array.from(\n\t\t\tlinear.find(findByClassName(/\\.TextView$/)),\n\t\t).map(\n\t\t\ttextView => textView.text(),\n\t\t).some((text, index) => {\n\t\t\tif (text !== \"简洁版\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst btn = linear.children().filter(btn => btn.clickable())[index];\n\t\t\tif (btn) {\n\t\t\t\tbtn.click();\n\t\t\t\toptions.handle[text] = true;\n\t\t\t\tconsole.log(`“${options.appName}”首页已切换为“${text}”`);\n\t\t\t\tdelay();\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t});\n}\n\nconst browserConfig = {\n\tpackageName: \"com.android.browser\",\n\tactivity: \"com.android.browser.BrowserActivity\",\n\tentry: openCfgPageBySubPage,\n\twalk: switchBroHomePage,\n\t// done: console.log,\n};\n\n// 在“关于手机”手机界面，点击“MIUI版本” 10次\nfunction click10 (listView, options) {\n\tlistView = selector().packageName(settingsPackageName).scrollable(true).findOne();\n\tlet textView;\n\tdo {\n\t\ttextView = selector().packageName(settingsPackageName).filter(\n\t\t\ttextView => /\\bcard_title$/.test(textView.id()) && /^MIUI/.test(textView.text()),\n\t\t).findOnce();\n\t} while (!textView && listView.scrollForward());\n\tif (textView) {\n\t\tconst text = textView.text();\n\t\tconst btnMiui = findClickableParent(textView);\n\t\tfor (let index = 1; index < 0xF; index++) {\n\t\t\tbtnMiui.click();\n\t\t\tconsole.log(`已点击“${text}”${index}次`);\n\t\t\toptions.handle[text] = index;\n\t\t}\n\t}\n}\n// function openInstallerHelper () {\n// \tconst packageName = \"com.miui.packageinstaller\";\n// \tconst appName = app.getAppName(packageName);\n\n// \tclickButton(\n// \t\tselector().filter(btn => (\n// \t\t\tappName === btn.text() && /\\bandroid\\b/.test(btn.packageName())\n// \t\t)).findOnce()\n// \t);\n// \tclickButton(\n// \t\tselector().packageName(packageName).text(\"确定\").findOnce()\n// \t);\n// }\n\n// function getSystemConfig (key) {\n// \treturn getSysConfig(\"Secure\", key);\n// }\n\nconst cleanerList = [\n\t{\n\t\tname: \"系统安全\",\n\t\tpackageName: settingsPackageName,\n\t\tintent: \"ACTION_SECURITY_SETTINGS\",\n\t\tregSubPage: /(广告|链接调用)/,\n\t\tregSwitchOff: /诊断数据|广告推荐|链接调用|用户体验/,\n\t},\n\t{\n\t\tname: \"关于手机\",\n\t\tpackageName: settingsPackageName,\n\t\tintent: \"ACTION_DEVICE_INFO_SETTINGS\",\n\t\tregSubPage: null,\n\t\tregSwitchOff: null,\n\t\tregSwitchOn: null,\n\t\twalk: click10,\n\t},\n\t{\n\t\tname: \"开发者选项\",\n\t\tpackageName: settingsPackageName,\n\t\tintent: \"ACTION_APPLICATION_DEVELOPMENT_SETTINGS\",\n\t\tregSubPage: null,\n\t\tregSwitchOff: null,\n\t\tregSwitchOn: /^USB\\b/,\n\t\tmax: 3,\n\t},\n\t{\n\t\t// `广告服务` 位于 `安全` 的子页面\n\t\tname: \"广告服务\",\n\t\tpackageName: settingsPackageName,\n\t\tactivity: \".ad.AdServiceSettings\",\n\t\tregSwitchOff: /.*/,\n\t\tregSwitchOn: null,\n\t},\n\t{\n\t\t// 小米帐号\n\t\tpackageName: \"com.xiaomi.account\",\n\t\t// activity: \".settings.SystemAdActivity\",\n\t\tactivity: \".ui.AccountSettingsActivity\",\n\t\tregSubPage: /^关于.*?[帐账]号|系统.*?广告$/,\n\t\tregSwitchOff: /^系统.*?广告$/,\n\t},\n\t{\n\t\t// 手机管家 -> 设置页\n\t\tpackageName: securityCenterPackageName,\n\t\tactivity: \"com.miui.securityscan.ui.settings.SettingsActivity\",\n\t},\n\t{\n\t\t// 手机管家 -> 应用管理\n\t\tname: \"应用管理\",\n\t\tpackageName: securityCenterPackageName,\n\t\tactivity: \"com.miui.appmanager.AppManagerMainActivity\",\n\t\tentry: openCfgPageByPopupMenu,\n\t},\n\t{\n\t\t// 垃圾清理 -> 设置页\n\t\tpackageName: \"com.miui.cleanmaster\",\n\t\tactivity: \"com.miui.optimizecenter.settings.SettingsActivity\",\n\t},\n\t{\n\t\t// 应用商店\n\t\tpackageName: marketPackageName,\n\t\t// activity: \".ui.MarketTabActivity\",\n\t\tentry: openCfgPageBySubPage,\n\t},\n\t{\n\t\t// 应用包管理组件\n\t\tpackageName: \"com.miui.packageinstaller\",\n\t\tfn: function (options) {\n\t\t\tconst googlePkgName = \"com.google.android.packageinstaller\";\n\t\t\tlet googleAppName = app.getAppName(googlePkgName);\n\t\t\tconst miuiPkgName = \"com.miui.packageinstaller\";\n\t\t\tconst Intent = android.content.Intent;\n\n\t\t\tconst Uri = android.net.Uri;\n\n\t\t\tfunction installApk (apkPath, resolver) {\n\t\t\t\tconst intent = new Intent();\n\t\t\t\tintent.setAction(Intent.ACTION_VIEW);\n\t\t\t\tif (resolver) {\n\t\t\t\t\tintent.setComponent(new android.content.ComponentName(\"android\", \"com.android.internal.app.ResolverActivity\"));\n\t\t\t\t} else {\n\t\t\t\t\tintent.setPackage(miuiPkgName);\n\t\t\t\t}\n\t\t\t\tintent.setDataAndType(\n\t\t\t\t\tUri.parse(apkPath),\n\t\t\t\t\t\"application/vnd.android.package-archive\",\n\t\t\t\t);\n\t\t\t\tapp.startActivity(intent);\n\t\t\t}\n\n\t\t\tfunction closeApp (pkgName) {\n\t\t\t\tif (selector().packageName(pkgName).findOne(0x500)) {\n\t\t\t\t\tback();\n\t\t\t\t\tdelay();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction selectApp (pkgName, appName) {\n\t\t\t\tappName = appName || app.getAppName(pkgName);\n\t\t\t\tconst checkBox = selector().filter(checkBox =>\n\t\t\t\t\t/\\balways_option$/.test(checkBox.id()),\n\t\t\t\t).packageName(\"android\").checkable(true).findOne(0x500);\n\t\t\t\tif (checkBox) {\n\t\t\t\t\tif (!checkBox.checked()) {\n\t\t\t\t\t\tclickButton(\n\t\t\t\t\t\t\tcheckBox,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tclickButton(\n\t\t\t\t\t\tselector().text(appName).packageName(\"android\").findOnce(),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcloseApp(pkgName);\n\t\t\t}\n\n\t\t\tinstallApk(googleAppName ? `content://${context.getPackageName()}/null.apk` : \"file:///system/priv-app/PackageInstaller/PackageInstaller.apk\");\n\t\t\tif (selector().packageName(miuiPkgName).findOne(0x500)) {\n\t\t\t\tconst btnSetting = selector().id(\"setting_icon\").packageName(miuiPkgName).clickable(true).findOnce();\n\t\t\t\tif (btnSetting) {\n\t\t\t\t\t// 关闭安装监控、安全检查\n\t\t\t\t\tclickButton(btnSetting);\n\t\t\t\t\twalkListView(options);\n\t\t\t\t}\n\t\t\t\tif (!googleAppName) {\n\t\t\t\t\t// 自动安装google安装器\n\t\t\t\t\tclickButton(\n\t\t\t\t\t\tselector().text(\"安装\").packageName(miuiPkgName).findOne(),\n\t\t\t\t\t);\n\t\t\t\t\tclickButton(\n\t\t\t\t\t\tselector().text(\"完成\").packageName(miuiPkgName).findOne(),\n\t\t\t\t\t);\n\t\t\t\t\tgoogleAppName = app.getAppName(googlePkgName);\n\t\t\t\t}\n\t\t\t\tcloseApp(miuiPkgName);\n\t\t\t}\n\n\t\t\tif (googleAppName) {\n\t\t\t\t// 设置默认由miui安装器卸载APP\n\t\t\t\tconst intent = new Intent();\n\t\t\t\tintent.setAction(Intent.ACTION_DELETE);\n\t\t\t\tintent.setData(\n\t\t\t\t\tUri.parse(\"package:\" + miuiPkgName),\n\t\t\t\t);\n\t\t\t\tapp.startActivity(intent);\n\t\t\t\tselectApp(miuiPkgName);\n\t\t\t\tcloseApp(miuiPkgName);\n\t\t\t\t// 设置默认由google安装器安装APP\n\t\t\t\tinstallApk(\"content://null.null/null.apk\", true);\n\t\t\t\tselectApp(googlePkgName, googleAppName);\n\t\t\t}\n\t\t},\n\t\tregSwitchOff: /安全检查|安装监控|纯净模式|资源推荐/,\n\t},\n\t{\n\t\t// 应用商店\n\t\tpackageName: marketPackageName,\n\t\taction: \".ui.CommonWebActivity\",\n\t\tfn: (configs) => {\n\t\t\tstartPkg(configs);\n\t\t\t[\n\t\t\t\t\"我的\",\n\t\t\t\t\"系统应用管理\",\n\t\t\t].every(text => clickButton(\n\t\t\t\tselector().packageName(configs.packageName).text(text).findOne(0xFFF),\n\t\t\t));\n\t\t},\n\t},\n\t{\n\t\t// 下载管理程序\n\t\tpackageName: \"com.android.providers.downloads.ui\",\n\t\tactivity: \".activity.DownloadSettingActivity\",\n\t},\n\t{\n\t\t// 日历\n\t\tpackageName: \"com.android.calendar\",\n\t\tactivity: \".settings.CalendarActionbarSettingsActivity\",\n\t},\n\t{\n\t\t// 时钟\n\t\tpackageName: \"com.android.deskclock\",\n\t\tactivity: \".settings.SettingsActivity\",\n\t\tregSubPage: /(生活早报|更多.*?设置)/,\n\t\tregSwitchOff: /生活早报/,\n\t},\n\t{\n\t\t// 小米社区\n\t\tpackageName: \"com.xiaomi.vipaccount\",\n\t\tactivity: \".ui.home.page.HomeFrameActivity\",\n\t\tentry: openCfgPageBySubPage,\n\t},\n\t{\n\t\t// 小米天气\n\t\tpackageName: \"com.miui.weather2\",\n\t\tactivity: \".ActivityWeatherMain\",\n\t\tentry: openCfgPageByPopupMenu,\n\t},\n\t{\n\t\t// 小米视频\n\t\tpackageName: \"com.miui.video\",\n\t\tactivity: \".feature.mine.setting.SettingActivity\",\n\t},\n\t// {\n\t// \t// 音乐\n\t// \t// v3.51.1.1 下载 https://xiaoheiya.lanzoum.com/iVMeWnhkh5c\n\t// \tpackageName: \"com.miui.player\",\n\t// \tactivity: \".phone.ui.MusicSettings\",\n\t// \t// start: function () {\n\t// \t// \tapp.uninstall(\"com.miui.player\");\n\t// \t// },\n\t// \t// \"com.tencent.qqmusiclite.activity.MainActivity\",\n\t// },\n\t{\n\t\t// 小爱语音\n\t\tpackageName: \"com.miui.voiceassist\",\n\t\tactivity: \"com.xiaomi.voiceassistant.settings.MiuiVoiceSettingActivity\",\n\t},\n\t{\n\t\t// 搜索\n\t\tpackageName: \"com.android.quicksearchbox\",\n\t\tactivity: \".preferences.SearchSettingsPreferenceActivity\",\n\t\tregSubPage: /^(.*?快捷方式|搜索项|.*?展示模块|热搜榜单)$/,\n\t\tregSwitchOn: /广告过滤/,\n\t\tregSwitchOff: /桌面搜索框$|搜索精选|热门资讯/,\n\t\twalk: (listView, opts) => {\n\t\t\tif (selector().text(\"热榜管理\").id(\"action_bar_title\").findOnce()) {\n\t\t\t\tlet linearList = Array.from(listView.children());\n\t\t\t\tlinearList = linearList.slice(\n\t\t\t\t\t0,\n\t\t\t\t\tlinearList.findIndex(isFrameLayout),\n\t\t\t\t).reverse();\n\t\t\t\tlinearList.forEach(linear => {\n\t\t\t\t\tlinear.findOne(selector().clickable(true)).click();\n\t\t\t\t});\n\t\t\t\tif (linearList.length) {\n\t\t\t\t\tconsole.log(\"已关闭“热榜管理”中的所模块\");\n\t\t\t\t\treturn {\n\t\t\t\t\t\t热搜榜s: false,\n\t\t\t\t\t};\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(\"“热榜管理”中的所模块已处于关闭状态\");\n\t\t\t\t\topts.handle[\"热搜榜s\"] = false;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t},\n\t{\n\t\t// 小米浏览器国际版\n\t\t...browserConfig,\n\t\tpackageName: \"com.mi.globalbrowser\",\n\t},\n\t{\n\t\t// 小米浏览器国内版\n\t\t...browserConfig,\n\t},\n\t// {\n\t// \t// 讯飞输入法\n\t// \tpackageName: \"com.iflytek.inputmethod\",\n\t// \t// activity: \".LauncherActivity\",\n\t// \tentry: openCfgPageBySubPage,\n\t// },\n\t// {\n\t// \t// 知乎\n\t// \tpackageName: \"com.zhihu.android\",\n\t// \tactivity: \".app.ui.activity.MainActivity\",\n\t// \tentry: openCfgPageBySubPage,\n\t// \tdisableScroll: true,\n\t// \t// 打开知乎去广告插件-知了\n\t// \tregSubPage: /^知了$/,\n\t// \tregSwitchOn: /^(启用知了|去.*?广告)$/,\n\t// \tregSwitchOff: false,\n\t// \tdone: function (result) {\n\t// \t\tif (!Object.keys(result.handle).length) {\n\t// \t\t\tstartTask(this);\n\t// \t\t} else if (!result.handle[\"知了\"]) {\n\t// \t\t\tconsole.hide();\n\t// \t\t\tif (dialogs.confirm(\"是否下载去广告版知乎？\")) {\n\t// \t\t\t\tdownApp(this.packageName);\n\t// \t\t\t\tstartTask(this);\n\t// \t\t\t}\n\t// \t\t} else if (result[\"知了\"][\"启用知了\"]) {\n\t// \t\t\ttoastLog(\"重启知乎后生效\");\n\t// \t\t}\n\t// \t}\n\t// },\n\n].map(menuItem => {\n\tif (typeof menuItem === \"string\") {\n\t\tmenuItem = {\n\t\t\tpackageName: menuItem,\n\t\t};\n\t}\n\treturn menuItem;\n});\n\n// function offAdByWriteSettings () {\n// \tconst settings = requestSettings({\n// \t\twriteSettings: true,\n// \t\taccessibility: true,\n// \t\tdrawOverlay: true,\n// \t});\n\n// \tif (settings.writeSettings) {\n// \t\ttry {\n// \t\t\t// https://developer.android.google.cn/reference/android/provider/Settings\n// \t\t\t// 系统安全 -> 广告服务 -> 个性化广告推荐：关闭\n// \t\t\tSettings.Global.putInt(resolver, \"personalized_ad_enabled\", 0);\n// \t\t\t[\n// \t\t\t\t// 网页链接调用服务\n// \t\t\t\t\"http_invoke_app\",\n// \t\t\t\t// 加入“用户体验改进计划”\n// \t\t\t\t\"upload_log_pref\",\n// \t\t\t\t// 自动发送诊断数据\n// \t\t\t\t\"upload_debug_log_pref\",\n// \t\t\t].forEach(key => Settings.Secure.putInt(resolver, key, 0));\n// \t\t} catch (ex) {\n// \t\t\t//\n// \t\t}\n// \t}\n// \tif (settings.drawOverlay) {\n// \t\t// 如果有悬浮窗权限，打开控制台\n// \t\tconsole.clear();\n// \t\tconsole.show();\n// \t}\n// }\n\n// function offAppAd () {\n// \t// offAdBywriteSettings();\n// \tconst menuItemList = cleanerList.filter((appInfo) => {\n// \t\tif (appInfo.packageName && !appInfo.appName && !appInfo.name) {\n// \t\t\tappInfo.appName = app.getAppName(appInfo.packageName);\n// \t\t\tif (!appInfo.appName) {\n// \t\t\t\treturn false;\n// \t\t\t}\n// \t\t}\n// \t\tappInfo.summary = appDesc[appInfo.packageName] || \"\";\n// \t\treturn true;\n// \t});\n\n// \tmultiChoice({\n// \t\ttitle: \"请选择要关闭广告的APP\",\n// \t\titemList: menuItemList,\n// \t\tchecked: true,\n// \t}).then(tasks => {\n// \t\tconsole.show();\n// \t\tconst timerSkipPopupPage = setInterval(skipPopupPage, 0x50);\n// \t\ttasks.forEach(task => {\n// \t\t\t// if (task.test && !task.test(task)) {\n// \t\t\t// \treturn;\n// \t\t\t// }\n// \t\t\tconsole.log(task.name || task.appName);\n// \t\t\t// ;\n// \t\t});\n// \t\tclearInterval(timerSkipPopupPage);\n// \t}).then(taskList => {\n// \t\toffAppAd();\n// \t}).catch(console.error);\n// };\n\nconst actionManageNames = {\n\tACTION_MANAGE_UNKNOWN_APP_SOURCES: \"安装未知应用\",\n\tACTION_MANAGE_OVERLAY_PERMISSION: \"允许显示在其他应用的上层\",\n\tACTION_MANAGE_WRITE_SETTINGS: \"允许修改系统设置\",\n};\n\nfunction getSettings (key) {\n\tconsole.log(key);\n}\n\nfunction runTask (taskInfo) {\n\tif (taskInfo.action === \"clearAnim\") {\n\t\treturn clearAnim();\n\t} else if (/^ACTION_MANAGE_/.test(taskInfo.action)) {\n\t\t// 打开各种单项权限管理界面\n\t\treturn startTask({\n\t\t\tpackageName: settingsPackageName,\n\t\t\tintent: taskInfo.action,\n\t\t\tregSwitchOff: taskInfo.checked ? null : /.*/,\n\t\t\tregSwitchOn: taskInfo.checked ? /.*/ : null,\n\t\t\tname: actionManageNames[taskInfo.action] || taskInfo.action,\n\t\t});\n\t}\n\n\tlet cleaner = cleanerList.filter(cleaner => (\n\t\tcleaner.packageName === taskInfo.packageName\n\t));\n\n\tif (cleaner.length > 1) {\n\t\tcleaner = cleanerList.filter(cleaner => (\n\t\t\t(cleaner.action || cleaner.activity || cleaner.intent) === taskInfo.action\n\t\t));\n\t}\n\tcleaner = cleaner[0];\n\tif (DEBUG && !cleaner) {\n\t\tconsole.error(\"未找到适配的任务\");\n\t\tconsole.error(taskInfo);\n\t}\n\tconst name = taskInfo.name || taskInfo.appName || cleaner.name;\n\tif (name) {\n\t\tcleaner.name = name;\n\t}\n\tif (cleaner.settings && !cleaner.settings.every(getSettings)) {\n\t\tconsole.log(`跳过任务：${name}`);\n\t\treturn;\n\t}\n\treturn cleaner.fn ? cleaner.fn(cleaner) : startTask(cleaner);\n}\n\nfunction runTaskList (taskList) {\n\tauto.waitFor();\n\tconst timerSkipPopupPage = setInterval(skipPopupPage, 0x50);\n\n\tif (Array.isArray(taskList)) {\n\t\tif (floaty.checkPermission()) {\n\t\t\tconsole.show();\n\t\t}\n\t\ttaskList = taskList.map(runTask);\n\t} else {\n\t\ttaskList = runTask(taskList);\n\t}\n\tclearInterval(timerSkipPopupPage);\n\tconsole.hide();\n\treturn taskList;\n}\n\nfunction init () {\n\tconst configFilePath = files.join(\n\t\tcontext.getExternalFilesDir(null),\n\t\t\"taskList.json\",\n\t);\n\trunTaskList(\n\t\tJSON.parse(\n\t\t\tfiles.read(configFilePath),\n\t\t),\n\t);\n\tfiles.remove(configFilePath);\n}\n\ninit();\n"
  },
  {
    "path": "src/miui_cleaner_app/settings.js",
    "content": "const waitForBack = require(\"./waitForBack\");\nconst serviceMgr = require(\"./serviceMgr\");\nconst project = require(\"./project.json\");\nconst dialogs = require(\"./dialogs\");\n\nconst settingsPackageName = \"com.android.settings\";\nconst settingsPrototype = Object.create(android.provider.Settings);\nsettingsPrototype.keys = function () {\n\treturn Object.getOwnPropertyNames(this).sort();\n};\nsettingsPrototype.has = Object.prototype.hasOwnProperty;\nsettingsPrototype.get = function (key) {\n\tif (this.has(key)) {\n\t\treturn this[key];\n\t}\n};\nconst requestCache = {};\nfunction lazyAction (key, action) {\n\tlet resolve = requestCache[key];\n\tif (!resolve) {\n\t\tresolve = Promise.resolve();\n\t}\n\tif (action) {\n\t\tresolve = resolve.then(action);\n\t}\n\trequestCache[key] = resolve;\n\treturn resolve;\n}\n\nsettingsPrototype.set = function (key, expectValue, reason) {\n\tif (this.has(key)) {\n\t\tthis[key] = expectValue;\n\t\tif ((this[key] !== expectValue)) {\n\t\t\tif (expectValue) {\n\t\t\t\tconst space = settingProperties[key].space;\n\t\t\t\tif (((space && space !== \"Secure\") || /^accessibility/.test(key)) && !this.writeSettings && this.accessibilityServiceEnabled) {\n\t\t\t\t\tlazyAction(\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\t() => this.set(\"writeSettings\", expectValue, reason),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst depend = settingProperties[key].depend;\n\t\t\t\tif (depend && !this[depend]) {\n\t\t\t\t\tlazyAction(\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\t() => this.set(depend, expectValue, reason),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tlazyAction(\n\t\t\t\tkey,\n\t\t\t\t() => requestSettings(key, expectValue, reason),\n\t\t\t);\n\t\t}\n\t\tlazyAction(\n\t\t\tkey,\n\t\t\t() => this[key],\n\t\t);\n\t}\n\treturn lazyAction(key);\n};\nsettingsPrototype.forEach = function (callbackFn, thisArg) {\n\tthis.keys().forEach(\n\t\t(key) => {\n\t\t\tcallbackFn.call(thisArg || this, settings[key], key, settings);\n\t\t},\n\t\tthisArg,\n\t);\n};\nconst settings = Object.create(settingsPrototype);\n\nconst actions = {\n\tACTION_MANAGE_UNKNOWN_APP_SOURCES: /^requestInstallPackages$/,\n\tACTION_MANAGE_OVERLAY_PERMISSION: /^drawOverlays$/,\n\tACTION_MANAGE_WRITE_SETTINGS: /^writeSettings$/,\n\tACTION_DEVICE_INFO_SETTINGS: /^development$/,\n\tACTION_APPLICATION_DEVELOPMENT_SETTINGS: /^adb/,\n};\n\nfunction requestSettings (key, expectValue, reason) {\n\tif (key === \"accessibilityServiceEnabled\") {\n\t\treason = reason ? `，以便“${project.name}”能${reason}` : \"\";\n\t\treturn dialogs.confirm(\n\t\t\t`请在下个页面，点击“已下载的服务”，然后${expectValue ? \"打开\" : \"关闭\"}“${project.name}”的无障碍服务开关${reason}。`,\n\t\t\t{\n\t\t\t\ttitle: \"权限请求\",\n\t\t\t},\n\t\t).then((confirm) => {\n\t\t\treturn confirm && waitForBack(() => {\n\t\t\t\tif (expectValue) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tauto();\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t//\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tapp.startActivity(new android.content.Intent(settings.ACTION_ACCESSIBILITY_SETTINGS));\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\tfor (const actionName in actions) {\n\t\tif (actions[actionName].test(key)) {\n\t\t\treturn settings.set(\n\t\t\t\t\"accessibilityServiceEnabled\",\n\t\t\t\ttrue,\n\t\t\t\treason,\n\t\t\t).then(accessibilityServiceEnabled => (\n\t\t\t\taccessibilityServiceEnabled && serviceMgr({\n\t\t\t\t\tpackageName: settingsPackageName,\n\t\t\t\t\tchecked: expectValue,\n\t\t\t\t\taction: actionName,\n\t\t\t\t})\n\t\t\t));\n\t\t}\n\t}\n}\nfunction tryCmd (cmd, root = true) {\n\ttry {\n\t\treturn shell(cmd, root);\n\t} catch (ex) {\n\t\tif (root) {\n\t\t\treturn tryCmd(cmd, false);\n\t\t}\n\t}\n}\n\n// function switchACheckBox (expect = true, packageName = settingsPackageName) {\n// \tlet checkBox = selector().packageName(packageName).checkable(true).findOne();\n// \tlet value = checkBox.checked();\n// \tif (value === expect) {\n// \t\treturn expect;\n// \t} else {\n// \t\tcheckBox = findClickableParent(checkBox);\n// \t\tif (checkBox) {\n// \t\t\tcheckBox.click();\n// \t\t\tvalue = expect;\n// \t\t}\n// \t}\n// \treturn value;\n// }\n\nfunction pmPermission (key, permission) {\n\t// pm grant org.autojs.autoxjs.v6 android.permission.WRITE_SETTINGS\n\t// pm grant org.autojs.autoxjs.v6 android.permission.WRITE_SECURE_SETTINGS\n\t// pm grant org.autojs.autoxjs.v6 android.permission.SYSTEM_ALERT_WINDOW\n\t// pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SETTINGS\n\t// pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SECURE_SETTINGS\n\t// pm grant com.github.gucong3000.miui.cleaner android.permission.SYSTEM_ALERT_WINDOW\n\t// grantRuntimePermission\n\t// revokeRuntimePermission\n\treturn (expectValue) => {\n\t\ttryCmd(`pm ${expectValue ? \"grant\" : \"revoke\"} ${context.getPackageName()} android.permission.${permission}`, expectValue);\n\t};\n}\n\nconst accessServicesName = context.getPackageName() + \"/com.stardust.autojs.core.accessibility.AccessibilityService\";\nconst settingProperties = {\n\t// rhinoVersion: {\n\t// \tget: () => org.mozilla.javascript.Context.getCurrentContext().getImplementationVersion(),\n\t// },\n\trequestInstallPackages: {\n\t\tenumerable: true,\n\t\tget: () => context.getPackageManager().canRequestPackageInstalls(),\n\t\t// get: () => Boolean(context.checkCallingOrSelfPermission(\"android.permission.REQUEST_INSTALL_PACKAGES\")),\n\t\tset: pmPermission(\"requestInstallPackages\", \"REQUEST_INSTALL_PACKAGES\"),\n\t},\n\twriteSettings: {\n\t\tenumerable: true,\n\t\tget: () => settings.System.canWrite(context),\n\t\t// get: () => Boolean(context.checkCallingOrSelfPermission(\"android.permission.WRITE_SETTINGS\")),\n\t\tset: pmPermission(\"writeSettings\", \"WRITE_SETTINGS\"),\n\t},\n\twriteSecureSettings: {\n\t\tenumerable: true,\n\t\tget: () => {\n\t\t\tif (!context.checkCallingOrSelfPermission(\"android.permission.WRITE_SECURE_SETTINGS\")) {\n\t\t\t\ttry {\n\t\t\t\t\tsettings.Secure.putInt(context.getContentResolver(), \"accessibility_enabled\", 1);\n\t\t\t\t} catch (ex) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t\tset: pmPermission(\"writeSecureSettings\", \"WRITE_SECURE_SETTINGS\"),\n\t},\n\tdrawOverlays: {\n\t\tenumerable: true,\n\t\tget: () => settings.canDrawOverlays(context),\n\t\t// get: () => Boolean(context.checkCallingOrSelfPermission(\"android.permission.SYSTEM_ALERT_WINDOW\")),\n\t\tset: pmPermission(\"drawOverlays\", \"SYSTEM_ALERT_WINDOW\"),\n\t},\n\taccessibilityServiceEnabled: {\n\t\tdepend: \"accessibility\",\n\t\tenumerable: true,\n\t\tget: () => Boolean(auto.service),\n\t\tset: (value) => {\n\t\t\tif (value) {\n\t\t\t\tsettings.accessibilityServices.add(accessServicesName);\n\t\t\t} else {\n\t\t\t\tsettings.accessibilityServices.delete(accessServicesName);\n\t\t\t}\n\t\t},\n\t},\n\tadbInput: {\n\t\tenumerable: true,\n\t\tdepend: \"adb\",\n\t\tget: () => settings.adb && (getAdbInput() || getAdbInput()),\n\t\tset: (expectValue) => {\n\t\t\tenableDependSetting(\"adb\", expectValue);\n\t\t\ttryCmd(\"setprop persist.security.adbinput \" + (expectValue ? 1 : 0));\n\t\t},\n\t},\n};\n\nfunction getAdbInput () {\n\treturn Boolean(tryCmd(\"getprop persist.security.adbinput\", false).result - 0);\n}\n\nfunction enableDependSetting (depend, value) {\n\tif (depend && value) {\n\t\tsettings[depend] = Boolean(value);\n\t}\n}\n// 基于系统设置的选项\n// https://developer.android.google.cn/reference/android/provider/Settings.Global\nfunction defineSettingProperty ({\n\tspace = \"Global\",\n\ttype = \"Int\",\n\tkey,\n\tdepend,\n\tget = Boolean,\n\tset = value => value ? 1 : 0,\n}) {\n\tconst propertyName = key\n\t\t.replace(/^enabled_|(_settings?)?_(enabled|status|pref)$/, \"\")\n\t\t.replace(/_(ad$|\\w)/g, s => s.slice(1).toUpperCase());\n\tconst descriptor = {\n\t\tenumerable: true,\n\t\tdepend,\n\t\tspace,\n\t\tget: () => {\n\t\t\tlet value;\n\t\t\ttry {\n\t\t\t\tvalue = settings[space][\"get\" + type](context.getContentResolver(), key);\n\t\t\t} catch (ex) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treturn get ? get(value) : value;\n\t\t},\n\t\tset: (expectValue) => {\n\t\t\texpectValue = set ? set(expectValue) : expectValue;\n\t\t\tenableDependSetting(depend, expectValue);\n\t\t\ttry {\n\t\t\t\tsettings[space][\"put\" + type](context.getContentResolver(), key, expectValue);\n\t\t\t} catch (ex) {\n\t\t\t\t// console.error(`将${propertyName}设置为${expectValue}时失败\\n${ex.message}`);\n\t\t\t}\n\t\t},\n\t};\n\tsettingProperties[propertyName] = descriptor;\n\tif (!depend) {\n\t\tdepend = space === \"Secure\" ? \"writeSecureSettings\" : \"writeSettings\";\n\t}\n}\n\n// 开发者选项\ndefineSettingProperty({\n\tkey: \"development_settings_enabled\",\n});\n\n// `USB调试`，既ADB权限\ndefineSettingProperty({\n\tkey: \"adb_enabled\",\n\tdepend: \"development\",\n});\n\n// 小米帐号 → 关于小米帐号 → 系统广告 → 系统工具广告\ndefineSettingProperty({\n\tkey: \"passport_ad_status\",\n\ttype: \"String\",\n\tget: value => !/^OFF$/i.test(value),\n\tset: value => value ? \"ON\" : \"OFF\",\n});\n\n// 系统安全 → 广告服务 → 个性化广告推荐\ndefineSettingProperty({\n\tkey: \"personalized_ad_enabled\",\n});\n\n// 无障碍服务列表\ndefineSettingProperty({\n\tspace: \"Secure\",\n\ttype: \"String\",\n\tkey: \"enabled_accessibility_services\",\n\tget: null,\n\tset: null,\n});\n\n((accessibilityServices) => {\n\tconst {\n\t\tset,\n\t\tget,\n\t} = accessibilityServices;\n\tlet services;\n\tconst toString = () => Array.from(services).join(\":\");\n\tconst proxy = {\n\t\ttoString,\n\t\tinspect: toString,\n\t\ttoJSON: toString,\n\t};\n\n\tObject.getOwnPropertyNames(Set.prototype).forEach(propertyName => {\n\t\tproxy[propertyName] = (...args) => {\n\t\t\tconst result = services[propertyName](...args);\n\t\t\tset(toString());\n\t\t\tsettings.accessibility = true;\n\t\t\treturn result;\n\t\t};\n\t});\n\n\taccessibilityServices.get = () => {\n\t\tservices = new Set(get().trim().split(/\\s*:\\s*/g));\n\t\treturn Object.create(proxy);\n\t};\n\tdelete accessibilityServices.set;\n})(settingProperties.accessibilityServices);\n\n[\n\t// 无障碍服务开关\n\t\"accessibility_enabled\",\n\t// 系统安全 → 网页链接调用服务\n\t\"http_invoke_app\",\n\t// 系统安全 →加入“用户体验改进计划”\n\t\"upload_log_pref\",\n\t// 系统安全 → 自动发送诊断数据\n\t\"upload_debug_log_pref\",\n].forEach(key => {\n\tdefineSettingProperty({\n\t\tspace: \"Secure\",\n\t\tkey,\n\t});\n});\n\nObject.defineProperties(\n\tsettings,\n\tsettingProperties,\n);\n\nsettings.accessibilityServiceEnabled = true;\nsettings.adb = true;\n\nmodule.exports = settings;\n"
  },
  {
    "path": "src/miui_cleaner_app/singleChoice.js",
    "content": "const emitItemShowEvent = require(\"./emitItemShowEvent\");\nconst project = require(\"./project.json\");\nconst View = android.view.View;\n\nfunction singleChoice (\n\t{\n\t\titemList,\n\t\ttitle,\n\t\ticon = \"./res/drawable/ic_android.png\",\n\t\tfn = null,\n\t},\n) {\n\ticon = /^\\w+:\\/\\//.test(icon) ? icon : (\"file://\" + files.path(icon));\n\n\tui.layout(`\n\t\t<frame>\n\t\t\t<vertical>\n\t\t\t\t<appbar>\n\t\t\t\t\t<toolbar id=\"toolbar\" title=\"${project.name}\" subtitle=\"${title}\" />\n\t\t\t\t</appbar>\n\t\t\t\t<relative id=\"progress\" h=\"*\" visibility=\"gone\">\n\t\t\t\t\t<progressbar layout_centerInParent=\"true\" />\n\t\t\t\t</relative>\n\t\t\t\t<list id=\"itemList\">\n\t\t\t\t\t<relative w=\"*\">\n\t\t\t\t\t\t<card w=\"*\" margin=\"0 0 0 10\" foreground=\"?selectableItemBackground\">\n\t\t\t\t\t\t\t<horizontal gravity=\"center_vertical\">\n\t\t\t\t\t\t\t\t<img id=\"icon\" h=\"48\" w=\"48\" src=\"{{this.icon || '${icon}'}}\" margin=\"10 10 0 10\" />\n\t\t\t\t\t\t\t\t<vertical h=\"auto\" layout_weight=\"1\" margin=\"10 0\">\n\t\t\t\t\t\t\t\t\t<text text=\"{{this.displayName || this.appName || this.name}}\" textColor=\"#333333\" textSize=\"16sp\" maxLines=\"1\" />\n\t\t\t\t\t\t\t\t\t<text text=\"{{this.summary}}\" textColor=\"#999999\" textSize=\"14sp\" maxLines=\"1\" />\n\t\t\t\t\t\t\t\t</vertical>\n\t\t\t\t\t\t\t</horizontal>\n\t\t\t\t\t\t</card>\n\t\t\t\t\t</relative>\n\t\t\t\t</list>\n\t\t\t</vertical>\n\t\t</frame>\n\t`);\n\n\temitItemShowEvent(ui.itemList, icon);\n\n\tui.itemList.on(\"item_click\", function (item, i, itemView, listView) {\n\t\tconsole.log(`已点击：${item.displayName || item.appName || item.name}`);\n\t\titem.fn ? item.fn(item, itemView) : fn(item, itemView);\n\t});\n\n\tui.itemList.on(\"item_long_click\", function (event, item, i, itemView, listView) {\n\t\tif (item.url) {\n\t\t\tapp.openUrl(item.url);\n\t\t}\n\t});\n\n\tfunction setDataSource (itemList) {\n\t\titemList.forEach((item) => {\n\t\t\tif (item.icon && !/^\\w+:\\/\\//.test(item.icon)) {\n\t\t\t\titem.icon = \"file://\" + files.path(item.icon);\n\t\t\t}\n\t\t});\n\t\tui.itemList.setDataSource(itemList);\n\t\tui.post(() => {\n\t\t\tui.progress.setVisibility(View.GONE);\n\t\t});\n\t}\n\n\tglobal.activity.setSupportActionBar(ui.toolbar);\n\tif (itemList.then) {\n\t\tui.progress.setVisibility(View.VISIBLE);\n\t\tthreads.start(function () {\n\t\t\titemList.then(itemList => {\n\t\t\t\tui.post(() => {\n\t\t\t\t\tsetDataSource(itemList);\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t} else {\n\t\tsetDataSource(itemList);\n\t}\n}\n\nmodule.exports = singleChoice;\n"
  },
  {
    "path": "src/miui_cleaner_app/startActivity.js",
    "content": "function startActivity (options) {\n\tif (options.className) {\n\t\toptions.className = options.className.replace(/^(?=\\.)/, options.packageName);\n\t}\n\ttry {\n\t\tapp.startActivity(options);\n\t} catch (ex) {\n\t\tconsole.error(ex);\n\t}\n}\n\nmodule.exports = startActivity;\n"
  },
  {
    "path": "src/miui_cleaner_app/support.js",
    "content": "const project = require(\"./project.json\");\nconst View = android.view.View;\nconst url = \"https://support.qq.com/products/565003\";\n\nfunction support () {\n\tconst postData = new URLSearchParams({\n\t\tclientInfo: project.name,\n\t\tclientVersion: project.versionName,\n\t\tos: `${device.brand}/${device.model}`,\n\t\tosVersion: device.fingerprint,\n\t\t// netType: \"\",\n\t\t// customInfo: ,\n\t\t// openid\n\t\t// nickname\n\t\t// avatar\n\t}).toString();\n\t// console.log(device);\n\n\tui.layout(`\n\t\t<vertical>\n\t\t\t<appbar>\n\t\t\t\t<toolbar id=\"toolbar\" title=\"${project.name}\" subtitle=\"${module.exports.name}\" />\n\t\t\t</appbar>\n\t\t\t<relative w=\"*\" w=\"*\">\n\t\t\t\t<webview id=\"webView\" layout_below=\"title\" w=\"*\" h=\"*\" />\n\t\t\t\t<progressbar id=\"progress\" indeterminate=\"true\" layout_centerHorizontal=\"true\" layout_alignParentTop=\"true\" w=\"*\" h=\"auto\"style=\"@style/Base.Widget.AppCompat.ProgressBar.Horizontal\" />\n\t\t\t</relative>\n\t\t</vertical>\n\t`);\n\tconst webView = ui.findView(\"webView\");\n\tconst progress = ui.findView(\"progress\");\n\tglobal.activity.setSupportActionBar(ui.findView(\"toolbar\"));\n\n\t// https://developer.android.google.cn/reference/android/webkit/WebChromeClient\n\twebView.setWebChromeClient(\n\t\tnew JavaAdapter(android.webkit.WebChromeClient, {\n\t\t\tonProgressChanged: (webView, i) => {\n\t\t\t\tif (i) {\n\t\t\t\t\tprogress.progress = i;\n\t\t\t\t}\n\t\t\t},\n\t\t}),\n\t);\n\t// https://developer.android.google.cn/reference/android/webkit/WebViewClient\n\twebView.setWebViewClient(new JavaAdapter(android.webkit.WebViewClient, {\n\t\tonPageStarted: () => {\n\t\t\tprogress.indeterminate = false;\n\t\t\tprogress.setVisibility(View.VISIBLE);\n\t\t},\n\t\tonPageFinished: () => {\n\t\t\tprogress.setVisibility(View.GONE);\n\t\t},\n\t\tonCloseWindow: () => {\n\t\t\tconsole.log(\"onCloseWindow\");\n\t\t},\n\t}));\n\n\tconst settings = webView.getSettings();\n\tsettings.setJavaScriptEnabled(true);\n\tsettings.setDomStorageEnabled(true);\n\n\twebView.loadUrl(`${url}?${postData}`);\n\tui.emitter.prependListener(\"back_pressed\", (e) => {\n\t\tif (webView.canGoBack()) {\n\t\t\twebView.goBack();\n\t\t} else {\n\t\t\tui.emitter.removeAllListeners(\"back_pressed\");\n\t\t\trequire(\"./index\").mainMenu();\n\t\t}\n\t\te.consumed = true;\n\t});\n}\n\nmodule.exports = {\n\tname: \"帮助与反馈\",\n\tsummary: \"帮助文档、联系作者\",\n\ticon: \"./res/drawable/ic_help.png\",\n\tfn: support,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/sysAppRm.js",
    "content": "const findClickableParent = require(\"./findClickableParent\");\nconst getApplicationInfo = require(\"./getApplicationInfo\");\nconst multiChoice = require(\"./multiChoice\");\nconst waitForBack = require(\"./waitForBack\");\nconst settings = require(\"./settings\");\nconst appDesc = require(\"./appDesc\");\nconst dialogs = require(\"./dialogs\");\n\nconst installerPackageName = \"com.miui.packageinstaller\";\nconst installerAppName = app.getAppName(installerPackageName);\n\nconst sysAppList = Object.keys(appDesc).map(packageName => ({\n\tpackageName,\n\tsummary: appDesc[packageName],\n}));\n\nfunction clickButton (button, text) {\n\tbutton = findClickableParent(button);\n\tif (button) {\n\t\tbutton.click();\n\t}\n}\n\n// // 卸载“纯净模式”\n// function getInstaller (appList) {\n// \tconst packageInfo = getApplicationInfo({\n// \t\tpackageName: installerPackageName,\n// \t\tsummary: \"降级安装v380，以便移除“纯净模式”\",\n// \t});\n// \tif (!packageInfo || packageInfo.getVersionCode() < 400) {\n// \t\t// 版本号小于400，则不含“纯净模式”\n// \t\treturn;\n// \t}\n// \tconst fileName = \"MiuiPackageInstaller.apk\";\n// \tconst srcPath = \"res/\" + fileName;\n// \tconst copyPath = \"/sdcard/Download/\" + fileName;\n// \tconst installPath = \"/data/local/tmp/\" + fileName;\n// \tfiles.copy(srcPath, copyPath);\n// \tpackageInfo.cmd = [\n// \t\t`mv ${copyPath} ${installPath}`,\n// \t\t\"pm install -d -g \" + installPath,\n// \t\t\"rm -rf \" + installPath,\n// \t].join(\"\\n\");\n// \tappList.push(packageInfo);\n// \tconsole.log(`发现${installerAppName}(${installerPackageName})，版本号${packageInfo.getVersionName()}，已释放版本号为v380的降级安装包到路径：${copyPath}`);\n// }\n\nconst whitelist = /^com\\.(miui\\.(voiceassist|personalassistant)|android\\.(quicksearchbox|chrome))$/;\nfunction getAppList () {\n\tconst appList = sysAppList.filter(item => {\n\t\tif (!getApplicationInfo(item)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (item.checked == null) {\n\t\t\titem.checked = !whitelist.test(item.packageName);\n\t\t}\n\t\t// item.isSysApp = appInfo.flags & android.content.pm.ApplicationInfo.FLAG_SYSTEM;\n\t\treturn true;\n\t});\n\treturn appList;\n}\n\nfunction installerHelper () {\n\t// 应用包安装程序选择界面，下次默认，不再提示\n\t// const isCheckBox = checkBox => (\n\t// \tcheckBox.checkable() && !checkBox.checked() && /下次默认.*不再提示/.test(checkBox.text()) && /\\bandroid\\b/.test(checkBox.packageName())\n\t// );\n\t// 应用包安装程序选择界面，通过“应用包管理组件”运行\n\tconst isInstallerSelect = btn => (\n\t\tinstallerAppName === btn.text() && /\\bandroid\\b/.test(btn.packageName())\n\t);\n\t// 应用包管理组件，确定、继续按钮\n\tconst isContinueBtn = btn => (\n\t\t/\\b(continue|ok)_button$/.test(btn.id()) && installerPackageName === btn.packageName()\n\t);\n\tclickButton(\n\t\tselector().filter(\n\t\t\tobj => isInstallerSelect(obj) || isContinueBtn(obj),\n\t\t).findOnce(),\n\t);\n\tsetTimeout(installerHelper, 0x50);\n}\n\nfunction removeByInstaler (taskList) {\n\tconst uninstallTaskList = taskList.filter(task => task.packageName !== installerPackageName);\n\tif (!uninstallTaskList.length) {\n\t\treturn taskList;\n\t}\n\n\ttoastLog(`尝试以常规权限卸载${uninstallTaskList.length}个应用`);\n\tlet helper;\n\treturn settings.set(\n\t\t\"accessibilityServiceEnabled\",\n\t\ttrue,\n\t\t\"自动化卸载这些APP\",\n\t).then(accessibilityServiceEnabled => {\n\t\tif (accessibilityServiceEnabled) {\n\t\t\thelper = threads.start(installerHelper);\n\t\t}\n\t}).then(() =>\n\t\twaitForBack(() => {\n\t\t\tuninstallTaskList.forEach(appInfo => {\n\t\t\t\tapp.uninstall(appInfo.packageName);\n\t\t\t});\n\t\t}).then(() => {\n\t\t\thelper && helper.interrupt();\n\t\t\treturn taskList;\n\t\t}),\n\t);\n}\n\nfunction removeByScript (tasks) {\n\ttasks = tasks.filter(\n\t\tappInfo => app.getAppName(appInfo.packageName),\n\t).map(appInfo => {\n\t\treturn appInfo.cmd || \"pm uninstall --user 0 \" + appInfo.packageName;\n\t});\n\tif (!tasks.length) {\n\t\treturn;\n\t}\n\n\tconst shFilePath = \"/sdcard/Download/MiuiCleaner.sh\";\n\ttasks.unshift(`pm grant ${context.getPackageName()} android.permission.WRITE_SECURE_SETTINGS`);\n\ttasks.unshift(\"#!/bin/sh\");\n\ttasks.push(\"rm -rf \" + shFilePath);\n\tconst script = tasks.join(\"\\n\") + \"\\n\";\n\tfiles.write(shFilePath, script);\n\tlet cmd = \"sh \" + shFilePath;\n\ttry {\n\t\tshell(cmd, true);\n\t\ttoastLog(`尝试以root权限卸载${tasks.length}个应用`);\n\t\treturn;\n\t} catch (ex) {\n\t\t//\n\t}\n\treturn settings.set(\"adbInput\", true, \"自动打开“USB调试(安全设置)”，让电脑端有权限卸载这些APP\").then((adbInput) => {\n\t\t// if (!adbInput) {\n\t\t// \ttoastLog(\"“USB调试(安全设置)”未打开，请打开后再试。\");\n\t\t// \treturn;\n\t\t// }\n\t\tcmd = \"adb shell \" + cmd;\n\t\ttoastLog(\"正以等候电脑端自动执行：\\t\" + cmd);\n\t\tconst timeout = Date.now() + 0x800 + tasks.length * 0x200;\n\t\tlet fileExist;\n\t\treturn new Promise((resolve) => {\n\t\t\tconst timer = setInterval(() => {\n\t\t\t\tlet wait;\n\t\t\t\tfileExist = files.exists(shFilePath);\n\t\t\t\tif (!fileExist) {\n\t\t\t\t\ttoastLog(\"电脑端自动执行成功\");\n\t\t\t\t} else if (Date.now() > timeout) {\n\t\t\t\t\twait = dialogs.prompt(\n\t\t\t\t\t\t\"等候电脑端自动执行超时，请打开本软件电脑端，或者在电脑手动执行命令：\",\n\t\t\t\t\t\tcmd,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnegative: false,\n\t\t\t\t\t\t\tcancelable: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t).then(() => {\n\t\t\t\t\t\tif (!files.exists(shFilePath)) {\n\t\t\t\t\t\t\ttoastLog(\"电脑端手动执行成功\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ttoastLog(\"电脑端手动执行失败\");\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tclearInterval(timer);\n\t\t\t\tresolve(wait);\n\t\t\t}, 0x200);\n\t\t});\n\t});\n}\n\nfunction sysAppRm () {\n\tconst itemList = getAppList();\n\tmultiChoice({\n\t\ttitle: \"请选择要卸载的应用或功能\",\n\t\titemList,\n\t}).then(\n\t\tremoveByInstaler,\n\t).then(\n\t\tremoveByScript,\n\t).then(\n\t\tsysAppRm,\n\t).catch(console.error);\n\trequire(\"./index\")();\n};\n\nmodule.exports = {\n\tname: \"预装APP卸载\",\n\tsummary: \"卸载系统广告组件及随机预装的APP\",\n\ticon: \"./res/drawable/ic_phone_recovery.png\",\n\tfn: sysAppRm,\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/test/getRemoteFileInfo.js",
    "content": "module.exports = (getRemoteFileInfo) => {\n\treturn DEBUG && Promise.all([\n\t\t// getRemoteFileInfo(\"https://www.firepx.com/app/android-mi-browser-google-play/\").then(miBrowserFirepx => {\n\t\t// \tconsole.assert(Array.isArray(miBrowserFirepx), \"MIUI浏览器应该是数组\");\n\t\t// \tmiBrowserFirepx.forEach(file => {\n\t\t// \t\tconsole.assert(/^Mi\\s+Browser-.*\\.apk$/.test(file.fileName), \"MIUI浏览器应该是数组\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://firepx.lanzoul.com/b00vs5efe#pwd=385m\").then(miBrowserFirepx => {\n\t\t// \tthrow new Error(\"密码错误，应该报错\");\n\t\t// }, error => {\n\t\t// \tconsole.assert(error.message === \"密码不正确\", error.info || \"密码不正确\");\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/tp/i7tit9c#pwd=6svq\").then(dianshijia => {\n\t\t// \t// 单文件，有密码\n\t\t// \tconsole.log(\"testcase\", dianshijia);\n\t\t// \tconsole.assert(/^电视家.*\\.apk$/.test(dianshijia.fileName), \"电视家文件名\");\n\t\t// \t// console.assert(!!dianshijia.url, \"没能查到电视家下载地址\");\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://zisu.lanzoum.com/tp/iI7LGwn5xjc\").then(installer => {\n\t\t// \t// 单文件，无密码\n\t\t// \tconsole.log(installer);\n\t\t// \tconsole.assert(/^应用包管理组件.*\\.apk$/.test(installer.fileName), \"应用包管理组件\");\n\t\t// \tconsole.assert(installer.id === \"iI7LGwn5xjc\", \"应用包管理组件，ID：iI7LGwn5xjc\");\n\t\t// \t// console.assert(!!installer.url, \"没能查到应用包管理组件下载地址\");\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://wwm.lanzoul.com/tp/idzsf0bh062h\").then(browser => {\n\t\t// \t// 单文件，无密码\n\t\t// \tconsole.log(browser);\n\t\t// \tconsole.assert(/^小米浏览器.*\\.apk$/.test(browser.fileName), \"小米浏览器\");\n\t\t// \tconsole.assert(browser.id === \"idzsf0bh062h\", \"小米浏览器，ID：idzsf0bh062h\");\n\t\t// \t// console.assert(!!browser.url, \"没能查到小米浏览器下载地址\");\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/b0f2uzq2b\").then(coolapk => {\n\t\t// \t// 文件夹，无密码\n\t\t// \tconsole.log(coolapk);\n\t\t// \tconsole.assert(Array.isArray(coolapk), \"酷安应该是数组\");\n\t\t// \tcoolapk.forEach(file => {\n\t\t// \t\tconsole.assert(/^(酷安|FuckCoolapk).*\\.apk$/.test(file.fileName), \"酷安应该是数组\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://gucong.lanzoub.com/b03pbkhif#pwd=miui\").then(cleaner => {\n\t\t// \tconsole.log(cleaner);\n\t\t// \t// 文件夹，有密码\n\t\t// \tconsole.assert(Array.isArray(cleaner), \"MiuiCleaner应该是数组\");\n\t\t// \tcleaner.forEach(file => {\n\t\t// \t\tconsole.assert(/^MiuiCleaner.*\\.zip$/.test(file.fileName), \"MiuiCleaner应该是数组\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/iHmmD06tw9xa\").then(appShare => {\n\t\t// \t// 特殊页面 - APP 信息\n\t\t// \tconsole.log(appShare);\n\t\t// \tconsole.assert(/^App分享.*\\.apk$/.test(appShare.fileName), \"App分享\");\n\t\t// \tconsole.assert(appShare.id === \"iHmmD06tw9xa\", \"App分享，ID：iI7LGwn5xjc\");\n\t\t// \tconsole.assert(appShare.size === 23383245, \"App分享，size：22.3 M\");\n\t\t// \tconsole.assert(appShare.time === 1655942400000, \"App分享，time：2022-06-23\");\n\t\t// \tconsole.assert(!appShare.url, \"没能查到App分享下载地址\");\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://wwe.lanzouw.com/b01v0g3wj#pwd=1233\").then(litiaotiao => {\n\t\t// \t// 特殊页面 带参数ID\n\t\t// \tconsole.assert(Array.isArray(litiaotiao), \"李跳跳应该是数组\");\n\t\t// \treturn Promise.all(\n\t\t// \t\tlitiaotiao.map(opts => {\n\t\t// \t\t\tconsole.assert(/\\?/.test(opts.id), \"李跳跳ID中有参数\");\n\t\t// \t\t\tconsole.assert(/^李跳跳.*\\.apk$/.test(opts.fileName), \"李跳跳应该是数组\");\n\t\t// \t\t\treturn getRemoteFileInfo(opts.referer).then(file => {\n\t\t// \t\t\t\tconsole.assert(!opts.url, \"没能查到李跳跳下载地址\");\n\t\t// \t\t\t\tconsole.assert(!!file.url, \"没能查到李跳跳下载地址\");\n\t\t// \t\t\t\tconsole.log(file.fileName, file.versionName);\n\t\t// \t\t\t\tObject.keys(opts).forEach(key => {\n\t\t// \t\t\t\t\tconsole.assert(opts[key] === file[key], \"李跳跳 : \" + key);\n\t\t// \t\t\t\t});\n\t\t// \t\t\t});\n\t\t// \t\t}),\n\t\t// \t);\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://github.com/WangDaYeeeeee/GeometricWeather/releases/latest\").then(weather => {\n\t\t// \tconsole.assert(Array.isArray(weather), \"几何天气应该是数组\");\n\t\t// \tweather.forEach(file => {\n\t\t// \t\tconsole.log(file);\n\t\t// \t\tconsole.assert(/^GeometricWeather.*\\.apk$/.test(file.fileName) || /\\.aab$/.test(file.fileName), \"几何天气文件名GeometricWeather.*\\.apk\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/b0f1d7s2h\").then(esexplorer => {\n\t\t// \tconsole.assert(Array.isArray(esexplorer), \"ES文件浏览器应该是数组\");\n\t\t// \tesexplorer.forEach(file => {\n\t\t// \t\t// console.log(file);\n\t\t// \t\tconsole.assert(/^\\wS文件(浏览|管理)器.*\\.apk$/.test(file.fileName), \"ES文件浏览器文件名\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouo.com/b0f2lkafe\").then(zhihu => {\n\t\t// \tconsole.log(zhihu);\n\t\t// \tconsole.assert(Array.isArray(zhihu), \"知乎应该是数组\");\n\t\t// \tzhihu.forEach(file => {\n\t\t// \t\tconsole.log(file.fileName, file.versionName);\n\t\t// \t\tconsole.assert(/^(知乎|Zhiliao).*\\.apk$/.test(file.fileName), \"知乎文件名\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/b0f1b6q8d\").then(tieba => {\n\t\t// \tconsole.log(tieba);\n\t\t// \tconsole.assert(Array.isArray(tieba), \"知乎应该是数组\");\n\t\t// \ttieba.forEach(file => {\n\t\t// \t\tconsole.log(file.fileName, file.versionName);\n\t\t// \t\tconsole.assert(/^(百度)?贴吧.*\\.apk$/.test(file.fileName), \"贴吧文件名\");\n\t\t// \t});\n\t\t// }),\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/b0f1avpib\").then(youku => {\n\t\t// \tconsole.log(youku);\n\t\t// }),\n\t\t// console.log(getRemoteFileInfo.toString()),\n\n\t\t// getRemoteFileInfo(\"https://m.32r.com/app/109976.html\").then(wps => {\n\t\t// \tconsole.log(wps);\n\t\t// }).catch(console.error),\n\n\t\t// getRemoteFileInfo(\"https://423down.lanzouv.com/b0f1gksne\").then(wps => {\n\t\t// \tconsole.log(wps);\n\t\t// }),\n\n\t\t// singleFile(\"iI7LGwn5xjc\").then(console.log);\n\n\t]).catch(console.error);\n};\n"
  },
  {
    "path": "src/miui_cleaner_app/test/services.js",
    "content": "function isObject (val) {\n\treturn val != null && typeof val === \"object\" && Array.isArray(val) === false;\n};\nfunction isBoolean (value) {\n\treturn value === true || value === false;\n}\nconst testCase = {\n\t小米帐号: {\n\t\t关于小米帐号: {\n\t\t\t系统广告: {\n\t\t\t\t系统工具广告: false,\n\t\t\t},\n\t\t},\n\t},\n\t广告服务: {\n\t\t个性化广告推荐: false,\n\t},\n\t系统安全: {\n\t\t\"加入“用户体验改进计划”\": false,\n\t\t\"自动发送诊断数据\": false,\n\t\t\"广告服务\": {\n\t\t\t个性化广告推荐: false,\n\t\t},\n\t\t\"网页链接调用服务\": {\n\t\t\t网页链接调用服务: false,\n\t\t},\n\t},\n\t手机管家: {\n\t\t在通知栏显示: false,\n\t\t在线服务: false,\n\t\t隐私设置: {\n\t\t\t仅在WLAN下推荐: true,\n\t\t\t个性化推荐: false,\n\t\t},\n\t},\n\t应用管理: {\n\t\t资源推荐: false,\n\t},\n\t垃圾清理: {\n\t\t扫描内存: false,\n\t\t推荐内容: false,\n\t\t仅在WLAN下推荐: true,\n\t},\n\t应用商店: {\n\t\t通知设置: {\n\t\t\t新手帮助: false,\n\t\t\t应用更新通知: false,\n\t\t\t点赞消息: false,\n\t\t\t评论消息: false,\n\t\t},\n\t\t通知栏快捷入口: false,\n\t\t隐私设置: {\n\t\t\t个性化服务: {\n\t\t\t\t个性化服务: false,\n\t\t\t},\n\t\t},\n\t\t功能设置: {\n\t\t\t显示福利活动: false,\n\t\t},\n\t},\n\t下载管理: {\n\t\t信息流设置: {\n\t\t\t仅在WLAN下加载: true,\n\t\t\t资源推荐: false,\n\t\t\t热榜推荐: false,\n\t\t},\n\t},\n\t日历: {\n\t\t功能设置: {\n\t\t\t显示天气服务: false,\n\t\t},\n\t\t用户体验计划: {\n\t\t\t内容推广: false,\n\t\t},\n\t},\n\t时钟: {\n\t\t更多闹钟设置: {\n\t\t\t显示生活早报: false,\n\t\t},\n\t},\n\t小米社区: {\n\t\t隐私管理: {\n\t\t\t详情页相似推荐: false,\n\t\t\t个性化广告: false,\n\t\t\t信息流推荐: false,\n\t\t},\n\t\t关闭私信: null,\n\t\t关闭私信消息提醒: true,\n\t},\n\t小米天气: {\n\t\t用户体验计划: {\n\t\t\t天气视频卡片: false,\n\t\t\t内容推广: false,\n\t\t},\n\t},\n\t小米视频: {\n\t\t隐私设置: {\n\t\t\t个性化内容推荐: false,\n\t\t\t个性化广告推荐: false,\n\t\t},\n\t\t消息与推送: {\n\t\t\t未读消息提醒: false,\n\t\t\t接收小米推送: false,\n\t\t},\n\t\t其他: {\n\t\t\t// \"已安装插件\": [\n\t\t\t// \t\"风行\",\n\t\t\t// \t\"爱奇艺\",\n\t\t\t// \t\"搜狐\",\n\t\t\t// ],\n\t\t\t在线服务: false,\n\t\t},\n\t},\n\t音乐: {\n\t\t/*\n\t\t个性化内容推荐: false,\n\t\t通知设置: {\n\t\t\t个性化内容推荐: false,\n\t\t\t通知设置: {\n\t\t\t\t通知提醒: false\n\t\t\t},\n\t\t\t仅在WLAN下自动下载: true\n\t\t},\n\t\t仅在WLAN下自动下载: true,\n\t\t*/\n\t\t在线内容服务: false,\n\t},\n\t小爱语音: {\n\t\t隐私管理: {\n\t\t\t隐私设置: {\n\t\t\t\t加入用户体验改进计划: false,\n\t\t\t\t小爱技巧推送服务: false,\n\t\t\t\t个性化推荐: false,\n\t\t\t\t个性化广告推荐: false,\n\t\t\t},\n\t\t},\n\t},\n\t搜索: {\n\t\t搜索快捷方式: {\n\t\t\t桌面搜索框: false,\n\t\t},\n\t\t首页展示模块: {\n\t\t\t热搜榜单: {\n\t\t\t\t热搜榜s: false,\n\t\t\t},\n\t\t\t搜索提示词: false,\n\t\t},\n\t\t搜索项: {\n\t\t\t搜索精选: false,\n\t\t},\n\t\t网站广告过滤: true,\n\t},\n\t浏览器: {\n\t\t主页设置: {\n\t\t\t简洁版: true,\n\t\t\t宫格位推送: false,\n\t\t},\n\t\t隐私防护: {\n\t\t\t广告过滤: {\n\t\t\t\t广告过滤: true,\n\t\t\t},\n\t\t},\n\t\t消息通知管理: {\n\t\t\t接收消息通知: false,\n\t\t},\n\t},\n\t小米浏览器: {\n\t\t首页设置: {\n\t\t\t简洁版: true,\n\t\t},\n\t\t隐私保护: {\n\t\t\t广告过滤: {\n\t\t\t\t广告过滤: true,\n\t\t\t},\n\t\t},\n\t\t高级: {\n\t\t\t浏览器广告: false,\n\t\t},\n\t\t通知栏快捷入口: false,\n\t\tFacebook快捷通知: false,\n\t},\n\t关于手机: {\n\t\t\"MIUI 版本\": 14,\n\t},\n\t开发者选项: {\n\t\t\"USB 调试\": true,\n\t\t\"USB安装\": true,\n\t\t\"USB调试（安全设置）\": true,\n\t},\n};\nfunction compiler (result, data, options, parent) {\n\tparent = parent || [];\n\tfor (const key in data) {\n\t\tconst curr = parent.concat(key);\n\t\tif (isObject(data[key])) {\n\t\t\tif (!result[key]) {\n\t\t\t\tconst error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中，“${curr.join(\"”→“\")}”未进行应有的处理`);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tcompiler(result[key], data[key], options, curr);\n\t\t} else if (data[key] !== result[key]) {\n\t\t\tconst error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中，“${curr.join(\"”→“\")}”应该为${JSON.stringify(data[key])}，实际为${JSON.stringify(result[key])}`);\n\t\t\tthrow error;\n\t\t}\n\t}\n\tfor (const key in result) {\n\t\tconst curr = parent.concat(key);\n\t\tif (isBoolean(result[key]) && data[key] !== result[key]) {\n\t\t\tconst error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中，“${curr.join(\"”→“\")}”进行了不当的处理${JSON.stringify(result[key])}`);\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nmodule.exports = (options, result) => {\n\tconst testCaseName = options.name || options.appName || app.getAppName(options.packageName);\n\tconst data = testCase[testCaseName];\n\n\tconsole.log(`“${testCaseName}”结果：\\n` + JSON.stringify(result.handle, 0, \"\\t\"));\n\ttry {\n\t\tif (!data) {\n\t\t\tconst error = new Error(`广告自动关闭模块功能异常: “${testCaseName}”中，未编写测试用例`);\n\t\t\tthrow error;\n\t\t}\n\t\tcompiler(result.handle, data, options);\n\t} catch (ex) {\n\t\tconsole.error(ex);\n\t}\n};\n\nmodule.exports.testCase = testCase;\n"
  },
  {
    "path": "src/miui_cleaner_app/update.js",
    "content": "const prettyBytes = require(\"pretty-bytes\");\nconst project = require(\"./project.json\");\nconst downFile = require(\"./downFile\");\nconst dialogs = require(\"./dialogs\");\nconst request = require(\"./fetch\");\n\nfunction iec (number, options) {\n\treturn prettyBytes(number, {\n\t\tbinary: true,\n\t\t...options,\n\t});\n}\n\nfunction download (remote, options) {\n\tconst downTask = downFile(options);\n\n\tlet progressDialog;\n\tlet view;\n\n\tfunction showProgressDialog () {\n\t\tif (progressDialog) {\n\t\t\tprogressDialog.show();\n\t\t\treturn;\n\t\t}\n\t\tview = ui.inflate(`\n\t\t\t<vertical>\n\t\t\t\t<progressbar id=\"progressbar\" indeterminate=\"true\" style=\"@style/Base.Widget.AppCompat.ProgressBar.Horizontal\" />\n\t\t\t\t<linear orientation=\"horizontal\">\n\t\t\t\t\t<text id=\"progresstxt\" layout_weight=\"1\" />\n\t\t\t\t\t<text id=\"speed\" text=\" \"/>\n\t\t\t\t</linear>\n\t\t\t</vertical>\n\t\t`);\n\t\tprogressDialog = dialogs(\n\t\t\t`正在下载新版本：${remote.versionName}`,\n\t\t\t{\n\t\t\t\ttitle: \"版本更新\",\n\t\t\t\tpositive: \"后台下载\",\n\t\t\t\tnegative: false,\n\t\t\t\tview,\n\t\t\t},\n\t\t);\n\t}\n\tdownTask.on(\"start\", showProgressDialog);\n\tdownTask.on(\"click\", showProgressDialog);\n\tdownTask.on(\"progress\", ({\n\t\tprogress,\n\t\tspeed,\n\t\tsize,\n\t}) => {\n\t\tview.progressbar.indeterminate = false;\n\t\tview.progressbar.max = size;\n\t\tview.progressbar.progress = progress;\n\t\tview.progresstxt.setText(`${iec(progress)}/${iec(size)}`);\n\t\tview.speed.setText(speed >= 0 ? `${iec(speed)}/s` : \"\");\n\t});\n\n\treturn downTask.then(intent => {\n\t\tlet confirm;\n\t\tif (progressDialog) {\n\t\t\tprogressDialog.dismiss();\n\t\t\tconfirm = dialogs.confirm(\"下载完毕，立即安装？\", {\n\t\t\t\ttitle: \"版本更新\",\n\t\t\t});\n\t\t} else {\n\t\t\tconfirm = Promise.resolve(true);\n\t\t}\n\t\treturn confirm.then(confirm => {\n\t\t\tif (confirm) {\n\t\t\t\treturn app.startActivity(intent);\n\t\t\t}\n\t\t});\n\t});\n}\n\nfunction getFastUrl (remote) {\n\treturn request(\n\t\t[\n\t\t\t\"github.com\",\n\t\t\t\"download.fastgit.org\",\n\t\t\t\"gh.api.99988866.xyz/https://github.com\",\n\t\t].map(\n\t\t\thost => `https://${host}/gucong3000/MiuiCleaner/releases/download/v${remote.versionName}/MiuiCleaner.apk`,\n\t\t),\n\t\t{\n\t\t\tmethod: \"HEAD\",\n\t\t},\n\t);\n}\n\n// https://zhuanlan.zhihu.com/p/314071453\n// http://raw.githubusercontent.com 替换为 http://raw.staticdn.net 即可加速。\n\n// GitHub + Jsdelivr\n// https://github.com.cnpmjs.org\n// https://hub.fastgit.org\n// 也就是说上面的镜像就是一个克隆版的 GitHub，你可以访问上面的镜像网站，网站的内容跟 GitHub 是完整同步的镜像，然后在这个网站里面进行下载克隆等操作。\n\n// GitHub 文件加速\n// 利用 Cloudflare Workers 对 github release 、archive 以及项目文件进行加速，部署无需服务器且自带CDN.\n\n// https://gh.api.99988866.xyz\n// https://g.ioiox.com\nrequest([\n\t\"https://cdn.jsdelivr.net/gh/gucong3000/MiuiCleaner/src/miui_cleaner_app/project.json\",\n\t\"https://raw.fastgit.org/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json\",\n\t// \"http://raw.staticdn.net/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json\",\n\t\"https://raw.githubusercontent.com/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json\",\n]).then(res => res.ok && res.json()).then(remote => {\n\tif (!remote) {\n\t\treturn;\n\t}\n\tif (project.versionCode >= parseInt(remote.versionCode)) {\n\t\treturn;\n\t}\n\treturn dialogs.confirm(\n\t\t`发现新版本：${remote.versionName}，是否升级？`,\n\t\t{\n\t\t\ttitle: \"版本更新\",\n\t\t\tneutral: true,\n\t\t},\n\t).then(confirm => {\n\t\tif (!confirm) {\n\t\t\tif (confirm === null) {\n\t\t\t\tapp.openUrl(\"https://github.com/gucong3000/MiuiCleaner/releases/latest\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\treturn getFastUrl(remote).then(options => download(remote, options));\n\t});\n}).catch(console.error);\n"
  },
  {
    "path": "src/miui_cleaner_app/waitForBack.js",
    "content": "\nfunction waitForBack (leave) {\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, 0x200);\n\t\tui.emitter.once(\"pause\", () => {\n\t\t\tui.emitter.once(\"resume\", resolve);\n\t\t\tclearTimeout(timer);\n\t\t});\n\t\tif (leave) {\n\t\t\tsetTimeout(leave, 0);\n\t\t};\n\t});\n};\n\nmodule.exports = waitForBack;\n"
  },
  {
    "path": "src/miui_cleaner_app/webView.js",
    "content": "const asyncFnWrap = (taskId, fn, args) => {\n\tfunction toErrorObj (reason) {\n\t\tif (reason instanceof Error) {\n\t\t\tconst errObj = {};\n\t\t\t[\n\t\t\t\t\"message\",\n\t\t\t\t\"name\",\n\t\t\t\t\"stack\",\n\t\t\t].forEach(key => {\n\t\t\t\tif (reason[key]) {\n\t\t\t\t\terrObj[key] = reason[key];\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn errObj;\n\t\t}\n\t\treturn reason;\n\t}\n\n\tlet value;\n\ttry {\n\t\tvalue = fn.apply(this, args);\n\t} catch (reason) {\n\t\treturn {\n\t\t\tstatus: \"rejected\",\n\t\t\treason: toErrorObj(reason),\n\t\t};\n\t}\n\n\tif (value && value.then) {\n\t\tconst jsbridge = `jsbridge://async:${taskId}`;\n\t\tPromise.resolve(value).then(\n\t\t\tvalue => ({\n\t\t\t\tstatus: \"fulfilled\",\n\t\t\t\tvalue,\n\t\t\t}),\n\t\t\treason => ({\n\t\t\t\tstatus: \"rejected\",\n\t\t\t\treason: toErrorObj(reason),\n\t\t\t}),\n\t\t).then(result => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tlocation.href = `${jsbridge}/${btoa(encodeURI(JSON.stringify(result)))}`;\n\t\t\t}, 0);\n\t\t});\n\t\treturn jsbridge;\n\t}\n\treturn {\n\t\tstatus: \"fulfilled\",\n\t\tvalue,\n\t};\n};\n\nfunction webView (options) {\n\tconsole.log(\"使用webView解析：\", options.url);\n\tconst webView = ui.inflate(`\n\t\t<webview />\n\t`);\n\n\tconst jsBridge = events.emitter();\n\tconst consoleBridge = events.emitter();\n\tlet taskId = 0;\n\tconst docReady = {};\n\n\t// https://developer.android.google.cn/reference/android/webkit/WebSettings\n\tconst settings = webView.getSettings();\n\tsettings.setBlockNetworkImage(true);\n\tsettings.setJavaScriptEnabled(true);\n\tsettings.setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);\n\tif (options.userAgent) {\n\t\tsettings.setUserAgentString(options.userAgent);\n\t}\n\tif (DEBUG && webView.setWebContentsDebuggingEnabled) {\n\t\twebView.setWebContentsDebuggingEnabled(true);\n\t}\n\t// let lastValue;\n\t// jsBridge.on(\"pageFinished\", (webView, url) => {\n\t// \tconsole.log(\"pageFinished\", webView, url, lastValue);\n\t// \tsetTimeout(() => {\n\t// \t\tconsole.log(lastValue);\n\t// \t}, 800);\n\t// });\n\n\tfunction evaluate (fn, args) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tfunction then (result) {\n\t\t\t\tif (result.status === \"fulfilled\") {\n\t\t\t\t\tresolve(result.value);\n\t\t\t\t} else if (result.status === \"rejected\") {\n\t\t\t\t\tconst reason = result.reason;\n\t\t\t\t\tif (reason) {\n\t\t\t\t\t\tconst error = new (global[result.reason.name] || Error)(reason.message, reason);\n\t\t\t\t\t\treject(Object.assign(error, reason));\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(reason);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfunction onReceiveValue (result) {\n\t\t\t\t// lastValue = result;\n\t\t\t\tresult = result && JSON.parse(result);\n\t\t\t\tif (result) {\n\t\t\t\t\tif (result.status) {\n\t\t\t\t\t\treturn then(result);\n\t\t\t\t\t} else if (typeof result === \"string\" && result.startsWith(\"jsbridge://\")) {\n\t\t\t\t\t\treturn jsBridge.once(result.slice(11), then);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treject(new Error(\"evaluateJavascript: no result\"));\n\t\t\t};\n\t\t\tjsBridge.once(\"receivedError\", reject);\n\t\t\twebView.evaluateJavascript(\n\t\t\t\t`javascript:(${asyncFnWrap.toString().trim()})(${taskId++}, (${fn.toString().trim()}), ${JSON.stringify(args)});`,\n\t\t\t\tnew JavaAdapter(\n\t\t\t\t\tandroid.webkit.ValueCallback,\n\t\t\t\t\t{\n\t\t\t\t\t\tonReceiveValue,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t});\n\t}\n\n\tfunction consoleMessage (msg) {\n\t\tconst MessageLevel = android.webkit.ConsoleMessage.MessageLevel;\n\t\tconst message = msg.message();\n\t\t// if (sourceId !== href) {\n\t\t// \treturn;\n\t\t// }\n\t\tlet level;\n\t\t// https://developer.android.google.cn/reference/android/webkit/ConsoleMessage\n\t\tswitch (msg.messageLevel()) {\n\t\t\tcase MessageLevel.TIP: {\n\t\t\t\tlevel = \"verbose\";\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase MessageLevel.WARNING: {\n\t\t\t\tif (message.startsWith(\"A parser-blocking,\") || message.startsWith(\"Mixed Content:\")) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlevel = \"warn\";\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase MessageLevel.ERROR: {\n\t\t\t\tlevel = \"error\";\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase MessageLevel.DEBUG: {\n\t\t\t\tlevel = \"info\";\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tlevel = \"log\";\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tconst href = webView.getUrl().replace(/#.*$/, \"\");\n\t\tconst sourceId = msg.sourceId() || href;\n\t\tconst stackTraces = [\n\t\t\t`${sourceId}:${msg.lineNumber()}`,\n\t\t];\n\t\tif (sourceId !== href) {\n\t\t\tstackTraces.push(href);\n\t\t}\n\t\tconsoleBridge.emit(\"data\", level, message, stackTraces.map(decodeURI));\n\t\tconsoleBridge.emit(level, message, stackTraces);\n\t}\n\n\tfunction pipeEvent (...args) {\n\t\treturn jsBridge.emit.bind(jsBridge, ...args);\n\t}\n\n\t// https://developer.android.google.cn/reference/android/webkit/WebChromeClient\n\twebView.setWebChromeClient(\n\t\tnew JavaAdapter(android.webkit.WebChromeClient, {\n\t\t\tonConsoleMessage: consoleMessage,\n\t\t\tonProgressChanged: pipeEvent(\"progressChanged\"),\n\t\t}),\n\t);\n\t// https://developer.android.google.cn/reference/android/webkit/WebViewClient\n\twebView.setWebViewClient(new JavaAdapter(android.webkit.WebViewClient, {\n\t\tshouldOverrideUrlLoading: (webView, webResourceRequest) => {\n\t\t\tlet url = webResourceRequest.getUrl().toString();\n\t\t\tif (url.startsWith(\"jsbridge://\")) {\n\t\t\t\turl = new URL(url);\n\t\t\t\tjsBridge.emit(url.host, JSON.parse(decodeURI(global.$base64.decode(url.pathname.slice(1)))));\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tonReceivedError: (webView, resourceRequest, resourceError) => {\n\t\t\tif (resourceRequest.isForMainFrame()) {\n\t\t\t\tjsBridge.emit(\"receivedError\", resourceError);\n\t\t\t}\n\t\t},\n\t\tonPageFinished: pipeEvent(\"pageFinished\"),\n\t}));\n\twebView.setDownloadListener({\n\t\tonDownloadStart: (url, userAgent, disposition, type, length) => {\n\t\t\tjsBridge.emit(\"downloadStart\", {\n\t\t\t\turl,\n\t\t\t\theaders: {\n\t\t\t\t\t\"content-disposition\": disposition,\n\t\t\t\t\t\"content-length\": length,\n\t\t\t\t\t\"content-type\": type,\n\t\t\t\t\t\"user-agent\": userAgent,\n\t\t\t\t\t// \"referer\": url.origin + url.pathname,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t});\n\n\tjsBridge.on(\"pageFinished\", checkReadyState);\n\tjsBridge.on(\"progressChanged\", checkReadyState);\n\tfunction checkReadyState (webView, ...args) {\n\t\tconst url = webView.getUrl();\n\t\tif (!url || /^\\w+:\\w+$/.test(url) || docReady[url]) {\n\t\t\treturn;\n\t\t}\n\t\treturn evaluate(() => (\n\t\t\tdocument.body?.children.length && document.readyState === \"complete\"\n\t\t)).then(readyState => {\n\t\t\tif (!docReady[url] && readyState) {\n\t\t\t\tdocReady[url] = true;\n\t\t\t\tjsBridge.emit(\"documentReady\", webView, ...args);\n\t\t\t}\n\t\t\treturn readyState;\n\t\t});\n\t}\n\n\tfunction ready (...args) {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tjsBridge.once(\"documentReady\", () => resolve());\n\t\t\tjsBridge.once(\"pageFinished\", () => resolve());\n\t\t\tjsBridge.once(\"receivedError\", reject);\n\t\t}).then(() => args.length && evaluate(...args));\n\t}\n\n\tfunction cancel () {\n\t\twebView.removeAllViews();\n\t\twebView.destroy();\n\t}\n\twebView.loadUrl(options.url);\n\treturn {\n\t\twebView,\n\t\tjsBridge,\n\t\tevaluate,\n\t\tconsole: consoleBridge,\n\t\tready,\n\t\tcancel,\n\t};\n}\n\nmodule.exports = webView;\n"
  },
  {
    "path": "src/miui_cleaner_cmd/main.cmd",
    "content": "@echo off\nchcp 65001>nul 2>nul\ntitle MiuiCleaner - MIUI广告清理工具\n\n:start\nadb shell pm list packages>\"%temp%\\adb_list_packages.tmp\" 2>&1\nif %errorlevel%==1 (\n\tfindstr /c:\"no devices\" \"%temp%\\adb_list_packages.tmp\">nul 2>nul && (\n\t\techo · 请确保已用数据线连接手机和电脑\n\t\techo · 请确保小米手机助手可正常连接手机\n\t\techo · 请确保已请确保USB调试模式已打开\n\t) || findstr /c:\"unauthorized\" \"%temp%\\adb_list_packages.tmp\">nul 2>nul && (\n\t\techo · 请在手机弹出授权提示时点击“确定”\n\t) || (\n\t\ttype \"%temp%\\adb_list_packages.tmp\"\n\t)\n\techo.\n\techo 请在排除故障后按任意键重试\n\tpause>nul 2>nul\n\tgoto:start\n) else if %errorlevel% geq 1 (\n\techo 错误：请将本程序移动到小米手机助手安装目录内再运行，按任意键前往小米手机助手官网\n\tpause>nul 2>nul\n\tstart http://zhushou.xiaomi.com/\n\tgoto:eof\n)\n\ncall:pkg_exist \"com.github.gucong3000.miui.cleaner\" || (\n\tif exist \"MiuiCleaner.apk\" (\n\t\tcall:apk_install\n\t) else (\n\t\techo 未在手机上找到“MiuiCleaner”，请将“MiuiCleaner.apk”拷贝至\"%CD%\"文件夹，或直接安装在手机后，按任意键重试\n\t\tpause>nul 2>nul\n\t\tgoto:start\n\t)\n)\n\ndel /f /s /q \"%temp%\\adb_list_packages.tmp\">nul 2>nul\n\nadb shell settings put secure install_non_market_apps 1>nul 2>nul\nadb shell settings put global development_settings_enabled 1>nul 2>nul\nadb shell settings put global adb_enabled 1>nul 2>nul\nadb shell setprop persist.security.adbinput 1>nul 2>nul\n\nadb shell pm grant com.github.gucong3000.miui.cleaner android.permission.REQUEST_INSTALL_PACKAGES>nul 2>nul\nadb shell pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SECURE_SETTINGS>nul 2>nul\nadb shell pm grant com.github.gucong3000.miui.cleaner android.permission.SYSTEM_ALERT_WINDOW>nul 2>nul\nadb shell pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SETTINGS>nul 2>nul\n\necho 正等候手机端发出指令...\ngoto:adb_server\n\n:pkg_exist\n    findstr /b /e /c:\"package:%~1\" \"%temp%\\adb_list_packages.tmp\">nul 2>nul\ngoto:eof\n\n:apk_install\n\techo 正在手机上安装 MiuiCleaner.apk，请在手机上弹出的“USB安装提示”窗口，点击“继续安装”按钮\n\tadb install -t -r -g MiuiCleaner.apk>nul 2>nul || (\n\t\techo 安装失败，如果手机上没有弹出“USB安装提示”窗口，请打开“设置 → 更多设置 → 开发者选项”，打开“USB安装”与“USB调试（安全设置）”\n\t\techo 如果手机上依然没有显示该弹窗，请打开“设置 → 应用设置 → 授权管理 → USB安装”，关闭对 MiuiCleaner 的限制\n\t\techo 排除以上故障后请按任意键重试\n\t\tpause>nul 2>nul\n\t\tcls\n\t\tgoto:apk_install\n\t)\n\tadb shell am start -n com.github.gucong3000.miui.cleaner/com.stardust.auojs.inrt.SplashActivity>nul 2>nul\ngoto:eof\n\n:adb_server\n\tadb shell sh /sdcard/Download/MiuiCleaner.sh>nul 2>nul && (\n\t\techo 执行成功！正等候手机端发出下一个指令...\n\t)\n\ttimeout /t 1>nul 2>nul\ngoto:adb_server\n"
  },
  {
    "path": "types/adbkit.d.ts",
    "content": "\n// import * as a from \"adbkit\";\n\ndeclare module \"adbkit\" {\n    import { ReadStream } from \"fs\";\n\n    export interface TcpUsbServer { }\n\n    export interface Connection { }\n\n    export interface Device {\n        id: string;\n        type: string;\n    }\n\n    export interface Client {\n        createTcpUsbBridge(serial: string): TcpUsbServer;\n        connection(): Promise<Connection>;\n        version(): any;\n        listDevices(): Promise<Device[]>;\n        screencap(serial: string): Promise<ReadStream>;\n    }\n\n    export interface ClientOptions {\n        host?: string;\n        port?: number;\n        bin?: string;\n    }\n\n    export function createClient(options?: ClientOptions): Client;\n\n    export var KeyCode: any;\n\n    export var util: any;\n}"
  },
  {
    "path": "types/auto.d.ts",
    "content": "/// <reference path=\"./modules/global.d.ts\" />\n/// <reference path=\"./modules/app.d.ts\" />\n/// <reference path=\"./modules/console.d.ts\" />\n/// <reference path=\"./modules/coordinate.d.ts\" />\n/// <reference path=\"./modules/root.d.ts\" />\n/// <reference path=\"./modules/device.d.ts\" />\n/// <reference path=\"./modules/dialogs.d.ts\" />\n/// <reference path=\"./modules/engines.d.ts\" />\n/// <reference path=\"./modules/events.d.ts\" />\n/// <reference path=\"./modules/floaty.d.ts\" />\n/// <reference path=\"./modules/files.d.ts\" />\n/// <reference path=\"./modules/media.d.ts\" />\n/// <reference path=\"./modules/sensors.d.ts\" />\n/// <reference path=\"./modules/http.d.ts\" />\n/// <reference path=\"./modules/images.d.ts\" />\n/// <reference path=\"./modules/colors.d.ts\" />\n/// <reference path=\"./modules/keys.d.ts\" />\n/// <reference path=\"./modules/storages.d.ts\" />\n/// <reference path=\"./modules/widgets.d.ts\" />\n/// <reference path=\"./modules/ui.d.ts\" />\n/// <reference path=\"./modules/threads.d.ts\" />\n\n\ndeclare global {\n\n}\n\nexport { };"
  },
  {
    "path": "types/autojs.d.ts",
    "content": "/* 内置模块 */\n\n/*\n * based on commit \"cf1e602\"\n * 文件结构\n * \n * -模块\n *     -命名空间\n *     -全局\n * \n * 未加入：WidgetsBasedAutomation、Shell、Thread、UI、Work with Java\n * \n */\ndeclare module 'global' {\n\n    /**\n     * 表示一个点（坐标）。\n     */\n    interface Point {\n        x: number;\n        y: number;\n    }\n\n    /**\n     * app模块提供一系列函数，用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。\n     */\n    namespace app {\n\n        /**\n         * 通过应用名称启动应用。如果该名称对应的应用不存在，则返回false; 否则返回true。如果该名称对应多个应用，则只启动其中某一个。\n         */\n        function launchApp(appName: string): boolean;\n\n        /** \n         * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n         */\n        function launch(packageName: string): boolean;\n\n        /**\n         * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n         */\n        function launchPackage(packageName: string): boolean;\n\n        /**\n         * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n         */\n        function getPackageName(appName: string): string;\n\n        /**\n         * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用，返回null。\n         */\n        function getAppName(packageName: string): string;\n\n        /**\n         * 打开应用的详情页(设置页)。如果找不到该应用，返回false; 否则返回true。\n         */\n        function openAppSetting(packageName: string): boolean;\n\n        /**\n         * 用其他应用查看文件。文件不存在的情况由查看文件的应用处理。如果找不出可以查看该文件的应用，则抛出ActivityNotException。\n         * \n         * @throws ActivityNotException\n         */\n        function viewFile(path: string): void;\n\n        /**\n         * 用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。如果找不出可以编辑该文件的应用，则抛出ActivityNotException。\n         * \n         * @throws ActivityNotException\n         */\n        function editFile(path: string): void;\n\n        /**\n         * 卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装，由应用卸载程序处理，可能弹出\"未找到应用\"的提示。\n         */\n        function uninstall(packageName: string): void;\n\n        /**\n         * 用浏览器打开网站url。网站的Url，如果不以\"http:// \"或\"https:// \"开头则默认是\"http:// \"。\n         */\n        function openUrl(url: string): void;\n\n        /**\n         * 发送邮件的参数，这些选项均是可选的。\n         */\n        interface SendEmailOptions {\n            /**\n             * 收件人的邮件地址。如果有多个收件人，则用字符串数组表示\n             */\n            email?: string | string[];\n            /**\n             * 抄送收件人的邮件地址。如果有多个抄送收件人，则用字符串数组表示\n             */\n            cc?: string | string[];\n            /**\n             * 密送收件人的邮件地址。如果有多个密送收件人，则用字符串数组表示\n             */\n            bcc?: string | string[];\n            /**\n             * 邮件主题(标题)\n             */\n            subject?: string;\n            /**\n             * 邮件正文\n             */\n            text?: string;\n            /**\n             * 附件的路径。\n             */\n            attachment?: string;\n        }\n\n        /**\n         * 根据选项options调用邮箱应用发送邮件。如果没有安装邮箱应用，则抛出ActivityNotException。\n         */\n        function sendEmail(options: SendEmailOptions): void;\n\n        /**\n         * 启动Auto.js的特定界面。该函数在Auto.js内运行则会打开Auto.js内的界面，在打包应用中运行则会打开打包应用的相应界面。\n         */\n        function startActivity(name: 'console' | 'settings'): void;\n\n        /**\n         * Intent(意图) 是一个消息传递对象，您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信.\n         */\n        interface Intent { }\n\n        /**\n         * 构造意图Intent对象所需设置。\n         */\n        interface IntentOptions {\n            action?: string;\n            type?: string;\n            data?: string;\n            category?: string[];\n            packageName?: string;\n            className?: string;\n            extras?: Object;\n        }\n\n        /**\n         * 根据选项，构造一个意图Intent对象。\n         */\n        function intent(options: IntentOptions): Intent;\n\n        /**\n         * 根据选项构造一个Intent，并启动该Activity。\n         */\n        function startActivity(intent: Intent): void;\n\n        /**\n         * 根据选项构造一个Intent，并发送该广播。\n         */\n        function sendBroadcast(intent: Intent): void;\n    }\n\n    /**\n     * 通过应用名称启动应用。如果该名称对应的应用不存在，则返回false; 否则返回true。如果该名称对应多个应用，则只启动其中某一个。\n     */\n    function launchApp(appName: string): boolean;\n\n    /** \n     * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n     */\n    function launch(packageName: string): boolean;\n\n    /**\n     * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n     */\n    function getPackageName(appName: string): string;\n\n    /**\n     * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n     */\n    function getPackageName(appName: string): string;\n\n    /**\n     * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用，返回null。\n     */\n    function getAppName(packageName: string): string;\n\n    /**\n     * 打开应用的详情页(设置页)。如果找不到该应用，返回false; 否则返回true。\n     */\n    function openAppSetting(packageName: string): boolean;\n\n\n    // interface Console {\n    //     show(): void;\n    //     verbose(): void;\n    // }\n\n    /**\n     * 控制台模块提供了一个和Web浏览器中相似的用于调试的控制台。用于输出一些调试信息、中间结果等。 console模块中的一些函数也可以直接作为全局函数使用，例如log, print等。\n     */\n    namespace console {\n\n        /**\n         * 显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。\n         */\n        function show(): void;\n\n        /**\n         * 隐藏控制台悬浮窗。\n         */\n        function hide(): void;\n\n        /**\n         * 清空控制台。\n         */\n        function clear(): void;\n\n        /**\n         * 打印到控制台，并带上换行符。 可以传入多个参数，第一个参数作为主要信息，其他参数作为类似于 printf(3) 中的代替值（参数都会传给 util.format()）。\n         */\n        function log(data: string, ...args: any[]): void;\n\n        /**\n         * 与console.log类似，但输出结果以灰色字体显示。输出优先级低于log，用于输出观察性质的信息。\n         */\n        function verbose(data: string, ...args: any[]): void;\n\n        /**\n         * 与console.log类似，但输出结果以绿色字体显示。输出优先级高于log, 用于输出重要信息。\n         */\n        function info(data: string, ...args: any[]): void;\n\n        /**\n         * 与console.log类似，但输出结果以蓝色字体显示。输出优先级高于info, 用于输出警告信息。\n         */\n        function warn(data: string, ...args: any[]): void;\n\n        /**\n         * 与console.log类似，但输出结果以红色字体显示。输出优先级高于warn, 用于输出错误信息。\n         */\n        function error(data: string, ...args: any[]): void;\n\n        /**\n         * 断言。如果value为false则输出错误信息message并停止脚本运行。\n         */\n        function assert(value: boolean, message: string);\n\n        /**\n         * 与console.log一样输出信息，并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。\n         */\n        function input(data: string, ...args: any[]): string | number | boolean;\n\n        /**\n         * 与console.log一样输出信息，并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。\n         */\n        function rawInput(data: string, ...args: any[]): string;\n\n        /**\n         * 设置控制台的大小，单位像素。\n         */\n        function setSize(wight: number, height: number): void;\n\n        /**\n         * 设置控制台的位置，单位像素。\n         */\n        function setPosition(x: number, y: number): void;\n\n    }\n\n\n    /**\n     * 打印到控制台，并带上换行符。 可以传入多个参数，第一个参数作为主要信息，其他参数作为类似于 printf(3) 中的代替值（参数都会传给 util.format()）。\n     */\n    function log(data: string, ...args: any[]): void;\n\n    /**\n     * 相当于log(text)。\n     */\n    function print(message: string | Object): void;\n\n\n    /* 基于坐标的触摸模拟 */\n\n    /**\n     * 设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时，屏幕宽度不一致会自动放缩坐标。\n     */\n    function setScreenMetrics(width: number, height: number): void;\n\n    /* 安卓7.0以上的触摸和手势模拟 */\n\n    /**\n     * Android7.0以上\n     * \n     * 模拟点击坐标(x, y)大约150毫秒，并返回是否点击成功。只有在点击执行完成后脚本才继续执行。\n     */\n    function click(x: number, y: number): void;\n\n    /**\n     * Android7.0以上\n     * \n     * 模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成（大约600毫秒）时脚本才会继续执行。\n     */\n    function longClick(x: number, y: number): void;\n\n    /**\n     * Android7.0以上\n     * \n     * 模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。\n     *\n     * 如果按住时间过短，那么会被系统认为是点击；如果时长超过500毫秒，则认为是长按。\n     */\n    function press(x: number, y: number, duration: number): void;\n\n    /**\n     * 模拟从坐标(x1, y1)滑动到坐标(x2, y2)，并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。\n     */\n    function swipe(x1: number, y1: number, x2: number, y2: number, duration: number): boolean;\n\n    type GesturePoint = [number, number];\n    /**\n     * 模拟手势操作。例如gesture(1000, [0, 0], [500, 500], [500, 1000])为模拟一个从(0, 0)到(500, 500)到(500, 100)的手势操作，时长为2秒。\n     */\n    function gesture(duration: number, point1: GesturePoint, point2: GesturePoint, ...points: GesturePoint[]): void;\n\n    type Gesture = [number, number, GesturePoint, GesturePoint] | [number, GesturePoint, GesturePoint];\n    /**\n     * 同时模拟多个手势。每个手势的参数为[delay, duration, 坐标], delay为延迟多久(毫秒)才执行该手势；duration为手势执行时长；坐标为手势经过的点的坐标。其中delay参数可以省略，默认为0。\n     */\n    function gestures(gesture: Gesture, ...gestures: Gesture[]): void;\n\n    /**\n     * RootAutomator是一个使用root权限来模拟触摸的对象，用它可以完成触摸与多点触摸，并且这些动作的执行没有延迟。\n     * \n     * 一个脚本中最好只存在一个RootAutomator，并且保证脚本结束退出他。\n     */\n    class RootAutomator {\n        /**\n         * 点击位置(x, y)。其中id是一个整数值，用于区分多点触摸，不同的id表示不同的\"手指\"。\n         */\n        tap(x: number, y: number, id?: number): void;\n\n        /**\n         * 模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。\n         */\n        swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;\n\n        /**\n         * 模拟按下位置(x, y)，时长为duration毫秒。\n         */\n        press(x: number, y: number, duration: number, id?: number): void;\n\n        /**\n         * 模拟长按位置(x, y)。\n         */\n        longPress(x: number, y: number, duration?: number, id?: number): void;\n\n        /**\n         * 模拟手指按下位置(x, y)。\n         */\n        touchDown(x: number, y: number, id?: number): void;\n\n        /**\n         * 模拟移动手指到位置(x, y)。\n         */\n        touchMove(x: number, y: number, id?: number): void;\n\n        /**\n         * 模拟手指弹起。\n         */\n        touchUp(id?: number): void;\n\n    }\n\n    /**\n     * 需要Root权限\n     * \n     * 实验API，请勿过度依赖\n     * \n     * 点击位置(x, y), 您可以通过\"开发者选项\"开启指针位置来确定点击坐标。\n     */\n    function Tap(x: number, y: number): void;\n\n    /**\n     * 需要Root权限\n     * \n     * 实验API，请勿过度依赖\n     * \n     * 滑动。从(x1, y1)位置滑动到(x2, y2)位置。\n     */\n    function Swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;\n\n    /**\n     * device模块提供了与设备有关的信息与操作，例如获取设备宽高，内存使用率，IMEI，调整设备亮度、音量等。\n     * \n     * 此模块的部分函数，例如调整音量，需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n     */\n    namespace device {\n\n        /**\n         * 设备屏幕分辨率宽度。例如1080。\n         */\n        var width: number;\n\n        /**\n         * 设备屏幕分辨率高度。例如1920。\n         */\n        var height: number;\n\n        /**\n         * 修订版本号，或者诸如\"M4-rc20\"的标识。\n         */\n        var buildId: string;\n\n        /**\n         * 设备的主板(?)名称。\n         */\n        var broad: string;\n\n        /**\n         * 与产品或硬件相关的厂商品牌，如\"Xiaomi\", \"Huawei\"等。\n         */\n        var brand: string;\n\n        /**\n         * 设备在工业设计中的名称（代号）。\n         */\n        var device: string;\n\n        /**\n         * 设备型号。\n         */\n        var model: string;\n\n        /**\n         * 整个产品的名称。\n         */\n        var product: string;\n\n        /**\n         * 设备Bootloader的版本。\n         */\n        var bootloader: string;\n\n        /**\n         * 设备的硬件名称(来自内核命令行或者/proc)。\n         */\n        var hardware: string;\n\n        /**\n         * 构建(build)的唯一标识码。\n         */\n        var fingerprint: string;\n\n        /**\n         * 硬件序列号。\n         */\n        var serial: string;\n\n        /**\n         * 安卓系统API版本。例如安卓4.4的sdkInt为19。\n         */\n        var sdkInt: number;\n\n        /**\n         * 设备固件版本号。\n         */\n        var incremental: string;\n\n        /**\n         * Android系统版本号。例如\"5.0\", \"7.1.1\"。\n         */\n        var release: string;\n\n        /**\n         * 基础操作系统。\n         */\n        var baseOS: string;\n\n        /**\n         * 安全补丁程序级别。\n         */\n        var securityPatch: string;\n\n        /**\n         * 开发代号，例如发行版是\"REL\"。\n         */\n        var codename: string;\n\n        /**\n         * 返回设备的IMEI。\n         */\n        function getIMEI(): string;\n\n        /**\n         * 返回设备的Android ID。\n         * \n         * Android ID为一个用16进制字符串表示的64位整数，在设备第一次使用时随机生成，之后不会更改，除非恢复出厂设置。\n         */\n        function getAndroidId(): string;\n\n        /**\n         * 返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取，否则会返回null。\n         * \n         * 可能的后续修改：未来可能增加有root权限的情况下通过root权限获取，从而在没有WLAN连接的情况下也能返回正确的Mac地址，因此请勿使用此函数判断WLAN连接。\n         */\n        function getMacAddress(): string;\n\n        /**\n         * 返回当前的(手动)亮度。范围为0~255。\n         */\n        function getBrightness(): number;\n\n        /**\n         * 返回当前亮度模式，0为手动亮度，1为自动亮度。\n         */\n        function getBrightnessMode(): number;\n\n        /**\n         * 设置当前手动亮度。如果当前是自动亮度模式，该函数不会影响屏幕的亮度。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setBrightness(b: number): void;\n\n        /**\n         * 设置当前亮度模式。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setBrightnessMode(mode: 0 | 1): void;\n\n        /**\n         * 返回当前媒体音量。\n         */\n        function getMusicVolume(): number;\n\n        /**\n         * 返回当前通知音量。\n         */\n        function getNotificationVolume(): number;\n\n        /**\n         * 返回当前闹钟音量。\n         */\n        function getAlarmVolume(): number;\n\n        /**\n         * 返回媒体音量的最大值。\n         */\n        function getMusicMaxVolume(): number;\n\n        /**\n         * 返回通知音量的最大值。\n         */\n        function getNotificationMaxVolume(): number;\n\n        /**\n         * 返回闹钟音量的最大值。\n         */\n        function getAlarmMaxVolume(): number;\n\n        /**\n         * 设置当前媒体音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setMusicVolume(volume: number): void;\n\n        /**\n         * 设置当前通知音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setNotificationVolume(volume: number): void;\n\n        /**\n         * 设置当前闹钟音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setAlarmVolume(volume: number): void;\n\n        /**\n         * 返回当前电量百分比。\n         */\n        function getBattery(): number;\n\n        /**\n         * 返回设备是否正在充电。\n         */\n        function isCharging(): boolean;\n\n        /**\n         * 返回设备内存总量，单位字节(B)。1MB = 1024 * 1024B。\n         */\n        function getTotalMem(): number;\n\n        /**\n         * 返回设备当前可用的内存，单位字节(B)。\n         */\n        function getAvailMem(): number;\n\n        /**\n         * 返回设备屏幕是否是亮着的。如果屏幕亮着，返回true; 否则返回false。\n         * \n         * 需要注意的是，类似于vivo xplay系列的息屏时钟不属于\"屏幕亮着\"的情况，虽然屏幕确实亮着但只能显示时钟而且不可交互，此时isScreenOn()也会返回false。\n         */\n        function isScreenOn(): boolean;\n\n        /**\n         * 唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。\n         */\n        function wakeUp(): void;\n\n        /**\n         * 如果屏幕没有点亮，则唤醒设备。\n         */\n        function wakeUpIfNeeded(): void;\n\n        /**\n         * 保持屏幕常亮。\n         * \n         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕，只能使得设备在无人操作的情况下保持屏幕常亮；同时，如果此函数调用时屏幕没有点亮，则会唤醒屏幕。\n         * \n         * 在某些设备上，如果不加参数timeout，只能在Auto.js的界面保持屏幕常亮，在其他界面会自动失效，这是因为设备的省电策略造成的。因此，建议使用比较长的时长来代替\"一直保持屏幕常亮\"的功能，例如device.keepScreenOn(3600 * 1000)。\n         * \n         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。\n         */\n        function keepScreenOn(timeout: number): void;\n\n        /**\n         * 保持屏幕常亮，但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作，不需要用户观看屏幕，可以让屏幕变暗来节省电量。\n         * \n         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕，只能使得设备在无人操作的情况下保持屏幕常亮；同时，如果此函数调用时屏幕没有点亮，则会唤醒屏幕。\n         * \n         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。\n         */\n        function keepScreenDim(timeout: number): void;\n\n        /**\n         * 取消设备保持唤醒状态。用于取消device.keepScreenOn(), device.keepScreenDim()等函数设置的屏幕常亮。\n         */\n        function cancelKeepingAwake(): void;\n\n        /**\n         * 使设备震动一段时间。\n         */\n        function vibrate(millis: number): void;\n\n        /**\n         * 如果设备处于震动状态，则取消震动。\n         */\n        function cancelVibration(): void;\n\n    }\n\n    /**\n     * dialogs 模块提供了简单的对话框支持，可以通过对话框和用户进行交互。\n     */\n    namespace dialogs {\n\n        /**\n         * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n         */\n        function alert(title: string, content?: string): void;\n\n        /**\n         * UI模式\n         * \n         * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n         */\n        function alert(title: string, content?: string, callback?: () => void): Promise<void>;\n\n        /**\n         * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n         */\n        function confirm(title: string, content?: string): boolean;\n\n        /**\n         * UI模式\n         * \n         * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n         */\n        function confirm(title: string, content?: string, callback?: (value: boolean) => void): Promise<boolean>;\n\n        /**\n         * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n         */\n        function rawInput(title: string, prefill?: string): string;\n\n        /**\n         * UI模式\n         * \n         * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n         */\n        function rawInput(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n\n        /**\n         * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于，会把输入的字符串用eval计算一遍再返回，返回的可能不是字符串。\n         */\n        function input(title: string, prefill?: string): any;\n\n        /**\n         * UI模式\n         * \n         * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于，会把输入的字符串用eval计算一遍再返回，返回的可能不是字符串。\n         */\n        function input(title: string, prefill?: string, callback?: (value: any) => void): Promise<any>;\n\n        /**\n         * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n         */\n        function prompt(title: string, prefill?: string): string;\n\n        /**\n         * UI模式\n         * \n         * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n         */\n        function prompt(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n\n        /**\n         * 显示一个带有选项列表的对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n         */\n        function select(title: string, items: string[]): number;\n\n        /**\n         * UI模式\n         * \n         * 显示一个带有选项列表的对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n         */\n        function select(title: string, items: string[], callback?: (value: number) => void): Promise<number>;\n\n        /**\n         * 显示一个单选列表对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n         */\n        function singleChoice(title: string, items: string[], index?: number): number;\n\n        /**\n         * UI模式\n         * \n         * 显示一个单选列表对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n         */\n        function singleChoice(title: string, items: string[], index?: number, callback?: (choice: number) => void): Promise<number>;\n\n        /**\n         * 显示一个多选列表对话框，等待用户选择，返回用户选择的选项索引的数组。如果用户取消了选择，返回[]。\n         */\n        function multiChoice(title: string, items: string[], indices?: number[]): number[];\n\n        /**\n         * UI模式\n         * \n         * 显示一个多选列表对话框，等待用户选择，返回用户选择的选项索引的数组。如果用户取消了选择，返回[]。\n         */\n        function multiChoice(title: string, items: string[], indices?: number[], callback?: (choices: number[]) => void): Promise<number[]>;\n\n\n    }\n\n    /**\n     * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n     */\n    function alert(title: string, content?: string): void;\n\n    /**\n     * UI模式\n     * \n     * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n     * \n     * 在ui模式下该函数返回一个Promise。\n     */\n    function alert(title: string, content?: string, callback?: () => void): Promise<void>;\n\n    /**\n     * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n     */\n    function confirm(title: string, content?: string): boolean;\n\n    /**\n     * UI模式\n     * \n     * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n     * \n     * 在ui模式下该函数返回一个Promise。\n     */\n    function confirm(title: string, content?: string, callback?: (value: boolean) => void): Promise<boolean>;\n\n    /**\n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function rawInput(title: string, prefill?: string): string;\n\n    /**\n     * UI模式\n     * \n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function rawInput(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n\n    /**\n     * engines模块包含了一些与脚本环境、脚本运行、脚本引擎有关的函数，包括运行其他脚本，关闭脚本等。\n     */\n    namespace engines {\n\n        /**\n         * 脚本引擎对象。\n         */\n        interface ScriptEngine {\n\n            /**\n             * 停止脚本引擎的执行。\n             */\n            forceStop(): void;\n\n            /**\n             * 返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹；对于其他脚本，例如字符串脚本，则为null或者执行时的设置值。\n             */\n            cwd(): string;\n        }\n\n        /**\n         * 执行脚本时返回的对象，可以通过他获取执行的引擎、配置等，也可以停止这个执行。\n         * \n         * 要停止这个脚本的执行，使用exectuion.getEngine().forceStop().\n         */\n        interface ScriptExecution {\n\n            /**\n             * 返回执行该脚本的脚本引擎对象(ScriptEngine)\n             */\n            getEngine(): ScriptEngine;\n\n            /**\n             * 返回该脚本的运行配置(ScriptConfig)\n             */\n            getConfig(): ScriptConfig;\n        }\n\n        /**\n         * 运行配置项。\n         */\n        interface ScriptConfig {\n\n            /**\n             * 延迟执行的毫秒数，默认为0。\n             */\n            delay?: number;\n\n            /**\n             * 循环运行次数，默认为1。0为无限循环。\n             */\n            loopTimes?: number;\n\n            /**\n             * 循环运行时两次运行之间的时间间隔，默认为0。\n             */\n            interval?: number;\n\n            /**\n             * 指定脚本运行的目录。这些路径会用于require时寻找模块文件。\n             */\n            path?: string | string[];\n\n            /**\n             * 返回一个字符串数组表示脚本运行时模块寻找的路径。\n             */\n            getpath?: string[];\n        }\n\n        /**\n         * 在新的脚本环境中运行脚本script。返回一个ScriptExectuion对象。\n         * \n         * 所谓新的脚本环境，指定是，脚本中的变量和原脚本的变量是不共享的，并且，脚本会在新的线程中运行。\n         */\n        function execScript(name: string, script: string, config?: ScriptConfig): ScriptExecution;\n\n        /**\n         * 在新的脚本环境中运行脚本文件path:string。返回一个ScriptExecution对象。\n         */\n        function execScriptFile(path: string, config?: ScriptConfig): ScriptExecution;\n\n        /**\n         * 在新的脚本环境中运行录制文件path:string。返回一个ScriptExecution对象。\n         */\n        function execAutoFile(path: string, config?: ScriptConfig): ScriptExecution;\n\n        /**\n         * 停止所有正在运行的脚本。包括当前脚本自身。\n         */\n        function stopAll(): void;\n\n        /**\n         * 停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。\n         */\n        function stopAllAndToast(): void;\n\n        /**\n         * 返回当前脚本的脚本引擎对象(ScriptEngine)\n         */\n        function myEngine(): void;\n    }\n\n\n    namespace events {\n\n        interface KeyEvent {\n            getAction();\n            getKeyCode(): number;\n            getEventTime(): number;\n            getDownTime(): number;\n            keyCodeToString(keyCode: number): string;\n        }\n\n        function emitter(): EventEmitter;\n\n        function observeKey(): void;\n\n        type Keys = 'volume_up' | 'volume_down' | 'home' | 'back' | 'menu';\n\n        function setKeyInterceptionEnabled(key: Keys, enabled: boolean);\n\n        function setKeyInterceptionEnabled(enabled: boolean);\n\n        function onKeyDown(keyName: Keys, listener: (e: KeyEvent) => void): void;\n\n        function onceKeyUp(keyName: Keys, listener: (e: KeyEvent) => void): void;\n\n        function removeAllKeyDownListeners(keyName: Keys): void;\n\n        function removeAllKeyUpListeners(keyName: Keys): void;\n\n        function observeTouch(): void;\n\n        function setTouchEventTimeout(timeout: number): void;\n\n        function getTouchEventTimeout(): number;\n\n        function onTouch(listener: (point: Point) => void): void;\n\n        function removeAllTouchListeners(): void;\n\n        function on(event: 'key' | 'key_down' | 'key_up', listener: (keyCode: number, e: KeyEvent) => void): void;\n\n        function on(event: 'exit', listener: () => void): void;\n\n        function observeNotification(): void;\n\n        function observeToast(): void;\n\n        /**\n         * 系统Toast对象\n         */\n        interface Toast {\n\n            /**\n             * 获取Toast的文本内容\n             */\n            getText(): string;\n\n            /**\n             * 获取发出Toast的应用包名\n             */\n            getPackageName(): void;\n\n        }\n\n        function onToast(listener: (toast: Toast) => void): void;\n\n        /**\n         * 通知对象，可以获取通知详情，包括通知标题、内容、发出通知的包名、时间等，也可以对通知进行操作，比如点击、删除。\n         */\n        interface Notification {\n            number: number;\n            when: number;\n            getPackageName(): string;\n            getTitle(): string;\n            getText(): string;\n            click(): void;\n            delete(): void;\n        }\n\n        function on(event: 'notification', listener: (notification: Notification) => void): void;\n\n    }\n\n    /**\n     * 按键事件中所有可用的按键名称\n     */\n    enum keys {\n        home,\n        back,\n        menu,\n        volume_up,\n        volume_down\n    }\n\n    interface EventEmitter {\n        defaultMaxListeners: number;\n        addListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        emit(eventName: string, ...args: any[]): boolean;\n        eventNames(): string[];\n        getMaxListeners(): number;\n        listenerCount(eventName: string): number;\n        on(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        once(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        prependListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        prependOnceListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        removeAllListeners(eventName?: string): EventEmitter;\n        removeListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n        setMaxListeners(n: number): EventEmitter;\n    }\n\n\n    namespace floaty {\n        function window(layout: any): FloatyWindow;\n        function closeAll(): void;\n        interface FloatyWindow {\n            setAdjustEnabled(enabled: boolean): void;\n            setPosition(x: number, y: number): void;\n            getX(): number;\n            getY(): number;\n            setSize(width: number, height: number): void;\n            getWidht(): number;\n            getHeight(): number;\n            close(): void;\n            exitOnClose(): void;\n        }\n    }\n\n\n    namespace files {\n        type byte = number;\n        function isFile(path: string): boolean;\n        function isDir(path: string): boolean;\n        function isEmptyDir(path: string): boolean;\n        function join(parent: string, ...child: string[]): string;\n        function create(path: string): boolean;\n        function createWithDirs(path: string): boolean;\n        function exists(path: string): boolean;\n        function ensureDir(path: string): void;\n        function read(path: string, encoding?: string): string;\n        function readBytes(path: string): byte[];\n        function write(path: string, text, encoding?: string): void;\n        function writeBytes(path: string, bytes: byte[]): void;\n        function append(path: string, text: string, encoding?: string): void;\n        function appendBytes(path: string, text: byte[], encoding?: string): void;\n        function copy(frompath: string, topath: string): boolean;\n        function move(frompath: string, topath: string): boolean;\n        function rename(path: string, newName): boolean;\n        function renameWithoutExtension(path: string, newName: string): boolean;\n        function getName(path: string): string;\n        function getNameWithoutExtension(path: string): string;\n        function getExtension(path: string): string;\n        function remove(path: string): boolean;\n        function removeDir(path: string): boolean;\n        function getSdcardPath(): string;\n        function cwd(): string;\n        function path(relativePath: string): string;\n        function listDir(path: string, filter: (filename: string) => boolean): string[];\n    }\n\n    interface ReadableTextFile {\n        read(): string;\n        read(maxCount: number): string;\n        readline(): string;\n        readlines(): string[];\n        close(): void;\n    }\n\n    interface WritableTextFile {\n        write(text: string): void;\n        writeline(line: string): void;\n        writelines(lines: string[]): void;\n        flush(): void;\n        close(): void;\n    }\n\n    function open(path: string, mode?: 'r', encoding?: string, bufferSize?: number): ReadableTextFile;\n    function open(path: string, mode?: 'w' | 'a', encoding?: string, bufferSize?: number): WritableTextFile;\n\n    namespace media {\n        function scanFile(path: string): void;\n        function playMusic(path: string, volume?: number, looping?: boolean);\n        function musicSeekTo(msec: number): void;\n        function pauseMusic(): void;\n        function resumeMusic(): void;\n        function stopMusic(): void;\n        function isMusicPlaying(): boolean;\n        function getMusicDuration(): number;\n        function getMusicCurrentPosition(): number;\n    }\n\n    namespace sensors {\n        interface SensorEventEmitter {\n            on(eventName: 'change', callback: (...args: number[]) => void): void;\n            on(eventName: 'accuracy_change', callback: (accuracy: number) => void): void;\n        }\n        function on(eventName: 'unsupported_sensor', callback: (sensorName: string) => void): void;\n        function register(sensorName: string, delay?: delay): SensorEventEmitter;\n        function unregister(emitter: SensorEventEmitter);\n        function unregisterAll(): void;\n        var ignoresUnsupportedSensor: boolean;\n        enum delay {\n            normal,\n            ui,\n            game,\n            fastest\n        }\n    }\n\n    function sleep(n: number): void;\n\n    function currentPackage(): string;\n\n    function currentActivity(): string;\n\n    function setClip(test: string): void;\n\n    function getClip(): string;\n\n    function toast(message: string): void;\n\n    function toastLog(message: string): void;\n\n    function waitForActivity(activity: string, period?: number): void;\n\n    function waitForPackage(packageName: string, period?: number): void;\n\n    function exit(): void;\n\n    function random(): number;\n    function random(min: number, max: number): number;\n\n\n    namespace http {\n        interface HttpRequestOptions {\n            header: { [key: string]: string },\n            method: 'GET' | 'POST' | 'PUT' | 'DELET' | 'PATCH';\n            contentType: string;\n            body: string | string[] | files.byte[]\n        }\n        interface Request {\n\n        }\n        interface Response {\n            statusCode: number;\n            statusMessage: string;\n            headers: { [key: string]: string };\n            body: ResponseBody;\n            request: Request;\n            url: string;\n            method: 'GET' | 'POST' | 'PUT' | 'DELET' | 'PATCH';\n        }\n        interface ResponseBody {\n            bytes(): files.byte[];\n            string(): string;\n            json(): object;\n            contentType: string;\n        }\n        function get(url: string, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n        function post(url: string, data: object, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n        function postJson(url: string, data?: object, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n\n        interface RequestMultipartBody {\n            file: ReadableTextFile | [string, string] | [string, string, string];\n        }\n        function postMultipart(url: string, files: RequestMultipartBody, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n        function postMultipart(url: string, files: { [key: string]: string } & RequestMultipartBody, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n\n        function request(url: string, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n\n    }\n\n\n    interface Image {\n        getWidth(): number;\n        getHeight(): number;\n        saveTo(path: string): void;\n        pixel(x: number, y: number): number;\n    }\n\n\n    namespace images {\n        function requestScreenCapture(landscape?: boolean): boolean;\n        function captureScreen(): Image;\n        function captureScreen(path: string): void;\n        function pixel(image: Image, x: number, y: number): number;\n        function save(image: Image, path: string): void;\n        function read(path: string): Image;\n        function load(url: string): Image;\n        interface FindColorOptions {\n            region?: [number, number] | [number, number, number, number];\n            threshold?: number;\n        }\n        function clip(image: Image, x: number, y: number, w: number, h: number): Image;\n        function findColor(image: Image, color: number | string, options: FindColorOptions): Point;\n        function findColorInRegion(image: Image, color: number | string, x: number, y: number, width?: number, height?: number, threshold?: number): Point;\n        function findColorEquals(image: Image, color: number | string, x?: number, y?: number, width?: number, height?: number): Point;\n        function detectsColor(image: Image, color: number | string, x: number, y: number, threshold?: number, algorithm?: 'diff'): Point;\n        interface FindImageOptions {\n            region?: [number, number] | [number, number, number, number];\n            threshold?: number;\n            level?: number;\n        }\n        function findImage(image: Image, template: Image, options?: FindImageOptions): Point;\n        function findImageInRegion(image: Image, template: Image, x: number, y: number, width?: number, height?: number, threshold?: number): Point;\n        function findMultiColors(image: Image, firstColor: number | string, colors: [number, number, number | string][], options?: FindColorOptions): Point;\n    }\n\n\n    namespace colors {\n        function toString(color: number): string;\n        function red(color: number | string): number;\n        function green(color: number | string): number;\n        function blue(color: number | string): number;\n        function alpha(color: number | string): number;\n        function rgb(red: number, green: number, blue: number): number;\n        function argb(alpha: number, red: number, green: number, blue: number): number;\n        function parseColor(colorStr: string): number;\n        function isSimilar(color1: number | string, color2: number | string, threshold: number, algorithm: 'diff' | 'rgb' | 'rgb+' | 'hs'): boolean;\n        function equals(color1: number | string, color2: number | string): boolean;\n    }\n\n\n    /* 全局按键 */\n    function back(): boolean;\n    function home(): boolean;\n    function powerDialog(): boolean;\n    function notifications(): boolean;\n    function quickSettings(): boolean;\n    function recents(): boolean;\n    function splitScreen(): boolean;\n    function Home(): void;\n    function Back(): void;\n    function Power(): void;\n    function Menu(): void;\n    function VolumeUp(): void;\n    function VolumeDown(): void;\n    function Camera(): void;\n    function Up(): void;\n    function Down(): void;\n    function Left(): void;\n    function Right(): void;\n    function OK(): void;\n    function Text(text: string): void;\n    function KeyCode(code: number | string): void;\n\n\n    // var module: { exports: any };\n\n\n    interface Storage {\n        get<T>(key: string, defaultValue?: T): T;\n        put<T>(key: string, value: T): void;\n        remove(key: string): void;\n        contains(key: string): boolean;\n        clear(): void;\n    }\n\n    namespace storages {\n        function create(name: string): Storage;\n        function remove(name: string): boolean;\n    }\n\n    function auto(mode?: 'fast' | 'normal'): void;\n    namespace auto {\n        function waitFor(): void;\n        function setMode(mode: 'fast' | 'normal'): void;\n    }\n    function selector(): UiSelector;\n    function click(text: string, index?: number): boolean;\n    function click(left: number, top: number, bottom: number, right: number): boolean;\n    function longClick(text: string, index?: number): boolean;\n    function scrollUp(index?: number): boolean;\n    function scrollDown(index?: number): boolean;\n    function setText(text: string): boolean;\n    function setText(index: number, text: string): boolean;\n    function input(text: string): boolean;\n    function input(index: number, text: string): boolean;\n\n    interface UiSelector {\n        text(str: string): UiSelector;\n        textContains(str: string): UiSelector;\n        textStartsWith(prefix: string): UiSelector;\n        textEndsWith(suffix: string): UiSelector;\n        textMatches(reg: string | RegExp): UiSelector;\n        desc(str: string): UiSelector;\n        descContains(str: string): UiSelector;\n        descStartsWith(prefix: string): UiSelector;\n        descEndsWith(suffix: string): UiSelector;\n        descMatches(reg: string | RegExp): UiSelector;\n        id(resId: string): UiSelector;\n        idContains(str: string): UiSelector;\n        idStartsWith(prefix: string): UiSelector;\n        idEndsWith(suffix: string): UiSelector;\n        idMatches(reg: string | RegExp): UiSelector;\n        className(str: string): UiSelector;\n        classNameContains(str: string): UiSelector;\n        classNameStartsWith(prefix: string): UiSelector;\n        classNameEndsWith(suffix: string): UiSelector;\n        classNameMatches(reg: string | RegExp): UiSelector;\n        packageName(str: string): UiSelector;\n        packageNameContains(str: string): UiSelector;\n        packageNameStartsWith(prefix: string): UiSelector;\n        packageNameEndsWith(suffix: string): UiSelector;\n        packageNameMatches(reg: string | RegExp): UiSelector;\n        bounds(left: number, top: number, right: number, buttom: number): UiSelector;\n        boundsInside(left: number, top: number, right: number, buttom: number): UiSelector;\n        boundsContains(left: number, top: number, right: number, buttom: number): UiSelector;\n        drawingOrder(order): UiSelector;\n        clickable(b: boolean): UiSelector;\n        longClickable(b: boolean): UiSelector;\n        checkable(b: boolean): UiSelector;\n        selected(b: boolean): UiSelector;\n        enabled(b: boolean): UiSelector;\n        scrollable(b: boolean): UiSelector;\n        editable(b: boolean): UiSelector;\n        multiLine(b: boolean): UiSelector;\n        findOne(): UiObject;\n        findOne(timeout: number): UiObject;\n        findOnce(): UiObject;\n        findOnce(i: number): UiObject;\n        find(): UiCollection;\n        untilFind(): UiCollection;\n        exists(): boolean;\n        waitFor(): void;\n        filter(filter: (obj: UiObject) => boolean)\n    }\n\n    interface UiObject {\n        click(): boolean;\n        longClick(): boolean;\n        setText(text: string): boolean;\n        copy(): boolean;\n        cut(): boolean;\n        paste(): boolean;\n        setSelection(start, end): boolean;\n        scrollForward(): boolean;\n        scrollBackward(): boolean;\n        select(): boolean;\n        collapse(): boolean;\n        expand(): boolean;\n        show(): boolean;\n        scrollUp(): boolean;\n        scrollDown(): boolean;\n        scrollLeft(): boolean;\n        scrollRight(): boolean;\n        children(): UiCollection;\n        childCount(): number;\n        child(i: number): UiObject;\n        parent(): UiObject;\n        bounds(): Rect;\n        boundsInParent(): Rect;\n        drawingOrder(): number;\n        id(): string;\n        text(): string;\n        findByText(str: string): UiCollection;\n        findOne(selector): UiObject;\n        find(selector): UiCollection;\n    }\n\n    interface UiCollection {\n        size(): number;\n        get(i: number): UiObject;\n        each(func: (obj: UiObject) => void): void;\n        empty(): boolean;\n        nonEmpty(): boolean;\n        find(selector): UiCollection;\n        findOne(selector): UiObject;\n    }\n\n    interface Rect {\n        left: number;\n        right: number;\n        top: number;\n        bottom: number;\n        centerX(): number;\n        centerY(): number;\n        width(): number;\n        height(): number;\n        contains(r): Rect;\n        intersect(r): Rect;\n    }\n\n}\n\n\nexport { };\n"
  },
  {
    "path": "types/modules/app.d.ts",
    "content": "/**\n * app模块提供一系列函数，用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。\n */\ndeclare namespace app {\n\n    /**\n     * 通过应用名称启动应用。如果该名称对应的应用不存在，则返回false; 否则返回true。如果该名称对应多个应用，则只启动其中某一个。\n     */\n    function launchApp(appName: string): boolean;\n\n    /** \n     * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n     */\n    function launch(packageName: string): boolean;\n\n    /**\n     * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n     */\n    function launchPackage(packageName: string): boolean;\n\n    /**\n     * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n     */\n    function getPackageName(appName: string): string;\n\n    /**\n     * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用，返回null。\n     */\n    function getAppName(packageName: string): string;\n\n    /**\n     * 打开应用的详情页(设置页)。如果找不到该应用，返回false; 否则返回true。\n     */\n    function openAppSetting(packageName: string): boolean;\n\n    /**\n     * 用其他应用查看文件。文件不存在的情况由查看文件的应用处理。如果找不出可以查看该文件的应用，则抛出ActivityNotException。\n     * \n     * @throws ActivityNotException\n     */\n    function viewFile(path: string): void;\n\n    /**\n     * 用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。如果找不出可以编辑该文件的应用，则抛出ActivityNotException。\n     * \n     * @throws ActivityNotException\n     */\n    function editFile(path: string): void;\n\n    /**\n     * 卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装，由应用卸载程序处理，可能弹出\"未找到应用\"的提示。\n     */\n    function uninstall(packageName: string): void;\n\n    /**\n     * 用浏览器打开网站url。网站的Url，如果不以\"http:// \"或\"https:// \"开头则默认是\"http:// \"。\n     */\n    function openUrl(url: string): void;\n\n    /**\n     * 发送邮件的参数，这些选项均是可选的。\n     */\n    interface SendEmailOptions {\n        /**\n         * 收件人的邮件地址。如果有多个收件人，则用字符串数组表示\n         */\n        email?: string | string[];\n        /**\n         * 抄送收件人的邮件地址。如果有多个抄送收件人，则用字符串数组表示\n         */\n        cc?: string | string[];\n        /**\n         * 密送收件人的邮件地址。如果有多个密送收件人，则用字符串数组表示\n         */\n        bcc?: string | string[];\n        /**\n         * 邮件主题(标题)\n         */\n        subject?: string;\n        /**\n         * 邮件正文\n         */\n        text?: string;\n        /**\n         * 附件的路径。\n         */\n        attachment?: string;\n    }\n\n    /**\n     * 根据选项options调用邮箱应用发送邮件。如果没有安装邮箱应用，则抛出ActivityNotException。\n     */\n    function sendEmail(options: SendEmailOptions): void;\n\n    /**\n     * 启动Auto.js的特定界面。该函数在Auto.js内运行则会打开Auto.js内的界面，在打包应用中运行则会打开打包应用的相应界面。\n     */\n    function startActivity(name: 'console' | 'settings'): void;\n\n    /**\n     * Intent(意图) 是一个消息传递对象，您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信.\n     */\n    interface Intent { }\n\n    /**\n     * 构造意图Intent对象所需设置。\n     */\n    interface IntentOptions {\n        action?: string;\n        type?: string;\n        data?: string;\n        category?: string[];\n        packageName?: string;\n        className?: string;\n        extras?: Object;\n    }\n\n    /**\n     * 根据选项，构造一个意图Intent对象。\n     */\n    function intent(options: IntentOptions): Intent;\n\n    /**\n     * 根据选项构造一个Intent，并启动该Activity。\n     */\n    function startActivity(intent: Intent): void;\n\n    /**\n     * 根据选项构造一个Intent，并发送该广播。\n     */\n    function sendBroadcast(intent: Intent): void;\n\n}\n\n/**\n * 通过应用名称启动应用。如果该名称对应的应用不存在，则返回false; 否则返回true。如果该名称对应多个应用，则只启动其中某一个。\n */\ndeclare function launchApp(appName: string): boolean;\n\n/** \n * 通过应用包名启动应用。如果该包名对应的应用不存在，则返回false；否则返回true。 \n */\ndeclare function launch(packageName: string): boolean;\n\n/**\n * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n */\ndeclare function getPackageName(appName: string): string;\n\n/**\n * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用，返回null；如果该名称对应多个应用，则只返回其中某一个的包名。\n */\ndeclare function getPackageName(appName: string): string;\n\n/**\n * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用，返回null。\n */\ndeclare function getAppName(packageName: string): string;\n\n/**\n * 打开应用的详情页(设置页)。如果找不到该应用，返回false; 否则返回true。\n */\ndeclare function openAppSetting(packageName: string): boolean;\n"
  },
  {
    "path": "types/modules/colors.d.ts",
    "content": "declare namespace colors {\n    function toString(color: number): string;\n    function red(color: number | string): number;\n    function green(color: number | string): number;\n    function blue(color: number | string): number;\n    function alpha(color: number | string): number;\n    function rgb(red: number, green: number, blue: number): number;\n    function argb(alpha: number, red: number, green: number, blue: number): number;\n    function parseColor(colorStr: string): number;\n    function isSimilar(color1: number | string, color2: number | string, threshold: number, algorithm: 'diff' | 'rgb' | 'rgb+' | 'hs'): boolean;\n    function equals(color1: number | string, color2: number | string): boolean;\n}\n\n"
  },
  {
    "path": "types/modules/console.d.ts",
    "content": "interface Console {\n    /**\n * 显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。\n */\n    show(): void;\n\n    /**\n     * 隐藏控制台悬浮窗。\n     */\n    hide(): void;\n\n    /**\n     * 清空控制台。\n     */\n    clear(): void;\n\n    /**\n     * 打印到控制台，并带上换行符。 可以传入多个参数，第一个参数作为主要信息，其他参数作为类似于 printf(3) 中的代替值（参数都会传给 util.format()）。\n     */\n    log(data: string, ...args: any[]): void;\n\n    /**\n     * 与console.log类似，但输出结果以灰色字体显示。输出优先级低于log，用于输出观察性质的信息。\n     */\n    verbose(data: string, ...args: any[]): void;\n\n    /**\n     * 与console.log类似，但输出结果以绿色字体显示。输出优先级高于log, 用于输出重要信息。\n     */\n    info(data: string, ...args: any[]): void;\n\n    /**\n     * 与console.log类似，但输出结果以蓝色字体显示。输出优先级高于info, 用于输出警告信息。\n     */\n    warn(data: string, ...args: any[]): void;\n\n    /**\n     * 与console.log类似，但输出结果以红色字体显示。输出优先级高于warn, 用于输出错误信息。\n     */\n    error(data: string, ...args: any[]): void;\n\n    /**\n     * 断言。如果value为false则输出错误信息message并停止脚本运行。\n     */\n    assert(value: boolean, message: string);\n\n    /**\n     * 与console.log一样输出信息，并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。\n     */\n    input(data: string, ...args: any[]): string | number | boolean;\n\n    /**\n     * 与console.log一样输出信息，并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。\n     */\n    rawInput(data: string, ...args: any[]): string;\n\n    /**\n     * 设置控制台的大小，单位像素。\n     */\n    setSize(wight: number, height: number): void;\n\n    /**\n     * 设置控制台的位置，单位像素。\n     */\n    setPosition(x: number, y: number): void;\n\n}\n\n/**\n * 打印到控制台，并带上换行符。 可以传入多个参数，第一个参数作为主要信息，其他参数作为类似于 printf(3) 中的代替值（参数都会传给 util.format()）。\n */\ndeclare function log(data: string, ...args: any[]): void;\n\n/**\n * 相当于log(text)。\n */\ndeclare function print(message: string | Object): void;\n\n"
  },
  {
    "path": "types/modules/coordinate.d.ts",
    "content": "/* 基于坐标的触摸模拟 */\n\n/**\n * 设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时，屏幕宽度不一致会自动放缩坐标。\n */\ndeclare function setScreenMetrics(width: number, height: number): void;\n\n/* 安卓7.0以上的触摸和手势模拟 */\n\n/**\n * Android7.0以上\n * \n * 模拟点击坐标(x, y)大约150毫秒，并返回是否点击成功。只有在点击执行完成后脚本才继续执行。\n */\ndeclare function click(x: number, y: number): void;\n\n/**\n * Android7.0以上\n * \n * 模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成（大约600毫秒）时脚本才会继续执行。\n */\ndeclare function longClick(x: number, y: number): void;\n\n/**\n * Android7.0以上\n * \n * 模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。\n *\n * 如果按住时间过短，那么会被系统认为是点击；如果时长超过500毫秒，则认为是长按。\n */\ndeclare function press(x: number, y: number, duration: number): void;\n\n/**\n * 模拟从坐标(x1, y1)滑动到坐标(x2, y2)，并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。\n */\ndeclare function swipe(x1: number, y1: number, x2: number, y2: number, duration: number): boolean;\n\ntype GesturePoint = [number, number];\n/**\n * 模拟手势操作。例如gesture(1000, [0, 0], [500, 500], [500, 1000])为模拟一个从(0, 0)到(500, 500)到(500, 100)的手势操作，时长为2秒。\n */\ndeclare function gesture(duration: number, point1: GesturePoint, point2: GesturePoint, ...points: GesturePoint[]): void;\n\ntype Gesture = [number, number, GesturePoint, GesturePoint] | [number, GesturePoint, GesturePoint];\n/**\n * 同时模拟多个手势。每个手势的参数为[delay, duration, 坐标], delay为延迟多久(毫秒)才执行该手势；duration为手势执行时长；坐标为手势经过的点的坐标。其中delay参数可以省略，默认为0。\n */\ndeclare function gestures(gesture: Gesture, ...gestures: Gesture[]): void;\n"
  },
  {
    "path": "types/modules/device.d.ts",
    "content": "/**\n * device模块提供了与设备有关的信息与操作，例如获取设备宽高，内存使用率，IMEI，调整设备亮度、音量等。\n * \n * 此模块的部分函数，例如调整音量，需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n */\ndeclare global {\n    namespace device {\n\n        /**\n         * 设备屏幕分辨率宽度。例如1080。\n         */\n        const width: number;\n\n        /**\n         * 设备屏幕分辨率高度。例如1920。\n         */\n        const height: number;\n\n        /**\n         * 修订版本号，或者诸如\"M4-rc20\"的标识。\n         */\n        const buildId: string;\n\n        /**\n         * 设备的主板(?)名称。\n         */\n        const broad: string;\n\n        /**\n         * 与产品或硬件相关的厂商品牌，如\"Xiaomi\", \"Huawei\"等。\n         */\n        const brand: string;\n\n        /**\n         * 设备在工业设计中的名称（代号）。\n         */\n        const device: string;\n\n        /**\n         * 设备型号。\n         */\n        const model: string;\n\n        /**\n         * 整个产品的名称。\n         */\n        const product: string;\n\n        /**\n         * 设备Bootloader的版本。\n         */\n        const bootloader: string;\n\n        /**\n         * 设备的硬件名称(来自内核命令行或者/proc)。\n         */\n        const hardware: string;\n\n        /**\n         * 构建(build)的唯一标识码。\n         */\n        const fingerprint: string;\n\n        /**\n         * 硬件序列号。\n         */\n        const serial: string;\n\n        /**\n         * 安卓系统API版本。例如安卓4.4的sdkInt为19。\n         */\n        const sdkInt: number;\n\n        /**\n         * 设备固件版本号。\n         */\n        const incremental: string;\n\n        /**\n         * Android系统版本号。例如\"5.0\", \"7.1.1\"。\n         */\n        const release: string;\n\n        /**\n         * 基础操作系统。\n         */\n        const baseOS: string;\n\n        /**\n         * 安全补丁程序级别。\n         */\n        const securityPatch: string;\n\n        /**\n         * 开发代号，例如发行版是\"REL\"。\n         */\n        const codename: string;\n\n        /**\n         * 返回设备的IMEI。\n         */\n        function getIMEI(): string;\n\n        /**\n         * 返回设备的Android ID。\n         * \n         * Android ID为一个用16进制字符串表示的64位整数，在设备第一次使用时随机生成，之后不会更改，除非恢复出厂设置。\n         */\n        function getAndroidId(): string;\n\n        /**\n         * 返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取，否则会返回null。\n         * \n         * 可能的后续修改：未来可能增加有root权限的情况下通过root权限获取，从而在没有WLAN连接的情况下也能返回正确的Mac地址，因此请勿使用此函数判断WLAN连接。\n         */\n        function getMacAddress(): string;\n\n        /**\n         * 返回当前的(手动)亮度。范围为0~255。\n         */\n        function getBrightness(): number;\n\n        /**\n         * 返回当前亮度模式，0为手动亮度，1为自动亮度。\n         */\n        function getBrightnessMode(): number;\n\n        /**\n         * 设置当前手动亮度。如果当前是自动亮度模式，该函数不会影响屏幕的亮度。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setBrightness(b: number): void;\n\n        /**\n         * 设置当前亮度模式。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setBrightnessMode(mode: 0 | 1): void;\n\n        /**\n         * 返回当前媒体音量。\n         */\n        function getMusicVolume(): number;\n\n        /**\n         * 返回当前通知音量。\n         */\n        function getNotificationVolume(): number;\n\n        /**\n         * 返回当前闹钟音量。\n         */\n        function getAlarmVolume(): number;\n\n        /**\n         * 返回媒体音量的最大值。\n         */\n        function getMusicMaxVolume(): number;\n\n        /**\n         * 返回通知音量的最大值。\n         */\n        function getNotificationMaxVolume(): number;\n\n        /**\n         * 返回闹钟音量的最大值。\n         */\n        function getAlarmMaxVolume(): number;\n\n        /**\n         * 设置当前媒体音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setMusicVolume(volume: number): void;\n\n        /**\n         * 设置当前通知音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setNotificationVolume(volume: number): void;\n\n        /**\n         * 设置当前闹钟音量。\n         * \n         * 此函数需要\"修改系统设置\"的权限。如果没有该权限，会抛出SecurityException并跳转到权限设置界面。\n         */\n        function setAlarmVolume(volume: number): void;\n\n        /**\n         * 返回当前电量百分比。\n         */\n        function getBattery(): number;\n\n        /**\n         * 返回设备是否正在充电。\n         */\n        function isCharging(): boolean;\n\n        /**\n         * 返回设备内存总量，单位字节(B)。1MB = 1024 * 1024B。\n         */\n        function getTotalMem(): number;\n\n        /**\n         * 返回设备当前可用的内存，单位字节(B)。\n         */\n        function getAvailMem(): number;\n\n        /**\n         * 返回设备屏幕是否是亮着的。如果屏幕亮着，返回true; 否则返回false。\n         * \n         * 需要注意的是，类似于vivo xplay系列的息屏时钟不属于\"屏幕亮着\"的情况，虽然屏幕确实亮着但只能显示时钟而且不可交互，此时isScreenOn()也会返回false。\n         */\n        function isScreenOn(): boolean;\n\n        /**\n         * 唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。\n         */\n        function wakeUp(): void;\n\n        /**\n         * 如果屏幕没有点亮，则唤醒设备。\n         */\n        function wakeUpIfNeeded(): void;\n\n        /**\n         * 保持屏幕常亮。\n         * \n         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕，只能使得设备在无人操作的情况下保持屏幕常亮；同时，如果此函数调用时屏幕没有点亮，则会唤醒屏幕。\n         * \n         * 在某些设备上，如果不加参数timeout，只能在Auto.js的界面保持屏幕常亮，在其他界面会自动失效，这是因为设备的省电策略造成的。因此，建议使用比较长的时长来代替\"一直保持屏幕常亮\"的功能，例如device.keepScreenOn(3600 * 1000)。\n         * \n         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。\n         */\n        function keepScreenOn(timeout: number): void;\n\n        /**\n         * 保持屏幕常亮，但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作，不需要用户观看屏幕，可以让屏幕变暗来节省电量。\n         * \n         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕，只能使得设备在无人操作的情况下保持屏幕常亮；同时，如果此函数调用时屏幕没有点亮，则会唤醒屏幕。\n         * \n         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。\n         */\n        function keepScreenDim(timeout: number): void;\n\n        /**\n         * 取消设备保持唤醒状态。用于取消device.keepScreenOn(), device.keepScreenDim()等函数设置的屏幕常亮。\n         */\n        function cancelKeepingAwake(): void;\n\n        /**\n         * 使设备震动一段时间。\n         */\n        function vibrate(millis: number): void;\n\n        /**\n         * 如果设备处于震动状态，则取消震动。\n         */\n        function cancelVibration(): void;\n\n    }\n\n}\n\nexport { };"
  },
  {
    "path": "types/modules/dialogs.d.ts",
    "content": "/**\n * dialogs 模块提供了简单的对话框支持，可以通过对话框和用户进行交互。\n */\ndeclare namespace dialogs {\n\n    /**\n     * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n     */\n    function alert(title: string, content?: string): void;\n\n    /**\n     * UI模式\n     * \n     * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n     */\n    function alert(title: string, content?: string, callback?: () => void): Promise<void>;\n\n    /**\n     * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n     */\n    function confirm(title: string, content?: string): boolean;\n\n    /**\n     * UI模式\n     * \n     * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n     */\n    function confirm(title: string, content?: string, callback?: (value: boolean) => void): Promise<boolean>;\n\n    /**\n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function rawInput(title: string, prefill?: string): string;\n\n    /**\n     * UI模式\n     * \n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function rawInput(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n\n    /**\n     * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于，会把输入的字符串用eval计算一遍再返回，返回的可能不是字符串。\n     */\n    function input(title: string, prefill?: string): any;\n\n    /**\n     * UI模式\n     * \n     * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于，会把输入的字符串用eval计算一遍再返回，返回的可能不是字符串。\n     */\n    function input(title: string, prefill?: string, callback?: (value: any) => void): Promise<any>;\n\n    /**\n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function prompt(title: string, prefill?: string): string;\n\n    /**\n     * UI模式\n     * \n     * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n     */\n    function prompt(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n\n    /**\n     * 显示一个带有选项列表的对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n     */\n    function select(title: string, items: string[]): number;\n\n    /**\n     * UI模式\n     * \n     * 显示一个带有选项列表的对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n     */\n    function select(title: string, items: string[], callback?: (value: number) => void): Promise<number>;\n\n    /**\n     * 显示一个单选列表对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n     */\n    function singleChoice(title: string, items: string[], index?: number): number;\n\n    /**\n     * UI模式\n     * \n     * 显示一个单选列表对话框，等待用户选择，返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择，返回-1。\n     */\n    function singleChoice(title: string, items: string[], index?: number, callback?: (choice: number) => void): Promise<number>;\n\n    /**\n     * 显示一个多选列表对话框，等待用户选择，返回用户选择的选项索引的数组。如果用户取消了选择，返回[]。\n     */\n    function multiChoice(title: string, items: string[], indices?: number[]): number[];\n\n    /**\n     * UI模式\n     * \n     * 显示一个多选列表对话框，等待用户选择，返回用户选择的选项索引的数组。如果用户取消了选择，返回[]。\n     */\n    function multiChoice(title: string, items: string[], indices?: number[], callback?: (choices: number[]) => void): Promise<number[]>;\n\n\n}\n\n/**\n * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n */\ndeclare function alert(title: string, content?: string): void;\n\n/**\n * UI模式\n * \n * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。\n * \n * 在ui模式下该函数返回一个Promise。\n */\ndeclare function alert(title: string, content?: string, callback?: () => void): Promise<void>;\n\n/**\n * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n */\ndeclare function confirm(title: string, content?: string): boolean;\n\n/**\n * UI模式\n * \n * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ，否则返回 false 。\n * \n * 在ui模式下该函数返回一个Promise。\n */\ndeclare function confirm(title: string, content?: string, callback?: (value: boolean) => void): Promise<boolean>;\n\n/**\n * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n */\ndeclare function rawInput(title: string, prefill?: string): string;\n\n/**\n * UI模式\n * \n * 显示一个包含输入框的对话框，等待用户输入内容，并在用户点击确定时将输入的字符串返回。如果用户取消了输入，返回null。\n */\ndeclare function rawInput(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;\n"
  },
  {
    "path": "types/modules/engines.d.ts",
    "content": "/**\n * engines模块包含了一些与脚本环境、脚本运行、脚本引擎有关的函数，包括运行其他脚本，关闭脚本等。\n */\ndeclare namespace engines {\n\n    /**\n     * 脚本引擎对象。\n     */\n    interface ScriptEngine {\n\n        /**\n         * 停止脚本引擎的执行。\n         */\n        forceStop(): void;\n\n        /**\n         * 返回脚本执行的路径。对于一个脚本文件而言为这个脚本所在的文件夹；对于其他脚本，例如字符串脚本，则为null或者执行时的设置值。\n         */\n        cwd(): string;\n    }\n\n    /**\n     * 执行脚本时返回的对象，可以通过他获取执行的引擎、配置等，也可以停止这个执行。\n     * \n     * 要停止这个脚本的执行，使用exectuion.getEngine().forceStop().\n     */\n    interface ScriptExecution {\n\n        /**\n         * 返回执行该脚本的脚本引擎对象(ScriptEngine)\n         */\n        getEngine(): ScriptEngine;\n\n        /**\n         * 返回该脚本的运行配置(ScriptConfig)\n         */\n        getConfig(): ScriptConfig;\n    }\n\n    /**\n     * 运行配置项。\n     */\n    interface ScriptConfig {\n\n        /**\n         * 延迟执行的毫秒数，默认为0。\n         */\n        delay?: number;\n\n        /**\n         * 循环运行次数，默认为1。0为无限循环。\n         */\n        loopTimes?: number;\n\n        /**\n         * 循环运行时两次运行之间的时间间隔，默认为0。\n         */\n        interval?: number;\n\n        /**\n         * 指定脚本运行的目录。这些路径会用于require时寻找模块文件。\n         */\n        path?: string | string[];\n\n        /**\n         * 返回一个字符串数组表示脚本运行时模块寻找的路径。\n         */\n        getpath?: string[];\n    }\n\n    /**\n     * 在新的脚本环境中运行脚本script。返回一个ScriptExectuion对象。\n     * \n     * 所谓新的脚本环境，指定是，脚本中的变量和原脚本的变量是不共享的，并且，脚本会在新的线程中运行。\n     */\n    function execScript(name: string, script: string, config?: ScriptConfig): ScriptExecution;\n\n    /**\n     * 在新的脚本环境中运行脚本文件path:string。返回一个ScriptExecution对象。\n     */\n    function execScriptFile(path: string, config?: ScriptConfig): ScriptExecution;\n\n    /**\n     * 在新的脚本环境中运行录制文件path:string。返回一个ScriptExecution对象。\n     */\n    function execAutoFile(path: string, config?: ScriptConfig): ScriptExecution;\n\n    /**\n     * 停止所有正在运行的脚本。包括当前脚本自身。\n     */\n    function stopAll(): void;\n\n    /**\n     * 停止所有正在运行的脚本并显示停止的脚本数量。包括当前脚本自身。\n     */\n    function stopAllAndToast(): void;\n\n    /**\n     * 返回当前脚本的脚本引擎对象(ScriptEngine)\n     */\n    function myEngine(): void;\n}\n"
  },
  {
    "path": "types/modules/events.d.ts",
    "content": "declare namespace events {\n\n    interface KeyEvent {\n        getAction();\n        getKeyCode(): number;\n        getEventTime(): number;\n        getDownTime(): number;\n        keyCodeToString(keyCode: number): string;\n    }\n\n    function emitter(): EventEmitter;\n\n    function observeKey(): void;\n\n    type Keys = 'volume_up' | 'volume_down' | 'home' | 'back' | 'menu';\n\n    function setKeyInterceptionEnabled(key: Keys, enabled: boolean);\n\n    function setKeyInterceptionEnabled(enabled: boolean);\n\n    function onKeyDown(keyName: Keys, listener: (e: KeyEvent) => void): void;\n\n    function onceKeyUp(keyName: Keys, listener: (e: KeyEvent) => void): void;\n\n    function removeAllKeyDownListeners(keyName: Keys): void;\n\n    function removeAllKeyUpListeners(keyName: Keys): void;\n\n    function observeTouch(): void;\n\n    function setTouchEventTimeout(timeout: number): void;\n\n    function getTouchEventTimeout(): number;\n\n    function onTouch(listener: (point: Point) => void): void;\n\n    function removeAllTouchListeners(): void;\n\n    function on(event: 'key' | 'key_down' | 'key_up', listener: (keyCode: number, e: KeyEvent) => void): void;\n\n    function on(event: 'exit', listener: () => void): void;\n\n    function observeNotification(): void;\n\n    function observeToast(): void;\n\n    /**\n     * 系统Toast对象\n     */\n    interface Toast {\n\n        /**\n         * 获取Toast的文本内容\n         */\n        getText(): string;\n\n        /**\n         * 获取发出Toast的应用包名\n         */\n        getPackageName(): void;\n\n    }\n\n    function onToast(listener: (toast: Toast) => void): void;\n\n    /**\n     * 通知对象，可以获取通知详情，包括通知标题、内容、发出通知的包名、时间等，也可以对通知进行操作，比如点击、删除。\n     */\n    interface Notification {\n        number: number;\n        when: number;\n        getPackageName(): string;\n        getTitle(): string;\n        getText(): string;\n        click(): void;\n        delete(): void;\n    }\n\n    function on(event: 'notification', listener: (notification: Notification) => void): void;\n\n}\n\n/**\n * 按键事件中所有可用的按键名称\n */\ndeclare enum keys {\n    home,\n    back,\n    menu,\n    volume_up,\n    volume_down\n}\n\ninterface EventEmitter {\n    defaultMaxListeners: number;\n    addListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    emit(eventName: string, ...args: any[]): boolean;\n    eventNames(): string[];\n    getMaxListeners(): number;\n    listenerCount(eventName: string): number;\n    on(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    once(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    prependListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    prependOnceListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    removeAllListeners(eventName?: string): EventEmitter;\n    removeListener(eventName: string, listener: (...args: any[]) => void): EventEmitter;\n    setMaxListeners(n: number): EventEmitter;\n}"
  },
  {
    "path": "types/modules/files.d.ts",
    "content": "declare namespace files {\n    type byte = number;\n    function isFile(path: string): boolean;\n    function isDir(path: string): boolean;\n    function isEmptyDir(path: string): boolean;\n    function join(parent: string, ...child: string[]): string;\n    function create(path: string): boolean;\n    function createWithDirs(path: string): boolean;\n    function exists(path: string): boolean;\n    function ensureDir(path: string): void;\n    function read(path: string, encoding?: string): string;\n    function readBytes(path: string): byte[];\n    function write(path: string, text, encoding?: string): void;\n    function writeBytes(path: string, bytes: byte[]): void;\n    function append(path: string, text: string, encoding?: string): void;\n    function appendBytes(path: string, text: byte[], encoding?: string): void;\n    function copy(frompath: string, topath: string): boolean;\n    function move(frompath: string, topath: string): boolean;\n    function rename(path: string, newName): boolean;\n    function renameWithoutExtension(path: string, newName: string): boolean;\n    function getName(path: string): string;\n    function getNameWithoutExtension(path: string): string;\n    function getExtension(path: string): string;\n    function remove(path: string): boolean;\n    function removeDir(path: string): boolean;\n    function getSdcardPath(): string;\n    function cwd(): string;\n    function path(relativePath: string): string;\n    function listDir(path: string, filter: (filename: string) => boolean): string[];\n}\n\ninterface ReadableTextFile {\n    read(): string;\n    read(maxCount: number): string;\n    readline(): string;\n    readlines(): string[];\n    close(): void;\n}\n\ninterface WritableTextFile {\n    write(text: string): void;\n    writeline(line: string): void;\n    writelines(lines: string[]): void;\n    flush(): void;\n    close(): void;\n}\n\ndeclare function open(path: string, mode?: 'r', encoding?: string, bufferSize?: number): ReadableTextFile;\ndeclare function open(path: string, mode?: 'w' | 'a', encoding?: string, bufferSize?: number): WritableTextFile;\n"
  },
  {
    "path": "types/modules/floaty.d.ts",
    "content": "declare namespace floaty {\n    function window(layout: any): FloatyWindow;\n    function closeAll(): void;\n    interface FloatyWindow {\n        setAdjustEnabled(enabled: boolean): void;\n        setPosition(x: number, y: number): void;\n        getX(): number;\n        getY(): number;\n        setSize(width: number, height: number): void;\n        getWidht(): number;\n        getHeight(): number;\n        close(): void;\n        exitOnClose(): void;\n    }\n}\n"
  },
  {
    "path": "types/modules/global.d.ts",
    "content": "interface Point {\n    x: number;\n    y: number;\n}\n\ndeclare function sleep(n: number): void;\n\ndeclare function currentPackage(): string;\n\ndeclare function currentActivity(): string;\n\ndeclare function setClip(test: string): void;\n\ndeclare function getClip(): string;\n\ndeclare function toast(message: string): void;\n\ndeclare function toastLog(message: string): void;\n\ndeclare function waitForActivity(activity: string, period?: number): void;\n\ndeclare function waitForPackage(packageName: string, period?: number): void;\n\ndeclare function exit(): void;\n\ndeclare function random(): number;\ndeclare function random(min: number, max: number): number;\n\n"
  },
  {
    "path": "types/modules/http.d.ts",
    "content": "/// <reference path=\"../auto.d.ts\" />\n\ndeclare namespace http {\n    interface HttpRequestOptions {\n        header: { [key: string]: string },\n        method: 'GET' | 'POST' | 'PUT' | 'DELET' | 'PATCH';\n        contentType: string;\n        body: string | string[] | files.byte[]\n    }\n    interface Request {\n\n    }\n    interface Response {\n        statusCode: number;\n        statusMessage: string;\n        headers: { [key: string]: string };\n        body: ResponseBody;\n        request: Request;\n        url: string;\n        method: 'GET' | 'POST' | 'PUT' | 'DELET' | 'PATCH';\n    }\n    interface ResponseBody {\n        bytes(): files.byte[];\n        string(): string;\n        json(): object;\n        contentType: string;\n    }\n    function get(url: string, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n    function post(url: string, data: object, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n    function postJson(url: string, data?: object, options?: HttpRequestOptions, callback?: (resp: Response) => void): Response;\n\n    interface RequestMultipartBody {\n        file: ReadableTextFile | [string, string] | [string, string, string];\n    }\n    function postMultipart(url: string, files: RequestMultipartBody, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n    function postMultipart(url: string, files: { [key: string]: string } & RequestMultipartBody, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n\n    function request(url: string, options?: HttpRequestOptions, callback?: (resp: Response) => void): void;\n\n}\n"
  },
  {
    "path": "types/modules/images.d.ts",
    "content": "/// <reference path=\"./global.d.ts\" />\n\ninterface Image {\n    getWidth(): number;\n    getHeight(): number;\n    saveTo(path: string): void;\n    pixel(x: number, y: number): number;\n}\n\n\ndeclare namespace images {\n    function requestScreenCapture(landscape?: boolean): boolean;\n    function captureScreen(): Image;\n    function captureScreen(path: string): void;\n    function pixel(image: Image, x: number, y: number): number;\n    function save(image: Image, path: string): void;\n    function read(path: string): Image;\n    function load(url: string): Image;\n    interface FindColorOptions {\n        region?: [number, number] | [number, number, number, number];\n        threshold?: number;\n    }\n    function clip(image: Image, x: number, y: number, w: number, h: number): Image;\n    function findColor(image: Image, color: number | string, options?: FindColorOptions): Point;\n    function findColorInRegion(image: Image, color: number | string, x: number, y: number, width?: number, height?: number, threshold?: number): Point;\n    function findColorEquals(image: Image, color: number | string, x?: number, y?: number, width?: number, height?: number): Point;\n    function detectsColor(image: Image, color: number | string, x: number, y: number, threshold?: number, algorithm?: 'diff'): Point;\n    interface FindImageOptions {\n        region?: [number, number] | [number, number, number, number];\n        threshold?: number;\n        level?: number;\n    }\n    function findImage(image: Image, template: Image, options?: FindImageOptions): Point;\n    function findImageInRegion(image: Image, template: Image, x: number, y: number, width?: number, height?: number, threshold?: number): Point;\n    function findMultiColors(image: Image, firstColor: number | string, colors: [number, number, number | string][], options?: FindColorOptions): Point;\n\n    function fromBase64(base64: string): Image;\n    function toBase64(img: Image): string;\n}\n"
  },
  {
    "path": "types/modules/keys.d.ts",
    "content": "declare function back(): boolean;\ndeclare function home(): boolean;\ndeclare function powerDialog(): boolean;\ndeclare function notifications(): boolean;\ndeclare function quickSettings(): boolean;\ndeclare function recents(): boolean;\ndeclare function splitScreen(): boolean;\ndeclare function Home(): void;\ndeclare function Back(): void;\ndeclare function Power(): void;\ndeclare function Menu(): void;\ndeclare function VolumeUp(): void;\ndeclare function VolumeDown(): void;\ndeclare function Camera(): void;\ndeclare function Up(): void;\ndeclare function Down(): void;\ndeclare function Left(): void;\ndeclare function Right(): void;\ndeclare function OK(): void;\ndeclare function Text(text: string): void;\ndeclare function KeyCode(code: number | string): void;\n"
  },
  {
    "path": "types/modules/media.d.ts",
    "content": "declare namespace media {\n    function scanFile(path: string): void;\n    function playMusic(path: string, volume?: number, looping?: boolean);\n    function musicSeekTo(msec: number): void;\n    function pauseMusic(): void;\n    function resumeMusic(): void;\n    function stopMusic(): void;\n    function isMusicPlaying(): boolean;\n    function getMusicDuration(): number;\n    function getMusicCurrentPosition(): number;\n}\n"
  },
  {
    "path": "types/modules/root.d.ts",
    "content": "/**\n * RootAutomator是一个使用root权限来模拟触摸的对象，用它可以完成触摸与多点触摸，并且这些动作的执行没有延迟。\n * \n * 一个脚本中最好只存在一个RootAutomator，并且保证脚本结束退出他。\n */\ndeclare class RootAutomator {\n    /**\n     * 点击位置(x, y)。其中id是一个整数值，用于区分多点触摸，不同的id表示不同的\"手指\"。\n     */\n    tap(x: number, y: number, id?: number): void;\n\n    /**\n     * 模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。\n     */\n    swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;\n\n    /**\n     * 模拟按下位置(x, y)，时长为duration毫秒。\n     */\n    press(x: number, y: number, duration: number, id?: number): void;\n\n    /**\n     * 模拟长按位置(x, y)。\n     */\n    longPress(x: number, y: number, duration?: number, id?: number): void;\n\n    /**\n     * 模拟手指按下位置(x, y)。\n     */\n    touchDown(x: number, y: number, id?: number): void;\n\n    /**\n     * 模拟移动手指到位置(x, y)。\n     */\n    touchMove(x: number, y: number, id?: number): void;\n\n    /**\n     * 模拟手指弹起。\n     */\n    touchUp(id?: number): void;\n\n}\n\n/**\n * 需要Root权限\n * \n * 实验API，请勿过度依赖\n * \n * 点击位置(x, y), 您可以通过\"开发者选项\"开启指针位置来确定点击坐标。\n */\ndeclare function Tap(x: number, y: number): void;\n\n/**\n * 需要Root权限\n * \n * 实验API，请勿过度依赖\n * \n * 滑动。从(x1, y1)位置滑动到(x2, y2)位置。\n */\ndeclare function Swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;\n"
  },
  {
    "path": "types/modules/sensors.d.ts",
    "content": "declare namespace sensors {\n    interface SensorEventEmitter {\n        on(eventName: 'change', callback: (...args: number[]) => void): void;\n        on(eventName: 'accuracy_change', callback: (accuracy: number) => void): void;\n    }\n    function on(eventName: 'unsupported_sensor', callback: (sensorName: string) => void): void;\n    function register(sensorName: string, delay?: delay): SensorEventEmitter;\n    function unregister(emitter: SensorEventEmitter);\n    function unregisterAll(): void;\n    var ignoresUnsupportedSensor: boolean;\n    enum delay {\n        normal,\n        ui,\n        game,\n        fastest\n    }\n}"
  },
  {
    "path": "types/modules/storages.d.ts",
    "content": "interface Storage {\n    get<T>(key: string, defaultValue?: T): T;\n    put<T>(key: string, value: T): void;\n    remove(key: string): void;\n    contains(key: string): boolean;\n    clear(): void;\n}\n\ndeclare namespace storages {\n    function create(name: string): Storage;\n    function remove(name: string): boolean;\n}"
  },
  {
    "path": "types/modules/threads.d.ts",
    "content": "declare namespace threads {\n\n    type ThreadTimerID = number;\n\n    interface Thread {\n        interrupt(): void;\n        join(timeout?: number);\n        isAlive(): boolean;\n        waitFor(): void;\n        setTimeout(callback: (...args: any[]) => void, delay: number, ...args: any[]): ThreadTimerID;\n        setInterval(callback: (...args: any[]) => void, delay: number, ...args: any[]): ThreadTimerID;\n        setImmediate(callback: (...args: any[]) => void, ...args: any[]): ThreadTimerID;\n        clearInterval(id: ThreadTimerID): void;\n        clearTimeout(id: ThreadTimerID): void;\n        clearImmediate(id: ThreadTimerID): void;\n    }\n\n    function start(action): Thread;\n    function shutDownAll(): void;\n    function currentThread(): Thread;\n    function disposable(): any;\n    function atomic(initialValue?: number): any;\n    function lock(): any;\n\n\n}"
  },
  {
    "path": "types/modules/ui.d.ts",
    "content": "// interface View {\n//     w: 'auto' | '*' | number;\n//     h: 'auto' | '*' | number;\n//     id: string;\n//     gravity: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'center_vertical' | 'center_horizontal' | string;\n//     layout_gravity: 'left' | 'right' | 'top' | 'bottom' | 'center' | 'center_vertical' | 'center_horizontal' | string;\n//     margin: number | string;\n//     marginLeft: number;\n//     marginRight: number;\n//     marginTop: number;\n//     marginBottom: number;\n//     padding\n//     paddingLeft: number;\n//     paddingRight: number;\n//     paddingTop: number;\n//     paddingBottom: number;\n//     bg\n//     alpha\n//     foreground\n//     minHeight\n//     minWidth\n//     visbility\n//     rotation\n//     transformPivotX\n//     transformPivotY\n//     style\n// }\n\n// interface UI {\n//     [id: string]: View | ((...args: any[]) => any);\n//     layout(xml: any): void;\n//     inflate(xml: any, parent?: View): void;\n//     findView(id: string): View;\n//     finish()\n//     setContentView(view: View)\n//     run(callback)\n//     post(callback, delay?: number): void;\n//     statusBarColor(color)\n//     showPopupMenu(view, menu)\n// }\n\n// declare const ui: UI;\n\ntype View = any;\n\ninterface UILike {\n    toString(): string;\n}\n\ndeclare namespace ui {\n    function layout(xml: UILike | any): void;\n    function inflate(xml: UILike | any, parent?: View): void;\n    function findView(id: string): View;\n    function finish()\n    function setContentView(view: View)\n    function run(callback)\n    function post(callback, delay?: number): void;\n    function statusBarColor(color)\n    function showPopupMenu(view, menu)\n}"
  },
  {
    "path": "types/modules/widgets.d.ts",
    "content": "declare function auto(mode?: 'fast' | 'normal'): void;\ndeclare namespace auto {\n    function waitFor(): void;\n    function setMode(mode: 'fast' | 'normal'): void;\n}\ndeclare function selector(): UiSelector;\ndeclare function click(text: string, index?: number): boolean;\ndeclare function click(left: number, top: number, bottom: number, right: number): boolean;\ndeclare function longClick(text: string, index?: number): boolean;\ndeclare function scrollUp(index?: number): boolean;\ndeclare function scrollDown(index?: number): boolean;\ndeclare function setText(text: string): boolean;\ndeclare function setText(index: number, text: string): boolean;\ndeclare function input(text: string): boolean;\ndeclare function input(index: number, text: string): boolean;\n\ndeclare interface UiSelector {\n    text(str: string): UiSelector;\n    textContains(str: string): UiSelector;\n    textStartsWith(prefix: string): UiSelector;\n    textEndsWith(suffix: string): UiSelector;\n    textMatches(reg: string | RegExp): UiSelector;\n    desc(str: string): UiSelector;\n    descContains(str: string): UiSelector;\n    descStartsWith(prefix: string): UiSelector;\n    descEndsWith(suffix: string): UiSelector;\n    descMatches(reg: string | RegExp): UiSelector;\n    id(resId: string): UiSelector;\n    idContains(str: string): UiSelector;\n    idStartsWith(prefix: string): UiSelector;\n    idEndsWith(suffix: string): UiSelector;\n    idMatches(reg: string | RegExp): UiSelector;\n    className(str: string): UiSelector;\n    classNameContains(str: string): UiSelector;\n    classNameStartsWith(prefix: string): UiSelector;\n    classNameEndsWith(suffix: string): UiSelector;\n    classNameMatches(reg: string | RegExp): UiSelector;\n    packageName(str: string): UiSelector;\n    packageNameContains(str: string): UiSelector;\n    packageNameStartsWith(prefix: string): UiSelector;\n    packageNameEndsWith(suffix: string): UiSelector;\n    packageNameMatches(reg: string | RegExp): UiSelector;\n    bounds(left: number, top: number, right: number, buttom: number): UiSelector;\n    boundsInside(left: number, top: number, right: number, buttom: number): UiSelector;\n    boundsContains(left: number, top: number, right: number, buttom: number): UiSelector;\n    drawingOrder(order): UiSelector;\n    clickable(b: boolean): UiSelector;\n    longClickable(b: boolean): UiSelector;\n    checkable(b: boolean): UiSelector;\n    selected(b: boolean): UiSelector;\n    enabled(b: boolean): UiSelector;\n    scrollable(b: boolean): UiSelector;\n    editable(b: boolean): UiSelector;\n    multiLine(b: boolean): UiSelector;\n    findOne(): UiObject;\n    findOne(timeout: number): UiObject;\n    findOnce(): UiObject;\n    findOnce(i: number): UiObject;\n    find(): UiCollection;\n    untilFind(): UiCollection;\n    exists(): boolean;\n    waitFor(): void;\n    filter(filter: (obj: UiObject) => boolean)\n}\n\ndeclare interface UiObject {\n    click(): boolean;\n    longClick(): boolean;\n    setText(text: string): boolean;\n    copy(): boolean;\n    cut(): boolean;\n    paste(): boolean;\n    setSelection(start, end): boolean;\n    scrollForward(): boolean;\n    scrollBackward(): boolean;\n    select(): boolean;\n    collapse(): boolean;\n    expand(): boolean;\n    show(): boolean;\n    scrollUp(): boolean;\n    scrollDown(): boolean;\n    scrollLeft(): boolean;\n    scrollRight(): boolean;\n    children(): UiCollection;\n    childCount(): number;\n    child(i: number): UiObject;\n    parent(): UiObject;\n    bounds(): Rect;\n    boundsInParent(): Rect;\n    drawingOrder(): number;\n    id(): string;\n    text(): string;\n    findByText(str: string): UiCollection;\n    findOne(selector): UiObject;\n    find(selector): UiCollection;\n}\n\ndeclare interface UiCollection {\n    size(): number;\n    get(i: number): UiObject;\n    each(func: (obj: UiObject) => void): void;\n    empty(): boolean;\n    nonEmpty(): boolean;\n    find(selector): UiCollection;\n    findOne(selector): UiObject;\n}\n\ndeclare interface Rect {\n    left: number;\n    right: number;\n    top: number;\n    bottom: number;\n    centerX(): number;\n    centerY(): number;\n    width(): number;\n    height(): number;\n    contains(r): Rect;\n    intersect(r): Rect;\n}"
  },
  {
    "path": "webpack.config.js",
    "content": "// Generated using webpack-cli https://github.com/webpack/webpack-cli\n// http://auto.moly.host/index.html#/template/template\nconst path = require(\"path\");\nconst webpack = require(\"webpack\");\nconst AutojsDeployPlugin = require(\"./autojs-deploy\");\nconst babelConfig = require(\"./babel.config\");\n\nconst config = {\n\tentry: {\n\t\tmain: \"./src/miui_cleaner_app/index.js\",\n\t\tservices: \"./src/miui_cleaner_app/services.js\",\n\t},\n\toutput: {\n\t\tpath: path.resolve(__dirname, \"dist/miui_cleaner_app\"),\n\t\tfilename: \"[name].js\",\n\t\tclean: true,\n\t},\n\ttarget: \"node\",\n\tplugins: [\n\t\tnew AutojsDeployPlugin({\n\t\t\t// {boolean|String|boolean[]|String[]} 添加`\"ui\";`前缀的chunk（output）名单，true代表project.json中定义的main，字符串代表文件名\n\t\t\t// {String} 必须的 project.json 的文件路径\n\t\t\tconfigFile: path.resolve(__dirname, \"src/miui_cleaner_app/project.json\"),\n\t\t}),\n\t],\n\tmodule: {\n\t\trules: [\n\t\t\t{\n\t\t\t\ttest: /\\.(js|jsx)$/i,\n\t\t\t\tuse: [\n\t\t\t\t\t{\n\t\t\t\t\t\tloader: \"babel-loader\",\n\t\t\t\t\t\toptions: babelConfig,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\ttest: /\\.(png|svg|jpg|gif)$/,\n\t\t\t\tuse: {\n\t\t\t\t\tloader: \"url-loader\",\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t},\n};\n\nmodule.exports = (env, args) => {\n\tconst isProduction = args.mode ? args.mode === \"production\" : env.WEBPACK_BUILD;\n\tif (isProduction) {\n\t\tconfig.mode = \"production\";\n\t\tconfig.devtool = false;\n\t\tconfig.plugins.push(\n\t\t\tnew webpack.DefinePlugin({\n\t\t\t\tDEBUG: JSON.stringify(false),\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tconfig.mode = \"development\";\n\t\tconfig.devtool = \"source-map\";\n\t\tconfig.plugins.push(\n\t\t\tnew webpack.DefinePlugin({\n\t\t\t\tDEBUG: JSON.stringify(true),\n\t\t\t}),\n\t\t);\n\t}\n\treturn config;\n};\n"
  }
]