Full Code of gucong3000/MiuiCleaner for AI

main fed27d29171f cached
66 files
249.8 KB
79.8k tokens
244 symbols
1 requests
Download .txt
Showing preview only (304K chars total). Download the full file or copy to clipboard to get everything.
Repository: gucong3000/MiuiCleaner
Branch: main
Commit: fed27d29171f
Files: 66
Total size: 249.8 KB

Directory structure:
gitextract_c6dwjbbo/

├── .editorconfig
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── webpack.yml
├── .gitignore
├── LICENSE
├── README.md
├── autojs-deploy.js
├── babel.config.js
├── package.json
├── project.js
├── src/
│   ├── miui_cleaner_app/
│   │   ├── 123pan.js
│   │   ├── appDesc.js
│   │   ├── appManager.js
│   │   ├── dialogs.js
│   │   ├── downApp.js
│   │   ├── downFile.js
│   │   ├── emitItemShowEvent.js
│   │   ├── fetch.js
│   │   ├── findClickableParent.js
│   │   ├── getApplicationInfo.js
│   │   ├── getRemoteFileInfo.js
│   │   ├── index.js
│   │   ├── instApk.js
│   │   ├── lanzou.js
│   │   ├── multiChoice.js
│   │   ├── offAppAd.js
│   │   ├── project.json
│   │   ├── recycle.js
│   │   ├── serviceMgr.js
│   │   ├── services.js
│   │   ├── settings.js
│   │   ├── singleChoice.js
│   │   ├── startActivity.js
│   │   ├── support.js
│   │   ├── sysAppRm.js
│   │   ├── test/
│   │   │   ├── getRemoteFileInfo.js
│   │   │   └── services.js
│   │   ├── update.js
│   │   ├── waitForBack.js
│   │   └── webView.js
│   └── miui_cleaner_cmd/
│       └── main.cmd
├── types/
│   ├── adbkit.d.ts
│   ├── auto.d.ts
│   ├── autojs.d.ts
│   └── modules/
│       ├── app.d.ts
│       ├── colors.d.ts
│       ├── console.d.ts
│       ├── coordinate.d.ts
│       ├── device.d.ts
│       ├── dialogs.d.ts
│       ├── engines.d.ts
│       ├── events.d.ts
│       ├── files.d.ts
│       ├── floaty.d.ts
│       ├── global.d.ts
│       ├── http.d.ts
│       ├── images.d.ts
│       ├── keys.d.ts
│       ├── media.d.ts
│       ├── root.d.ts
│       ├── sensors.d.ts
│       ├── storages.d.ts
│       ├── threads.d.ts
│       ├── ui.d.ts
│       └── widgets.d.ts
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[**/*.{cmd,bat}]
end_of_line = crlf

[**/*.{yml,yaml}]
indent_style = space
indent_size = 2


================================================
FILE: .eslintrc.js
================================================
module.exports = {
	env: {
		browser: true,
		commonjs: true,
		es6: true,
		node: true,
	},
	extends: [
		"standard",
	],
	parserOptions: {
		ecmaFeatures: {
			jsx: true,
		},
	},
	globals: {
		colors: true,
		com: true,
		org: true,
		importClass: true,
		storages: true,
		device: true,
		log: true,
		threads: true,
		exit: true,
		runtime: true,
		java: true,
		importPackage: true,
		ui: true,
		activity: true,
		context: true,
		sleep: true,
		android: true,
		toastLog: true,
		files: true,
		requestScreenCapture: true,
		http: true,
		toast: true,
		engines: true,
		random: true,
		events: true,
		press: true,
		gesture: true,
		getPackageName: true,
		shell: true,
		floaty: true,
		currentPackage: true,
		launch: true,
		app: true,
		images: true,
		launchApp: true,
		idEndsWith: true,
		textEndsWith: true,
		descEndsWith: true,
		back: true,
		dialogs: true,
		auto: true,
		setClip: true,
		getClip: true,
		javax: true,
		media: true,
		captureScreen: true,
		timers: true,
		selector: true,
		recents: true,
		swipe: true,
		waitForActivity: true,
		waitForPackage: true,
		currentActivity: true,
		JavaAdapter: true,
		__non_webpack_require__: true,
		DEBUG: true,
	},
	rules: {
		"indent": ["error", "tab", { SwitchCase: 1 }],
		"quotes": ["error", "double"],
		"semi": ["error", "always"],
		"block-spacing": ["error", "always"],
		"array-bracket-spacing": ["error", "never"],
		"quote-props": ["error", "consistent-as-needed"],
		"comma-dangle": ["error", "always-multiline"],
		"no-tabs": ["off"],
	},
};


================================================
FILE: .github/workflows/webpack.yml
================================================
name: NodeJS with Webpack

on:
  push:
    branches: [ "*" ]
  pull_request:
    branches: [ "*" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Use Node.js current
      uses: actions/setup-node@v3
      with:
        node-version: current

    - name: Build
      run: |
        npm install
        npm run build

    - name: Create Release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: ${{ github.ref }}
        draft: true
        prerelease: true

    - name: Upload Batch
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        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
        asset_path: ./dist/miui_cleaner_cmd/MiuiCleaner.cmd
        asset_name: MiuiCleaner.cmd
        asset_content_type: text/plain

    - name: Upload JavaScript Main
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        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
        asset_path: ./dist/miui_cleaner_app/main.js
        asset_name: main.js
        asset_content_type: text/javascript

    - name: Upload JavaScript Service
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        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
        asset_path: ./dist/miui_cleaner_app/services.js
        asset_name: services.js
        asset_content_type: text/javascript


================================================
FILE: .gitignore
================================================
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,node,linux,androidstudio,android
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,visualstudiocode,node,linux,androidstudio,android

### Android ###
# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Log/OS Files
*.log

# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json

# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml

# Keystore files
*.jks
*.keystore

# Google Services (e.g. APIs or Firebase)
google-services.json

# Android Profiling
*.hprof

### Android Patch ###
gen-external-apklibs

# Replacement of .externalNativeBuild directories introduced
# with Android Studio 3.5.

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### Node ###
# Logs
logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Node Patch ###
# Serverless Webpack directories
.webpack/

# Optional stylelint cache

# SvelteKit build / generate output
.svelte-kit

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

# Support for Project snippet scope
.vscode/*.code-snippets

# Ignore code-workspaces
*.code-workspace

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

### AndroidStudio ###
# Covers files to be ignored for android development using Android Studio.

# Built application files
*.ap_
*.aab

# Files for the ART/Dalvik VM
*.dex

# Java class files
*.class

# Generated files
bin/
gen/
out/

# Gradle files
.gradle

# Signing files
.signing/

# Local configuration file (sdk path, etc)

# Proguard folder generated by Eclipse
proguard/

# Log Files

# Android Studio
/*/build/
/*/local.properties
/*/out
/*/*/build
/*/*/production
.navigation/
*.ipr
*.swp

# Keystore files

# Google Services (e.g. APIs or Firebase)
# google-services.json

# Android Patch

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# NDK
obj/

# IntelliJ IDEA
*.iws
/out/

# User-specific configurations
.idea/caches/
.idea/libraries/
.idea/shelf/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
.idea/datasources.xml
.idea/dataSources.ids
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
.idea/assetWizardSettings.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/navEditor.xml

# Legacy Eclipse project files
.classpath
.project
.cproject
.settings/

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.war
*.ear

# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
hs_err_pid*

## Plugin-specific files:

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Mongo Explorer plugin
.idea/mongoSettings.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

### AndroidStudio Patch ###

!/gradle/wrapper/gradle-wrapper.jar

# End of https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,node,linux,androidstudio,android

# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

window_dump.xml


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Chaos

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# MiuiCleaner
----
MIUI广告清理工具

## 使用方法

- [点击下载最新版本MiuiCleaner](https://github.com/gucong3000/MiuiCleaner/releases/latest),将`MiuiCleaner.apk`安装到手机即可
- 使用“预装应用卸载”功能能时,需要root权限或者ADB权限(亦即“开发者选项”中“USB 调试”),未root用户在使用该功能时请在电脑上按以下步骤操作:
	- 在电脑上安装以下工具中任何一种,并确保其可以正常连接手机。
		- [小米手机助手](http://zhushou.xiaomi.com/)
		- [360手机助手](https://sj.360.cn/)
		- [豌豆荚](https://www.wandoujia.com/)
		- [Android SDK 平台工具](https://developer.android.google.cn/studio/releases/platform-tools?hl=zh-cn)
	- 在该工具的安装目录中搜索到`adb.exe`所在的子目录,将`MiuiCleaner.cmd`放入其中并运行(或者将这个目录加入环境变量`PATH`中)

## 功能介绍
### 预装应用卸载

勾选你想要卸载的APP,点击确定就可以一键删除了。支持以下62款应用的卸载:
<details>
<summary>点击查看详细名单</summary>

- APP 外置开屏广告
	- 广告分析
	- 小米系统广告解决方案(智能服务)
- 桌面广告 APP
	- 智能助理(负一屏)
	- 信息助手(负一屏)
	- 智能出行
	- 内容中心(趣看看)
	- 百度搜索框
	- 桌面搜索框(搜索/全局搜索)
	- 桌面搜索框(Google)
- 过时的 APP
	- 悬浮球
	- 小米闻声
	- 智慧生活
- 影音类 APP
	- 音乐
	- Mi Video
	- 小米视频
	- 腾讯视频小米版
	- 爱奇艺播放器
- 天气
	- 小米天气
- 支付、电商、理财类 APP
	- 小米商城
	- 小米商城系统组件(电商助手)
	- 小米钱包
	- 米币支付
	- 小米支付
	- 小米卡包
	- 小米金融(天星金融)
	- 小米金融(天星金融)- 安全组件
	- 小米金服安全组件
	- 银联可信服务安全组件小米版
- 低使用频率 APP
	- 小米换机
	- 小米社区
	- 用户反馈
	- KLO bug反馈
	- 服务与反馈
	- 我的服务
	- 小米画报
	- 动态壁纸
	- 动态壁纸获取
	- 壁纸备份
	- 壁纸编辑器
	- 收音机(蜻蜓FM)
	- WPS Office Lite
	- 阅读(番茄免费小说)
	- 阅读(多看阅读器)
	- 小米运动健康
- 浏览器
	- 小米浏览器
	- 小米浏览器(国际版)
	- Chrome
- 内置输入法
	- 百度输入法-小米版
	- 搜狗输入法-小米版
	- 讯飞输入法-小米版
	- 小米安全键盘
- 小米游戏中心
	- 游戏中心(旧版)
	- 游戏中心
	- 游戏服务
	- 游戏中心 - pad 版
	- Joyose
- SIM 卡应用
	- 小米移动
	- 全球上网
	- 小米云流量
	- 全球上网工具插件
	- SIM卡应用
- 快应用
	- 快应用中心
	- 快应用服务框架
- 语音助手
	- 语音唤醒
	- 小爱语音(小爱同学)
	- 小爱视觉(扫一扫)
	- 小爱翻译
	- 小爱通话(AI虚拟助手)
</details>

### 去广告应用

内置多款去广告应用的下载链接:
<details>
<summary>点击查看详细名单</summary>

- [李跳跳](https://www.123pan.com/s/A6cA-edAJh)
	> 广告自动跳过工具
- [Edge](https://www.coolapk.com/apk/com.microsoft.emmx)
	> 广告可关,可与Windows的Edge互动,有网页广告屏蔽功能
- [小米浏览器](https://com-globalbrowser.cn.aptoide.com/app)
	> 国际版,广告可关,有网页广告屏蔽功能
- [讯飞输入法](https://423down.lanzouv.com/b0f24av5i)
	> Google Play版,无广告
- 软件包安装程序
	> Google版,代替MIUI的“应用包管理程序”,无广告和审查功能
- [应用包管理组件](https://zisu.lanzoum.com/iI7LGwn5xjc)
	> MIUI软件包安装程序v3.8.0,不含“纯净模式”
- [QQ音乐简洁版](https://www.coolapk.com/apk/com.tencent.qqmusiclite)
	> MIUI 音乐APP套壳的产品
- [Holi 天气](https://www.coolapk.com/apk/com.joe.holi)
	> 无广告,体较小,更漂亮,替代“小米天气”
- [ES文件浏览器](https://423down.lanzouv.com/b0f1d7s2h)
	> 修改版,去广告,代替“小米视频”和“小米音乐”
- [WPS Office Lite](https://www.32r.com/app/109976.html)
	> 国际版,无广告,替代“文档查看器”
- [知乎](https://423down.lanzouo.com/b0f2lkafe)
	> 集成“知了”,可在“知了”中关闭所有广告
- [哔哩哔哩](https://423down.lanzouv.com/b0f1gksne)
	> 集成“哔哩漫游”,可在“哔哩漫游”中关闭所有广告(需点击其版本号7次)
- [优酷视频](https://423down.lanzouv.com/b0f1avpib)
	> 修改版,去广告
- [百度贴吧](https://423down.lanzouv.com/b0f1b6q8d)
	> 修改版,去广告
- [酷安](https://423down.lanzouv.com/b0f2uzq2b)
	> 应用商店,修改版,去广告
- [AppShare](https://appshare.muge.info/)
	> 应用商店,可下载MIUI国际版中提取的APP

</details>

### 关闭各应用广告

支持在以下17款应用中,自动搜索50多个广告开关的具体位置,并自动给予处置。
<details>
<summary>点击查看详细名单</summary>

- 小米帐号
	- 关于小米帐号
		- 系统广告
			- 系统工具广告:`关闭`
- 系统安全
	- 加入“用户体验改进计划”:`关闭`
	- 自动发送诊断数据:`关闭`
	- 广告服务
		- 个性化广告推荐:`关闭`
	- 网页链接调用服务
		- 网页链接调用服务:`关闭`
- 手机管家
	- 在通知栏显示:`关闭`
	- 在线服务:`关闭`
	- 隐私设置
		- 仅在WLAN下推荐:`打开`
		- 个性化推荐:`关闭`
- 应用管理
	- 资源推荐:`关闭`
- 垃圾清理
	- 扫描内存:`关闭`
	- 推荐内容:`关闭`
	- 仅在WLAN下推荐:`打开`
- 应用商店
	- 通知设置
		- 新手帮助:`关闭`
		- 应用更新通知:`关闭`
		- 点赞消息:`关闭`
		- 评论消息:`关闭`
	- 通知栏快捷入口:`关闭`
	- 隐私设置
		- 个性化服务
			- 个性化服务:`关闭`
	- 功能设置
		- 显示福利活动:`关闭`
- 下载管理
	- 信息流设置
		- 仅在WLAN下加载:`打开`
		- 资源推荐:`关闭`
		- 热榜推荐:`关闭`
- 日历
	- 功能设置
		- 显示天气服务:`关闭`
	- 用户体验计划
		- 内容推广:`关闭`
- 时钟
	- 更多闹钟设置
		- 显示生活早报:`关闭`
- 小米社区
	- 隐私管理
		- 详情页相似推荐:`关闭`
		- 个性化广告:`关闭`
		- 信息流推荐:`关闭`
	- 关闭私信消息提醒:`打开`
- 小米天气
	- 用户体验计划
		- 天气视频卡片:`关闭`
		- 内容推广:`关闭`
- 小米视频
	- 隐私设置
		- 个性化内容推荐:`关闭`
		- 个性化广告推荐:`关闭`
	- 消息与推送
		- 未读消息提醒:`关闭`
		- 接收小米推送:`关闭`
	- 其他
		- 在线服务:`关闭`
- 音乐
	- 在线内容服务:`关闭`
- 小爱语音
	- 隐私管理
		- 隐私设置
			- 加入用户体验改进计划:`关闭`
			- 小爱技巧推送服务:`关闭`
			- 个性化推荐:`关闭`
			- 个性化广告推荐:`关闭`
- 搜索
	- 搜索快捷方式
		- 桌面搜索框:`关闭`
	- 首页展示模块
		- 热搜榜单
			- 热搜榜s:`关闭`
		- 搜索提示词:`关闭`
	- 搜索项
		- 搜索精选:`关闭`
	- 网站广告过滤:`打开`
- 浏览器
	- 主页设置
		- 简洁版:`打开`
		- 宫格位推送:`关闭`
	- 隐私防护
		- 广告过滤
			- 广告过滤:`打开`
	- 消息通知管理
		- 接收消息通知:`关闭`
- 小米浏览器
	- 首页设置
		- 简洁版:`打开`
	- 隐私保护
		- 广告过滤
			- 广告过滤:`打开`
	- 高级
		- 浏览器广告:`关闭`
	- 通知栏快捷入口:`关闭`
	- Facebook快捷通知:`关闭`

</details>

[演示视频:MiuiCleaner新功能演示-广告全自动关闭](https://www.zhihu.com/zvideo/1555993019102552064)
### 应用管家

- 自启动管理
	> 自启动及后台运行权限管理
- 通知管理
	> 通知栏、悬浮提示、图标角标的管理
- APP卸载
	> APP的批量卸载
- APP管理
	> 手机管家的应用管理功能
- APP信息
	> 权限管理模块

### 回收站

你可以在这里重新安装已卸载的预装应用

## 常见问题
- 电脑连不上手机,咋办?
	> 确保数据线正常,确保驱动安装正常,可以用“360手机助手”等工具自动安装
- 删错了“XXX”,咋恢复?
	> 进入“回收站”或者“应用商店”,重新安装。

## CHANGELOG
- v2023.4.23.8
	- 预装应用卸载
		- 去除卸载“纯净模式”功能
	- 去广告APP
		- 李跳跳更新至v2.2
	- 关闭各应用广告
		- 新增替换软件包安装器功能
	- 回收站
		- 使用原生圆形进度条,不再卡UI
	- 修正MIUI 13+ 报错,无法找到设置项
	- 新增下载管理
	- 新增在线升级功能
	- 新增帮助与反馈功能
- v2022.10.20.7
	- 重构UI,减少对弹出框权限的依赖,菜单项加入描述信息、图标
	- 权限获取功能重构,修复不能正确请求权限的bug
	- 增加若干可卸载APP
	- 增加若干去广告APP
- v2022.9.25.5
	- PC端修复bug #1
	- 预装应用卸载
		- 修正:卸载多个应用时报“超时”的bug
		- 修正:单独卸载系统应用时流程卡死的bug
		- 新增:无`USB调试`权限时,自动获取授权的功能
	- 去广告应用
		- 新增:应用包管理组件
		- 新增:QQ音乐简洁版
	- 关闭各应用广告
		- 增强:小米帐号
		- 新增:系统安全
		- 新增:广告服务
		- 新增:小米帐号、系统安全、广告服务三个选项按需显示功能、相关广告已关,或者已通过“修改系统”权限自动关闭后,不显示
		- 删除:音乐APP的广告关闭功能
	- 应用管家
		- 新增:自启动管理
		- 新增:应用卸载
		- 删除:应用管理
- v2022.9.21.4
	- 新增手机端 GUI
	- 新增广告全自动关闭功能
	- 新增应用管家入口
		- 通知管理
		- 应用管理
		- 应用信息
	- 增加若干可卸载APP
		- 动态壁纸
		- 动态壁纸获取
		- 用户反馈
		- 服务与反馈
		- 小米运动健康
		- 小米云流量
		- Chrome
	- 大部分功能从电脑端实现转为用手机端实现
	- 删除卸载米家功能
- v2022.9.10.2
	- 修正文案丢字
	- “⋮”无法在Windows默认终端显示,改为“三个点”来迁就
- v2022.9.9.1
	- 新增内置 APP 广告关闭引导功能
	- 部分文案修改
	- APP卸载功能新增9款 APP 的支持
	- 软件推送向手机前,增加了判断是否已装的逻辑
	- 删除MIUI内置应用前,增加了是否MIUI环境的判断
- v2022.9.7.0
	- 首个版本
	- 提供若干内置APP卸载和恢复功能
	- 提供第三方APP替换功能


================================================
FILE: autojs-deploy.js
================================================
const { Adb } = require("@devicefarmer/adbkit");
const { readFile } = require("fs/promises");
const EventEmitter = require("events");
const path = require("path");
const {
	SourceMapConsumer,
} = require("source-map");

class AutojsDeployPlugin {
	constructor (options = {}) {
		this.options = {
			...AutojsDeployPlugin.defaultOptions,
			...options,
		};
		if (options.remoteDir) {
			options.remoteDir = path.posix.resolve(options.remoteDir);
		}
		this.adb = Adb.createClient(this.options.adb);
	}

	// 缺省配置
	static defaultOptions = {
		packageName: {},
		project: {},
		build: {
			ui: true,
		},
		deploy: {
			run: true,
			skipSourceMap: true,
		},
		logcat: {
			stdout: process.stdout,
			sourceMap: true,
		},
	};

	// 读取AutoJS项目的`project.json`文件
	async getProjectConfig () {
		let projectConfig = await readFile(
			this.options.configFile,
			"utf-8",
		);
		projectConfig = JSON.parse(projectConfig);
		this.options.project = projectConfig;
		if (!projectConfig.projectDirectory) {
			projectConfig.projectDirectory = projectConfig.packageName || projectConfig.name || require(path.join(process.cwd(), "package.json")).name;
		}
		projectConfig.projectDirectory = path.posix.resolve("/storage/emulated/0/脚本/", projectConfig.projectDirectory);
		return projectConfig;
	}

	// 获取手机中安装的AutoJS Pro 或者AutoX的包名
	async getPackageName (device) {
		let packageName = this.options.packageName[device.serial];
		if (!packageName) {
			let packages = await this.shell(device, "pm list package org.autojs.");
			packages = packages.trim().split(/\r?\n/g);
			if (packages.length) {
				packageName = packages[0].replace(/^package\s*:\s*/, "").trim();
				this.options.packageName[device.serial] = packageName;
			}
		}
		device.packageName = packageName;
		return device;
	}

	// 日志显示功能,AutoJS Pro可直接使用,AutoX需要在项目代码中配置日志文件路径:`console.setGlobalLogConfig({file: files.join(context.getExternalFilesDir("logs"),"log.txt")});`
	async logcat (compilation) {
		if (!this.options.logcat) {
			return;
		}

		const project = await this.getProjectConfig();
		const logcat = async (device, packageName) => {
			if (!packageName) {
				return;
			}
			let cmd = compilation.compiler.options.watch ? "tail -f" : "cat";
			cmd += ` /storage/emulated/0/Android/data/${packageName}/files/logs/log.txt`;
			const log = await device.shell(cmd);
			const transform = new LogTransform();
			const stdout = this.options.logcat.stdout || process.stdout;
			transform.colors = stdout.hasColors && stdout.hasColors() && (this.options.logcat.colors || transform.colors);
			transform.sourceMap = this.options.logcat.sourceMap;
			const startOptput = () => {
				console.log("> adb shell", cmd);
				log.pipe(
					transform,
				).pipe(
					stdout,
				);
			};
			if (compilation.compiler.options.watch) {
				log.once("data", startOptput);
			} else {
				startOptput();
			}
			this.options.logcat.manager.once("close", () => log.end());
			return log;
		};
		const start = async (device) => {
			return await Promise.all(
				[
					project.packageName,
					device.packageName,
				].map(packageName => logcat(device, packageName)),
			);
		};
		if (this.options.logcat.manager) {
			this.options.logcat.manager.emit("close");
		} else {
			this.options.logcat.manager = new EventEmitter();
			if (compilation.compiler.options.watch) {
				process.nextTick(async () => {
					const tracker = await this.adb.trackDevices();
					tracker.on("add", async (device) => {
						device = this.adb.getDevice(device.id);
						await device.waitForDevice();
						await this.getPackageName(device);
						await start(device);
					});
				});
			}
		}
		await this.eachDevice(start);
	}

	// 通过ADB在手机的shell中运行命令
	shell (device, ...args) {
		return device.shell(...args)
			.then(Adb.util.readAll)
			.then((output) => {
				return output.toString();
			});
	}

	// 遍历所有通过ADB连接到PC的手机
	async eachDevice (...args) {
		let devices = await this.adb.listDevices();
		devices = devices.map(device => this.adb.getDevice(device.id));
		devices = (await Promise.all(
			devices.map(async (device) => {
				await this.getPackageName(device);
				return device.packageName && device;
			}),
		)).filter(Boolean);
		return await Promise.all(devices.map(...args));
	}

	// 通过ADB将webpack输出的文件部署文件到手机上,会跳过sourceMap文件,手机目录在`options.remoteDir`中配置,未声明会自动选择`/storage/emulated/0/脚本/{project.json中的packageName、name或者package.json中的name}`
	deploy (compilation) {
		if (!this.options.deploy) {
			return;
		}
		const remoteDir = this.options.project.projectDirectory;
		let assets = compilation.getAssets();
		if (this.options.deploy.skipSourceMap) {
			assets = assets.filter(asset =>
				!("development" in asset.info) && !("extractedComments" in asset.info),
			);
		}
		const localDir = compilation.compiler.outputPath;
		return this.eachDevice(async (device) => {
			console.log(`> adb push ${localDir} ${remoteDir}`);
			await Promise.all(
				assets.map(asset => (
					device.push(
						path.join(localDir, asset.name),
						path.posix.join(remoteDir, asset.name),
					)
				)),
			);
			if (this.options.deploy.run) {
				const jsFileList = assets.filter(asset =>
					"javascriptModule" in asset.info,
				).map(asset => asset.name);
				let jsFile = jsFileList[0];
				if (jsFileList.length > 1) {
					const { main } = await this.getProjectConfig();
					if (main && jsFileList.includes(main)) {
						jsFile = main;
					}
				}
				if (!jsFile) {
					return;
				}

				console.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)}`);
				await device.startActivity({
					debug: true,
					action: "android.intent.action.MAIN",
					component: device.packageName + "/org.autojs.autojs.external.shortcut.ShortcutActivity",
					extras: {
						path: path.posix.join(remoteDir, jsFile),
					},
				});
			}
		});
	}

	// 在webpack变异文件的队列中加入“project.json”、js文件中添加`"ui";指令头`、收集webpack生成的sourceMap文件,供logcat相关功能调用
	async updateAsset (compilation) {
		const project = await this.getProjectConfig();

		if (compilation.compiler.options.watch && this.options.logcat?.sourceMap) {
			this.options.logcat.sourceMap = {};
			const remoteDir = project.projectDirectory;
			const posixPath = (sPath) => (path.isAbsolute(sPath) ? path.relative(process.cwd(), sPath) : sPath).replaceAll(path.win32.sep, path.posix.sep);
			const contextPath = posixPath(compilation.options.context);
			const outputPath = posixPath(compilation.compiler.outputPath);

			compilation.getAssets().forEach((asset) => {
				if ("development" in asset.info) {
					const sourceMap = JSON.parse(asset.source.source());
					sourceMap.sources = sourceMap.sources.map(file => {
						const uri = file.match(/^webpack:\/\/([^/]+\/)?\.\/(.*)$/);
						if (uri) {
							return path.posix.join(contextPath, uri[2]);
						}
						return file;
					});
					const file = sourceMap.file;
					sourceMap.file = path.posix.join(outputPath, file);
					this.options.logcat.sourceMap[path.posix.join(remoteDir, file)] = new SourceMapConsumer(sourceMap);
				}
			});
		}
		if (!this.options.build) {
			return;
		}
		const RawSource = compilation.compiler.webpack.sources.RawSource;
		compilation.emitAsset(
			"project.json",
			new RawSource(JSON.stringify(project, 0, compilation.options.mode === "development" ? 4 : 0)),
		);
		let ui = this.options.build.ui;
		if (ui) {
			if (!Array.isArray(ui)) {
				ui = [ui];
			}
			ui.forEach(fileName => {
				if (typeof fileName !== "string") {
					fileName = project.main;
				}
				compilation.updateAsset(
					fileName,
					(source) => {
						return new RawSource("\"ui\";" + source.source());
					},
				);
			});
		}
	}

	apply (compiler) {
		compiler.hooks.thisCompilation.tap(AutojsDeployPlugin.name, (compilation) => {
			compilation.hooks.processAssets.tapPromise(
				{
					name: AutojsDeployPlugin.name,
					stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
				},
				() => {
					return this.updateAsset(compilation);
				},
			);
		});
		compiler.hooks.done.tapPromise(AutojsDeployPlugin.name, async (stats) => {
			const compilation = stats.compilation;
			try {
				await this.deploy(compilation);
			} catch (ex) {
				if (ex.cause?.code === "ENOENT") {
					console.error("Could not find 'adb' in PATH. Please set options.adb of " + AutojsDeployPlugin.name);
				} else {
					throw ex;
				}
			}
			if (compilation.compiler.options.watch) {
				await this.logcat(compilation);
			}
		});
	}
}
module.exports = AutojsDeployPlugin;

const { Transform } = require("stream");
const styles = require("ansi-styles");
const json5 = require("json5");
class LogTransform extends Transform {
	constructor () {
		super();
		this.colors = {
			V: styles.gray,
			I: styles.green,
			W: styles.yellow,
			E: styles.red,
		};
		// "https://cdn.jsdelivr.net/gh/kkevsekk1/AutoX/autojs/src/main/assets",
		// "https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets"
		this.assets = "https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets";
	}

	// 将文件路径转换为source map映射的文件路径
	toSourcePath (options) {
		const consumer = this.sourceMap && this.sourceMap[options.file];
		if (consumer) {
			const originalPos = consumer.originalPositionFor && consumer.originalPositionFor(options);
			if (originalPos?.source) {
				options.file = originalPos.source;
				if (originalPos.name && (!options.name || !(/\b\w*E(rror|xception):/.test(options.name) || options.name.includes(originalPos.name)))) {
					options.name = originalPos.name;
				}
				options.line = originalPos.line;
				options.column = originalPos.column;
			} else {
				options.file = consumer.file || consumer;
			}
		}
		return options;
	}

	// 将日志中的错误信息中的trace统一格式并转换文件路径
	traceFormat (options) {
		if (options.pos) {
			const arrPos = options.pos.match(/\d+/g);
			options.line = +arrPos[0] || 1;
			options.column = +arrPos[1] || 0;
		}
		let {
			file,
			prefix,
			name,
			line,
			column,
		} = this.toSourcePath(options);
		if (file.startsWith("file:///android_asset/modules/")) {
			file = this.assets + file.slice(21) + "#L" + line;
		} else {
			file = [file, line || "1", column || "0"].join(":");
		}
		prefix = prefix || "";
		if (name) {
			return `${prefix}${name} (${file})`;
		} else {
			return `${prefix}${file}`;
		}
	}

	_transform (string, encoding, callback) {
		string = string.toString();
		if (this.sourceMap) {
			string = string.replaceAll(
				/\bfile:\/\/\/android_asset(\/.*?)#(\d+)/g,
				// "https://cdn.jsdelivr.net/gh/kkevsekk1/AutoX/autojs/src/main/assets/modules/__json2__.js#L493",
				// "https://github.dev/kkevsekk1/AutoX/tree/dev-test/autojs/src/main/assets" + file + "#L" + line,
				(s, file, line) => this.assets + file + "#L" + line,
			).replaceAll(
				// 替换以下两种错误日志格式中的文件路径和行号(文件路径和行号带括号):
				// XxxError: error_messarg (/some/path/to/file:69:54)
				//     at function_name (/some/path/to/file:69:54)
				/^((?:[\d:.]+\/[A-Z]:|\s*at)\s+)?(.*?)\s+\((.*?)((?:[:#]\d+)+)\)$/gm,
				(s, prefix, name, file, pos) => this.traceFormat({
					prefix,
					name,
					file,
					pos,
				}),
			).replaceAll(
				// 替换以下两种错误日志格式中的文件路径和行号(文件路径和行号不带括号,函数名如果存在、带括号):
				//     at /some/path/to/file:69:54 (function_name)
				//     at /some/path/to/file:69:54
				/^(\s*at\s+)(.*?)((?:[:#]\d+)+)(?:\s+\((.*)\))?$/gm,
				(s, prefix, file, pos, name) => this.traceFormat({
					prefix,
					file,
					pos,
					name,
				}),
			).replaceAll(
				// 替换类似JSON格式的报错
				// { [JavaException: message ]
				//   fileName: 'file:///android_asset/modules/filename.js',
				//   lineNumber: 8848 }`;
				/\{\s+\[(.+)\]([\s\S]*?)\}/gm,
				(s, message, jsonBody) => {
					let errInfo;
					try {
					/* eslint no-new-func: "off" */
						errInfo = json5.parse(`{${jsonBody}}`);
					} catch (ex) {
						// return s;
					}
					if (!errInfo || !errInfo.fileName || !/\b\w*E(rror|xception)/.test(message)) {
						return s;
					}
					return `${message}\n${this.traceFormat({
						prefix: "\tat ",
						file: errInfo.fileName,
						line: +errInfo.lineNumber,
						column: +errInfo.columnNumber,
					})}`;
				},
			);
		}
		if (this.colors) {
			let currColor = null;
			string = string.replaceAll(/^([\d:.]+)\/([A-Z]):\s/gm, (s, timestamp, level) => {
				s = "";
				const newColor = this.colors[level] || null;
				if (newColor !== currColor) {
					if (currColor) {
						s += currColor.close;
					}
					if (newColor) {
						s += newColor.open;
					}
					currColor = newColor;
				}
				// if (this.timestamp) {
				// 	s += `${timestamp}/${level}: `;
				// }
				return s;
			});
			if (currColor) {
				string += currColor.close;
				currColor = null;
			}
		}
		callback(null, string);
	}

	_flush (callback) {
		callback();
	}
}

// require("fs").createReadStream("lot.txt").pipe(process.output);
// const fs = require("fs");
// const logCat = new LogCat();
// const sourceMap = JSON.parse(
// 	fs.readFileSync("dist/miui_cleaner_app/main.js.map", "utf-8"),
// );

// let context = process.cwd();
// let output = path.resolve("dist/miui_cleaner_app");
// context = path.relative(process.cwd(), context);
// output = path.relative(process.cwd(), output).replaceAll(path.win32.sep, path.posix.sep);
// sourceMap.sources = sourceMap.sources.map(file => {
// 	const uri = file.match(/^webpack:\/\/([^/]+\/)?(\.\/.*)$/);
// 	if (uri) {
// 		return path.posix.join(context, uri[2]);
// 	}
// 	return file;
// });
// sourceMap.file = path.posix.join(output, "main.js");
// logCat.sourceMap = {
// 	"/storage/emulated/0/脚本/com.github.gucong3000.miui.cleaner/main.js": "dist/miui_cleaner_app/main.js" || new SourceMapConsumer(sourceMap),
// };
// fs.createReadStream("log.txt").pipe(logCat).pipe(process.stdout);


================================================
FILE: babel.config.js
================================================
module.exports = {
	sourceType: "script",
	targets: {
		rhino: "1.7.13",
	},
	presets: [
		"@babel/preset-env",
	],
	plugins: [
		"@babel/plugin-transform-runtime",
		"@babel/plugin-syntax-jsx",
	],
};


================================================
FILE: package.json
================================================
{
	"name": "miui_cleaner",
	"version": "2023.4.23.8",
	"private": true,
	"description": "MIUI广告清理工具",
	"scripts": {
		"test": "npx eslint *.js src/**/*.js",
		"test:fix": "npm run test -- --fix",
		"start": "npx webpack --config webpack.config.js --watch --mode=development",
		"build:pack": "npx webpack --config webpack.config.js --mode=production",
		"build:pull": "adb pull sdcard/脚本/com.github.gucong3000.miui.cleaner/build ./dist/",
		"build:init": "sh -c \"mkdir -p dist/miui_cleaner_cmd\" && node project.js",
		"build": "npm run build:init && npm run build:pack",
		"deploy:res": "adb push ./res /sdcard/脚本/com.github.gucong3000.miui.cleaner/",
		"deploy": "npm run build & npm run deploy:res",
		"dump:ui": "adb shell uiautomator dump && (adb shell cat /sdcard/window_dump.xml | xmllint --format - > window_dump.xml) && code window_dump.xml",
		"dump:act": "adb shell dumpsys activity activities | grep Hist",
		"dump": "npm run dump:ui && npm run dump:act"
	},
	"author": "GuCong",
	"license": "MIT",
	"devDependencies": {
		"@auto.pro/webpack-plugin": "^8.13.3",
		"@babel/core": "^7.19.1",
		"@babel/plugin-syntax-jsx": "^7.18.6",
		"@babel/plugin-transform-runtime": "^7.19.1",
		"@babel/preset-env": "^7.19.1",
		"@devicefarmer/adbkit": "^3.2.3",
		"ansi-styles": "^5.2.0",
		"babel-loader": "^8.2.5",
		"eslint": "^8.24.0",
		"eslint-config-standard": "^17.0.0",
		"webpack": "^5.74.0",
		"webpack-cli": "^4.10.0",
		"wrapper-webpack-plugin": "^2.2.2"
	},
	"dependencies": {
		"blob-polyfill": "^7.0.20220408",
		"core-js": "^3.26.0",
		"debounce": "^1.2.1",
		"headers-polyfill": "^3.1.2",
		"json5": "^2.2.1",
		"pretty-bytes": "^5.6.0"
	}
}


================================================
FILE: project.js
================================================
const fs = require("fs/promises");
const { spawnSync } = require("node:child_process");

(async () => {
	const [
		packageConfig,
		appConfig,
		cmd,
		readme,
	] = await Promise.all([
		readFile("package.json"),
		readFile("src/miui_cleaner_app/project.json"),
		readFile("src/miui_cleaner_cmd/main.cmd"),
		readFile("README.md"),
	]);
	const pkgInfo = packageConfig.json;
	const appInfo = appConfig.json;

	const date = new Date();
	let versionCode;

	if (process.env.GITHUB_RUN_NUMBER) {
		versionCode = process.env.GITHUB_RUN_NUMBER - 0;
	} else {
		const controller = new AbortController();
		versionCode = await Promise.any(
			[
				"https://cdn.jsdelivr.net/gh/gucong3000/MiuiCleaner/src/miui_cleaner_app/project.json",
				"https://raw.fastgit.org/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json",
				"https://raw.githubusercontent.com/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json",
			].map(async url => {
				const res = await fetch(url, { signal: controller.signal });
				const data = await res.json();
				return data.versionCode + 1;
			}),
		);
		controller.abort();
	}

	const versionName = [
		date.getUTCFullYear(),
		date.getUTCMonth() + 1,
		date.getUTCDate(),
		versionCode,
	].join(".");
	pkgInfo.version = versionName;
	appInfo.versionCode = versionCode;
	appInfo.versionName = versionName;
	appInfo.launchConfig.splashText = pkgInfo.description;
	cmd.constents = cmd.constents.replace(
		/^title\s+.*$/im,
		`title ${appInfo.name} - ${pkgInfo.description}`,
	);

	const distCmd = fs.writeFile(
		"dist/miui_cleaner_cmd/MiuiCleaner.cmd",
		spawnSync(
			"iconv",
			[
				"--from-code=utf-8",
				"--to-code=gb18030",
			],
			{
				input: cmd.constents.replace(
					/^chcp\s+\d+/im, "chcp 936",
				).replace(
					/^title\s+.*$/im,
					`title ${appInfo.name} - ${pkgInfo.description} -v${pkgInfo.version}`,
				).replace(/\r?\n/g, "\r\n"),
			},
		).stdout,
	);
	updateDoc(readme);

	await Promise.all([
		appConfig.update(),
		packageConfig.update(),
		cmd.update(),
		readme.update(),
		distCmd,
	]);
})(
);

async function readFile (path) {
	let constents = await fs.readFile(path);
	constents = constents.toString("utf-8");
	const json = /\.json$/.test(path) && JSON.parse(constents);

	const file = {
		json,
		constents,
		update: (...args) => {
			let newContents;
			if (file.json) {
				newContents = JSON.stringify(file.json, 0, "\t");
			} else {
				newContents = file.constents;
			}
			const finalNewline = /\.(cmd|bat)$/i.test(path) ? "\r\n" : "\n";
			newContents = newContents.replace(/\r?\n/g, finalNewline);
			if (constents.trim() !== newContents.trim()) {
				return fs.writeFile(path, newContents.trim() + finalNewline, ...args);
			}
		},
	};
	return file;
}

// 保持文档的描述和关闭广告的单元测试数据一致
function updateDoc (readme) {
	const docResult = [];
	const testCase = require("./src/miui_cleaner_app/test/services").testCase;
	delete testCase["关于手机"];
	delete testCase["开发者选项"];

	printTestCase(testCase);

	readme.constents = readme.constents.replace(/(#+\s*关闭各应用广告[\s\S]*?<\/summary>[\s\S]*?)-[\s\S]*(<\/details>)/, (s, prefix, suffix) => {
		return prefix + docResult.join("\n") + "\n\n" + suffix;
	});

	function printTestCase (data, deep = 0) {
		for (const caseName in data) {
			if (data[caseName] === true) {
				docResult.push("\t".repeat(deep) + "- " + caseName + ":`打开`");
			} else if (data[caseName] === false) {
				docResult.push("\t".repeat(deep) + "- " + caseName + ":`关闭`");
			} else if (
				typeof data[caseName] !== "object" ||
			data[caseName] === null ||
			(caseName === "广告服务" && !deep)
			) {
				continue;
			} else {
				docResult.push("\t".repeat(deep) + "- " + caseName);
				printTestCase(data[caseName], deep + 1);
			}
		}
	}
}


================================================
FILE: src/miui_cleaner_app/123pan.js
================================================
const jsonParse = require("json5/lib/parse");
const fetch = global.fetch || require("./fetch");
const atob = global.atob || global.$base64.decode;

// const webView = global.ui && require("./webView");
let userAgent;
try {
	userAgent = android.webkit.WebSettings.getDefaultUserAgent(context);
} catch (ex) {
	userAgent = "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";
}

async function getFileInfo (url) {
	url = parseUrl(url);
	const res = await fetch(
		url.href,
		{
			headers: {
				"accept": "text/html",
				"user-agent": userAgent,
			},
		},
	);
	await checkResponse(res);
	const html = await res.text();
	let initialProps = html.match(/\b(window\.)?g_initialProps\s*=\s*(.*);/m);
	initialProps = initialProps && jsonParse(initialProps[2]);
	return parseFileInfo(initialProps.reslist.data.InfoList, url, initialProps);
}

async function parseFileInfo (fileInfo, url, initialProps) {
	if (Array.isArray(fileInfo)) {
		// 解析多个结果
		fileInfo = await Promise.all(fileInfo.map(fileInfo => parseFileInfo(fileInfo, url, initialProps)));
		if (fileInfo.length === 1) {
			fileInfo = fileInfo[0];
		}
	} else if (fileInfo.Etag || fileInfo.S3KeyFlag) {
		// 解析单个文件
		fileInfo.publicPath = initialProps.publicPath;
		fileInfo.shareKey = initialProps.res.data.ShareKey;
		fileInfo = new FileInfo(fileInfo);
	} else {
		// 解析文件夹
		const 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);
		let res = await fetch(
			api.href,
			{
				headers: {
					"accept": "application/json",
					"user-agent": userAgent,
				},
				referrerPolicy: "no-referrer",
			},
		);
		await checkResponse(res);
		res = await res.json();
		fileInfo = await parseFileInfo(res.data.InfoList, url, initialProps);
	}
	return fileInfo;
}

async function checkResponse (res) {
	if (!res.ok) {
		throw new Error(`status: ${res.status}\nmessage: ${res.message || JSON.stringify(await res.text())}\n    at ${res.url}`);
	}
}

function parseUrl (url) {
	if (!url.href) {
		url = new URL(url);
	}
	return url;
}

class FileInfo {
	constructor (data) {
		Object.assign(this, data);
	}

	get fileName () {
		return this.FileName;
	}

	get size () {
		return this.Size;
	}

	get lastModified () {
		return Date.parse(this.UpdateAt);
	}

	get id () {
		return this.FileId;
	}

	async getLocation (redirect) {
		const data = await getRealFile(this, redirect);
		Object.assign(this, data);
		return this;
	}
}

async function parse (url, options) {
	const data = await getFileInfo(url, options || {});
	if (Array.isArray(data)) {
		return data.map(data => new FileInfo(data));
	}
	return new FileInfo(data);
}

async function getRealFile (fileInfo, redirect) {
	let res = await fetch("https://www.123pan.com/a/api/share/download/info", {
		headers: {
			"accept": "application/json",
			"content-type": "application/json;charset=UTF-8",
			"user-agent": userAgent,
		},
		referrerPolicy: "no-referrer",
		body: JSON.stringify({
			ShareKey: fileInfo.shareKey,
			FileID: fileInfo.FileId,
			S3keyFlag: fileInfo.S3KeyFlag,
			Size: fileInfo.Size,
			Etag: fileInfo.Etag,
		}),
		method: "POST",
	});
	await checkResponse(res);
	res = await res.json();
	fileInfo.location = decodeURI(atob(new URL(res.data.DownloadURL).searchParams.get("params")));
	// if (redirect) {
	// 	//
	// }
	return fileInfo;
}
module.exports = parse;
// (async () => {
// 	let file = await parse("https://423down.lanzouv.com/tp/iKBGf0hcsq5e");
// 	console.log(file.url);
// 	file = await parse("https://423down.lanzouv.com/tp/iKBGf0hcsq5e");
// 	console.log(file.url);
// })();
//
// getFileInfoFromUrl("https://www.123pan.com/s/A6cA-gT9Jh").then(async file => console.log(await file.getLocation(true)));
// getFileInfoFromUrl("https://www.123pan.com/s/A6cA-dJAJh").then(file => console.log(file));

// getFileInfoFromUrl("https://www.123pan.com/s/ZYAZVv-TBYjd.html").then(file => console.log(file));


================================================
FILE: src/miui_cleaner_app/appDesc.js
================================================

// https://gist.github.com/mcxiaoke/0a4c639d04e94c45eb6c787c0f98940a
// https://fengooge.blogspot.com/2019/03/taking-ADB-to-uninstall-system-applications-in-MIUI-without-root.html

module.exports = {
	// APP 外置开屏广告
	"com.miui.analytics": "广告分析",
	"com.miui.systemAdSolution": "小米广告联盟∑的开屏广告",
	// 桌面广告 APP
	"com.miui.personalassistant": "负一屏",
	"com.mi.android.globalminusscreen": "负一屏",
	"com.miui.smarttravel": "智能出行",
	"com.miui.newhome": "趣看看",
	"com.android.quicksearchbox": "桌面搜索框/搜索/全局搜索",
	"com.google.android.googlequicksearchbox": "桌面搜索框(Google)",
	"com.baidu.searchbox": "桌面搜索框(百度)",
	// 过时的 APP
	"com.miui.touchassistant": "悬浮球/Quickball",
	"com.miui.accessibility": "听障辅助工具",
	"com.miui.hybrid.accessory": "古早版智能家居",
	// 影音类 APP
	"com.miui.player": "QQ音乐简洁版,应替换成ES文件浏览器",
	"com.miui.videoplayer": "Mi Video,应替换成ES文件浏览器",
	"com.miui.video": "小米视频,应替换成ES文件浏览器",
	"com.tencent.qqlivexiaomi": "小米视频插件-腾讯视频小米版",
	"com.qiyi.video.sdkplayer": "小米视频插件-爱奇艺播放器",
	// 天气
	"com.miui.weather2": "小米天气,应替换成Holi天气",
	// 支付、电商、理财类 APP
	"com.xiaomi.shop": "小米商城",
	"com.xiaomi.ab": "小米商城系统组件/电商助手",
	"com.mipay.wallet": "小米钱包",
	"com.xiaomi.payment": "米币支付",
	"com.miui.nextpay": "小米支付",
	"com.xiaomi.pass": "小米卡包",
	"com.xiaomi.jr": "小米金融/天星金融",
	"com.xiaomi.jr.security": "小米金融/天星金融-安全组件",
	"com.xiaomi.mifisecurity": "小米金服安全组件",
	"com.unionpay.tsmservice.mi": "银联可信服务安全组件小米版",
	// 低使用频率 APP
	"com.miui.huanji": "小米换机",
	"com.xiaomi.vipaccount": "小米社区",
	"com.miui.bugreport": "bug反馈",
	"com.miui.klo.bugreport": "KLO bug反馈",
	"com.miui.miservice": "服务与反馈",
	"com.miui.vipservice": "我的服务",
	"com.mfashiongallery.emag": "小米画报",
	"com.android.wallpaper": "动态壁纸",
	"com.android.wallpaper.livepicker": "动态壁纸获取",
	"com.android.wallpaperbackup": "壁纸备份",
	"com.android.wallpapercropper": "壁纸编辑器",
	"com.miui.fm": "收音机/蜻蜓FM",
	"cn.wps.moffice_eng.xiaomi.lite": "WPS Office Lite,应替换成ES文件浏览器",
	"com.dragon.read": "阅读/番茄免费小说",
	"com.duokan.reader": "阅读/多看阅读器",
	"com.mi.health": "小米健康/小米运动健康",
	// 浏览器
	"com.android.browser": "小米浏览器",
	"com.mi.globalbrowser": "小米浏览器(国际版)",
	"com.android.chrome": "Chrome",
	// 内置输入法
	"com.baidu.input_mi": "百度输入法-小米版",
	"com.sohu.inputmethod.sogou.xiaomi": "搜狗输入法-小米版",
	"com.iflytek.inputmethod.miui": "讯飞输入法-小米版",
	"com.miui.securityinputmethod": "小米安全键盘",
	// 小米游戏中心
	"com.xiaomi.migameservice": "// 游戏中心(旧版)",
	"com.xiaomi.gamecenter": "游戏中心",
	"com.xiaomi.gamecenter.sdk.service": "游戏中心-SDK服务",
	"com.xiaomi.gamecenter.pad": "游戏中心-pad版",
	"com.xiaomi.joyose": "云控/温控/记步",
	// SIM 卡应用
	"com.miui.virtualsim": "全球上网",
	"com.xiaomi.mimobile": "小米移动",
	"com.xiaomi.mimobile.cloudsim": "小米移动-小米云流量",
	"com.xiaomi.mimobile.noti": "小米移动-全球上网-插件",
	"com.android.stk": "SIM卡应用",
	// 快应用
	"com.miui.quickappCenter.miAppStore": "快应用中心/快应用商店",
	"com.miui.hybrid": "快应用服务框架",
	// 语音助手
	"com.miui.voiceassist": "小爱语音/小爱同学",
	"com.miui.voicetrigger": "语音唤醒语音助手",
	"com.xiaomi.scanner": "小爱视觉/扫一扫",
	"com.xiaomi.aiasst.vision": "小爱翻译",
	"com.xiaomi.aiasst.service": "小爱通话(AI虚拟助手)",
	// 翻译
	"com.miui.translationservice": "MIUI翻译服务",
	"com.miui.translation.kingsoft": "MIUI翻译-金山",
	"com.miui.translation.xmcloud": "MIUI翻译-小米云",
	"com.miui.translation.youdao": "MIUI翻译-有道",
};


================================================
FILE: src/miui_cleaner_app/appManager.js
================================================
const startActivity = require("./startActivity");
const singleChoice = require("./singleChoice");

// https://blog.unidevel.cn/xiao-mi-dian-zi-shu-shang-yi-xie-yin-cang-de-she-zhi/

const actions = [
	{
		name: "自启动管理",
		summary: "自启动及后台运行权限管理",
		icon: "./res/drawable/ic_check_list.png",
		packageName: "com.miui.securitycenter",
		className: "com.miui.permcenter.autostart.AutoStartManagementActivity",
	},
	{
		name: "通知管理",
		summary: "通知栏、悬浮提示、图标角标的管理",
		icon: "./res/drawable/ic_item_list.png",
		packageName: "com.miui.notification",
		className: "miui.notification.management.activity.NotificationAppListActivity",
	},
	{
		name: "APP卸载",
		summary: "APP的批量卸载",
		icon: "./res/drawable/ic_recovery.png",
		packageName: "com.miui.cleanmaster",
		className: "com.miui.optimizecenter.deepclean.installedapp.InstalledAppsActivity",
	},
	{
		name: "APP管理",
		summary: "手机管家的应用管理功能",
		icon: "./res/drawable/ic_settings.png",
		packageName: "com.miui.securitycenter",
		className: "com.miui.appmanager.AppManagerMainActivity",
	},
	// {
	// 	name: "应用升级",
	// 	packageName: "com.xiaomi.market",
	// 	className: ".ui.UpdateListActivity",
	// 	summary: "APP的更新管理",
	// },
	{
		name: "APP信息",
		summary: "权限管理模块",
		icon: "./res/drawable/ic_key.png",
		packageName: "com.android.settings",
		className: ".applications.ManageApplications",
	},
	// {
	// 	name: "甜品盒",
	// 	summary: "彩蛋",
	// 	icon: "./res/drawable/ic_android.png",
	// 	packageName: "com.android.systemui",
	// 	className: ".DessertCase",
	// },
	// {
	// 	name: "Marshmallow Land",
	// 	summary: "彩蛋",
	// 	icon: "./res/drawable/ic_android.png",
	// 	packageName: "com.android.systemui",
	// 	className: ".egg.MLandActivity",
	// },
].filter(action => (
	app.getAppName(action.packageName)
));

const name = "APP管家";
const icon = "./res/drawable/ic_phone_settings.png";

function appManager () {
	singleChoice({
		title: name,
		icon,
		itemList: actions,
		fn: startActivity,
	});
	require("./index")();
}

module.exports = {
	name,
	icon,
	summary: "广告相关权限管理",
	fn: appManager,
};


================================================
FILE: src/miui_cleaner_app/dialogs.js
================================================
const resString = com.stardust.autojs.R.string;
const AlertDialog = android.app.AlertDialog;

const btnLabelMap = {
	positive: resString.ok,
	negative: resString.cancel,
	neutral: "在浏览器中打开",
};

function alertDialog (
	message,
	options,
) {
	options = {
		positive: true,
		negative: true,
		neutral: false,
		cancelable: false,
		message,
		...options,
	};
	const builder = new AlertDialog.Builder(activity);
	const emitter = options.emitter || events.emitter();

	function createListener (eventKeyName, eventName) {
		const listener = {};
		listener[`on${eventKeyName}`] = (...args) => {
			console.log("对话框事件:", eventName);
			emitter.emit(eventName, ...args);
		};
		return listener;
	}

	function setAttr (dialog, filter) {
		let keys = Object.keys(dialog);
		if (filter) {
			keys = keys.filter(filter);
		}
		keys.forEach(key => {
			key = key.match(/^set?(On)?(\w+?)(Button|Listener)?$/);
			if (!key) {
				return;
			}
			const setName = key[0];
			const attrKeyName = key[2];
			const attrName = attrKeyName.replace(/^\w/, w => w.toLowerCase());
			const type = key[3] || attrKeyName;
			let attrValue;
			if (type === "Listener") {
				attrValue = createListener(attrKeyName, attrName.replace(/[A-Z]/, w => "_" + w.toLowerCase()));
			} else if (attrName in options) {
				attrValue = options[attrName];
				if (type === "Button") {
					if (attrValue) {
						attrValue = [
							typeof attrValue === "string" ? attrValue : btnLabelMap[attrName],
							createListener("Click", attrName),
						];
					} else {
						return;
					}
				} else if (type === "MultiChoiceItems") {
					attrValue = [
						attrValue.map(String),
						attrValue.map(Boolean),
						createListener("Click", "multi_choice"),
					];
				} else if (type === "Items") {
					attrValue = [
						attrValue.map(String),
						createListener("Click", "single_choice"),
					];
				}
			} else {
				return;
			}
			dialog[setName].apply(dialog, Array.isArray(attrValue) ? attrValue : [attrValue]);
		});
	}

	if (options.view) {
		const frame = ui.inflate("<frame padding=\"22 0\"></frame >");
		let viewList = options.view;
		viewList = Array.isArray(options.view) ? viewList : [viewList];
		viewList.forEach(view => {
			if (typeof view === "string") {
				view = ui.inflate(view, frame);
			}
			frame.addView(view);
		});
		options.view = frame;
	}

	setAttr(builder);
	ui.post(() => {
		const dialog = builder.create();
		setAttr(dialog, attrName => !builder[attrName]);
		dialog.show();
		console.log(`对话框:“${options.message || options.title}”`);
		return dialog;
	}, 1);
	return {
		emitter,
		then: (...args) => {
			return new Promise(resolve => {
				function call (...args) {
					ui.post(() => {
						resolve(...args);
					});
				}
				emitter.once("positive", () => { call(true); });
				emitter.once("negative", () => { call(false); });
				emitter.once("neutral", () => { call(null); });
				emitter.once("cancel", () => { call(); });
				emitter.once("single_choice", (dialog, index) => {
					call(options.items[index]);
				});
			}).then(...args);
		},
	};
}

function confirm (
	message,
	options,
) {
	return alertDialog(
		message,
		{
			...options,
		},
	);
}

function alert (
	message,
	options,
) {
	return alertDialog(
		message,
		{
			negative: false,
			...options,
		},
	).then(() => {});
}

function prompt (
	message,
	value,
	options,
) {
	const view = options.view || ui.inflate(`<input text="${value || ""}" />`);
	return alertDialog(
		message,
		{
			view,
			...options,
		},
	).then(result => {
		if (result) {
			return view.getText().toString();
		} else {
			return null;
		}
	});
}

function singleChoice (
	items,
	options,
) {
	return alertDialog(
		null,
		{
			items,
			positive: false,
			// negative: false,
			...options,
		},
	);
}

module.exports = Object.assign(alertDialog, {
	confirm,
	alert,
	prompt,
	singleChoice,
});


================================================
FILE: src/miui_cleaner_app/downApp.js
================================================
const getApplicationInfo = require("./getApplicationInfo");
const getRemoteFileInfo = require("./getRemoteFileInfo");
const singleChoice = require("./singleChoice");
const prettyBytes = require("pretty-bytes");
const downFile = require("./downFile");
const dialogs = require("./dialogs");

// https://github.abskoop.workers.dev/
// http://fastgit.org/
// https://download.fastgit.org/skylot/jadx/releases/download/v1.4.4/jadx-gui-1.4.4-no-jre-win.exe
// https://download.fastgit.org/MrIkso/ArscEditor/releases/download/1.0.2/ArscEditor-1.0.2.zip

const appList = [
	{
		name: "李跳跳",
		summary: "干净小巧的广告自动跳过工具",
		icon: "https://litiaotiao.cn/apple-touch-icon.png",
		packageName: "hello.litiaotiao.app",
		url: "https://www.123pan.com/s/ZYAZVv-TBYjd",
		filter: function (files) {
			return files.filter(file => {
				return /李跳跳|MissLee/.test(file.fileName) && !file.fileName.includes("真实好友");
			});
		},
	},
	{
		name: "QQ音乐简洁版",
		summary: "MIUI音乐APP套壳的产品",
		icon: "https://m.32r.com/logo/210807/202108070906595774.png",
		packageName: "com.tencent.qqmusiclite",
		url: "https://www.coolapk.com/apk/com.tencent.qqmusiclite",
	},
	{
		name: "Edge",
		summary: "浏览器,微软出品,带广告屏蔽功能",
		icon: "https://edgefrecdn.azureedge.net/welcome/static/favicon.png",
		packageName: "com.microsoft.emmx",
		url: "https://app.mi.com/details?id=com.microsoft.emmx",
	},
	{
		name: "小米浏览器",
		summary: "国际版",
		icon: "https://m.32r.com/logo/210519/202105191427372351.png",
		packageName: "com.mi.globalbrowser",
		url: "https://wwm.lanzoul.com/tp/idzsf0bh062h",
	},
	{
		name: "讯飞输入法",
		summary: "定制版、Google Play版",
		icon: "https://srf.xunfei.cn/favicon.ico",
		packageName: "com.iflytek.inputmethod",
		// url: "https://app.meizu.com/apps/public/detail?package_name=com.iflytek.inputmethod",
		// url: "https://m.32r.com/app/7401.html",
		url: "https://firepx.lanzoul.com/b00vf92jc#pwd=647w",
	},
	// {
	// 	name: "软件包安装程序",
	// 	summary: "Google版",
	// 	packageName: "com.google.android.packageinstaller",
	// 	icon: "https://file.1xiazai.net/d/file/android/20220728/202266164286724.png",
	// 	url: {
	// 		// 3.01 MB 版本号 未知 适用于安卓 13 SDK 33
	// 		33: "https://www.123pan.com/s/OZe0Vv-iOKl3",
	// 		// 3.14 MB 版本号 12-7567768 适用于安卓 12 SDK 31
	// 		31: "https://www.123pan.com/s/OZe0Vv-LOKl3",
	// 		// 3.13 MB 版本号 11-7532981 适用于安卓 11 SDK 30
	// 		30: "https://www.123pan.com/s/OZe0Vv-zOKl3",
	// 		// 1.83 MB 版本号 10-7029319 适用于安卓 10 SDK 29
	// 		29: "https://www.123pan.com/s/OZe0Vv-tOKl3",
	// 		// 8.55 MB 版本号 9-7126274 适用于安卓 9 SDK 28
	// 		28: "https://www.123pan.com/s/OZe0Vv-qOKl3",
	// 	}[device.sdkInt],
	// },
	{
		name: "应用包管理组件",
		summary: "MIUI软件包安装程序v3.8.0,不含“纯净模式”",
		icon: "http://pic.danji100.com/upload/2022-4/20224261118377118.png",
		packageName: "com.miui.packageinstaller",
		url: "https://zisu.lanzoum.com/tp/iI7LGwn5xjc",
		filter: function (files) {
			files = files.map(file => {
				const miuiInst = file.fileName.match(/(应用包管理组件).*?([\d.]+)-(\d+).*?(\.\w+)$/);
				if (miuiInst) {
					const appName = miuiInst[1];
					const versionCode = Number.parseInt(miuiInst[2].replace(/\./g, ""), 10);
					const versionName = `${Array.from(String(versionCode)).join(".")}-${miuiInst[3]}`;
					file.fileName = `${appName}_v${versionName}${miuiInst[4]}`;
					file.versionName = versionName;
					file.versionCode = versionCode;
					console.log(file);
				}
				return file;
			});
		},
	},
	{
		name: "几何天气",
		summary: "干净、小巧、漂亮、功能多",
		icon: "https://raw.fastgit.org/WangDaYeeeeee/GeometricWeather/master/app/src/main/res/drawable/ic_launcher.png",
		packageName: "wangdaye.com.geometricweather",
		url: "https://github.com/WangDaYeeeeee/GeometricWeather/releases/latest",
		filter: function (files) {
			const appInfo = this;
			files = files.filter(file => {
				const verInfo = file.url.match(/\/(.+?)\/.*?\.\1_(\w+)\.\w+$/);
				if (verInfo) {
					const verName = verInfo[1];
					const verType = verInfo[2];
					file.versionName = `${verName}_${verType}`;
					file.versionCode = Number.parseInt(verName.replace(/\./, ""), 10);
				}
				return verInfo;
			});

			if (appInfo.appName) {
				let subVer = appInfo.getVersionName().match(/_\w+$/);
				if (subVer) {
					subVer = subVer[0] + ".apk";
					return files.filter(file => file.fileName.endsWith(subVer));
				}
			}
			return [files[files.length - 1]];
		},
	},
	{
		name: "ES文件浏览器",
		summary: "去广告版,替代MIUI视频、音乐、文档查看器",
		icon: "https://m.32r.com/logo/220311/202203111728435421.png",
		packageName: "com.estrongs.android.pop",
		url: "https://423down.lanzouv.com/b0f1d7s2h",
	},
	{
		name: "WPS Office Lite",
		summary: "国际版,无广告,替代“文档查看器”",
		icon: "https://m.32r.com/logo/220908/202209081617517363.png",
		packageName: "cn.wps.moffice_i18n",
		url: "https://m.32r.com/app/109976.html",
	},
	{
		name: "知乎",
		summary: "集成“知了”,“设置→知了”中有去广告开关",
		icon: "https://static.zhihu.com/heifetz/assets/apple-touch-icon-60.8f6c52aa.png",
		packageName: "com.zhihu.android",
		url: "https://www.123pan.com/s/A6cA-dJAJh",
		// url: "https://423down.lanzouo.com/b0f2lkafe",
		// url: "https://m.32r.com/app/80966.html",
		// https://www.423down.com/11775.html
		filter: function (files) {
			return files.filter(file => {
				return /知乎.*知了/.test(file.fileName);
			});
		},
	},
	{
		name: "哔哩哔哩",
		summary: "“设置→哔哩漫游→关于版本”点五下有惊喜",
		icon: "https://m.32r.com/logo/221114/202211141125334046.png",
		packageName: "tv.danmaku.bili",
		// url: "https://www.123pan.com/s/A6cA-gT9Jh",
		url: "https://423down.lanzouv.com/b0f1gksne",
		// https://www.423down.com/12235.html
		filter: function (files) {
			return files.filter(file => {
				return /哔哩哔哩.*漫游/.test(file.fileName);
			});
		},
	},
	{
		name: "优酷视频",
		summary: "去广告版",
		icon: "https://img.alicdn.com/tfs/TB1WeJ9Xrj1gK0jSZFuXXcrHpXa-195-195.png",
		packageName: "com.youku.phone",
		url: "https://423down.lanzouv.com/b0f1avpib",
		filter: function (files) {
			return files.filter(file => {
				file.fileName = file.fileName.replace(/忧(?=酷)/g, "优");
				return file.fileName.includes("优酷视频");
			});
		},
	},
	{
		name: "高德地图",
		summary: "Google版、纯净版",
		icon: "https://m.amap.com/img/screenLogo.png",
		packageName: "com.autonavi.minimap",
		url: "https://423down.lanzouv.com/b0f29j15c",
	},
	{
		name: "百度贴吧",
		summary: "去广告版",
		icon: "https://m.32r.com/logo/210810/202108101711331977.png",
		packageName: "com.baidu.tieba",
		url: "https://423down.lanzouv.com/b0f1b6q8d",
	},
	{
		name: "酷安",
		summary: "应用商店,去广告版",
		icon: "https://static.coolapk.com/static/web/v8/images/header-logo.png",
		packageName: "com.coolapk.market",
		url: "https://423down.lanzouv.com/b0f2uzq2b",
	},
	{
		name: "App分享",
		summary: "应用商店,刷机包,国际版提取的APP",
		icon: "http://pic.xfdown.com/uploads/2022-5/2022551511344265.png",
		packageName: "info.muge.appshare",
		url: "https://423down.lanzouv.com/tp/iHmmD06tw9xa",
	},
];

function formatSize (number, options) {
	if (!number || !Number.isSafeInteger(number)) {
		return number;
	}
	return prettyBytes(number, {
		binary: true,
		...options,
	});
}

function formatDate (number) {
	if (!number || !Number.isSafeInteger(number)) {
		return number;
	}
	const dateFormat = android.text.format.DateFormat.getDateFormat(activity);
	return dateFormat.format(number) || number;
}

async function download (appInfo, item) {
	if (typeof appInfo === "string") {
		appInfo = appList.find(info => info.packageName === appInfo);
	}
	if (/^\w+:\/\/app.mi.com\//i.test(appInfo.url)) {
		app.startActivity({
			action: "android.intent.action.VIEW",
			data: "market://details?id=" + appInfo.packageName,
		});
		return;
	}
	const View = android.view.View;
	let progress = item.progress;
	if (progress) {
		progress.setVisibility(View.VISIBLE);
		progress.indeterminate = true;
	} else {
		progress = ui.inflate(`
			<progressbar id="progress" indeterminate="true" layout_centerHorizontal="true" layout_alignParentBottom="true" w="*" h="auto"style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" />
		`, item, true);
	}
	function hideProgress () {
		// console.log(progress);
		progress.setVisibility(View.GONE);
		// item.removeView(progress);
		// item.invalidate();
		// progress.invalidate();
	}
	let file;
	let getLocationTask;
	function getLocation () {
		if (file && file.getLocation) {
			getLocationTask = file.getLocation(true);
		}
	};
	try {
		file = await getRemoteFiles(appInfo);
		if (file.length > 1) {
			const choice = await dialogs.singleChoice(file.map(file => ({
				toString: () => file.fileName + "\n" + [
					file.versionName,
					formatSize(file.size),
					formatDate(file.lastModified),
				].filter(Boolean).join(" | "),
				file,
			})), {
				title: `请选择要下载的“${appInfo.appName || appInfo.name}”版本`,
				neutral: true,
			});
			file = choice && choice.file;
			getLocation();
		} else {
			file = file[0];
			getLocation();
			const localVer = appInfo.appName && appInfo.getVersionName();
			const confirm = await dialogs.confirm([
				file.versionName && `版本:${(localVer ? `${localVer} → ` : "") + file.versionName}`,
				file.size && `大小:${formatSize(file.size)}`,
				file.lastModified && `日期:${formatDate(file.lastModified)}`,
			].filter(Boolean).join("\n"), {
				title: `是否${appInfo.appName ? "更新" : "下载"}“${appInfo.appName || appInfo.name}”?`,
				neutral: true,
			});
			file = confirm && file;
		}
	} catch (ex) {
		console.error(ex);
		file = null;
	}

	if (file) {
		await getLocationTask;
		const downTask = downFile(file);
		downTask.on("progress", (e) => {
			progress.indeterminate = false;
			progress.max = e.size;
			progress.progress = e.progress;
		});
		const intent = await downTask;
		const confirm = intent.getPackage() || (await dialogs.confirm(`“${file.fileName}”下载完毕,立即安装?`, {
			title: "确认安装",
		}));
		if (confirm) {
			app.startActivity(intent);
		}
	} else {
		if (file === null && appInfo.url) {
			app.openUrl(appInfo.url);
		}
	}
	hideProgress();
}

function verCompare (verA, verB) {
	function splitVer (versionName) {
		return versionName.replace(/^\D+|\D+$/g, "").split(/\./g);
	}
	function parseNum (str) {
		return Number.parseInt(str, 10) || 0;
	}
	verA = splitVer(verA);
	verB = splitVer(verB);
	const length = Math.max(verA.length, verB.length);
	let result;
	for (let i = 0; i < length && !result; i++) {
		result = parseNum(verA[i]) - parseNum(verB[i]);
	}
	return result;
}

function fileCompare (b, a) {
	let result;
	if (a.versionCode && b.versionCode) {
		result = a.versionCode - b.versionCode;
	}
	if (a.versionName && b.versionName) {
		result = result || verCompare(a.versionName, b.versionName);
	}
	if (a.lastModified && b.lastModified) {
		result = result || a.lastModified - b.lastModified;
	}
	return result;
}

function getRemoteFiles (appInfo) {
	return getRemoteFileInfo(appInfo.url).then(fileList => {
		if (!fileList) {
			return;
		}
		if (!Array.isArray(fileList)) {
			fileList = [fileList];
		}
		if (appInfo.filter) {
			fileList = appInfo.filter(fileList) || fileList;
		} else if (fileList.length > 1) {
			fileList = fileList.filter(file => file.fileName.includes(appInfo.name));
		}
		fileList = fileList.sort(fileCompare);
		if (fileList.length > 1 && fileList[0].versionName) {
			fileList = fileList.filter(file => file.versionName === fileList[0].versionName);
		}
		if (fileList.length > 1) {
			const mouse = fileList.find(file => /耗/.test(file.fileName));
			if (mouse) {
				fileList = [mouse];
			}
		}
		return fileList;
	});
}

function downApp () {
	appList.forEach((appInfo) => {
		getApplicationInfo(appInfo);
		if (appInfo.appName) {
			appInfo.displayName = appInfo.appName + " v" + appInfo.getVersionName();
			// if (!/^\w+:\/\/app.mi.com\//i.test(appInfo.url)) {
			// 	getRemoteFiles(appInfo);
			// }
		} else {
			delete appInfo.displayName;
		}
	});
	singleChoice({
		title: "请选择要下载的APP",
		itemList: appList,
		fn: download,
	});
	require("./index")();
}
// downApp.download = downApp;
module.exports = {
	name: "去广告APP",
	summary: "各APP的去广告版和广告自动跳过工具",
	icon: "./res/drawable/ic_download.png",
	fn: downApp,
};


================================================
FILE: src/miui_cleaner_app/downFile.js
================================================
const DownloadManager = android.app.DownloadManager;
const Cursor = android.database.Cursor;
const Intent = android.content.Intent;

const downloadManager = context.getSystemService(context.DOWNLOAD_SERVICE);
const mimeTypeMap = android.webkit.MimeTypeMap.getSingleton();
const emitter = events.emitter();

function getValOfCursor (cursor, columnName, columnType) {
	let columnIndex = cursor.getColumnIndex(columnName);
	if (columnIndex < 0) {
		columnName = DownloadManager["COLUMN_" + columnName] || DownloadManager[columnName];
		columnIndex = cursor.getColumnIndex(columnName);
	}
	if (columnIndex < 0) {
		return;
	}
	if (!columnType) {
		switch (cursor.getType(columnIndex)) {
			case Cursor.FIELD_TYPE_INTEGER:
				columnType = "Long";
				break;
			case Cursor.FIELD_TYPE_FLOAT:
				columnType = "Float";
				break;
			case Cursor.FIELD_TYPE_STRING:
				columnType = "String";
				break;
			case Cursor.FIELD_TYPE_BLOB:
				columnType = "Blob";
				break;
		}
	}
	return cursor[`get${columnType}`](columnIndex);
}

function queryDownList (callback, query) {
	const cursor = downloadManager.query(query || new DownloadManager.Query());
	const valueOf = getValOfCursor.bind(cursor, cursor);
	let result;
	if (cursor) {
		if (cursor.moveToFirst()) {
			do {
				if ((result = callback(valueOf))) {
					break;
				}
			} while (cursor.moveToNext());
		}
		cursor.close();
	}
	return result;
}

function guessFileName (disposition) {
	if (disposition) {
		const fileName = disposition.match(/(^|;)\s*filename\*?\s*=\s*(UTF-8(''|\/))?(.*?)(;|\s|$)/i);
		return fileName && decodeURI(fileName[4]);
	}
}

function readConfig (options) {
	// let disposition;
	// if (options.headers) {
	// 	Object.keys(options.headers).forEach(key => {
	// 		switch (key.toLowerCase()) {
	// 			case "content-disposition": {
	// 				disposition = options.headers[key];
	// 				break;
	// 			}
	// 			case "content-length": {
	// 				options.size = +options.headers[key];
	// 				break;
	// 			}
	// 			case "content-type": {
	// 				if (options.headers[key] !== "application/octet-stream") {
	// 					options.mimeType = options.headers[key];
	// 				}
	// 				break;
	// 			}
	// 		}
	// 	});
	// }
	options.location = decodeURI(options.location || options.url);
	if (!options.fileName) {
		options.fileName = (guessFileName(options.disposition) || android.webkit.URLUtil.guessFileName(options.location, null, null)).replace(/_(Coolapk|\d+)(?=\.\w+$)/i, "");
	}
	if (!options.mimeType || /^application\/octet-stream$/.test(options.mimeType)) {
		options.mimeType = mimeTypeMap.getMimeTypeFromExtension(files.getExtension(options.fileName));
	}
	return options;
}

function downFile (options) {
	options = readConfig(options);
	let downId;
	let complete;
	const downEmitter = Object.create(events.emitter());

	const promise = new Promise((resolve, reject) => {
		downEmitter.on("complete", resolve);
		// downEmitter.on("cancel", reject);
		// downEmitter.on("error", reject);
	});

	function emitProgressEvent (progressEvent) {
		if (!progressEvent.size) {
			progressEvent.size = options.size;
		}
		downEmitter.emit("progress", progressEvent);
	}

	function emitCompleteEvent (intent) {
		if (!intent) {
			intent = new Intent();
			intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downId);
		}
		intent.setDataAndType(
			downloadManager.getUriForDownloadedFile(downId),
			options.mimeType,
		);
		intent.setAction(Intent.ACTION_VIEW);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
		const googleInstaller = "com.google.android.packageinstaller";

		intent.setPackage(app.getAppName(googleInstaller) ? googleInstaller : null);

		downEmitter.emit("complete", intent);
		downEmitter.removeAllListeners();
		throttle[downId] = null;
		complete = true;
	}

	function startTask () {
		queryDownList((valueOf) => {
			if (options.location === valueOf("URI")) {
				downId = valueOf("ID");
			} else {
				return;
			}
			switch (valueOf("STATUS")) {
				case DownloadManager.STATUS_SUCCESSFUL: {
					emitCompleteEvent();
					break;
				}
				case DownloadManager.STATUS_PAUSED:
				case DownloadManager.STATUS_FAILED: {
					try {
						app.launchPackage("com.android.providers.downloads.ui");
					} catch (ex) {
						app.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
					}
					// falls through
				}
				default: {
					downEmitter.emit("start", downId);
					emitProgressEvent(createProgressEvent(valueOf));
				}
			}
			return true;
		});

		if (!downId) {
			const request = new DownloadManager.Request(android.net.Uri.parse(options.location));
			request.addRequestHeader("User-Agent", options.userAgent || android.webkit.WebSettings.getDefaultUserAgent(context));
			if (options.referer) {
				request.addRequestHeader("Referer", options.referer);
			}
			// request.setDestinationInExternalPublicDir(android.os.Environment.DIRECTORY_DOWNLOADS, options.fileName);
			request.setDestinationInExternalFilesDir(context, android.os.Environment.DIRECTORY_DOWNLOADS, options.fileName);
			request.setMimeType(options.mimeType);
			console.log("开始下载:", options);
			downId = downloadManager.enqueue(request);
			downEmitter.emit("start", downId);
		}

		emitter.on(`${downId}.click`, (...args) => downEmitter.emit("click", ...args));

		if (!complete) {
			emitter.on(`${downId}.progress`, emitProgressEvent);
			emitter.once(`${downId}.complete`, (...args) => {
				console.log("下载完毕:", options);
				emitter.removeAllListeners(`${downId}.complete`);
				emitter.removeAllListeners(`${downId}.progress`);
				emitCompleteEvent(...args);
				// emitter.removeAllListeners(`${downId}.click`);
			});
			startDownReceiver();
		}
	}
	downEmitter.then = (...args) => promise.then(...args);
	setTimeout(startTask, 0);
	return downEmitter;
}

function registerReceiver (sysActionName, onReceive) {
	context.registerReceiver(
		new JavaAdapter(android.content.BroadcastReceiver, {
			onReceive,
		}),
		new android.content.IntentFilter(sysActionName),
	);
}

registerReceiver(DownloadManager.ACTION_DOWNLOAD_COMPLETE, (context, intent) => {
	emitter.emit(`${intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)}.complete`, intent);
});

registerReceiver(DownloadManager.ACTION_NOTIFICATION_CLICKED, (context, intent) => {
	intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS).forEach(downId => {
		emitter.emit(`${downId}.click`, intent);
	});
});

function createProgressEvent (valueOf) {
	// 已经下载文件大小
	const progress = valueOf("BYTES_DOWNLOADED_SO_FAR");
	const speed = valueOf("downloading_current_speed");
	// 下载文件的总大小
	const size = valueOf("TOTAL_SIZE_BYTES");
	return {
		progress,
		speed: speed >= 0 ? speed : null,
		size,
	};
}

let downStatus = false;
let throttle = {};

function downReceiver () {
	let running;
	queryDownList(valueOf => {
		if (valueOf("STATUS") === DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
			return;
		}
		const downId = valueOf("ID");
		const progressEvent = createProgressEvent(valueOf);
		if (progressEvent.progress > 0 || progressEvent.size > 0) {
			const key = JSON.stringify(progressEvent);
			if (throttle[downId] !== key) {
				emitter.emit(`${downId}.progress`, progressEvent);
				throttle[downId] = key;
			}
		}
		running = true;
	});
	if (running) {
		setTimeout(downReceiver, 0x200);
	} else {
		throttle = {};
	}
	downStatus = running || false;
}
function startDownReceiver () {
	if (!downStatus) {
		downReceiver();
	}
}

downFile.queryDownList = queryDownList;
module.exports = downFile;


================================================
FILE: src/miui_cleaner_app/emitItemShowEvent.js
================================================
const debounce = require("debounce");
const Rect = android.graphics.Rect;
const inNightMode = Boolean(activity.getApplicationContext().getResources().getConfiguration().uiMode & android.content.res.Configuration.UI_MODE_NIGHT_YES);
function emitItemShowEvent (listView, defaultIcon) {
	const itemList = new Map();
	listView.on("item_bind", function (itemView, itemHolder) {
		itemList.set(itemView, itemHolder);
		setTimeout(() => {
			listView.emit("item_show", itemHolder.item, itemView, listView);
			if (itemHolder.item.loadIcon || (itemHolder.item.icon && /^https?:/i.test(itemHolder.item.icon))) {
				itemView.icon.clearColorFilter();
			}
		}, 0);
	});
	listView.on("scroll_change", debounce(() => {
		const parentRect = new Rect();
		listView.getGlobalVisibleRect(parentRect);
		function isVisible (target) {
			const rect = new Rect();
			target.getGlobalVisibleRect(rect);
			return parentRect.contains(rect) || parentRect.intersect(rect);
		}
		itemList.forEach((itemHolder, itemView) => {
			if (isVisible(itemView)) {
				listView.emit("item_show", itemHolder.item, itemView, listView);
			}
		});
	}, 80));
	listView.on("item_show", function (item, itemView, listView) {
		const imageView = itemView.icon;
		if (item.loadIcon) {
			imageView.setImageDrawable(item.loadIcon());
		} else if (!(item.icon && /^https?:/i.test(item.icon))) {
			imageView.setColorFilter(android.graphics.Color.parseColor(inNightMode ? "#FFCCCCCC" : "#FF333333"));
			return;
		}
		imageView.clearColorFilter();
	});
}
module.exports = emitItemShowEvent;


================================================
FILE: src/miui_cleaner_app/fetch.js
================================================
const okhttp3 = global.Packages?.okhttp3;
const Headers = global.Headers || require("headers-polyfill").Headers;
// const ReadableStream = global.ReadableStream || require("web-streams-ponyfill").ReadableStream;
const Blob = global.Blob || require("blob-polyfill").Blob;
// const FormData = global.FormData || require("formdata-polyfill").Headers;

function fetchAny (url, options = {}) {
	if (Array.isArray(url)) {
		let controller;
		if (!options.signal) {
			if (global.AbortController) {
				controller = new AbortController();
				options.signal = controller.signal;
			} else {
				options.signal = events.emitter(threads.currentThread());
			}
		}
		return Promise.any(url.map(url => fetch(url, options))).then(res => {
			if (controller) {
				controller.abort();
			} else if (options.signal?.emit) {
				options.signal.emit("abort");
			}
			return res;
		});
	} else {
		return fetch(url, options);
	}
}

function fetch (url, options = {}) {
	return new Promise((resolve, reject) => {
		options = {
			redirect: "follow",
			method: "GET",
			...options,
		};
		options.method = options.method.toUpperCase();
		// console.time(url);
		const client = http.client().newBuilder()
			.followRedirects(/^follow$/i.test(options.redirect))
			.build();
		const call = client.newCall(http.buildRequest(url, options));
		const work = events.emitter(threads.currentThread());
		work.once("response", resolve);
		work.once("error", reject);
		call.enqueue(new okhttp3.Callback({
			onResponse: function (call, res) {
				try {
					res = wrapResponse(res, options);
				} catch (ex) {
					work.emit("error", ex);
					return;
				}
				work.emit("response", res);
				// console.timeEnd(url);
			},
			onFailure: function (call, err) {
				work.emit("error", err);
			},
		}));

		if (options.signal) {
			const abort = () => {
				call.isCanceled() || call.cancel();
				work.emit("error", new Error(options.signal.reason || "The user aborted a request."));
			};
			if (options.signal.aborted) {
				return abort();
			}
			if (options.signal.addEventListener) {
				options.signal.addEventListener("abort", abort);
			} else if (options.signal.on) {
				options.signal.on("abort", abort);
			}
		}
	});
}

const _response = new Map();
class Response {
	get status () {
		return _response.get(this).code();
	}

	get ok () {
		return this.status >= 200 && this.status < 300;
	}

	get url () {
		return _response.get(this).request().url().toString();
	}

	get redirected () {
		return _response.get(this).isRedirect();
	}

	get statusText () {
		return _response.get(this).message();
	}

	get headers () {
		return _response.get(_response.get(this)).getHeaders();
	}

	get body () {
		return _response.get(_response.get(this)).getBody();
	}

	blob () {
		return _response.get(_response.get(this)).getBlob();
	}

	arrayBuffer () {
		this.blob().arrayBuffer();
	}

	text () {
		return _response.get(_response.get(this)).getBodyText();
	}

	json () {
		return this.text().then(text => JSON.parse(text));
	}

	clone () {
		const response = Object.create(Response.prototype);
		_response.set(response, _response.get(this));
		return response;
	}
}
function hexToArrayUint8Array (input) {
	const view = new Uint8Array(input.length / 2);
	for (let i = 0; i < input.length; i += 2) {
		view[i / 2] = parseInt(input.substring(i, i + 2), 16);
	}
	return view;
}
// https://square.github.io/okhttp/4.x/okhttp/okhttp3/-response/
function wrapResponse (res, options) {
	if (/^error$/i.test(options.redirect) && res.isRedirect()) {
		throw new Error("unexpected redirect");
	}
	const response = Object.create(Response.prototype);
	_response.set(response, res);
	let headers;
	const body = res.body();
	const bodyByteString = body.byteString();
	body.close();
	const contentType = body.contentType();
	let text;
	const bodyToText = () => {
		if (text === undefined) {
			text = new java.lang.String(bodyByteString.toByteArray(), contentType.charset() || "UTF-8");
		}
		return text;
	};
	let blob;
	const bodyToBlob = () => {
		if (!blob) {
			blob = blob = new Blob([
				hexToArrayUint8Array(bodyByteString.hex()),
			], {
				type: `${contentType.type()}/${contentType.subtype()}`,
			});
		}
		return blob;
	};
	const resProps = {
		getHeaders: () => {
			if (!headers) {
				headers = new Headers();
				res.headers().forEach(entry => {
					headers.append(entry.first, entry.second);
				});
			}
			return headers;
		},
		getBlob: () => {
			return Promise.resolve().then(bodyToBlob);
		},
		getBody: () => {
			return bodyToBlob().stream();
		},
		getBodyText: (fnName) => {
			return Promise.resolve().then(bodyToText);
		},
	};
	_response.set(res, resProps);
	return response;
}

module.exports = fetchAny;

// fetch(
// 	"https://developer.lanzoug.com/file/?UjRbZQw9UGEHDgY+AzYGalJtAjpe5FPCA5BVtlaZU/sG41CUAchX5lOWB/gAsweqUKYC41ewUNtSs1fHAOUEUVIEW+sM2FCMB3wGYQN5BjFSJgIxXixTtAOLVfxW7VPOBo1Q4gHgV71T4AfoAMEH4FCeAqhXJlAzUiVXOgB6BGNSO1tgDDRQWwc4BjQDagY1UjkCPl42U2ADPVVjVjhTdAZgUHQBYVc0UzwHYABkBz9QPwIxVy5QIlIlV2wAbgQ1UmBbPAx+UDQHZQZ/A2UGP1InAjNeNFNoAz1ValY6U2EGM1A3AThXNVNgBzcAMAcyUDoCN1c+UGFSYldnAD8EMlJiWzwMM1A2B2gGYwNkBjJSOgIpXmBTIQNvVXVWf1MhBmNQdQE1V2dTOAdoAGMHMFA6AjBXLlAmUjxXPAA5BGNSb1s9DGdQMgdoBmgDZgYzUjoCMl4yU3cDYlU/Vn1TbwY3UDEBalc6Uz0HYABnBzBQOwIzVy5QJ1IlVyYAYQQ0UmdbNAxpUDQHaQZoA2MGMVIwAiFedFM4A3RVblY4U2IGMlApAW1XOlM8B38AZgc0UD8CKVc7UGNSc1c1ADAEOFJi",
// 	{
// 		redirect: "follow",
// 		// method: "HEAD",
// 		headers: {
// 			"accept": "*/*",
// 			"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",
// 			"referer": "https://423down.lanzouv.com/tp/ic1wllc",
// 			"x-forwarded-for": "202.247.192.146",
// 			"client-ip": "59.142.129.197",
// 		},
// 	},
// );


================================================
FILE: src/miui_cleaner_app/findClickableParent.js
================================================
/**
 * 向上查找 UiObject 的父节点,找到可点击的祖先节点
 * @param {UiObject} node 节点
 * @returns
 */
function findClickableParent (node) {
	return !node || node.clickable() ? node : findClickableParent(node.parent());
}
module.exports = findClickableParent;


================================================
FILE: src/miui_cleaner_app/getApplicationInfo.js
================================================
// const PackageManager = android.content.pm.PackageManager;
const pm = context.getPackageManager();
// https://developer.android.google.cn/reference/kotlin/android/content/pm/ApplicationInfo
// https://developer.android.google.cn/reference/kotlin/android/content/pm/PackageInfo
function getApplicationInfo (options) {
	let appInfo;
	let packageInfo;
	// const getPackageInfo = () => packageInfo || (packageInfo = pm.getPackageInfo(options.packageName, PackageManager.GET_SIGNING_CERTIFICATES));
	const getPackageInfo = () => packageInfo || (packageInfo = pm.getPackageInfo(options.packageName, 0));

	try {
		appInfo = pm.getApplicationInfo(options.packageName, 0);
	} catch (ex) {
		return null;
	}
	if (!options.appName) {
		const appName = pm.getApplicationLabel(appInfo).toString();
		if (appName === options.packageName) {
			if (!options.name && options.summary) {
				options.name = options.summary;
				options.summary = options.packageName;
			}
		} else {
			options.appName = appName;
		}
	}

	// if (!options.getSignature) {
	// 	options.getSignature = () => getPackageInfo().signingInfo.getApkContentsSigners();
	// }
	if (!options.loadIcon && appInfo.icon) {
		options.loadIcon = () => appInfo.loadIcon(pm);
	}
	options.getVersionName = () => {
		let versionName = getPackageInfo().versionName;
		if (options.packageName === "com.miui.packageinstaller") {
			versionName = versionName.replace(/^\d+(?=-)/, () => Array.from(String(packageInfo.getLongVersionCode())).join("."));
		}
		return versionName;
	};
	options.getVersionCode = () => getPackageInfo().getLongVersionCode();
	options.getUpdateTime = () => getPackageInfo().lastUpdateTime;
	return options;
}
module.exports = getApplicationInfo;

// console.log(
// 	getApplicationInfo({
// 		packageName: "org.autojs.autoxjs.v6",
// 	}),
// );

// const apkPath = "/data/app/org.autojs.autoxjs.v6-XPge4R-XoervO0iNge1BhQ==/base.apk";

// const apkPath = "/storage/emulated/0/Android/data/org.autojs.autoxjs.v6/files/Download/GeometricWeather.3.013_pub-2.apk";
// const info = pm.getPackageArchiveInfo(
// 	apkPath,
// 	PackageManager.GET_SIGNING_CERTIFICATES,
// );
// const appInfo = info.applicationInfo;

// appInfo.sourceDir = apkPath;
// appInfo.publicSourceDir = apkPath;
// console.log(
// 	appInfo.loadLabel(pm).toString(),
// );
// console.log(
// 	appInfo.packageName,
// );
// console.log(
// 	appInfo.loadLabel(pm).toString(),
// );

// const zipFile = $zip.open(apkPath);
// log(zipFile.getPath());
// log();
// zipFile.getFileHeaders().forEach(file => {
// 	const fileName = file.getFileName();
// 	if (/^META-INF\/.+?\..*SA$/i.test(fileName)) {
// 		console.log(fileName);
// 		console.log(file.getSignature());
// 	}
// });


================================================
FILE: src/miui_cleaner_app/getRemoteFileInfo.js
================================================
const fetch = require("./fetch");
const lanzou = require("./lanzou");
const _123pan = require("./123pan");

class Asset {
	constructor (data) {
		Object.assign(this, data);
	}

	async getLocation () {
		const location = await parseGithubRelease(new URL(this.url), false);
		if (location) {
			this.location = location;
		}
		return this;
	}
}

function parseGithubRelease (url, redirect) {
	const pathInfo = url.pathname.match(/\/releases\/(.+)$/);
	if (!pathInfo) {
		return;
	}
	if (pathInfo[1].includes("/")) {
		return fetch(
			[
				url.href,
				url.protocol + "//gh.api.99988866.xyz/" + url.href,
				url.protocol + "//download.fastgit.org" + url.pathname + url.search,
			],
			{
				redirect: redirect ? "follow" : "manual",
				method: "HEAD",
			},
		).then(res => {
			const location = res.headers.get("location");
			if (location) {
				return location;
			} else if (res.ok) {
				return res.url;
			}
		});
	} else {
		url.hostname = "api." + url.hostname;
		url.pathname = "/repos" + url.pathname;
		return fetch(url.href).then(res => res.json()).then(release => {
			const referer = release.html_url;
			const versionName = release.tag_name.replace(/^v/, "");
			return release.assets.map(
				asset => new Asset({
					fileName: asset.name,
					type: asset.content_type,
					size: asset.size,
					lastModified: Date.parse(asset.updated_at),
					id: asset.node_id,
					url: asset.browser_download_url,
					referer,
					versionName,
				}),
			);
		});
	}
}

function parse32r (url) {
	let id = url.pathname.match(/^\/\w+\/(\w+?)(\.\w+)?$/i);
	if (!id) {
		return;
	}
	id = id[1];
	const htmlUrl = `https://m.32r.com/app/${id}.html`;
	const appUrl = `https://m.32r.com/downapp/${id}`;
	let html;
	let res = {};
	return Promise.all([
		fetch(
			appUrl,
			{
				method: "HEAD",
				headers: {
					Referer: htmlUrl,
				},
			},
		).then(data => {
			res = data;
		}, console.error),
		fetch(
			htmlUrl,
		).then(res => res.text()).then(data => {
			html = data;
		}, console.error),
	]).then(() => {
		let json = html && html.match(/<script type="application\/ld\+json">\s*([\s\S]+?)\s*<\/script>/);
		if (json) {
			json = JSON.parse(json[1]);
		} else {
			json = {};
		}
		let versionName = json.title?.match(/\d+(\.+\d+)+/);
		versionName = versionName && versionName[0];
		return {
			fileName: res.url && files.getName(new URL(res.url).pathname).replace(/_\d+(?=\.\w+$)/i, ""),
			size: +res.headers?.get("content-length"),
			lastModified: Date.parse(res.headers?.get("last-modified") || json.upDate || json.pubDate),
			id,
			url: res.url || appUrl,
			referer: htmlUrl,
			versionName,
		};
	});
}

function getVersionForFile (fileInfo) {
	if (Array.isArray(fileInfo)) {
		fileInfo.forEach(getVersion);
	} else {
		getVersion(fileInfo);
	}
	return fileInfo;
}

function getVersion (fileInfo) {
	const versionName = fileInfo.fileName.match(/\d+(\.+\d+)+/);
	if (versionName) {
		fileInfo.versionName = versionName[0];
	}
	const versionCode = fileInfo.fileName.match(/\(\s*(\d+)\s*\)/);
	if (versionCode) {
		fileInfo.versionCode = +versionCode[1];
	}
}

function getOptsFromUrl (url) {
	const options = {};
	url.hash.replace(/^#+/, "").split(/\s*&\s*/g).forEach(
		value => {
			value = value.split(/\s*=\s*/g);
			options[value[0]] = value[1];
		},
	);
	return options;
}

function getRemoteFileInfo (url) {
	if (!url.href) {
		url = new URL(url);
	}
	// if (/(^|\.)firepx\.com$/i.test(url.hostname)) {
	// 	return request(url.href).then(res => {
	// 		const body = res.body.string();
	// 		const link = body.match(/<a\s*\bhref="(.*?)".*密码\s*[::]\s*(\w+)/);
	// 		if (link) {
	// 			const newUrl = new URL(link[1], url.href);
	// 			if (newUrl.hostname !== url.hostname) {
	// 				newUrl.hash = "#pwd=" + link[2];
	// 				return toDownOpts(newUrl);
	// 			}
	// 		}
	// 		return openWeb(url);
	// 	});
	// } else
	if (url.hostname === "github.com") {
		console.log("正在解析Github API", url.href);
		return parseGithubRelease(url);
	} else if (/^(\w+\.)*32r(\.\w+)*$/i.test(url.hostname)) {
		console.log("正在解析网页", url.href);
		return parse32r(url);
	} else if (/^(\w+\.)*lanzou\w*(\.\w+)*$/.test(url.hostname)) {
		console.log("正在解析网盘", url.href);
		return lanzou(url, getOptsFromUrl(url)).then(getVersionForFile);
	} else if (/^(\w+\.)*123pan(\.\w+)*$/.test(url.hostname)) {
		console.log("正在解析网盘", url.href);
		return _123pan(url, getOptsFromUrl(url)).then(getVersionForFile);
	}
}

// function createFnCache (fn, cache = {}) {
// 	return (url) => {
// 		const key = encodeURI(url);
// 		const result = cache[key];
// 		if (result) {
// 			return Promise.resolve(result);
// 		}
// 		return fn(url).then(result => {
// 			cache[key] = result;
// 			return result;
// 		});
// 	};
// }

// module.exports = createFnCache(getRemoteFileInfo);
module.exports = getRemoteFileInfo;

if (DEBUG) {
	require("./test/getRemoteFileInfo")(module.exports);
}


================================================
FILE: src/miui_cleaner_app/index.js
================================================
console.setGlobalLogConfig({
	file: files.join(
		context.getExternalFilesDir("logs"),
		"log.txt",
	),
});

delete global.Promise;
require("core-js/modules/web.url.js");
require("core-js/modules/web.url-search-params");
require("core-js/modules/es.promise");
require("core-js/modules/es.promise.any");
require("core-js/modules/es.promise.finally");

const singleChoice = require("./singleChoice");

const mainActions = [
	require("./sysAppRm"),
	require("./downApp"),
	require("./offAppAd"),
	require("./appManager"),
	require("./recycle"),
	require("./support"),
	{
		name: "控制台",
		summary: "查看运行日志",
		fn: () => {
			setTimeout(() => {
				const settings = require("./settings");
				console.log(
					[
						`SDK: ${device.sdkInt}`,
						`Android: ${device.release}`,
						`MIUI: ${device.incremental}`,
						"settings:",
					].concat(
						settings.keys().map(key => `\t${key}: ${JSON.stringify(settings[key])}`),
					).join("\n"),
				);
			}, 0);
			return app.startActivity("console");
		},
		icon: "./res/drawable/ic_log.png",
	},
	{
		name: "退出",
		summary: "再见",
		icon: "./res/drawable/ic_exit.png",
		fn: () => {
			if (DEBUG) {
				ui.finish();
			} else {
				java.lang.System.exit(0);
			}
		},
	},
];

function mainMenu () {
	singleChoice({
		title: "请选择你的操作",
		itemList: mainActions,
	});
}

function regBack () {
	ui.emitter.removeAllListeners("back_pressed");
	ui.emitter.once("back_pressed", (e) => {
		e.consumed = true;
		mainMenu();
	});
}

(() => {
	if (DEBUG) {
		const thisPackageName = context.getPackageName();
		console.log("DEBUG in", thisPackageName);
		if (thisPackageName === "com.github.gucong3000.miui.cleaner") {
			if (!engines.myEngine().source.toString().startsWith("/")) {
				engines.execScriptFile(`/storage/emulated/0/脚本/${thisPackageName}/main.js`);
				return;
			}
		} else {
			// app.launch("com.github.gucong3000.miui.cleaner");
		}
	}
	mainMenu();
	require("./update");
})();
module.exports = regBack;
module.exports.mainMenu = mainMenu;


================================================
FILE: src/miui_cleaner_app/instApk.js
================================================
/**
 * 调用 APK 安装界面
 */
function instApk (apk) {
	try {
		app.viewFile(apk);
	} catch (ex) {
		return false;
	}
	return true;
}

module.exports = instApk;


================================================
FILE: src/miui_cleaner_app/lanzou.js
================================================
const jsonParse = require("json5/lib/parse");
const fetch = global.fetch || require("./fetch");
// const webView = global.ui && require("./webView");
let userAgent;
try {
	userAgent = android.webkit.WebSettings.getDefaultUserAgent(context);
} catch (ex) {
	userAgent = "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";
}
const storage = {};
const realFileCache = {};

async function getFileInfoFromUrl (url, options) {
	url = parseUrl(url);
	const res = await fetch(
		url.href,
		{
			headers: {
				"accept": "text/html",
				"user-agent": userAgent,
				"x-forwarded-for": getRandomIP(),
				"client-ip": getRandomIP(),
			},
		},
	);
	await checkResponse(res);
	const html = await res.text();
	let fileName = html.match(/\bclass="(md|appname)"[^<>]*>\s*(.+?)\s*<\/?\w+/);
	fileName = fileName && fileName[2];
	let size = html.match(/\b(id|class)="(submit|mtt)"[^<>]*>.*?\(\s*(\d+.*?)\s*\)\s*<\/?\w+/);
	size = size && size[3];
	let lastModified = html.match(/\bclass="appinfotime"[^<>]*>\s*(.+?)\s*<\/?\w+/);
	if (lastModified) {
		lastModified = lastModified[1];
	} else {
		lastModified = html.match(/\bclass="mt2"[^<>]*>\s*(时间.*?)?\s*<\/\w+>\s*(.*?)\s*</);
		lastModified = lastModified && lastModified[2];
	}
	const fileInfo = {
		fileName,
		size,
		lastModified,
	};

	const that = Object.assign({}, options);
	function getVal (code) {
		if (code in that) {
			return that[code];
		}
		try {
			return jsonParse(code);
		} catch (ex) {
			// console.error(ex);
		}
	}
	for await (const script of html.match(/<script\s+type="text\/javascript">[\s\S]+?<\/script>/ig).map(
		script => script.slice(31, -9).trim(),
	)) {
		const hostname = script.match(/(['"])(https?:\/\/(\w+\.)*lanzoug\w*(\.\w+)+\/file\/?)\1/i);
		const pathname = script.match(/(['"])(\?\S{256,})\1/);
		if (hostname && pathname) {
			console.log("发现无密码的单文件:", url.href);
			fileInfo.url = new URL(pathname[2], hostname[2]).href;
			break;
		}
		const ajaxCode = script.match(/\$.ajax\({([\s\S]+?)}\);?/);
		if (!ajaxCode) {
			continue;
		}
		script.slice(0, ajaxCode.index).split(/\r?\n/).forEach(line => {
			line = line.trim();
			if (line.startsWith("//")) {
				return;
			}
			line = line.match(/^((var|let|const)\s+)?(\w+)\s*=\s*(.*?)(;|$)/);
			if (line) {
				that[line[3]] = getVal(line[4]) || that[line[3]];
			}
		});
		const ajaxConfig = {};
		let inData;
		const data = {};
		ajaxCode[1].replace(/\s*(}\s*,?)\s*/g, "\n$1\n").replace(/(,|{)\s*/g, "$1\n").split(/\r?\n/).forEach(line => {
			line = line.trim();
			if (line.startsWith("//")) {
				return false;
			} else if (/^}\s*,?$/.test(line)) {
				inData = false;
			} else if ((line = line.match(/^(['"])?(\S+)\1\s*:\s*(.+?)\s*,?$/))) {
				const key = line[2];
				const value = line[3];
				if (key === "data") {
					inData = true;
					ajaxConfig.data = data;
				} else if (inData) {
					data[key] = getVal(value);
				} else {
					ajaxConfig[key] = getVal(value);
				}
			}
		});
		if (ajaxConfig.url && ajaxConfig.data) {
			// 有密码的单文件或者文件夹
			return getFileInfoByAjax(url, fileInfo, ajaxConfig, options);
		}
	}
	fileInfo.options = options;
	return parseFileInfo(fileInfo, url);
}

function parseFileInfo (fileInfo, url) {
	if (typeof fileInfo.size === "string") {
		const size = fileInfo.size.match(/^([+-\d.]+)\s*(\w+)?$/);
		const BIBYTE_UNITS = "BKMGTPEZY";
		const number = Number.parseFloat(size[1]);
		const exponent = BIBYTE_UNITS.indexOf(size[2][0].toUpperCase());
		if (Number.isNaN(number) || exponent < 0) {
			delete fileInfo.size;
		} else {
			fileInfo.size = Math.round(number * Math.pow(1024, exponent));
		}
	}
	if (typeof fileInfo.lastModified === "string") {
		let lastTime = fileInfo.lastModified.match(/^(\d+)\s*(.*)前$/);
		if (lastTime) {
			fileInfo.lastModified = Date.now() - (+lastTime[1] * 1000 * ({
				天: 60 * 60 * 24,
				小时: 60 * 60,
				分钟: 60,
				分: 60,
				秒钟: 1,
				秒: 1,
			}[lastTime[2]]));
		} else if ((lastTime = fileInfo.lastModified.match(/^(.*)天\s*((?:\d+:+)*\d+)/))) {
			let time = lastTime[2].split(":");
			time = Date.parse(new Date().toLocaleDateString() + ` ${time[0]}:${time[1]}:${time[2] || 0} GMT+0800`);
			time -= ({
				昨: 1,
				前: 2,
			}[lastTime[1]] || 0) * 60 * 60 * 24 * 1000;
			fileInfo.lastModified = time;
		} else {
			fileInfo.lastModified = Date.parse(fileInfo.lastModified) || fileInfo.lastModified;
		}
	}
	if (fileInfo.id) {
		fileInfo.referer = new URL("/tp/" + fileInfo.id, url.origin).href;
	} else {
		const id = getIdByUrl(url);
		fileInfo.id = id;
		fileInfo.referer = `${url.origin}/tp/${id}`;
	}

	if (fileInfo.url) {
		fileInfo.expires = Date.now() + 1800000;
	}
	storage[fileInfo.id] = fileInfo;
	return fileInfo;
}

function getIdByUrl (url) {
	return url.pathname.replace(/^(\/+tp)*\/+/, "") + url.search;
}

async function getFileInfoByAjax (url, fileInfo, ajaxConfig, options) {
	const res = await fetch(
		new URL(ajaxConfig.url, url.origin).href,
		{
			headers: {
				"content-type": "application/x-www-form-urlencoded",
				"accept": "application/json",
				"x-requested-with": "XMLHttpRequest",
				"user-agent": userAgent,
				"referer": url.href,
				"x-forwarded-for": getRandomIP(),
				"client-ip": getRandomIP(),
			},
			body: new URLSearchParams(ajaxConfig.data).toString(),
			method: (ajaxConfig.type || "POST").toUpperCase(),
		},
	);
	await checkResponse(res);
	let data = await res.json();
	if (Array.isArray(data.text)) {
		console.log("发现文件夹:", url.href);
		data = data.text.map(file => parseFileInfo({
			fileName: file.name_all,
			size: file.size,
			lastModified: file.time,
			id: file.id,
		}, url));
		storage[getIdByUrl(url)] = data.map(dada => dada.id);
	} else if (data.url && data.inf && data.dom) {
		console.log("发现需要密码的单文件:", url.href);
		fileInfo.fileName = data.inf;
		fileInfo.url = new URL(data.url, new URL("/file/", data.dom)).href;
		fileInfo.options = options;
		data = parseFileInfo(fileInfo, url);
	} else {
		// 除非网络异常,否则大概里是密码错了
		throw new Error(data.info || data);
	}
	return data;
}

// function reqFileInfoByWeb (url) {
// 	const web = webView({
// 		url: url.href,
// 		userAgent: uaMobile,
// 	});
// 	return web.ready(
// 		() => document.documentElement.innerHTML,
// 	).then(html => getFileInfo(url, html, (ajaxInfo) => web.evaluate(
// 		ajaxInfo => fetch(ajaxInfo.url, {
// 			headers: {
// 				"accept": "application/json",
// 				"content-type": "application/x-www-form-urlencoded",
// 				"x-requested-with": "XMLHttpRequest",
// 			},
// 			body: new URLSearchParams(ajaxInfo.data).toString(),
// 			method: ajaxInfo.type,
// 		}).then(response => response.json()),
// 		[ajaxInfo],
// 	), "WebView: ")).finally(() => web.cancel);
// };

function getFileInfo (url, options) {
	url = parseUrl(url);
	const id = getIdByUrl(url);
	const fileInfo = storage[id];
	if (fileInfo) {
		if (Array.isArray(fileInfo)) {
			return getFileInfoFromUrl(url, options).catch(ex => {
				return fileInfo.map(id => storage[id]);
			});
		} else {
			if (!options.reqUrl || (fileInfo.url && fileInfo.expires > Date.now())) {
				return Promise.resolve(fileInfo);
			}
			if (fileInfo.options) {
				options = Object.assign(fileInfo.options, options);
			}
		}
	}
	return getFileInfoFromUrl(url, options);
}
async function getRealFile (fileInfo, redirect) {
	const cache = realFileCache[fileInfo.id];
	const now = Date.now();
	if (cache) {
		if (cache.expires && now >= cache.expires) {
			// console.log("命中缓存,但已过期:", fileInfo);
			delete realFileCache[fileInfo.id];
		} else {
			// console.log("命中缓存:", fileInfo);
			return Promise.resolve(cache);
		}
	}
	if (fileInfo.url && fileInfo.expires > now) {
		const res = await fetch(fileInfo.url, {
			method: "HEAD",
			redirect: redirect ? "follow" : "manual",
			// redirect: "error",
			headers: {
				"Accept": "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8",
				"Accept-Encoding": "gzip, deflate, br",
				"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",
				// "Cache-Control": "no-cache",
				// "Connection": "keep-alive",
				// "Pragma": "no-cache",
				"Upgrade-Insecure-Requests": 1,
				"Cookie": "down_ip=1",
				"User-Agent": userAgent,
				"referer": fileInfo.url,
				"x-forwarded-for": getRandomIP(),
				"client-ip": getRandomIP(),
			},
			referrer: fileInfo.url,
		});
		const location = res.headers.get("location");
		if (location) {
			fileInfo.location = location;
		} else {
			await checkResponse(res);
		}
		let disposition = res.headers.get("content-disposition");
		if (disposition) {
			disposition = disposition && disposition.match(/(^|;)\s*filename\*?\s*=\s*(UTF-8(''|\/))?(.*?)(;|\s|$)/i);
			disposition = disposition && decodeURI(disposition[4]);
			fileInfo.fileName = disposition || fileInfo.fileName;
			fileInfo.location = location || res.url;
			const expires = res.headers.get("expires");
			if (expires) {
				fileInfo.expires = Date.parse(expires);
			}
			const type = res.headers.get("content-type");
			if (type && !/^application\/octet-stream$/i.test(type)) {
				fileInfo.type = type;
			}
			const size = +res.headers.get("content-length");
			if (size) {
				fileInfo.size = size;
			}
			const lastModified = res.headers.get("last-modified");
			if (lastModified) {
				fileInfo.lastModified = Date.parse(lastModified);
			}
		}
		if (fileInfo.location) {
			realFileCache[fileInfo.id] = fileInfo;
			return fileInfo;
		} else {
			throw new Error("unknow error: \nat " + res.url);
		}
	}
	if (fileInfo.referer) {
		return getRealFile(await getFileInfo(fileInfo.referer, {
			reqUrl: true,
		}));
	}
}

async function checkResponse (res) {
	if (!res.ok) {
		throw new Error(`status: ${res.status}\nmessage: ${res.message || JSON.stringify(await res.text())}\n    at ${res.url}`);
	}
}

function parseUrl (url) {
	if (!url.href) {
		url = new URL(url);
	}
	return url;
}

function getRandomIP () {
	let ip = [218, 66, 60, 202, 204, 59, 61, 222, 221, 62, 63, 64, 122, 211];
	ip = [
		ip[Math.floor(Math.random() * ip.length)],
	];
	for (let i = 0; i < 3; i++) {
		ip.push(Math.floor(Math.random() * 256));
	}
	return ip.join(".");
}

class FileInfo {
	constructor (data) {
		Object.assign(this, data);
	}

	async getLocation (redirect) {
		const data = await getRealFile(this, redirect);
		Object.assign(this, data);
		return this;
	}
}

async function parse (url, options) {
	const data = await getFileInfo(url, options || {});
	if (Array.isArray(data)) {
		return data.map(data => new FileInfo(data));
	}
	return new FileInfo(data);
}
module.exports = parse;
// (async () => {
// 	let file = await parse("https://423down.lanzouv.com/tp/iKBGf0hcsq5e");
// 	console.log(file.url);
// 	file = await parse("https://423down.lanzouv.com/tp/iKBGf0hcsq5e");
// 	console.log(file.url);
// })();


================================================
FILE: src/miui_cleaner_app/multiChoice.js
================================================
const emitItemShowEvent = require("./emitItemShowEvent");
const project = require("./project.json");

function multiChoice (
	{
		itemList = [],
		title = "多选",
		icon = "./res/drawable/ic_android.png",
		checked = true,
	},
) {
	icon = /^\w+:\/\//.test(icon) ? icon : ("file://" + files.path(icon));

	ui.layout(`
		<frame>
			<vertical>
				<appbar>
					<toolbar id="toolbar" title="${project.name}" subtitle="${title}" />
				</appbar>
				<list id="itemList">
					<card w="*" h="auto" margin="0 0 0 10" foreground="?selectableItemBackground">
						<horizontal gravity="center_vertical">
							<img id="icon" h="48" w="48" src="{{this.icon || '${icon}'}}" margin="10 10 0 10" />
							<vertical h="auto" layout_weight="1" margin="10 0">
								<text text="{{this.displayName || this.appName || this.name}}" textColor="#333333" textSize="16sp" maxLines="1" />
								<text text="{{this.summary}}" textColor="#999999" textSize="14sp" maxLines="1" />
							</vertical>
							<checkbox id="checkbox" checked="{{this.checked == null ? ${checked} : this.checked}}"  marginRight="10" />
						</horizontal>
					</card>
				</list>
			</vertical>
			<fab id="done" w="auto" h="auto" src="@drawable/ic_done_white_48dp" margin="0 32" layout_gravity="bottom|center" tint="#ffffff" />
		</frame>
	`);

	emitItemShowEvent(ui.itemList, icon);

	ui.itemList.on("item_bind", function (itemView, itemHolder) {
		itemView.checkbox.on("check", function (checked) {
			const item = itemHolder.item;
			item.checked = checked;
			bindDoneBtnVisibility(checked);
		});
	});

	ui.itemList.on("item_click", function (item, i, itemView, listView) {
		itemView.checkbox.checked = !itemView.checkbox.checked;
	});

	function bindDoneBtnVisibility (checked) {
		if (checked || itemList.some(item => item.checked)) {
			ui.done.show();
		} else {
			ui.done.hide();
		}
	}

	global.activity.setSupportActionBar(ui.toolbar);
	itemList.forEach((item) => {
		if (item.icon && !/^\w+:\/\//.test(item.icon)) {
			item.icon = "file://" + files.path(item.icon);
		}
	});
	bindDoneBtnVisibility();
	ui.itemList.setDataSource(itemList);

	return new Promise((resolve) => {
		ui.done.on("click", () => {
			resolve(itemList.filter(item => item.checked));
		});
	});
}

module.exports = multiChoice;


================================================
FILE: src/miui_cleaner_app/offAppAd.js
================================================
const getApplicationInfo = require("./getApplicationInfo");
const multiChoice = require("./multiChoice");
const serviceMgr = require("./serviceMgr");
const settings = require("./settings");
const settingsPackageName = "com.android.settings";

const cleanerList = [
	{
		name: "先清理后台应用",
		summary: "推荐,增加操作成功率",
		action: "clearAnim",
	},
	{
		// 设置→小米帐号→关于小米帐号→系统广告→“关闭系统工具广告”
		summary: "设置→小米帐号→关闭系统工具广告",
		packageName: "com.xiaomi.account",
		action: ".ui.AccountSettingsActivity",
		settings: ["passportAD"],
	},
	{
		displayName: "系统安全",
		summary: "系统安全→网页拉活应用、日志上传等",
		packageName: settingsPackageName,
		action: "ACTION_SECURITY_SETTINGS",
		settings: [
			// 网页链接调用服务
			"httpInvokeApp",
			// 加入“用户体验改进计划”
			"uploadLog",
			// 自动发送诊断数据
			"uploadDebugLog",
		],
	},
	{
		// `广告服务` 位于 `安全` 的子页面
		displayName: "广告服务",
		summary: "系统安全→广告服务→个性化广告推荐",
		packageName: settingsPackageName,
		action: ".ad.AdServiceSettings",
		settings: ["personalizedAD"],
	},
	{
		// 应用商店
		packageName: "com.xiaomi.market",
		summary: "新手帮助、个性化服务、福利活动等",
		// activity: ".ui.MarketTabActivity",
	},
	{
		// 应用包管理组件
		summary: "安装监控、纯净模式、安全检查",
		packageName: "com.miui.packageinstaller",
		// action: "com.android.browser.BrowserActivity",
	},
	{
		// 下载管理程序
		packageName: "com.android.providers.downloads.ui",
		summary: "资源推荐、热榜推荐",
		action: ".activity.DownloadSettingActivity",
	},
	{
		// 手机管家→设置页
		summary: "在线服务、个性化推荐等",
		packageName: "com.miui.securitycenter",
		action: "com.miui.securityscan.ui.settings.SettingsActivity",
	},
	{
		// 手机管家→应用管理→设置页
		displayName: "应用管理",
		summary: "资源推荐",
		packageName: "com.miui.securitycenter",
		action: "com.miui.appmanager.AppManagerMainActivity",
	},
	{
		// 手机管家→垃圾清理→设置页
		packageName: "com.miui.cleanmaster",
		summary: "推荐内容、扫描内存",
		action: "com.miui.optimizecenter.settings.SettingsActivity",
	},
	{
		// 日历
		packageName: "com.android.calendar",
		summary: "天气服务、内容推广",
		action: ".settings.CalendarActionbarSettingsActivity",
	},
	{
		// 时钟
		packageName: "com.android.deskclock",
		summary: "生活早报",
		action: ".settings.SettingsActivity",
	},
	{
		// 小米社区
		packageName: "com.xiaomi.vipaccount",
		summary: "详情页相似推荐、个性化广告、信息流推荐",
		action: ".ui.home.page.HomeFrameActivity",
	},
	{
		// 小米天气
		packageName: "com.miui.weather2",
		summary: "天气视频卡片,内容推广",
		action: ".ActivityWeatherMain",
	},
	{
		// 小米视频
		packageName: "com.miui.video",
		summary: "内容推荐、广告推荐、在线服务、消息",
		action: ".feature.mine.setting.SettingActivity",
	},
	{
		// 小爱语音
		packageName: "com.miui.voiceassist",
		summary: "小爱技巧推送、个性化推荐、广告推荐等",
		action: "com.xiaomi.voiceassistant.settings.MiuiVoiceSettingActivity",
	},
	{
		// 搜索
		packageName: "com.android.quicksearchbox",
		summary: "热搜榜单、搜索精选等,开:广告过滤",
		action: ".preferences.SearchSettingsPreferenceActivity",
	},
	{
		// 小米浏览器国际版
		packageName: "com.mi.globalbrowser",
		summary: "宫格位、消息等,开:广告过滤、简洁版首页",
		action: "com.android.browser.BrowserActivity",
	},
	{
		// 小米浏览器国内版
		summary: "广告、消息等,开:广告过滤、简洁版首页",
		packageName: "com.android.browser",
		action: "com.android.browser.BrowserActivity",
	},
];

function getCleanerList () {
	return cleanerList.filter((cleaner) => {
		// 通过系统设置判断是否已经关闭该板块的广告,如果已经全部关闭,则不显示该模块
		if (cleaner.settings && cleaner.settings.every(key => settings[key] === false)) {
			cleaner.checked = false;
		}
		return !cleaner.packageName || getApplicationInfo(cleaner);
	});
}

function offAppAd () {
	multiChoice({
		title: "请选择要关闭广告的APP",
		itemList: getCleanerList(),
		checked: true,
	}).then(cleanerList => (
		settings.set(
			"accessibilityServiceEnabled",
			true,
			"操作其他APP的广告开关",
		).then(accessibilityServiceEnabled => {
			if (!accessibilityServiceEnabled) {
				return accessibilityServiceEnabled;
			}
			return settings.set(
				"drawOverlays",
				true,
			).then(
				() => serviceMgr(cleanerList),
			);
		})
	)).then(
		offAppAd,
	).catch(console.error);
	require("./index")();
};

module.exports = {
	name: "关闭各APP广告",
	summary: "自动查询并关闭各APP中的广告",
	icon: "./res/drawable/ic_no_ad.png",
	fn: offAppAd,
};


================================================
FILE: src/miui_cleaner_app/project.json
================================================
{
	"icon": "res/drawable/ic_launcher.png",
	"launchConfig": {
		"displaySplash": false,
		"hideLogs": false,
		"permissions": [],
		"serviceDesc": "自动操作(关闭 MIUI 广告)所需,若关闭则只能执行其他功能。",
		"splashIcon": "res/drawable/ic_launcher.png",
		"splashText": "MIUI广告清理工具",
		"stableMode": false,
		"volumeUpcontrol": true
	},
	"main": "main.js",
	"name": "MiuiCleaner",
	"packageName": "com.github.gucong3000.miui.cleaner",
	"scripts": {},
	"useFeatures": [],
	"versionCode": 8,
	"versionName": "2023.4.23.8"
}


================================================
FILE: src/miui_cleaner_app/recycle.js
================================================
const singleChoice = require("./singleChoice");
const serviceMgr = require("./serviceMgr");
const settings = require("./settings");
const instApk = require("./instApk");
const appDesc = require("./appDesc");

const marketPackageName = "com.xiaomi.market";

function launchMarket () {
	return settings.set(
		"accessibilityServiceEnabled",
		true,
		"自动打开“应用商店”的“系统应用管理”",
	).then(accessibilityServiceEnabled => {
		if (accessibilityServiceEnabled) {
			return serviceMgr({
				packageName: marketPackageName,
				action: ".ui.CommonWebActivity",
				name: "系统应用管理",
			});
		} else {
			return app.launchPackage(marketPackageName);
		}
	});
}

function getInstalledPackages () {
	const pm = context.getPackageManager();
	return Array.from(
		pm.getInstalledApplications(android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES),
	).map(appInfo => {
		if (app.getAppName(appInfo.packageName)) {
			return null;
		}
		const summary = appDesc[appInfo.packageName] || "";
		let appName = pm.getApplicationLabel(appInfo).toString();
		if (appName === appInfo.packageName) {
			appName = null;
		}
		return {
			summary: appName ? summary : appInfo.packageName,
			packageName: appInfo.packageName,
			loadIcon: appInfo.icon && (() => appInfo.loadIcon(pm)),
			apk: appInfo.sourceDir,
			name: appName || summary,
			appName,
		};
	}).filter(Boolean).sort((app1, app2) => (
		app1.packageName.localeCompare(app2.packageName)
	)).concat({
		name: "其他",
		loadIcon: () => pm.getApplicationInfo(marketPackageName, 0).loadIcon(pm),
		fn: launchMarket,
		summary: "去应用商店下载其他小米官方应用",
	});
}
let requestInstallPackages;
function recycle () {
	singleChoice({
		title: "请选择要恢复的应用",
		itemList: {
			then: (...args) => Promise.resolve(getInstalledPackages()).then(...args),
		},
		fn: function (appInfo) {
			if (!requestInstallPackages) {
				requestInstallPackages = settings.set("requestInstallPackages", true, "打开应用安装权限");
			}
			return requestInstallPackages.then(() => {
				instApk(appInfo.apk);
				console.log("正在恢复:", appInfo);
			});
		},
	});
	require("./index")();
}

module.exports = {
	name: "回收站",
	summary: "恢复已卸载的APP",
	icon: "./res/drawable/ic_recovery.png",
	fn: recycle,
};


================================================
FILE: src/miui_cleaner_app/serviceMgr.js
================================================
let scriptEngine;
function sleep (time) {
	return new Promise((resolve) => setTimeout(resolve, time));
}

function waitForEngineStart () {
	return sleep(0x50).then(() => {
		if (scriptEngine.engine) {
			return scriptEngine.engine;
		} else {
			return waitForEngineStart();
		}
	});
}

function waitForEngineStop () {
	return sleep(0x50).then(() => {
		if (scriptEngine && scriptEngine.engine && !scriptEngine.engine.destroyed) {
			return waitForEngineStop();
		}
	});
}

function getEngine (task) {
	return Promise.resolve().then(() => {
		if (!scriptEngine || !scriptEngine.engine || scriptEngine.engine.destroyed) {
			scriptEngine = engines.execScriptFile("./services.js");
		}
	});
}

// function runAutoActions (cleanerList) {
// 	(
// 		cleanerList.length
// 			? serviceMgr(cleanerList.shift())
// 			: Promise.resolve()
// 	).then(() => {
// 		return runAutoActions(cleanerList);
// 	});
// }

function parseTaskInfo (taskInfo) {
	return {
		packageName: taskInfo.packageName,
		action: taskInfo.action,
		name: taskInfo.name || taskInfo.appName,
		checked: taskInfo.checked,
	};
}

function start (taskList) {
	files.write(
		files.join(
			context.getExternalFilesDir(null),
			"taskList.json",
		),
		JSON.stringify(
			Array.isArray(taskList)
				? taskList.map(parseTaskInfo)
				: parseTaskInfo(taskList),
		),
	);
	return getEngine().then(waitForEngineStart).then(waitForEngineStop);
}

module.exports = start;


================================================
FILE: src/miui_cleaner_app/services.js
================================================
const findClickableParent = require("./findClickableParent");
const servicesTest = DEBUG && require("./test/services");

const thisPackageName = context.getPackageName();
const securityCenterPackageName = "com.miui.securitycenter";
const settingsPackageName = "com.android.settings";
const marketPackageName = "com.xiaomi.market";

function delay (time) {
	sleep(time || 0x200);
}

function findByClassName (reg) {
	return selector().filter(
		uiObject => reg.test(uiObject.className()),
	);
}

/**
 * 向上查找 UiObject 的父节点,找到可滚动的祖先节点
 * @param {UiObject} node 节点
 * @returns
 */
function findScrollableParent (node) {
	return !node || node.scrollable() ? node : findScrollableParent(node.parent());
}

function isFrameLayout (linear) {
	return /\.FrameLayout$/.test(linear.className());
}

function getDefaultValue (value, defaultValu) {
	return value == null
		? defaultValu
		: value
	;
}

// 打开USB调试、关闭下载管理程序资源推荐等,会弹出的二次确认,自动点击
function skipConfirmPopup () {
	let btn;
	let dialog;
	do {
		const checkBox = selector().id("check_box").packageName("com.miui.securitycenter").findOnce();
		if (checkBox && checkBox.checkable() && checkBox.clickable() && !checkBox.checked()) {
			checkBox.click();
		}
		btn = selector().filter(btn =>
			/\b(ok|accept|intercept_warn_allow)$/.test(btn.id()) ||
			/^(确[认定](关闭)?|删除)$/.test(btn.text()),
		).findOne(0x200);
		if (btn) {
			const time = /\((\d+)\)$/.exec(btn.text());
			if (time) {
				delay(time[1] * 1000);
			} else {
				clickButton(btn);
			}
		}
		dialog = selector().filter(dialog =>
			/\bdialog(_\w+)*$/.test(dialog.id()),
		).findOnce();
		if (dialog) {
			delay();
		}
	} while (btn || dialog);
}

function walkListView (options = {}) {
	const adCheckBoxList = [];
	const adLinearList = [];
	let inNotifyMgr;
	const listView = findByClassName(/\.(RecyclerView|ListView)$/).filter(view => {
		const packageName = view.packageName();
		return options.packageName === packageName || (inNotifyMgr = packageName === settingsPackageName);
	}).findOne();
	delay();
	const regSwitchOn = inNotifyMgr
		? null
		: getDefaultValue(
			options.regSwitchOn,
			/^(仅在(WLAN|Wi-?Fi)下.*?|.*?广告(拦截|过滤)|去.*?广告)$/i,
		);
	const regSwitchOff = inNotifyMgr
		? /^允许通知$/
		: getDefaultValue(
			options.regSwitchOff,
			/猜你喜欢|天气视频|桌面搜索|用户体验|在线(内容)?服务|个性化|消息|广告|热[榜门]|推[荐广]|宫格[栏位]|技巧|热点|新闻|[资快]讯|推送(服务|通知)|通知栏|扫描内存|福利活动|显示天气服务/,
		);
	const regSubPage = inNotifyMgr
		? null
		: getDefaultValue(
			options.regSubPage,
			/^(高级|其他|消息[与和]?推送|.*?广告(拦截|过滤)|去.*?广告|(.*?((消息)?通知|隐私|功能|个性化|信息流|用户体验|[主首]页|闹钟)(设置|管理|服务|计划|[防保]护)))$/,
		);
	if (!options.handle) {
		options.handle = {};
	};
	const result = {};
	if (options.walk) {
		Object.assign(
			result,
			options.walk(listView, options),
		);
	}
	let linearList = listView.children();
	linearList = Array.from(linearList).filter(linear => linear && linear.clickable() && !isFrameLayout(linear));

	linearList.forEach(linear => {
		const textViewList = Array.from(linear.find(findByClassName(/\.TextView$/)));
		if (!textViewList.length) {
			return;
		}
		const textList = textViewList.map(textView => textView.text());

		// 判断按钮是否处置过了
		const key = textList.join();
		if (key in options.handle) {
			return;
		}
		options.handle[key] = null;
		const checkBox = linear.findOne(selector().checkable(true));
		if (checkBox && !checkBox.enabled()) {
			return;
		}
		textList.some((text) => {
			// 如果 linear 是复选框(CheckBox、Switch)
			if (checkBox) {
				let expect;
				if (regSwitchOn && regSwitchOn.test(text)) {
					expect = true;
				} else if (regSwitchOff && regSwitchOff.test(text)) {
					expect = false;
				} else {
					// continue textList的循环体
					return false;
				}
				// 添加至 adCheckBoxList 中暂存
				adCheckBoxList.push({
					linear,
					checkBox,
					expect,
					text: textList[0],
				});
				// break textList的循环体
				return true;
			} else if (regSubPage && regSubPage.test(text)) {
				// 添加至 adLinearList 中暂存
				adLinearList.push({
					linear,
					text: textList[0],
				});
				// break textList的循环体
				return true;
			}
			// continue textList的循环体
			return false;
		});
	});

	// 统一处置广告相关开关
	adCheckBoxList.forEach(adCheckBox => {
		if (!adCheckBox.checkBox.enabled()) {
			return;
		}
		// CheckBox的checked属性与预期不符,
		if (adCheckBox.expect !== adCheckBox.checkBox.checked()) {
			// click一下
			adCheckBox.linear.click();
			skipConfirmPopup();
			console.log(`已${adCheckBox.expect ? "打开" : "关闭"}“${adCheckBox.text}”`);
			result[adCheckBox.text] = adCheckBox.expect;
		} else {
			console.log(`“${adCheckBox.text}”已处于${adCheckBox.expect ? "打开" : "关闭"}状态`);
		}
		options.handle[adCheckBox.text] = adCheckBox.expect;
	});

	adLinearList.forEach(adLinear => {
		// 统一处置广告相关子页面
		if (options.handle[adLinear.text]) {
			return;
		}
		adLinear.linear.click();
		console.log(`已点击“${adLinear.text}”`);
		delay();
		const subResult = walkListView({
			...options,
			handle: {},
		});
		options.handle[adLinear.text] = subResult.handle;
		delete subResult.handle;
		result[adLinear.text] = subResult;
	});

	if (
		// 判断页面是否需要下滚
		listView.scrollable() &&
		!(
			options.disableScroll ||
			inNotifyMgr ||
			(options.max && Object.keys(options.handle).filter(key => options.handle[key]).length >= options.max)
		) &&
		listView.scrollForward()
	) {
		console.log("页面滑动");
		delay();
		Object.assign(result, walkListView(options));
	} else {
		console.log("返回");
		back();
		delay();
	}
	result.handle = options.handle;
	return result;
}

function getDelayTimeByPackageName (packageName) {
	if (!/\bbrowser\b/.test(packageName) && (/^com\.(android|(xiao)?mi(ui)?)\./.test(packageName) || /inputmethod/.test(packageName))) {
		// 系统应用等500毫秒
		return 0x200;
	}
	// 第三方应用等2秒
	// console.log("任务两秒后开始");
	return 0x800;
}

/**
 * 启动应用
 * @param {string} packageName 包名
 * @param {string} className Activity 的类名
 * @returns {bool|null} 是否成功启动
 */
function startPkg ({
	packageName,
	activity,
	name,
}) {
	const result = app.launchPackage(packageName);
	if (result) {
		toastLog(`正在启动“${name}”,“${packageName}”`);
		const delayTime = getDelayTimeByPackageName(packageName);
		if (activity && activity.startsWith(packageName)) {
			waitForActivity(activity);
		} else {
			waitForPackage(packageName);
			delay(delayTime);
		}
		delay(delayTime);
	} else {
		console.error(`未安装“${packageName}”`);
	}
	return result;
}

/**
 * 启动 Activity
 * @param {string} packageName 包名
 * @param {string} className Activity 的类名
 * @returns {bool|null} 是否成功启动
 */
function startAct ({
	packageName,
	activity,
	name,
}) {
	const className = activity.replace(/^(?=\.)/, packageName);
	const opts = {
		packageName,
		className,
	};
	let result;
	try {
		result = app.startActivity(opts);
	} catch (ex) {
		if (/Permission Denial/.test(ex.message)) {
			console.error(ex);
			return startPkg({
				packageName,
				activity: className,
			});
		} else {
			console.error(ex);
		}
		return false;
	}

	toastLog(`正在启动“${name}”,“${packageName}/${activity}”`);
	waitForActivity(className);
	delay(getDelayTimeByPackageName(packageName));
	return result || true;
}

function startIntent ({
	packageName,
	intent,
	name,
}) {
	intent = android.provider.Settings[intent];
	app.startActivity(
		/\bMANAGE_/.test(intent)
			? {
				action: intent,
				data: "package:" + thisPackageName,
			}
			: new android.content.Intent(intent),
	);

	waitForPackage(packageName);
	toastLog(`正在启动“${name}”,“${intent}”`);
	delay();
}

function clickButton (btnLabelList, text) {
	if (!Array.isArray(btnLabelList)) {
		btnLabelList = [btnLabelList];
	}
	btnLabelList = btnLabelList.filter(btnLabel => {
		const button = findClickableParent(btnLabel);
		if (button) {
			button.click();
			text = text || btnLabel.text();
		}
		return button;
	});
	if (btnLabelList.length) {
		console.log(`已点击“${text}”`);
		delay();
		return btnLabelList;
	} else {
		return null;
	}
}

/**
 * @returns 跳过隐私、用户协议、登录页面、授权页面等
 */
function skipPopupPage () {
	const currAct = currentActivity();
	let btnText;
	if (/^com\.xiaomi\.market\..*?privacy/i.test(currAct)) {
		// 应用商店用户协议页
		btnText = "同意";
	} if (/\bAlertDialog/i.test(currAct)) {
		btnText = "确定";
	} if (/\bConfirmStart/i.test(currAct)) {
		// MIUI 允许启动其他APP
		btnText = "允许";
	} else {
		clickButton(
			// 知乎用户协议页
			// 知乎账号登录页
			selector().filter(
				btn => /^com\.zhihu\.android\b/.test(btn.packageName()) && /^(tv_agree_continue|ivBack)$/.test(btn.id()),
			).findOnce(),
		);
		clickButton(
			// 小米视频、音乐、时钟、小米浏览器国际版等MIUI APP的用户协议页
			selector().filter(
				btn => /^com\.(xiao)?mi(ui)?\./.test(btn.packageName()) && (/\b(v_enable|cta_agree|cta_positive|privacy_continue_btn)$/.test(btn.id()) || /^同意$/.test(btn.text())),
			).findOnce(),
		);
		clickButton(
			// 天气、日历、时钟、小米社区等MIUI APP的用户协议页
			selector().packageName(securityCenterPackageName).id("cta_positive").findOnce(),
		);
	}
	btnText && clickButton(selector().text(btnText).findOne());
}

/**
 * 用 UiSelector 查找之后,选取最右上角的那个
 **/
function findOneByRightTopCorner (uiSelector) {
	return Array.from(
		uiSelector.untilFind(),
	).filter(findClickableParent).sort((uiObjectA, uiObjectB) => {
		const rectA = uiObjectA.bounds();
		const rectB = uiObjectB.bounds();
		return rectA.top - rectB.top || rectB.right - rectA.right;
	})[0];
}

/**
 * 用 UiSelector 查找之后,选取最右下角的那个
 **/
function findOneByRightBottomCorner (uiObjList) {
	return uiObjList.filter(findClickableParent).sort((uiObjectA, uiObjectB) => {
		const rectA = uiObjectA.bounds();
		const rectB = uiObjectB.bounds();
		return rectB.bottom - rectA.bottom || rectB.right - rectA.right;
	})[0];
}

/**
 * 先点击“ ⋮ ” ,再在弹出菜单中点“设置”按钮
 * APP 右上角的“ ⋮ ” 各应用情况
 * 天气:ID:activity_main_more、desc:更多设置
 * 日历:ID:setting_button、desc:设置
 * 下载管理:ID:more、desc:设置
 */
function openCfgPageByPopupMenu (options) {
	return clickButton(
		findOneByRightTopCorner(
			selector().filter(
				btn => /\b(\w+_)*?(menu|more|settings?)(_\w+)*?$/.test(btn.id()) || /^(更多)?设置$/.test(btn.desc()),
			),
		),
		" ⋮ ",
	) &&
	clickButton(selector().text("设置").packageName(options.packageName).untilFind());
}

/**
 * 先点击“ ≡ ”或者“我的”,再点“⚙️”按钮
 * “≡”按钮各应用情况
 * miui浏览器(简洁模式):ID:无、desc:菜单
**/
function openCfgPageBySubPage (options) {
	const btnMineList = [];
	const btnMenuList = [];
	selector().packageName(options.packageName).filter(btn => {
		if (findScrollableParent(btn) || !findClickableParent(btn)) {
			return false;
		} else if (/^(我的|未登录|个人中心)$/.test(btn.text() || btn.desc()) || /\b(\w+_)*?(my|mine)(_\w+)*?$/.test(btn.id())) {
			btnMineList.push(btn);
		} else if (btn.desc() === "菜单") {
			btnMenuList.push(btn);
		} else {
			return false;
		}
		return true;
	}).untilFind();
	const btnMine = findOneByRightBottomCorner(btnMineList);
	const btnMenu = findOneByRightBottomCorner(btnMenuList);

	let btn;
	let btnText;
	if (btnMenu) {
		btn = btnMenu;
		btnText = " ≡ ";
	} else if (btnMine) {
		btn = btnMine;
	} else {
		return null;
	}

	return clickButton(
		btn,
		btnText || btn.text() || "我的",
	) && openCfgPage(options);
}

/**
 * 应用包安装程序:ID:setting_icon、desc:设置
 * APP “⚙️”按钮各应用情况
 * 应用商店:ID:setting_icon、desc:设置
 * 知乎:ID:setting_btn、desc:无
 * miui浏览器(默认模式):ID:无、desc:无
 * 应用包安装程序:ID:setting_icon、desc:设置、注:没有“我的”
 */
function openCfgPage (options) {
	return clickButton(
		findOneByRightTopCorner(
			selector().packageName(options.packageName).filter(
				btn => /^(更多)?设置$/.test(btn.text() || btn.desc()) || /\b(\w+_)*?settings?(_\w+)*?$/.test(btn.id()),
			),
		),
		"⚙️",
	);
}

/**
 * 调用MIUI后台清理功能
 */
function clearAnim () {
	try {
		recents();
	} catch (ex) {
		console.error(ex);
		return;
	}
	toastLog("正在清理后台应用");
	delay();
	clickButton(selector().id("clearAnimView").findOne(0x500), "X");
	delay(0x400);
}

function startTask (options) {
	options = { ...options };
	if (options.intent) {
		startIntent(options);
	} else if (options.activity) {
		startAct(options);
	} else {
		startPkg(options);
	}
	const entry = options.entry;
	if (entry) {
		entry(options);
	}
	const result = walkListView(options);

	// if (entry && entry === openCfgPageBySubPage) {
	// 	back();
	// }
	while (currentPackage() !== thisPackageName) {
		back();
		delay();
	}

	if (options.done) {
		options.done(result);
	}
	if (DEBUG && servicesTest) {
		servicesTest(options, result);
	}
	return result;
}

function switchBroHomePage (listView, options) {
	// 小米浏览器及国际版的特别控件————版式切换
	Array.from(listView.children()).some(linear => {
		if (!linear) {
			return false;
		}
		return Array.from(
			linear.find(findByClassName(/\.TextView$/)),
		).map(
			textView => textView.text(),
		).some((text, index) => {
			if (text !== "简洁版") {
				return false;
			}
			const btn = linear.children().filter(btn => btn.clickable())[index];
			if (btn) {
				btn.click();
				options.handle[text] = true;
				console.log(`“${options.appName}”首页已切换为“${text}”`);
				delay();
			}
			return true;
		});
	});
}

const browserConfig = {
	packageName: "com.android.browser",
	activity: "com.android.browser.BrowserActivity",
	entry: openCfgPageBySubPage,
	walk: switchBroHomePage,
	// done: console.log,
};

// 在“关于手机”手机界面,点击“MIUI版本” 10次
function click10 (listView, options) {
	listView = selector().packageName(settingsPackageName).scrollable(true).findOne();
	let textView;
	do {
		textView = selector().packageName(settingsPackageName).filter(
			textView => /\bcard_title$/.test(textView.id()) && /^MIUI/.test(textView.text()),
		).findOnce();
	} while (!textView && listView.scrollForward());
	if (textView) {
		const text = textView.text();
		const btnMiui = findClickableParent(textView);
		for (let index = 1; index < 0xF; index++) {
			btnMiui.click();
			console.log(`已点击“${text}”${index}次`);
			options.handle[text] = index;
		}
	}
}
// function openInstallerHelper () {
// 	const packageName = "com.miui.packageinstaller";
// 	const appName = app.getAppName(packageName);

// 	clickButton(
// 		selector().filter(btn => (
// 			appName === btn.text() && /\bandroid\b/.test(btn.packageName())
// 		)).findOnce()
// 	);
// 	clickButton(
// 		selector().packageName(packageName).text("确定").findOnce()
// 	);
// }

// function getSystemConfig (key) {
// 	return getSysConfig("Secure", key);
// }

const cleanerList = [
	{
		name: "系统安全",
		packageName: settingsPackageName,
		intent: "ACTION_SECURITY_SETTINGS",
		regSubPage: /(广告|链接调用)/,
		regSwitchOff: /诊断数据|广告推荐|链接调用|用户体验/,
	},
	{
		name: "关于手机",
		packageName: settingsPackageName,
		intent: "ACTION_DEVICE_INFO_SETTINGS",
		regSubPage: null,
		regSwitchOff: null,
		regSwitchOn: null,
		walk: click10,
	},
	{
		name: "开发者选项",
		packageName: settingsPackageName,
		intent: "ACTION_APPLICATION_DEVELOPMENT_SETTINGS",
		regSubPage: null,
		regSwitchOff: null,
		regSwitchOn: /^USB\b/,
		max: 3,
	},
	{
		// `广告服务` 位于 `安全` 的子页面
		name: "广告服务",
		packageName: settingsPackageName,
		activity: ".ad.AdServiceSettings",
		regSwitchOff: /.*/,
		regSwitchOn: null,
	},
	{
		// 小米帐号
		packageName: "com.xiaomi.account",
		// activity: ".settings.SystemAdActivity",
		activity: ".ui.AccountSettingsActivity",
		regSubPage: /^关于.*?[帐账]号|系统.*?广告$/,
		regSwitchOff: /^系统.*?广告$/,
	},
	{
		// 手机管家 -> 设置页
		packageName: securityCenterPackageName,
		activity: "com.miui.securityscan.ui.settings.SettingsActivity",
	},
	{
		// 手机管家 -> 应用管理
		name: "应用管理",
		packageName: securityCenterPackageName,
		activity: "com.miui.appmanager.AppManagerMainActivity",
		entry: openCfgPageByPopupMenu,
	},
	{
		// 垃圾清理 -> 设置页
		packageName: "com.miui.cleanmaster",
		activity: "com.miui.optimizecenter.settings.SettingsActivity",
	},
	{
		// 应用商店
		packageName: marketPackageName,
		// activity: ".ui.MarketTabActivity",
		entry: openCfgPageBySubPage,
	},
	{
		// 应用包管理组件
		packageName: "com.miui.packageinstaller",
		fn: function (options) {
			const googlePkgName = "com.google.android.packageinstaller";
			let googleAppName = app.getAppName(googlePkgName);
			const miuiPkgName = "com.miui.packageinstaller";
			const Intent = android.content.Intent;

			const Uri = android.net.Uri;

			function installApk (apkPath, resolver) {
				const intent = new Intent();
				intent.setAction(Intent.ACTION_VIEW);
				if (resolver) {
					intent.setComponent(new android.content.ComponentName("android", "com.android.internal.app.ResolverActivity"));
				} else {
					intent.setPackage(miuiPkgName);
				}
				intent.setDataAndType(
					Uri.parse(apkPath),
					"application/vnd.android.package-archive",
				);
				app.startActivity(intent);
			}

			function closeApp (pkgName) {
				if (selector().packageName(pkgName).findOne(0x500)) {
					back();
					delay();
				}
			}

			function selectApp (pkgName, appName) {
				appName = appName || app.getAppName(pkgName);
				const checkBox = selector().filter(checkBox =>
					/\balways_option$/.test(checkBox.id()),
				).packageName("android").checkable(true).findOne(0x500);
				if (checkBox) {
					if (!checkBox.checked()) {
						clickButton(
							checkBox,
						);
					}
					clickButton(
						selector().text(appName).packageName("android").findOnce(),
					);
				}
				closeApp(pkgName);
			}

			installApk(googleAppName ? `content://${context.getPackageName()}/null.apk` : "file:///system/priv-app/PackageInstaller/PackageInstaller.apk");
			if (selector().packageName(miuiPkgName).findOne(0x500)) {
				const btnSetting = selector().id("setting_icon").packageName(miuiPkgName).clickable(true).findOnce();
				if (btnSetting) {
					// 关闭安装监控、安全检查
					clickButton(btnSetting);
					walkListView(options);
				}
				if (!googleAppName) {
					// 自动安装google安装器
					clickButton(
						selector().text("安装").packageName(miuiPkgName).findOne(),
					);
					clickButton(
						selector().text("完成").packageName(miuiPkgName).findOne(),
					);
					googleAppName = app.getAppName(googlePkgName);
				}
				closeApp(miuiPkgName);
			}

			if (googleAppName) {
				// 设置默认由miui安装器卸载APP
				const intent = new Intent();
				intent.setAction(Intent.ACTION_DELETE);
				intent.setData(
					Uri.parse("package:" + miuiPkgName),
				);
				app.startActivity(intent);
				selectApp(miuiPkgName);
				closeApp(miuiPkgName);
				// 设置默认由google安装器安装APP
				installApk("content://null.null/null.apk", true);
				selectApp(googlePkgName, googleAppName);
			}
		},
		regSwitchOff: /安全检查|安装监控|纯净模式|资源推荐/,
	},
	{
		// 应用商店
		packageName: marketPackageName,
		action: ".ui.CommonWebActivity",
		fn: (configs) => {
			startPkg(configs);
			[
				"我的",
				"系统应用管理",
			].every(text => clickButton(
				selector().packageName(configs.packageName).text(text).findOne(0xFFF),
			));
		},
	},
	{
		// 下载管理程序
		packageName: "com.android.providers.downloads.ui",
		activity: ".activity.DownloadSettingActivity",
	},
	{
		// 日历
		packageName: "com.android.calendar",
		activity: ".settings.CalendarActionbarSettingsActivity",
	},
	{
		// 时钟
		packageName: "com.android.deskclock",
		activity: ".settings.SettingsActivity",
		regSubPage: /(生活早报|更多.*?设置)/,
		regSwitchOff: /生活早报/,
	},
	{
		// 小米社区
		packageName: "com.xiaomi.vipaccount",
		activity: ".ui.home.page.HomeFrameActivity",
		entry: openCfgPageBySubPage,
	},
	{
		// 小米天气
		packageName: "com.miui.weather2",
		activity: ".ActivityWeatherMain",
		entry: openCfgPageByPopupMenu,
	},
	{
		// 小米视频
		packageName: "com.miui.video",
		activity: ".feature.mine.setting.SettingActivity",
	},
	// {
	// 	// 音乐
	// 	// v3.51.1.1 下载 https://xiaoheiya.lanzoum.com/iVMeWnhkh5c
	// 	packageName: "com.miui.player",
	// 	activity: ".phone.ui.MusicSettings",
	// 	// start: function () {
	// 	// 	app.uninstall("com.miui.player");
	// 	// },
	// 	// "com.tencent.qqmusiclite.activity.MainActivity",
	// },
	{
		// 小爱语音
		packageName: "com.miui.voiceassist",
		activity: "com.xiaomi.voiceassistant.settings.MiuiVoiceSettingActivity",
	},
	{
		// 搜索
		packageName: "com.android.quicksearchbox",
		activity: ".preferences.SearchSettingsPreferenceActivity",
		regSubPage: /^(.*?快捷方式|搜索项|.*?展示模块|热搜榜单)$/,
		regSwitchOn: /广告过滤/,
		regSwitchOff: /桌面搜索框$|搜索精选|热门资讯/,
		walk: (listView, opts) => {
			if (selector().text("热榜管理").id("action_bar_title").findOnce()) {
				let linearList = Array.from(listView.children());
				linearList = linearList.slice(
					0,
					linearList.findIndex(isFrameLayout),
				).reverse();
				linearList.forEach(linear => {
					linear.findOne(selector().clickable(true)).click();
				});
				if (linearList.length) {
					console.log("已关闭“热榜管理”中的所模块");
					return {
						热搜榜s: false,
					};
				} else {
					console.log("“热榜管理”中的所模块已处于关闭状态");
					opts.handle["热搜榜s"] = false;
				}
			}
		},
	},
	{
		// 小米浏览器国际版
		...browserConfig,
		packageName: "com.mi.globalbrowser",
	},
	{
		// 小米浏览器国内版
		...browserConfig,
	},
	// {
	// 	// 讯飞输入法
	// 	packageName: "com.iflytek.inputmethod",
	// 	// activity: ".LauncherActivity",
	// 	entry: openCfgPageBySubPage,
	// },
	// {
	// 	// 知乎
	// 	packageName: "com.zhihu.android",
	// 	activity: ".app.ui.activity.MainActivity",
	// 	entry: openCfgPageBySubPage,
	// 	disableScroll: true,
	// 	// 打开知乎去广告插件-知了
	// 	regSubPage: /^知了$/,
	// 	regSwitchOn: /^(启用知了|去.*?广告)$/,
	// 	regSwitchOff: false,
	// 	done: function (result) {
	// 		if (!Object.keys(result.handle).length) {
	// 			startTask(this);
	// 		} else if (!result.handle["知了"]) {
	// 			console.hide();
	// 			if (dialogs.confirm("是否下载去广告版知乎?")) {
	// 				downApp(this.packageName);
	// 				startTask(this);
	// 			}
	// 		} else if (result["知了"]["启用知了"]) {
	// 			toastLog("重启知乎后生效");
	// 		}
	// 	}
	// },

].map(menuItem => {
	if (typeof menuItem === "string") {
		menuItem = {
			packageName: menuItem,
		};
	}
	return menuItem;
});

// function offAdByWriteSettings () {
// 	const settings = requestSettings({
// 		writeSettings: true,
// 		accessibility: true,
// 		drawOverlay: true,
// 	});

// 	if (settings.writeSettings) {
// 		try {
// 			// https://developer.android.google.cn/reference/android/provider/Settings
// 			// 系统安全 -> 广告服务 -> 个性化广告推荐:关闭
// 			Settings.Global.putInt(resolver, "personalized_ad_enabled", 0);
// 			[
// 				// 网页链接调用服务
// 				"http_invoke_app",
// 				// 加入“用户体验改进计划”
// 				"upload_log_pref",
// 				// 自动发送诊断数据
// 				"upload_debug_log_pref",
// 			].forEach(key => Settings.Secure.putInt(resolver, key, 0));
// 		} catch (ex) {
// 			//
// 		}
// 	}
// 	if (settings.drawOverlay) {
// 		// 如果有悬浮窗权限,打开控制台
// 		console.clear();
// 		console.show();
// 	}
// }

// function offAppAd () {
// 	// offAdBywriteSettings();
// 	const menuItemList = cleanerList.filter((appInfo) => {
// 		if (appInfo.packageName && !appInfo.appName && !appInfo.name) {
// 			appInfo.appName = app.getAppName(appInfo.packageName);
// 			if (!appInfo.appName) {
// 				return false;
// 			}
// 		}
// 		appInfo.summary = appDesc[appInfo.packageName] || "";
// 		return true;
// 	});

// 	multiChoice({
// 		title: "请选择要关闭广告的APP",
// 		itemList: menuItemList,
// 		checked: true,
// 	}).then(tasks => {
// 		console.show();
// 		const timerSkipPopupPage = setInterval(skipPopupPage, 0x50);
// 		tasks.forEach(task => {
// 			// if (task.test && !task.test(task)) {
// 			// 	return;
// 			// }
// 			console.log(task.name || task.appName);
// 			// ;
// 		});
// 		clearInterval(timerSkipPopupPage);
// 	}).then(taskList => {
// 		offAppAd();
// 	}).catch(console.error);
// };

const actionManageNames = {
	ACTION_MANAGE_UNKNOWN_APP_SOURCES: "安装未知应用",
	ACTION_MANAGE_OVERLAY_PERMISSION: "允许显示在其他应用的上层",
	ACTION_MANAGE_WRITE_SETTINGS: "允许修改系统设置",
};

function getSettings (key) {
	console.log(key);
}

function runTask (taskInfo) {
	if (taskInfo.action === "clearAnim") {
		return clearAnim();
	} else if (/^ACTION_MANAGE_/.test(taskInfo.action)) {
		// 打开各种单项权限管理界面
		return startTask({
			packageName: settingsPackageName,
			intent: taskInfo.action,
			regSwitchOff: taskInfo.checked ? null : /.*/,
			regSwitchOn: taskInfo.checked ? /.*/ : null,
			name: actionManageNames[taskInfo.action] || taskInfo.action,
		});
	}

	let cleaner = cleanerList.filter(cleaner => (
		cleaner.packageName === taskInfo.packageName
	));

	if (cleaner.length > 1) {
		cleaner = cleanerList.filter(cleaner => (
			(cleaner.action || cleaner.activity || cleaner.intent) === taskInfo.action
		));
	}
	cleaner = cleaner[0];
	if (DEBUG && !cleaner) {
		console.error("未找到适配的任务");
		console.error(taskInfo);
	}
	const name = taskInfo.name || taskInfo.appName || cleaner.name;
	if (name) {
		cleaner.name = name;
	}
	if (cleaner.settings && !cleaner.settings.every(getSettings)) {
		console.log(`跳过任务:${name}`);
		return;
	}
	return cleaner.fn ? cleaner.fn(cleaner) : startTask(cleaner);
}

function runTaskList (taskList) {
	auto.waitFor();
	const timerSkipPopupPage = setInterval(skipPopupPage, 0x50);

	if (Array.isArray(taskList)) {
		if (floaty.checkPermission()) {
			console.show();
		}
		taskList = taskList.map(runTask);
	} else {
		taskList = runTask(taskList);
	}
	clearInterval(timerSkipPopupPage);
	console.hide();
	return taskList;
}

function init () {
	const configFilePath = files.join(
		context.getExternalFilesDir(null),
		"taskList.json",
	);
	runTaskList(
		JSON.parse(
			files.read(configFilePath),
		),
	);
	files.remove(configFilePath);
}

init();


================================================
FILE: src/miui_cleaner_app/settings.js
================================================
const waitForBack = require("./waitForBack");
const serviceMgr = require("./serviceMgr");
const project = require("./project.json");
const dialogs = require("./dialogs");

const settingsPackageName = "com.android.settings";
const settingsPrototype = Object.create(android.provider.Settings);
settingsPrototype.keys = function () {
	return Object.getOwnPropertyNames(this).sort();
};
settingsPrototype.has = Object.prototype.hasOwnProperty;
settingsPrototype.get = function (key) {
	if (this.has(key)) {
		return this[key];
	}
};
const requestCache = {};
function lazyAction (key, action) {
	let resolve = requestCache[key];
	if (!resolve) {
		resolve = Promise.resolve();
	}
	if (action) {
		resolve = resolve.then(action);
	}
	requestCache[key] = resolve;
	return resolve;
}

settingsPrototype.set = function (key, expectValue, reason) {
	if (this.has(key)) {
		this[key] = expectValue;
		if ((this[key] !== expectValue)) {
			if (expectValue) {
				const space = settingProperties[key].space;
				if (((space && space !== "Secure") || /^accessibility/.test(key)) && !this.writeSettings && this.accessibilityServiceEnabled) {
					lazyAction(
						key,
						() => this.set("writeSettings", expectValue, reason),
					);
				}
				const depend = settingProperties[key].depend;
				if (depend && !this[depend]) {
					lazyAction(
						key,
						() => this.set(depend, expectValue, reason),
					);
				}
			}
			lazyAction(
				key,
				() => requestSettings(key, expectValue, reason),
			);
		}
		lazyAction(
			key,
			() => this[key],
		);
	}
	return lazyAction(key);
};
settingsPrototype.forEach = function (callbackFn, thisArg) {
	this.keys().forEach(
		(key) => {
			callbackFn.call(thisArg || this, settings[key], key, settings);
		},
		thisArg,
	);
};
const settings = Object.create(settingsPrototype);

const actions = {
	ACTION_MANAGE_UNKNOWN_APP_SOURCES: /^requestInstallPackages$/,
	ACTION_MANAGE_OVERLAY_PERMISSION: /^drawOverlays$/,
	ACTION_MANAGE_WRITE_SETTINGS: /^writeSettings$/,
	ACTION_DEVICE_INFO_SETTINGS: /^development$/,
	ACTION_APPLICATION_DEVELOPMENT_SETTINGS: /^adb/,
};

function requestSettings (key, expectValue, reason) {
	if (key === "accessibilityServiceEnabled") {
		reason = reason ? `,以便“${project.name}”能${reason}` : "";
		return dialogs.confirm(
			`请在下个页面,点击“已下载的服务”,然后${expectValue ? "打开" : "关闭"}“${project.name}”的无障碍服务开关${reason}。`,
			{
				title: "权限请求",
			},
		).then((confirm) => {
			return confirm && waitForBack(() => {
				if (expectValue) {
					try {
						auto();
					} catch (ex) {
						//
					}
				} else {
					app.startActivity(new android.content.Intent(settings.ACTION_ACCESSIBILITY_SETTINGS));
				}
			});
		});
	}
	for (const actionName in actions) {
		if (actions[actionName].test(key)) {
			return settings.set(
				"accessibilityServiceEnabled",
				true,
				reason,
			).then(accessibilityServiceEnabled => (
				accessibilityServiceEnabled && serviceMgr({
					packageName: settingsPackageName,
					checked: expectValue,
					action: actionName,
				})
			));
		}
	}
}
function tryCmd (cmd, root = true) {
	try {
		return shell(cmd, root);
	} catch (ex) {
		if (root) {
			return tryCmd(cmd, false);
		}
	}
}

// function switchACheckBox (expect = true, packageName = settingsPackageName) {
// 	let checkBox = selector().packageName(packageName).checkable(true).findOne();
// 	let value = checkBox.checked();
// 	if (value === expect) {
// 		return expect;
// 	} else {
// 		checkBox = findClickableParent(checkBox);
// 		if (checkBox) {
// 			checkBox.click();
// 			value = expect;
// 		}
// 	}
// 	return value;
// }

function pmPermission (key, permission) {
	// pm grant org.autojs.autoxjs.v6 android.permission.WRITE_SETTINGS
	// pm grant org.autojs.autoxjs.v6 android.permission.WRITE_SECURE_SETTINGS
	// pm grant org.autojs.autoxjs.v6 android.permission.SYSTEM_ALERT_WINDOW
	// pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SETTINGS
	// pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SECURE_SETTINGS
	// pm grant com.github.gucong3000.miui.cleaner android.permission.SYSTEM_ALERT_WINDOW
	// grantRuntimePermission
	// revokeRuntimePermission
	return (expectValue) => {
		tryCmd(`pm ${expectValue ? "grant" : "revoke"} ${context.getPackageName()} android.permission.${permission}`, expectValue);
	};
}

const accessServicesName = context.getPackageName() + "/com.stardust.autojs.core.accessibility.AccessibilityService";
const settingProperties = {
	// rhinoVersion: {
	// 	get: () => org.mozilla.javascript.Context.getCurrentContext().getImplementationVersion(),
	// },
	requestInstallPackages: {
		enumerable: true,
		get: () => context.getPackageManager().canRequestPackageInstalls(),
		// get: () => Boolean(context.checkCallingOrSelfPermission("android.permission.REQUEST_INSTALL_PACKAGES")),
		set: pmPermission("requestInstallPackages", "REQUEST_INSTALL_PACKAGES"),
	},
	writeSettings: {
		enumerable: true,
		get: () => settings.System.canWrite(context),
		// get: () => Boolean(context.checkCallingOrSelfPermission("android.permission.WRITE_SETTINGS")),
		set: pmPermission("writeSettings", "WRITE_SETTINGS"),
	},
	writeSecureSettings: {
		enumerable: true,
		get: () => {
			if (!context.checkCallingOrSelfPermission("android.permission.WRITE_SECURE_SETTINGS")) {
				try {
					settings.Secure.putInt(context.getContentResolver(), "accessibility_enabled", 1);
				} catch (ex) {
					return false;
				}
			}
			return true;
		},
		set: pmPermission("writeSecureSettings", "WRITE_SECURE_SETTINGS"),
	},
	drawOverlays: {
		enumerable: true,
		get: () => settings.canDrawOverlays(context),
		// get: () => Boolean(context.checkCallingOrSelfPermission("android.permission.SYSTEM_ALERT_WINDOW")),
		set: pmPermission("drawOverlays", "SYSTEM_ALERT_WINDOW"),
	},
	accessibilityServiceEnabled: {
		depend: "accessibility",
		enumerable: true,
		get: () => Boolean(auto.service),
		set: (value) => {
			if (value) {
				settings.accessibilityServices.add(accessServicesName);
			} else {
				settings.accessibilityServices.delete(accessServicesName);
			}
		},
	},
	adbInput: {
		enumerable: true,
		depend: "adb",
		get: () => settings.adb && (getAdbInput() || getAdbInput()),
		set: (expectValue) => {
			enableDependSetting("adb", expectValue);
			tryCmd("setprop persist.security.adbinput " + (expectValue ? 1 : 0));
		},
	},
};

function getAdbInput () {
	return Boolean(tryCmd("getprop persist.security.adbinput", false).result - 0);
}

function enableDependSetting (depend, value) {
	if (depend && value) {
		settings[depend] = Boolean(value);
	}
}
// 基于系统设置的选项
// https://developer.android.google.cn/reference/android/provider/Settings.Global
function defineSettingProperty ({
	space = "Global",
	type = "Int",
	key,
	depend,
	get = Boolean,
	set = value => value ? 1 : 0,
}) {
	const propertyName = key
		.replace(/^enabled_|(_settings?)?_(enabled|status|pref)$/, "")
		.replace(/_(ad$|\w)/g, s => s.slice(1).toUpperCase());
	const descriptor = {
		enumerable: true,
		depend,
		space,
		get: () => {
			let value;
			try {
				value = settings[space]["get" + type](context.getContentResolver(), key);
			} catch (ex) {
				return;
			}
			return get ? get(value) : value;
		},
		set: (expectValue) => {
			expectValue = set ? set(expectValue) : expectValue;
			enableDependSetting(depend, expectValue);
			try {
				settings[space]["put" + type](context.getContentResolver(), key, expectValue);
			} catch (ex) {
				// console.error(`将${propertyName}设置为${expectValue}时失败\n${ex.message}`);
			}
		},
	};
	settingProperties[propertyName] = descriptor;
	if (!depend) {
		depend = space === "Secure" ? "writeSecureSettings" : "writeSettings";
	}
}

// 开发者选项
defineSettingProperty({
	key: "development_settings_enabled",
});

// `USB调试`,既ADB权限
defineSettingProperty({
	key: "adb_enabled",
	depend: "development",
});

// 小米帐号 → 关于小米帐号 → 系统广告 → 系统工具广告
defineSettingProperty({
	key: "passport_ad_status",
	type: "String",
	get: value => !/^OFF$/i.test(value),
	set: value => value ? "ON" : "OFF",
});

// 系统安全 → 广告服务 → 个性化广告推荐
defineSettingProperty({
	key: "personalized_ad_enabled",
});

// 无障碍服务列表
defineSettingProperty({
	space: "Secure",
	type: "String",
	key: "enabled_accessibility_services",
	get: null,
	set: null,
});

((accessibilityServices) => {
	const {
		set,
		get,
	} = accessibilityServices;
	let services;
	const toString = () => Array.from(services).join(":");
	const proxy = {
		toString,
		inspect: toString,
		toJSON: toString,
	};

	Object.getOwnPropertyNames(Set.prototype).forEach(propertyName => {
		proxy[propertyName] = (...args) => {
			const result = services[propertyName](...args);
			set(toString());
			settings.accessibility = true;
			return result;
		};
	});

	accessibilityServices.get = () => {
		services = new Set(get().trim().split(/\s*:\s*/g));
		return Object.create(proxy);
	};
	delete accessibilityServices.set;
})(settingProperties.accessibilityServices);

[
	// 无障碍服务开关
	"accessibility_enabled",
	// 系统安全 → 网页链接调用服务
	"http_invoke_app",
	// 系统安全 →加入“用户体验改进计划”
	"upload_log_pref",
	// 系统安全 → 自动发送诊断数据
	"upload_debug_log_pref",
].forEach(key => {
	defineSettingProperty({
		space: "Secure",
		key,
	});
});

Object.defineProperties(
	settings,
	settingProperties,
);

settings.accessibilityServiceEnabled = true;
settings.adb = true;

module.exports = settings;


================================================
FILE: src/miui_cleaner_app/singleChoice.js
================================================
const emitItemShowEvent = require("./emitItemShowEvent");
const project = require("./project.json");
const View = android.view.View;

function singleChoice (
	{
		itemList,
		title,
		icon = "./res/drawable/ic_android.png",
		fn = null,
	},
) {
	icon = /^\w+:\/\//.test(icon) ? icon : ("file://" + files.path(icon));

	ui.layout(`
		<frame>
			<vertical>
				<appbar>
					<toolbar id="toolbar" title="${project.name}" subtitle="${title}" />
				</appbar>
				<relative id="progress" h="*" visibility="gone">
					<progressbar layout_centerInParent="true" />
				</relative>
				<list id="itemList">
					<relative w="*">
						<card w="*" margin="0 0 0 10" foreground="?selectableItemBackground">
							<horizontal gravity="center_vertical">
								<img id="icon" h="48" w="48" src="{{this.icon || '${icon}'}}" margin="10 10 0 10" />
								<vertical h="auto" layout_weight="1" margin="10 0">
									<text text="{{this.displayName || this.appName || this.name}}" textColor="#333333" textSize="16sp" maxLines="1" />
									<text text="{{this.summary}}" textColor="#999999" textSize="14sp" maxLines="1" />
								</vertical>
							</horizontal>
						</card>
					</relative>
				</list>
			</vertical>
		</frame>
	`);

	emitItemShowEvent(ui.itemList, icon);

	ui.itemList.on("item_click", function (item, i, itemView, listView) {
		console.log(`已点击:${item.displayName || item.appName || item.name}`);
		item.fn ? item.fn(item, itemView) : fn(item, itemView);
	});

	ui.itemList.on("item_long_click", function (event, item, i, itemView, listView) {
		if (item.url) {
			app.openUrl(item.url);
		}
	});

	function setDataSource (itemList) {
		itemList.forEach((item) => {
			if (item.icon && !/^\w+:\/\//.test(item.icon)) {
				item.icon = "file://" + files.path(item.icon);
			}
		});
		ui.itemList.setDataSource(itemList);
		ui.post(() => {
			ui.progress.setVisibility(View.GONE);
		});
	}

	global.activity.setSupportActionBar(ui.toolbar);
	if (itemList.then) {
		ui.progress.setVisibility(View.VISIBLE);
		threads.start(function () {
			itemList.then(itemList => {
				ui.post(() => {
					setDataSource(itemList);
				});
			});
		});
	} else {
		setDataSource(itemList);
	}
}

module.exports = singleChoice;


================================================
FILE: src/miui_cleaner_app/startActivity.js
================================================
function startActivity (options) {
	if (options.className) {
		options.className = options.className.replace(/^(?=\.)/, options.packageName);
	}
	try {
		app.startActivity(options);
	} catch (ex) {
		console.error(ex);
	}
}

module.exports = startActivity;


================================================
FILE: src/miui_cleaner_app/support.js
================================================
const project = require("./project.json");
const View = android.view.View;
const url = "https://support.qq.com/products/565003";

function support () {
	const postData = new URLSearchParams({
		clientInfo: project.name,
		clientVersion: project.versionName,
		os: `${device.brand}/${device.model}`,
		osVersion: device.fingerprint,
		// netType: "",
		// customInfo: ,
		// openid
		// nickname
		// avatar
	}).toString();
	// console.log(device);

	ui.layout(`
		<vertical>
			<appbar>
				<toolbar id="toolbar" title="${project.name}" subtitle="${module.exports.name}" />
			</appbar>
			<relative w="*" w="*">
				<webview id="webView" layout_below="title" w="*" h="*" />
				<progressbar id="progress" indeterminate="true" layout_centerHorizontal="true" layout_alignParentTop="true" w="*" h="auto"style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" />
			</relative>
		</vertical>
	`);
	const webView = ui.findView("webView");
	const progress = ui.findView("progress");
	global.activity.setSupportActionBar(ui.findView("toolbar"));

	// https://developer.android.google.cn/reference/android/webkit/WebChromeClient
	webView.setWebChromeClient(
		new JavaAdapter(android.webkit.WebChromeClient, {
			onProgressChanged: (webView, i) => {
				if (i) {
					progress.progress = i;
				}
			},
		}),
	);
	// https://developer.android.google.cn/reference/android/webkit/WebViewClient
	webView.setWebViewClient(new JavaAdapter(android.webkit.WebViewClient, {
		onPageStarted: () => {
			progress.indeterminate = false;
			progress.setVisibility(View.VISIBLE);
		},
		onPageFinished: () => {
			progress.setVisibility(View.GONE);
		},
		onCloseWindow: () => {
			console.log("onCloseWindow");
		},
	}));

	const settings = webView.getSettings();
	settings.setJavaScriptEnabled(true);
	settings.setDomStorageEnabled(true);

	webView.loadUrl(`${url}?${postData}`);
	ui.emitter.prependListener("back_pressed", (e) => {
		if (webView.canGoBack()) {
			webView.goBack();
		} else {
			ui.emitter.removeAllListeners("back_pressed");
			require("./index").mainMenu();
		}
		e.consumed = true;
	});
}

module.exports = {
	name: "帮助与反馈",
	summary: "帮助文档、联系作者",
	icon: "./res/drawable/ic_help.png",
	fn: support,
};


================================================
FILE: src/miui_cleaner_app/sysAppRm.js
================================================
const findClickableParent = require("./findClickableParent");
const getApplicationInfo = require("./getApplicationInfo");
const multiChoice = require("./multiChoice");
const waitForBack = require("./waitForBack");
const settings = require("./settings");
const appDesc = require("./appDesc");
const dialogs = require("./dialogs");

const installerPackageName = "com.miui.packageinstaller";
const installerAppName = app.getAppName(installerPackageName);

const sysAppList = Object.keys(appDesc).map(packageName => ({
	packageName,
	summary: appDesc[packageName],
}));

function clickButton (button, text) {
	button = findClickableParent(button);
	if (button) {
		button.click();
	}
}

// // 卸载“纯净模式”
// function getInstaller (appList) {
// 	const packageInfo = getApplicationInfo({
// 		packageName: installerPackageName,
// 		summary: "降级安装v380,以便移除“纯净模式”",
// 	});
// 	if (!packageInfo || packageInfo.getVersionCode() < 400) {
// 		// 版本号小于400,则不含“纯净模式”
// 		return;
// 	}
// 	const fileName = "MiuiPackageInstaller.apk";
// 	const srcPath = "res/" + fileName;
// 	const copyPath = "/sdcard/Download/" + fileName;
// 	const installPath = "/data/local/tmp/" + fileName;
// 	files.copy(srcPath, copyPath);
// 	packageInfo.cmd = [
// 		`mv ${copyPath} ${installPath}`,
// 		"pm install -d -g " + installPath,
// 		"rm -rf " + installPath,
// 	].join("\n");
// 	appList.push(packageInfo);
// 	console.log(`发现${installerAppName}(${installerPackageName}),版本号${packageInfo.getVersionName()},已释放版本号为v380的降级安装包到路径:${copyPath}`);
// }

const whitelist = /^com\.(miui\.(voiceassist|personalassistant)|android\.(quicksearchbox|chrome))$/;
function getAppList () {
	const appList = sysAppList.filter(item => {
		if (!getApplicationInfo(item)) {
			return false;
		}
		if (item.checked == null) {
			item.checked = !whitelist.test(item.packageName);
		}
		// item.isSysApp = appInfo.flags & android.content.pm.ApplicationInfo.FLAG_SYSTEM;
		return true;
	});
	return appList;
}

function installerHelper () {
	// 应用包安装程序选择界面,下次默认,不再提示
	// const isCheckBox = checkBox => (
	// 	checkBox.checkable() && !checkBox.checked() && /下次默认.*不再提示/.test(checkBox.text()) && /\bandroid\b/.test(checkBox.packageName())
	// );
	// 应用包安装程序选择界面,通过“应用包管理组件”运行
	const isInstallerSelect = btn => (
		installerAppName === btn.text() && /\bandroid\b/.test(btn.packageName())
	);
	// 应用包管理组件,确定、继续按钮
	const isContinueBtn = btn => (
		/\b(continue|ok)_button$/.test(btn.id()) && installerPackageName === btn.packageName()
	);
	clickButton(
		selector().filter(
			obj => isInstallerSelect(obj) || isContinueBtn(obj),
		).findOnce(),
	);
	setTimeout(installerHelper, 0x50);
}

function removeByInstaler (taskList) {
	const uninstallTaskList = taskList.filter(task => task.packageName !== installerPackageName);
	if (!uninstallTaskList.length) {
		return taskList;
	}

	toastLog(`尝试以常规权限卸载${uninstallTaskList.length}个应用`);
	let helper;
	return settings.set(
		"accessibilityServiceEnabled",
		true,
		"自动化卸载这些APP",
	).then(accessibilityServiceEnabled => {
		if (accessibilityServiceEnabled) {
			helper = threads.start(installerHelper);
		}
	}).then(() =>
		waitForBack(() => {
			uninstallTaskList.forEach(appInfo => {
				app.uninstall(appInfo.packageName);
			});
		}).then(() => {
			helper && helper.interrupt();
			return taskList;
		}),
	);
}

function removeByScript (tasks) {
	tasks = tasks.filter(
		appInfo => app.getAppName(appInfo.packageName),
	).map(appInfo => {
		return appInfo.cmd || "pm uninstall --user 0 " + appInfo.packageName;
	});
	if (!tasks.length) {
		return;
	}

	const shFilePath = "/sdcard/Download/MiuiCleaner.sh";
	tasks.unshift(`pm grant ${context.getPackageName()} android.permission.WRITE_SECURE_SETTINGS`);
	tasks.unshift("#!/bin/sh");
	tasks.push("rm -rf " + shFilePath);
	const script = tasks.join("\n") + "\n";
	files.write(shFilePath, script);
	let cmd = "sh " + shFilePath;
	try {
		shell(cmd, true);
		toastLog(`尝试以root权限卸载${tasks.length}个应用`);
		return;
	} catch (ex) {
		//
	}
	return settings.set("adbInput", true, "自动打开“USB调试(安全设置)”,让电脑端有权限卸载这些APP").then((adbInput) => {
		// if (!adbInput) {
		// 	toastLog("“USB调试(安全设置)”未打开,请打开后再试。");
		// 	return;
		// }
		cmd = "adb shell " + cmd;
		toastLog("正以等候电脑端自动执行:\t" + cmd);
		const timeout = Date.now() + 0x800 + tasks.length * 0x200;
		let fileExist;
		return new Promise((resolve) => {
			const timer = setInterval(() => {
				let wait;
				fileExist = files.exists(shFilePath);
				if (!fileExist) {
					toastLog("电脑端自动执行成功");
				} else if (Date.now() > timeout) {
					wait = dialogs.prompt(
						"等候电脑端自动执行超时,请打开本软件电脑端,或者在电脑手动执行命令:",
						cmd,
						{
							negative: false,
							cancelable: true,
						},
					).then(() => {
						if (!files.exists(shFilePath)) {
							toastLog("电脑端手动执行成功");
						} else {
							toastLog("电脑端手动执行失败");
						}
					});
				} else {
					return;
				}
				clearInterval(timer);
				resolve(wait);
			}, 0x200);
		});
	});
}

function sysAppRm () {
	const itemList = getAppList();
	multiChoice({
		title: "请选择要卸载的应用或功能",
		itemList,
	}).then(
		removeByInstaler,
	).then(
		removeByScript,
	).then(
		sysAppRm,
	).catch(console.error);
	require("./index")();
};

module.exports = {
	name: "预装APP卸载",
	summary: "卸载系统广告组件及随机预装的APP",
	icon: "./res/drawable/ic_phone_recovery.png",
	fn: sysAppRm,
};


================================================
FILE: src/miui_cleaner_app/test/getRemoteFileInfo.js
================================================
module.exports = (getRemoteFileInfo) => {
	return DEBUG && Promise.all([
		// getRemoteFileInfo("https://www.firepx.com/app/android-mi-browser-google-play/").then(miBrowserFirepx => {
		// 	console.assert(Array.isArray(miBrowserFirepx), "MIUI浏览器应该是数组");
		// 	miBrowserFirepx.forEach(file => {
		// 		console.assert(/^Mi\s+Browser-.*\.apk$/.test(file.fileName), "MIUI浏览器应该是数组");
		// 	});
		// }),
		// getRemoteFileInfo("https://firepx.lanzoul.com/b00vs5efe#pwd=385m").then(miBrowserFirepx => {
		// 	throw new Error("密码错误,应该报错");
		// }, error => {
		// 	console.assert(error.message === "密码不正确", error.info || "密码不正确");
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/tp/i7tit9c#pwd=6svq").then(dianshijia => {
		// 	// 单文件,有密码
		// 	console.log("testcase", dianshijia);
		// 	console.assert(/^电视家.*\.apk$/.test(dianshijia.fileName), "电视家文件名");
		// 	// console.assert(!!dianshijia.url, "没能查到电视家下载地址");
		// }),
		// getRemoteFileInfo("https://zisu.lanzoum.com/tp/iI7LGwn5xjc").then(installer => {
		// 	// 单文件,无密码
		// 	console.log(installer);
		// 	console.assert(/^应用包管理组件.*\.apk$/.test(installer.fileName), "应用包管理组件");
		// 	console.assert(installer.id === "iI7LGwn5xjc", "应用包管理组件,ID:iI7LGwn5xjc");
		// 	// console.assert(!!installer.url, "没能查到应用包管理组件下载地址");
		// }),
		// getRemoteFileInfo("https://wwm.lanzoul.com/tp/idzsf0bh062h").then(browser => {
		// 	// 单文件,无密码
		// 	console.log(browser);
		// 	console.assert(/^小米浏览器.*\.apk$/.test(browser.fileName), "小米浏览器");
		// 	console.assert(browser.id === "idzsf0bh062h", "小米浏览器,ID:idzsf0bh062h");
		// 	// console.assert(!!browser.url, "没能查到小米浏览器下载地址");
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/b0f2uzq2b").then(coolapk => {
		// 	// 文件夹,无密码
		// 	console.log(coolapk);
		// 	console.assert(Array.isArray(coolapk), "酷安应该是数组");
		// 	coolapk.forEach(file => {
		// 		console.assert(/^(酷安|FuckCoolapk).*\.apk$/.test(file.fileName), "酷安应该是数组");
		// 	});
		// }),
		// getRemoteFileInfo("https://gucong.lanzoub.com/b03pbkhif#pwd=miui").then(cleaner => {
		// 	console.log(cleaner);
		// 	// 文件夹,有密码
		// 	console.assert(Array.isArray(cleaner), "MiuiCleaner应该是数组");
		// 	cleaner.forEach(file => {
		// 		console.assert(/^MiuiCleaner.*\.zip$/.test(file.fileName), "MiuiCleaner应该是数组");
		// 	});
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/iHmmD06tw9xa").then(appShare => {
		// 	// 特殊页面 - APP 信息
		// 	console.log(appShare);
		// 	console.assert(/^App分享.*\.apk$/.test(appShare.fileName), "App分享");
		// 	console.assert(appShare.id === "iHmmD06tw9xa", "App分享,ID:iI7LGwn5xjc");
		// 	console.assert(appShare.size === 23383245, "App分享,size:22.3 M");
		// 	console.assert(appShare.time === 1655942400000, "App分享,time:2022-06-23");
		// 	console.assert(!appShare.url, "没能查到App分享下载地址");
		// }),
		// getRemoteFileInfo("https://wwe.lanzouw.com/b01v0g3wj#pwd=1233").then(litiaotiao => {
		// 	// 特殊页面 带参数ID
		// 	console.assert(Array.isArray(litiaotiao), "李跳跳应该是数组");
		// 	return Promise.all(
		// 		litiaotiao.map(opts => {
		// 			console.assert(/\?/.test(opts.id), "李跳跳ID中有参数");
		// 			console.assert(/^李跳跳.*\.apk$/.test(opts.fileName), "李跳跳应该是数组");
		// 			return getRemoteFileInfo(opts.referer).then(file => {
		// 				console.assert(!opts.url, "没能查到李跳跳下载地址");
		// 				console.assert(!!file.url, "没能查到李跳跳下载地址");
		// 				console.log(file.fileName, file.versionName);
		// 				Object.keys(opts).forEach(key => {
		// 					console.assert(opts[key] === file[key], "李跳跳 : " + key);
		// 				});
		// 			});
		// 		}),
		// 	);
		// }),
		// getRemoteFileInfo("https://github.com/WangDaYeeeeee/GeometricWeather/releases/latest").then(weather => {
		// 	console.assert(Array.isArray(weather), "几何天气应该是数组");
		// 	weather.forEach(file => {
		// 		console.log(file);
		// 		console.assert(/^GeometricWeather.*\.apk$/.test(file.fileName) || /\.aab$/.test(file.fileName), "几何天气文件名GeometricWeather.*\.apk");
		// 	});
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/b0f1d7s2h").then(esexplorer => {
		// 	console.assert(Array.isArray(esexplorer), "ES文件浏览器应该是数组");
		// 	esexplorer.forEach(file => {
		// 		// console.log(file);
		// 		console.assert(/^\wS文件(浏览|管理)器.*\.apk$/.test(file.fileName), "ES文件浏览器文件名");
		// 	});
		// }),
		// getRemoteFileInfo("https://423down.lanzouo.com/b0f2lkafe").then(zhihu => {
		// 	console.log(zhihu);
		// 	console.assert(Array.isArray(zhihu), "知乎应该是数组");
		// 	zhihu.forEach(file => {
		// 		console.log(file.fileName, file.versionName);
		// 		console.assert(/^(知乎|Zhiliao).*\.apk$/.test(file.fileName), "知乎文件名");
		// 	});
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/b0f1b6q8d").then(tieba => {
		// 	console.log(tieba);
		// 	console.assert(Array.isArray(tieba), "知乎应该是数组");
		// 	tieba.forEach(file => {
		// 		console.log(file.fileName, file.versionName);
		// 		console.assert(/^(百度)?贴吧.*\.apk$/.test(file.fileName), "贴吧文件名");
		// 	});
		// }),
		// getRemoteFileInfo("https://423down.lanzouv.com/b0f1avpib").then(youku => {
		// 	console.log(youku);
		// }),
		// console.log(getRemoteFileInfo.toString()),

		// getRemoteFileInfo("https://m.32r.com/app/109976.html").then(wps => {
		// 	console.log(wps);
		// }).catch(console.error),

		// getRemoteFileInfo("https://423down.lanzouv.com/b0f1gksne").then(wps => {
		// 	console.log(wps);
		// }),

		// singleFile("iI7LGwn5xjc").then(console.log);

	]).catch(console.error);
};


================================================
FILE: src/miui_cleaner_app/test/services.js
================================================
function isObject (val) {
	return val != null && typeof val === "object" && Array.isArray(val) === false;
};
function isBoolean (value) {
	return value === true || value === false;
}
const testCase = {
	小米帐号: {
		关于小米帐号: {
			系统广告: {
				系统工具广告: false,
			},
		},
	},
	广告服务: {
		个性化广告推荐: false,
	},
	系统安全: {
		"加入“用户体验改进计划”": false,
		"自动发送诊断数据": false,
		"广告服务": {
			个性化广告推荐: false,
		},
		"网页链接调用服务": {
			网页链接调用服务: false,
		},
	},
	手机管家: {
		在通知栏显示: false,
		在线服务: false,
		隐私设置: {
			仅在WLAN下推荐: true,
			个性化推荐: false,
		},
	},
	应用管理: {
		资源推荐: false,
	},
	垃圾清理: {
		扫描内存: false,
		推荐内容: false,
		仅在WLAN下推荐: true,
	},
	应用商店: {
		通知设置: {
			新手帮助: false,
			应用更新通知: false,
			点赞消息: false,
			评论消息: false,
		},
		通知栏快捷入口: false,
		隐私设置: {
			个性化服务: {
				个性化服务: false,
			},
		},
		功能设置: {
			显示福利活动: false,
		},
	},
	下载管理: {
		信息流设置: {
			仅在WLAN下加载: true,
			资源推荐: false,
			热榜推荐: false,
		},
	},
	日历: {
		功能设置: {
			显示天气服务: false,
		},
		用户体验计划: {
			内容推广: false,
		},
	},
	时钟: {
		更多闹钟设置: {
			显示生活早报: false,
		},
	},
	小米社区: {
		隐私管理: {
			详情页相似推荐: false,
			个性化广告: false,
			信息流推荐: false,
		},
		关闭私信: null,
		关闭私信消息提醒: true,
	},
	小米天气: {
		用户体验计划: {
			天气视频卡片: false,
			内容推广: false,
		},
	},
	小米视频: {
		隐私设置: {
			个性化内容推荐: false,
			个性化广告推荐: false,
		},
		消息与推送: {
			未读消息提醒: false,
			接收小米推送: false,
		},
		其他: {
			// "已安装插件": [
			// 	"风行",
			// 	"爱奇艺",
			// 	"搜狐",
			// ],
			在线服务: false,
		},
	},
	音乐: {
		/*
		个性化内容推荐: false,
		通知设置: {
			个性化内容推荐: false,
			通知设置: {
				通知提醒: false
			},
			仅在WLAN下自动下载: true
		},
		仅在WLAN下自动下载: true,
		*/
		在线内容服务: false,
	},
	小爱语音: {
		隐私管理: {
			隐私设置: {
				加入用户体验改进计划: false,
				小爱技巧推送服务: false,
				个性化推荐: false,
				个性化广告推荐: false,
			},
		},
	},
	搜索: {
		搜索快捷方式: {
			桌面搜索框: false,
		},
		首页展示模块: {
			热搜榜单: {
				热搜榜s: false,
			},
			搜索提示词: false,
		},
		搜索项: {
			搜索精选: false,
		},
		网站广告过滤: true,
	},
	浏览器: {
		主页设置: {
			简洁版: true,
			宫格位推送: false,
		},
		隐私防护: {
			广告过滤: {
				广告过滤: true,
			},
		},
		消息通知管理: {
			接收消息通知: false,
		},
	},
	小米浏览器: {
		首页设置: {
			简洁版: true,
		},
		隐私保护: {
			广告过滤: {
				广告过滤: true,
			},
		},
		高级: {
			浏览器广告: false,
		},
		通知栏快捷入口: false,
		Facebook快捷通知: false,
	},
	关于手机: {
		"MIUI 版本": 14,
	},
	开发者选项: {
		"USB 调试": true,
		"USB安装": true,
		"USB调试(安全设置)": true,
	},
};
function compiler (result, data, options, parent) {
	parent = parent || [];
	for (const key in data) {
		const curr = parent.concat(key);
		if (isObject(data[key])) {
			if (!result[key]) {
				const error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中,“${curr.join("”→“")}”未进行应有的处理`);
				throw error;
			}
			compiler(result[key], data[key], options, curr);
		} else if (data[key] !== result[key]) {
			const error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中,“${curr.join("”→“")}”应该为${JSON.stringify(data[key])},实际为${JSON.stringify(result[key])}`);
			throw error;
		}
	}
	for (const key in result) {
		const curr = parent.concat(key);
		if (isBoolean(result[key]) && data[key] !== result[key]) {
			const error = new Error(`广告自动关闭模块功能异常: “${options.name || app.getAppName(options.packageName)}”中,“${curr.join("”→“")}”进行了不当的处理${JSON.stringify(result[key])}`);
			throw error;
		}
	}
}

module.exports = (options, result) => {
	const testCaseName = options.name || options.appName || app.getAppName(options.packageName);
	const data = testCase[testCaseName];

	console.log(`“${testCaseName}”结果:\n` + JSON.stringify(result.handle, 0, "\t"));
	try {
		if (!data) {
			const error = new Error(`广告自动关闭模块功能异常: “${testCaseName}”中,未编写测试用例`);
			throw error;
		}
		compiler(result.handle, data, options);
	} catch (ex) {
		console.error(ex);
	}
};

module.exports.testCase = testCase;


================================================
FILE: src/miui_cleaner_app/update.js
================================================
const prettyBytes = require("pretty-bytes");
const project = require("./project.json");
const downFile = require("./downFile");
const dialogs = require("./dialogs");
const request = require("./fetch");

function iec (number, options) {
	return prettyBytes(number, {
		binary: true,
		...options,
	});
}

function download (remote, options) {
	const downTask = downFile(options);

	let progressDialog;
	let view;

	function showProgressDialog () {
		if (progressDialog) {
			progressDialog.show();
			return;
		}
		view = ui.inflate(`
			<vertical>
				<progressbar id="progressbar" indeterminate="true" style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal" />
				<linear orientation="horizontal">
					<text id="progresstxt" layout_weight="1" />
					<text id="speed" text=" "/>
				</linear>
			</vertical>
		`);
		progressDialog = dialogs(
			`正在下载新版本:${remote.versionName}`,
			{
				title: "版本更新",
				positive: "后台下载",
				negative: false,
				view,
			},
		);
	}
	downTask.on("start", showProgressDialog);
	downTask.on("click", showProgressDialog);
	downTask.on("progress", ({
		progress,
		speed,
		size,
	}) => {
		view.progressbar.indeterminate = false;
		view.progressbar.max = size;
		view.progressbar.progress = progress;
		view.progresstxt.setText(`${iec(progress)}/${iec(size)}`);
		view.speed.setText(speed >= 0 ? `${iec(speed)}/s` : "");
	});

	return downTask.then(intent => {
		let confirm;
		if (progressDialog) {
			progressDialog.dismiss();
			confirm = dialogs.confirm("下载完毕,立即安装?", {
				title: "版本更新",
			});
		} else {
			confirm = Promise.resolve(true);
		}
		return confirm.then(confirm => {
			if (confirm) {
				return app.startActivity(intent);
			}
		});
	});
}

function getFastUrl (remote) {
	return request(
		[
			"github.com",
			"download.fastgit.org",
			"gh.api.99988866.xyz/https://github.com",
		].map(
			host => `https://${host}/gucong3000/MiuiCleaner/releases/download/v${remote.versionName}/MiuiCleaner.apk`,
		),
		{
			method: "HEAD",
		},
	);
}

// https://zhuanlan.zhihu.com/p/314071453
// http://raw.githubusercontent.com 替换为 http://raw.staticdn.net 即可加速。

// GitHub + Jsdelivr
// https://github.com.cnpmjs.org
// https://hub.fastgit.org
// 也就是说上面的镜像就是一个克隆版的 GitHub,你可以访问上面的镜像网站,网站的内容跟 GitHub 是完整同步的镜像,然后在这个网站里面进行下载克隆等操作。

// GitHub 文件加速
// 利用 Cloudflare Workers 对 github release 、archive 以及项目文件进行加速,部署无需服务器且自带CDN.

// https://gh.api.99988866.xyz
// https://g.ioiox.com
request([
	"https://cdn.jsdelivr.net/gh/gucong3000/MiuiCleaner/src/miui_cleaner_app/project.json",
	"https://raw.fastgit.org/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json",
	// "http://raw.staticdn.net/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json",
	"https://raw.githubusercontent.com/gucong3000/MiuiCleaner/main/src/miui_cleaner_app/project.json",
]).then(res => res.ok && res.json()).then(remote => {
	if (!remote) {
		return;
	}
	if (project.versionCode >= parseInt(remote.versionCode)) {
		return;
	}
	return dialogs.confirm(
		`发现新版本:${remote.versionName},是否升级?`,
		{
			title: "版本更新",
			neutral: true,
		},
	).then(confirm => {
		if (!confirm) {
			if (confirm === null) {
				app.openUrl("https://github.com/gucong3000/MiuiCleaner/releases/latest");
			}
			return;
		}
		return getFastUrl(remote).then(options => download(remote, options));
	});
}).catch(console.error);


================================================
FILE: src/miui_cleaner_app/waitForBack.js
================================================

function waitForBack (leave) {
	return new Promise((resolve) => {
		const timer = setTimeout(resolve, 0x200);
		ui.emitter.once("pause", () => {
			ui.emitter.once("resume", resolve);
			clearTimeout(timer);
		});
		if (leave) {
			setTimeout(leave, 0);
		};
	});
};

module.exports = waitForBack;


================================================
FILE: src/miui_cleaner_app/webView.js
================================================
const asyncFnWrap = (taskId, fn, args) => {
	function toErrorObj (reason) {
		if (reason instanceof Error) {
			const errObj = {};
			[
				"message",
				"name",
				"stack",
			].forEach(key => {
				if (reason[key]) {
					errObj[key] = reason[key];
				}
			});
			return errObj;
		}
		return reason;
	}

	let value;
	try {
		value = fn.apply(this, args);
	} catch (reason) {
		return {
			status: "rejected",
			reason: toErrorObj(reason),
		};
	}

	if (value && value.then) {
		const jsbridge = `jsbridge://async:${taskId}`;
		Promise.resolve(value).then(
			value => ({
				status: "fulfilled",
				value,
			}),
			reason => ({
				status: "rejected",
				reason: toErrorObj(reason),
			}),
		).then(result => {
			setTimeout(() => {
				location.href = `${jsbridge}/${btoa(encodeURI(JSON.stringify(result)))}`;
			}, 0);
		});
		return jsbridge;
	}
	return {
		status: "fulfilled",
		value,
	};
};

function webView (options) {
	console.log("使用webView解析:", options.url);
	const webView = ui.inflate(`
		<webview />
	`);

	const jsBridge = events.emitter();
	const consoleBridge = events.emitter();
	let taskId = 0;
	const docReady = {};

	// https://developer.android.google.cn/reference/android/webkit/WebSettings
	const settings = webView.getSettings();
	settings.setBlockNetworkImage(true);
	settings.setJavaScriptEnabled(true);
	settings.setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
	if (options.userAgent) {
		settings.setUserAgentString(options.userAgent);
	}
	if (DEBUG && webView.setWebContentsDebuggingEnabled) {
		webView.setWebContentsDebuggingEnabled(true);
	}
	// let lastValue;
	// jsBridge.on("pageFinished", (webView, url) => {
	// 	console.log("pageFinished", webView, url, lastValue);
	// 	setTimeout(() => {
	// 		console.log(lastValue);
	// 	}, 800);
	// });

	function evaluate (fn, args) {
		return new Promise((resolve, reject) => {
			function then (result) {
				if (result.status === "fulfilled") {
					resolve(result.value);
				} else if (result.status === "rejected") {
					const reason = result.reason;
					if (reason) {
						const error = new (global[result.reason.name] || Error)(reason.message, reason);
						reject(Object.assign(error, reason));
					} else {
						reject(reason);
					}
				}
			}
			function onReceiveValue (result) {
				// lastValue = result;
				result = result && JSON.parse(result);
				if (result) {
					if (result.status) {
						return then(result);
					} else if (typeof result === "string" && result.startsWith("jsbridge://")) {
						return jsBridge.once(result.slice(11), then);
					}
				}
				reject(new Error("evaluateJavascript: no result"));
			};
			jsBridge.once("receivedError", reject);
			webView.evaluateJavascript(
				`javascript:(${asyncFnWrap.toString().trim()})(${taskId++}, (${fn.toString().trim()}), ${JSON.stringify(args)});`,
				new JavaAdapter(
					android.webkit.ValueCallback,
					{
						onReceiveValue,
					},
				),
			);
		});
	}

	function consoleMessage (msg) {
		const MessageLevel = android.webkit.ConsoleMessage.MessageLevel;
		const message = msg.message();
		// if (sourceId !== href) {
		// 	return;
		// }
		let level;
		// https://developer.android.google.cn/reference/android/webkit/ConsoleMessage
		switch (msg.messageLevel()) {
			case MessageLevel.TIP: {
				level = "verbose";
				break;
			}
			case MessageLevel.WARNING: {
				if (message.startsWith("A parser-blocking,") || message.startsWith("Mixed Content:")) {
					return;
				}
				level = "warn";
				break;
			}
			case MessageLevel.ERROR: {
				level = "error";
				break;
			}
			case MessageLevel.DEBUG: {
				level = "info";
				break;
			}
			default: {
				level = "log";
				break;
			}
		}
		const href = webView.getUrl().replace(/#.*$/, "");
		const sourceId = msg.sourceId() || href;
		const stackTraces = [
			`${sourceId}:${msg.lineNumber()}`,
		];
		if (sourceId !== href) {
			stackTraces.push(href);
		}
		consoleBridge.emit("data", level, message, stackTraces.map(decodeURI));
		consoleBridge.emit(level, message, stackTraces);
	}

	function pipeEvent (...args) {
		return jsBridge.emit.bind(jsBridge, ...args);
	}

	// https://developer.android.google.cn/reference/android/webkit/WebChromeClient
	webView.setWebChromeClient(
		new JavaAdapter(android.webkit.WebChromeClient, {
			onConsoleMessage: consoleMessage,
			onProgressChanged: pipeEvent("progressChanged"),
		}),
	);
	// https://developer.android.google.cn/reference/android/webkit/WebViewClient
	webView.setWebViewClient(new JavaAdapter(android.webkit.WebViewClient, {
		shouldOverrideUrlLoading: (webView, webResourceRequest) => {
			let url = webResourceRequest.getUrl().toString();
			if (url.startsWith("jsbridge://")) {
				url = new URL(url);
				jsBridge.emit(url.host, JSON.parse(decodeURI(global.$base64.decode(url.pathname.slice(1)))));
				return true;
			}
			return false;
		},
		onReceivedError: (webView, resourceRequest, resourceError) => {
			if (resourceRequest.isForMainFrame()) {
				jsBridge.emit("receivedError", resourceError);
			}
		},
		onPageFinished: pipeEvent("pageFinished"),
	}));
	webView.setDownloadListener({
		onDownloadStart: (url, userAgent, disposition, type, length) => {
			jsBridge.emit("downloadStart", {
				url,
				headers: {
					"content-disposition": disposition,
					"content-length": length,
					"content-type": type,
					"user-agent": userAgent,
					// "referer": url.origin + url.pathname,
				},
			});
		},
	});

	jsBridge.on("pageFinished", checkReadyState);
	jsBridge.on("progressChanged", checkReadyState);
	function checkReadyState (webView, ...args) {
		const url = webView.getUrl();
		if (!url || /^\w+:\w+$/.test(url) || docReady[url]) {
			return;
		}
		return evaluate(() => (
			document.body?.children.length && document.readyState === "complete"
		)).then(readyState => {
			if (!docReady[url] && readyState) {
				docReady[url] = true;
				jsBridge.emit("documentReady", webView, ...args);
			}
			return readyState;
		});
	}

	function ready (...args) {
		return new Promise((resolve, reject) => {
			jsBridge.once("documentReady", () => resolve());
			jsBridge.once("pageFinished", () => resolve());
			jsBridge.once("receivedError", reject);
		}).then(() => args.length && evaluate(...args));
	}

	function cancel () {
		webView.removeAllViews();
		webView.destroy();
	}
	webView.loadUrl(options.url);
	return {
		webView,
		jsBridge,
		evaluate,
		console: consoleBridge,
		ready,
		cancel,
	};
}

module.exports = webView;


================================================
FILE: src/miui_cleaner_cmd/main.cmd
================================================
@echo off
chcp 65001>nul 2>nul
title MiuiCleaner - MIUI广告清理工具

:start
adb shell pm list packages>"%temp%\adb_list_packages.tmp" 2>&1
if %errorlevel%==1 (
	findstr /c:"no devices" "%temp%\adb_list_packages.tmp">nul 2>nul && (
		echo · 请确保已用数据线连接手机和电脑
		echo · 请确保小米手机助手可正常连接手机
		echo · 请确保已请确保USB调试模式已打开
	) || findstr /c:"unauthorized" "%temp%\adb_list_packages.tmp">nul 2>nul && (
		echo · 请在手机弹出授权提示时点击“确定”
	) || (
		type "%temp%\adb_list_packages.tmp"
	)
	echo.
	echo 请在排除故障后按任意键重试
	pause>nul 2>nul
	goto:start
) else if %errorlevel% geq 1 (
	echo 错误:请将本程序移动到小米手机助手安装目录内再运行,按任意键前往小米手机助手官网
	pause>nul 2>nul
	start http://zhushou.xiaomi.com/
	goto:eof
)

call:pkg_exist "com.github.gucong3000.miui.cleaner" || (
	if exist "MiuiCleaner.apk" (
		call:apk_install
	) else (
		echo 未在手机上找到“MiuiCleaner”,请将“MiuiCleaner.apk”拷贝至"%CD%"文件夹,或直接安装在手机后,按任意键重试
		pause>nul 2>nul
		goto:start
	)
)

del /f /s /q "%temp%\adb_list_packages.tmp">nul 2>nul

adb shell settings put secure install_non_market_apps 1>nul 2>nul
adb shell settings put global development_settings_enabled 1>nul 2>nul
adb shell settings put global adb_enabled 1>nul 2>nul
adb shell setprop persist.security.adbinput 1>nul 2>nul

adb shell pm grant com.github.gucong3000.miui.cleaner android.permission.REQUEST_INSTALL_PACKAGES>nul 2>nul
adb shell pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SECURE_SETTINGS>nul 2>nul
adb shell pm grant com.github.gucong3000.miui.cleaner android.permission.SYSTEM_ALERT_WINDOW>nul 2>nul
adb shell pm grant com.github.gucong3000.miui.cleaner android.permission.WRITE_SETTINGS>nul 2>nul

echo 正等候手机端发出指令...
goto:adb_server

:pkg_exist
    findstr /b /e /c:"package:%~1" "%temp%\adb_list_packages.tmp">nul 2>nul
goto:eof

:apk_install
	echo 正在手机上安装 MiuiCleaner.apk,请在手机上弹出的“USB安装提示”窗口,点击“继续安装”按钮
	adb install -t -r -g MiuiCleaner.apk>nul 2>nul || (
		echo 安装失败,如果手机上没有弹出“USB安装提示”窗口,请打开“设置 → 更多设置 → 开发者选项”,打开“USB安装”与“USB调试(安全设置)”
		echo 如果手机上依然没有显示该弹窗,请打开“设置 → 应用设置 → 授权管理 → USB安装”,关闭对 MiuiCleaner 的限制
		echo 排除以上故障后请按任意键重试
		pause>nul 2>nul
		cls
		goto:apk_install
	)
	adb shell am start -n com.github.gucong3000.miui.cleaner/com.stardust.auojs.inrt.SplashActivity>nul 2>nul
goto:eof

:adb_server
	adb shell sh /sdcard/Download/MiuiCleaner.sh>nul 2>nul && (
		echo 执行成功!正等候手机端发出下一个指令...
	)
	timeout /t 1>nul 2>nul
goto:adb_server


================================================
FILE: types/adbkit.d.ts
================================================

// import * as a from "adbkit";

declare module "adbkit" {
    import { ReadStream } from "fs";

    export interface TcpUsbServer { }

    export interface Connection { }

    export interface Device {
        id: string;
        type: string;
    }

    export interface Client {
        createTcpUsbBridge(serial: string): TcpUsbServer;
        connection(): Promise<Connection>;
        version(): any;
        listDevices(): Promise<Device[]>;
        screencap(serial: string): Promise<ReadStream>;
    }

    export interface ClientOptions {
        host?: string;
        port?: number;
        bin?: string;
    }

    export function createClient(options?: ClientOptions): Client;

    export var KeyCode: any;

    export var util: any;
}

================================================
FILE: types/auto.d.ts
================================================
/// <reference path="./modules/global.d.ts" />
/// <reference path="./modules/app.d.ts" />
/// <reference path="./modules/console.d.ts" />
/// <reference path="./modules/coordinate.d.ts" />
/// <reference path="./modules/root.d.ts" />
/// <reference path="./modules/device.d.ts" />
/// <reference path="./modules/dialogs.d.ts" />
/// <reference path="./modules/engines.d.ts" />
/// <reference path="./modules/events.d.ts" />
/// <reference path="./modules/floaty.d.ts" />
/// <reference path="./modules/files.d.ts" />
/// <reference path="./modules/media.d.ts" />
/// <reference path="./modules/sensors.d.ts" />
/// <reference path="./modules/http.d.ts" />
/// <reference path="./modules/images.d.ts" />
/// <reference path="./modules/colors.d.ts" />
/// <reference path="./modules/keys.d.ts" />
/// <reference path="./modules/storages.d.ts" />
/// <reference path="./modules/widgets.d.ts" />
/// <reference path="./modules/ui.d.ts" />
/// <reference path="./modules/threads.d.ts" />


declare global {

}

export { };

================================================
FILE: types/autojs.d.ts
================================================
/* 内置模块 */

/*
 * based on commit "cf1e602"
 * 文件结构
 * 
 * -模块
 *     -命名空间
 *     -全局
 * 
 * 未加入:WidgetsBasedAutomation、Shell、Thread、UI、Work with Java
 * 
 */
declare module 'global' {

    /**
     * 表示一个点(坐标)。
     */
    interface Point {
        x: number;
        y: number;
    }

    /**
     * app模块提供一系列函数,用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。
     */
    namespace app {

        /**
         * 通过应用名称启动应用。如果该名称对应的应用不存在,则返回false; 否则返回true。如果该名称对应多个应用,则只启动其中某一个。
         */
        function launchApp(appName: string): boolean;

        /** 
         * 通过应用包名启动应用。如果该包名对应的应用不存在,则返回false;否则返回true。 
         */
        function launch(packageName: string): boolean;

        /**
         * 通过应用包名启动应用。如果该包名对应的应用不存在,则返回false;否则返回true。 
         */
        function launchPackage(packageName: string): boolean;

        /**
         * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回null;如果该名称对应多个应用,则只返回其中某一个的包名。
         */
        function getPackageName(appName: string): string;

        /**
         * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用,返回null。
         */
        function getAppName(packageName: string): string;

        /**
         * 打开应用的详情页(设置页)。如果找不到该应用,返回false; 否则返回true。
         */
        function openAppSetting(packageName: string): boolean;

        /**
         * 用其他应用查看文件。文件不存在的情况由查看文件的应用处理。如果找不出可以查看该文件的应用,则抛出ActivityNotException。
         * 
         * @throws ActivityNotException
         */
        function viewFile(path: string): void;

        /**
         * 用其他应用编辑文件。文件不存在的情况由编辑文件的应用处理。如果找不出可以编辑该文件的应用,则抛出ActivityNotException。
         * 
         * @throws ActivityNotException
         */
        function editFile(path: string): void;

        /**
         * 卸载应用。执行后会会弹出卸载应用的提示框。如果该包名的应用未安装,由应用卸载程序处理,可能弹出"未找到应用"的提示。
         */
        function uninstall(packageName: string): void;

        /**
         * 用浏览器打开网站url。网站的Url,如果不以"http:// "或"https:// "开头则默认是"http:// "。
         */
        function openUrl(url: string): void;

        /**
         * 发送邮件的参数,这些选项均是可选的。
         */
        interface SendEmailOptions {
            /**
             * 收件人的邮件地址。如果有多个收件人,则用字符串数组表示
             */
            email?: string | string[];
            /**
             * 抄送收件人的邮件地址。如果有多个抄送收件人,则用字符串数组表示
             */
            cc?: string | string[];
            /**
             * 密送收件人的邮件地址。如果有多个密送收件人,则用字符串数组表示
             */
            bcc?: string | string[];
            /**
             * 邮件主题(标题)
             */
            subject?: string;
            /**
             * 邮件正文
             */
            text?: string;
            /**
             * 附件的路径。
             */
            attachment?: string;
        }

        /**
         * 根据选项options调用邮箱应用发送邮件。如果没有安装邮箱应用,则抛出ActivityNotException。
         */
        function sendEmail(options: SendEmailOptions): void;

        /**
         * 启动Auto.js的特定界面。该函数在Auto.js内运行则会打开Auto.js内的界面,在打包应用中运行则会打开打包应用的相应界面。
         */
        function startActivity(name: 'console' | 'settings'): void;

        /**
         * Intent(意图) 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信.
         */
        interface Intent { }

        /**
         * 构造意图Intent对象所需设置。
         */
        interface IntentOptions {
            action?: string;
            type?: string;
            data?: string;
            category?: string[];
            packageName?: string;
            className?: string;
            extras?: Object;
        }

        /**
         * 根据选项,构造一个意图Intent对象。
         */
        function intent(options: IntentOptions): Intent;

        /**
         * 根据选项构造一个Intent,并启动该Activity。
         */
        function startActivity(intent: Intent): void;

        /**
         * 根据选项构造一个Intent,并发送该广播。
         */
        function sendBroadcast(intent: Intent): void;
    }

    /**
     * 通过应用名称启动应用。如果该名称对应的应用不存在,则返回false; 否则返回true。如果该名称对应多个应用,则只启动其中某一个。
     */
    function launchApp(appName: string): boolean;

    /** 
     * 通过应用包名启动应用。如果该包名对应的应用不存在,则返回false;否则返回true。 
     */
    function launch(packageName: string): boolean;

    /**
     * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回null;如果该名称对应多个应用,则只返回其中某一个的包名。
     */
    function getPackageName(appName: string): string;

    /**
     * 获取应用名称对应的已安装的应用的包名。如果该找不到该应用,返回null;如果该名称对应多个应用,则只返回其中某一个的包名。
     */
    function getPackageName(appName: string): string;

    /**
     * 获取应用包名对应的已安装的应用的名称。如果该找不到该应用,返回null。
     */
    function getAppName(packageName: string): string;

    /**
     * 打开应用的详情页(设置页)。如果找不到该应用,返回false; 否则返回true。
     */
    function openAppSetting(packageName: string): boolean;


    // interface Console {
    //     show(): void;
    //     verbose(): void;
    // }

    /**
     * 控制台模块提供了一个和Web浏览器中相似的用于调试的控制台。用于输出一些调试信息、中间结果等。 console模块中的一些函数也可以直接作为全局函数使用,例如log, print等。
     */
    namespace console {

        /**
         * 显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。
         */
        function show(): void;

        /**
         * 隐藏控制台悬浮窗。
         */
        function hide(): void;

        /**
         * 清空控制台。
         */
        function clear(): void;

        /**
         * 打印到控制台,并带上换行符。 可以传入多个参数,第一个参数作为主要信息,其他参数作为类似于 printf(3) 中的代替值(参数都会传给 util.format())。
         */
        function log(data: string, ...args: any[]): void;

        /**
         * 与console.log类似,但输出结果以灰色字体显示。输出优先级低于log,用于输出观察性质的信息。
         */
        function verbose(data: string, ...args: any[]): void;

        /**
         * 与console.log类似,但输出结果以绿色字体显示。输出优先级高于log, 用于输出重要信息。
         */
        function info(data: string, ...args: any[]): void;

        /**
         * 与console.log类似,但输出结果以蓝色字体显示。输出优先级高于info, 用于输出警告信息。
         */
        function warn(data: string, ...args: any[]): void;

        /**
         * 与console.log类似,但输出结果以红色字体显示。输出优先级高于warn, 用于输出错误信息。
         */
        function error(data: string, ...args: any[]): void;

        /**
         * 断言。如果value为false则输出错误信息message并停止脚本运行。
         */
        function assert(value: boolean, message: string);

        /**
         * 与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串用eval计算后返回。
         */
        function input(data: string, ...args: any[]): string | number | boolean;

        /**
         * 与console.log一样输出信息,并在控制台显示输入框等待输入。按控制台的确认按钮后会将输入的字符串直接返回。
         */
        function rawInput(data: string, ...args: any[]): string;

        /**
         * 设置控制台的大小,单位像素。
         */
        function setSize(wight: number, height: number): void;

        /**
         * 设置控制台的位置,单位像素。
         */
        function setPosition(x: number, y: number): void;

    }


    /**
     * 打印到控制台,并带上换行符。 可以传入多个参数,第一个参数作为主要信息,其他参数作为类似于 printf(3) 中的代替值(参数都会传给 util.format())。
     */
    function log(data: string, ...args: any[]): void;

    /**
     * 相当于log(text)。
     */
    function print(message: string | Object): void;


    /* 基于坐标的触摸模拟 */

    /**
     * 设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。
     */
    function setScreenMetrics(width: number, height: number): void;

    /* 安卓7.0以上的触摸和手势模拟 */

    /**
     * Android7.0以上
     * 
     * 模拟点击坐标(x, y)大约150毫秒,并返回是否点击成功。只有在点击执行完成后脚本才继续执行。
     */
    function click(x: number, y: number): void;

    /**
     * Android7.0以上
     * 
     * 模拟长按坐标(x, y), 并返回是否成功。只有在长按执行完成(大约600毫秒)时脚本才会继续执行。
     */
    function longClick(x: number, y: number): void;

    /**
     * Android7.0以上
     * 
     * 模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。
     *
     * 如果按住时间过短,那么会被系统认为是点击;如果时长超过500毫秒,则认为是长按。
     */
    function press(x: number, y: number, duration: number): void;

    /**
     * 模拟从坐标(x1, y1)滑动到坐标(x2, y2),并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。
     */
    function swipe(x1: number, y1: number, x2: number, y2: number, duration: number): boolean;

    type GesturePoint = [number, number];
    /**
     * 模拟手势操作。例如gesture(1000, [0, 0], [500, 500], [500, 1000])为模拟一个从(0, 0)到(500, 500)到(500, 100)的手势操作,时长为2秒。
     */
    function gesture(duration: number, point1: GesturePoint, point2: GesturePoint, ...points: GesturePoint[]): void;

    type Gesture = [number, number, GesturePoint, GesturePoint] | [number, GesturePoint, GesturePoint];
    /**
     * 同时模拟多个手势。每个手势的参数为[delay, duration, 坐标], delay为延迟多久(毫秒)才执行该手势;duration为手势执行时长;坐标为手势经过的点的坐标。其中delay参数可以省略,默认为0。
     */
    function gestures(gesture: Gesture, ...gestures: Gesture[]): void;

    /**
     * RootAutomator是一个使用root权限来模拟触摸的对象,用它可以完成触摸与多点触摸,并且这些动作的执行没有延迟。
     * 
     * 一个脚本中最好只存在一个RootAutomator,并且保证脚本结束退出他。
     */
    class RootAutomator {
        /**
         * 点击位置(x, y)。其中id是一个整数值,用于区分多点触摸,不同的id表示不同的"手指"。
         */
        tap(x: number, y: number, id?: number): void;

        /**
         * 模拟一次从(x1, y1)到(x2, y2)的时间为duration毫秒的滑动。
         */
        swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;

        /**
         * 模拟按下位置(x, y),时长为duration毫秒。
         */
        press(x: number, y: number, duration: number, id?: number): void;

        /**
         * 模拟长按位置(x, y)。
         */
        longPress(x: number, y: number, duration?: number, id?: number): void;

        /**
         * 模拟手指按下位置(x, y)。
         */
        touchDown(x: number, y: number, id?: number): void;

        /**
         * 模拟移动手指到位置(x, y)。
         */
        touchMove(x: number, y: number, id?: number): void;

        /**
         * 模拟手指弹起。
         */
        touchUp(id?: number): void;

    }

    /**
     * 需要Root权限
     * 
     * 实验API,请勿过度依赖
     * 
     * 点击位置(x, y), 您可以通过"开发者选项"开启指针位置来确定点击坐标。
     */
    function Tap(x: number, y: number): void;

    /**
     * 需要Root权限
     * 
     * 实验API,请勿过度依赖
     * 
     * 滑动。从(x1, y1)位置滑动到(x2, y2)位置。
     */
    function Swipe(x1: number, x2: number, y1: number, y2: number, duration?: number): void;

    /**
     * device模块提供了与设备有关的信息与操作,例如获取设备宽高,内存使用率,IMEI,调整设备亮度、音量等。
     * 
     * 此模块的部分函数,例如调整音量,需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
     */
    namespace device {

        /**
         * 设备屏幕分辨率宽度。例如1080。
         */
        var width: number;

        /**
         * 设备屏幕分辨率高度。例如1920。
         */
        var height: number;

        /**
         * 修订版本号,或者诸如"M4-rc20"的标识。
         */
        var buildId: string;

        /**
         * 设备的主板(?)名称。
         */
        var broad: string;

        /**
         * 与产品或硬件相关的厂商品牌,如"Xiaomi", "Huawei"等。
         */
        var brand: string;

        /**
         * 设备在工业设计中的名称(代号)。
         */
        var device: string;

        /**
         * 设备型号。
         */
        var model: string;

        /**
         * 整个产品的名称。
         */
        var product: string;

        /**
         * 设备Bootloader的版本。
         */
        var bootloader: string;

        /**
         * 设备的硬件名称(来自内核命令行或者/proc)。
         */
        var hardware: string;

        /**
         * 构建(build)的唯一标识码。
         */
        var fingerprint: string;

        /**
         * 硬件序列号。
         */
        var serial: string;

        /**
         * 安卓系统API版本。例如安卓4.4的sdkInt为19。
         */
        var sdkInt: number;

        /**
         * 设备固件版本号。
         */
        var incremental: string;

        /**
         * Android系统版本号。例如"5.0", "7.1.1"。
         */
        var release: string;

        /**
         * 基础操作系统。
         */
        var baseOS: string;

        /**
         * 安全补丁程序级别。
         */
        var securityPatch: string;

        /**
         * 开发代号,例如发行版是"REL"。
         */
        var codename: string;

        /**
         * 返回设备的IMEI。
         */
        function getIMEI(): string;

        /**
         * 返回设备的Android ID。
         * 
         * Android ID为一个用16进制字符串表示的64位整数,在设备第一次使用时随机生成,之后不会更改,除非恢复出厂设置。
         */
        function getAndroidId(): string;

        /**
         * 返回设备的Mac地址。该函数需要在有WLAN连接的情况下才能获取,否则会返回null。
         * 
         * 可能的后续修改:未来可能增加有root权限的情况下通过root权限获取,从而在没有WLAN连接的情况下也能返回正确的Mac地址,因此请勿使用此函数判断WLAN连接。
         */
        function getMacAddress(): string;

        /**
         * 返回当前的(手动)亮度。范围为0~255。
         */
        function getBrightness(): number;

        /**
         * 返回当前亮度模式,0为手动亮度,1为自动亮度。
         */
        function getBrightnessMode(): number;

        /**
         * 设置当前手动亮度。如果当前是自动亮度模式,该函数不会影响屏幕的亮度。
         * 
         * 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
         */
        function setBrightness(b: number): void;

        /**
         * 设置当前亮度模式。
         * 
         * 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
         */
        function setBrightnessMode(mode: 0 | 1): void;

        /**
         * 返回当前媒体音量。
         */
        function getMusicVolume(): number;

        /**
         * 返回当前通知音量。
         */
        function getNotificationVolume(): number;

        /**
         * 返回当前闹钟音量。
         */
        function getAlarmVolume(): number;

        /**
         * 返回媒体音量的最大值。
         */
        function getMusicMaxVolume(): number;

        /**
         * 返回通知音量的最大值。
         */
        function getNotificationMaxVolume(): number;

        /**
         * 返回闹钟音量的最大值。
         */
        function getAlarmMaxVolume(): number;

        /**
         * 设置当前媒体音量。
         * 
         * 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
         */
        function setMusicVolume(volume: number): void;

        /**
         * 设置当前通知音量。
         * 
         * 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
         */
        function setNotificationVolume(volume: number): void;

        /**
         * 设置当前闹钟音量。
         * 
         * 此函数需要"修改系统设置"的权限。如果没有该权限,会抛出SecurityException并跳转到权限设置界面。
         */
        function setAlarmVolume(volume: number): void;

        /**
         * 返回当前电量百分比。
         */
        function getBattery(): number;

        /**
         * 返回设备是否正在充电。
         */
        function isCharging(): boolean;

        /**
         * 返回设备内存总量,单位字节(B)。1MB = 1024 * 1024B。
         */
        function getTotalMem(): number;

        /**
         * 返回设备当前可用的内存,单位字节(B)。
         */
        function getAvailMem(): number;

        /**
         * 返回设备屏幕是否是亮着的。如果屏幕亮着,返回true; 否则返回false。
         * 
         * 需要注意的是,类似于vivo xplay系列的息屏时钟不属于"屏幕亮着"的情况,虽然屏幕确实亮着但只能显示时钟而且不可交互,此时isScreenOn()也会返回false。
         */
        function isScreenOn(): boolean;

        /**
         * 唤醒设备。包括唤醒设备CPU、屏幕等。可以用来点亮屏幕。
         */
        function wakeUp(): void;

        /**
         * 如果屏幕没有点亮,则唤醒设备。
         */
        function wakeUpIfNeeded(): void;

        /**
         * 保持屏幕常亮。
         * 
         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。
         * 
         * 在某些设备上,如果不加参数timeout,只能在Auto.js的界面保持屏幕常亮,在其他界面会自动失效,这是因为设备的省电策略造成的。因此,建议使用比较长的时长来代替"一直保持屏幕常亮"的功能,例如device.keepScreenOn(3600 * 1000)。
         * 
         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。
         */
        function keepScreenOn(timeout: number): void;

        /**
         * 保持屏幕常亮,但允许屏幕变暗来节省电量。此函数可以用于定时脚本唤醒屏幕操作,不需要用户观看屏幕,可以让屏幕变暗来节省电量。
         * 
         * 此函数无法阻止用户使用锁屏键等正常关闭屏幕,只能使得设备在无人操作的情况下保持屏幕常亮;同时,如果此函数调用时屏幕没有点亮,则会唤醒屏幕。
         * 
         * 可以使用device.cancelKeepingAwake()来取消屏幕常亮。
         */
        function keepScreenDim(timeout: number): void;

        /**
         * 取消设备保持唤醒状态。用于取消device.keepScreenOn(), device.keepScreenDim()等函数设置的屏幕常亮。
         */
        function cancelKeepingAwake(): void;

        /**
         * 使设备震动一段时间。
         */
        function vibrate(millis: number): void;

        /**
         * 如果设备处于震动状态,则取消震动。
         */
        function cancelVibration(): void;

    }

    /**
     * dialogs 模块提供了简单的对话框支持,可以通过对话框和用户进行交互。
     */
    namespace dialogs {

        /**
         * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。
         */
        function alert(title: string, content?: string): void;

        /**
         * UI模式
         * 
         * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确定脚本才继续运行。
         */
        function alert(title: string, content?: string, callback?: () => void): Promise<void>;

        /**
         * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ,否则返回 false 。
         */
        function confirm(title: string, content?: string): boolean;

        /**
         * UI模式
         * 
         * 显示一个包含“确定”和“取消”按钮的提示对话框。如果用户点击“确定”则返回 true ,否则返回 false 。
         */
        function confirm(title: string, content?: string, callback?: (value: boolean) => void): Promise<boolean>;

        /**
         * 显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。
         */
        function rawInput(title: string, prefill?: string): string;

        /**
         * UI模式
         * 
         * 显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。
         */
        function rawInput(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;

        /**
         * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于,会把输入的字符串用eval计算一遍再返回,返回的可能不是字符串。
         */
        function input(title: string, prefill?: string): any;

        /**
         * UI模式
         * 
         * 等效于 eval(dialogs.rawInput(title, prefill, callback)), 该函数和rawInput的区别在于,会把输入的字符串用eval计算一遍再返回,返回的可能不是字符串。
         */
        function input(title: string, prefill?: string, callback?: (value: any) => void): Promise<any>;

        /**
         * 显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。
         */
        function prompt(title: string, prefill?: string): string;

        /**
         * UI模式
         * 
         * 显示一个包含输入框的对话框,等待用户输入内容,并在用户点击确定时将输入的字符串返回。如果用户取消了输入,返回null。
         */
        function prompt(title: string, prefill?: string, callback?: (value: string) => void): Promise<string>;

        /**
         * 显示一个带有选项列表的对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。
         */
        function select(title: string, items: string[]): number;

        /**
         * UI模式
         * 
         * 显示一个带有选项列表的对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。
         */
        function select(title: string, items: string[], callback?: (value: number) => void): Promise<number>;

        /**
         * 显示一个单选列表对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。
         */
        function singleChoice(title: string, items: string[], index?: number): number;

        /**
         * UI模式
         * 
         * 显示一个单选列表对话框,等待用户选择,返回用户选择的选项索引(0 ~ item.length - 1)。如果用户取消了选择,返回-1。
         */
        function singleChoice(title: string, items: string[], index?: number, callback?: (choice: number) => void): Promise<number>;

        /**
         * 显示一个多选列表对话框,等待用户选择,返回用户选择的选项索引的数组。如果用户取消了选择,返回[]。
         */
        function multiChoice(title: string, items: string[], indices?: number[]): number[];

        /**
         * UI模式
Download .txt
gitextract_c6dwjbbo/

├── .editorconfig
├── .eslintrc.js
├── .github/
│   └── workflows/
│       └── webpack.yml
├── .gitignore
├── LICENSE
├── README.md
├── autojs-deploy.js
├── babel.config.js
├── package.json
├── project.js
├── src/
│   ├── miui_cleaner_app/
│   │   ├── 123pan.js
│   │   ├── appDesc.js
│   │   ├── appManager.js
│   │   ├── dialogs.js
│   │   ├── downApp.js
│   │   ├── downFile.js
│   │   ├── emitItemShowEvent.js
│   │   ├── fetch.js
│   │   ├── findClickableParent.js
│   │   ├── getApplicationInfo.js
│   │   ├── getRemoteFileInfo.js
│   │   ├── index.js
│   │   ├── instApk.js
│   │   ├── lanzou.js
│   │   ├── multiChoice.js
│   │   ├── offAppAd.js
│   │   ├── project.json
│   │   ├── recycle.js
│   │   ├── serviceMgr.js
│   │   ├── services.js
│   │   ├── settings.js
│   │   ├── singleChoice.js
│   │   ├── startActivity.js
│   │   ├── support.js
│   │   ├── sysAppRm.js
│   │   ├── test/
│   │   │   ├── getRemoteFileInfo.js
│   │   │   └── services.js
│   │   ├── update.js
│   │   ├── waitForBack.js
│   │   └── webView.js
│   └── miui_cleaner_cmd/
│       └── main.cmd
├── types/
│   ├── adbkit.d.ts
│   ├── auto.d.ts
│   ├── autojs.d.ts
│   └── modules/
│       ├── app.d.ts
│       ├── colors.d.ts
│       ├── console.d.ts
│       ├── coordinate.d.ts
│       ├── device.d.ts
│       ├── dialogs.d.ts
│       ├── engines.d.ts
│       ├── events.d.ts
│       ├── files.d.ts
│       ├── floaty.d.ts
│       ├── global.d.ts
│       ├── http.d.ts
│       ├── images.d.ts
│       ├── keys.d.ts
│       ├── media.d.ts
│       ├── root.d.ts
│       ├── sensors.d.ts
│       ├── storages.d.ts
│       ├── threads.d.ts
│       ├── ui.d.ts
│       └── widgets.d.ts
└── webpack.config.js
Download .txt
SYMBOL INDEX (244 symbols across 47 files)

FILE: autojs-deploy.js
  class AutojsDeployPlugin (line 9) | class AutojsDeployPlugin {
    method constructor (line 10) | constructor (options = {}) {
    method getProjectConfig (line 39) | async getProjectConfig () {
    method getPackageName (line 54) | async getPackageName (device) {
    method logcat (line 69) | async logcat (compilation) {
    method shell (line 130) | shell (device, ...args) {
    method eachDevice (line 139) | async eachDevice (...args) {
    method deploy (line 152) | deploy (compilation) {
    method updateAsset (line 203) | async updateAsset (compilation) {
    method apply (line 256) | apply (compiler) {
  class LogTransform (line 290) | class LogTransform extends Transform {
    method constructor (line 291) | constructor () {
    method toSourcePath (line 305) | toSourcePath (options) {
    method traceFormat (line 324) | traceFormat (options) {
    method _transform (line 350) | _transform (string, encoding, callback) {
    method _flush (line 433) | _flush (callback) {

FILE: project.js
  function readFile (line 85) | async function readFile (path) {
  function updateDoc (line 111) | function updateDoc (readme) {

FILE: src/miui_cleaner_app/123pan.js
  function getFileInfo (line 13) | async function getFileInfo (url) {
  function parseFileInfo (line 31) | async function parseFileInfo (fileInfo, url, initialProps) {
  function checkResponse (line 63) | async function checkResponse (res) {
  function parseUrl (line 69) | function parseUrl (url) {
  class FileInfo (line 76) | class FileInfo {
    method constructor (line 77) | constructor (data) {
    method fileName (line 81) | get fileName () {
    method size (line 85) | get size () {
    method lastModified (line 89) | get lastModified () {
    method id (line 93) | get id () {
    method getLocation (line 97) | async getLocation (redirect) {
  function parse (line 104) | async function parse (url, options) {
  function getRealFile (line 112) | async function getRealFile (fileInfo, redirect) {

FILE: src/miui_cleaner_app/appManager.js
  function appManager (line 69) | function appManager () {

FILE: src/miui_cleaner_app/dialogs.js
  function alertDialog (line 10) | function alertDialog (
  function confirm (line 123) | function confirm (
  function alert (line 135) | function alert (
  function prompt (line 148) | function prompt (
  function singleChoice (line 169) | function singleChoice (

FILE: src/miui_cleaner_app/downApp.js
  function formatSize (line 211) | function formatSize (number, options) {
  function formatDate (line 221) | function formatDate (number) {
  function download (line 229) | async function download (appInfo, item) {
  function verCompare (line 322) | function verCompare (verA, verB) {
  function fileCompare (line 339) | function fileCompare (b, a) {
  function getRemoteFiles (line 353) | function getRemoteFiles (appInfo) {
  function downApp (line 380) | function downApp () {

FILE: src/miui_cleaner_app/downFile.js
  function getValOfCursor (line 9) | function getValOfCursor (cursor, columnName, columnType) {
  function queryDownList (line 37) | function queryDownList (callback, query) {
  function guessFileName (line 54) | function guessFileName (disposition) {
  function readConfig (line 61) | function readConfig (options) {
  function downFile (line 93) | function downFile (options) {
  function registerReceiver (line 196) | function registerReceiver (sysActionName, onReceive) {
  function createProgressEvent (line 215) | function createProgressEvent (valueOf) {
  function downReceiver (line 231) | function downReceiver () {
  function startDownReceiver (line 255) | function startDownReceiver () {

FILE: src/miui_cleaner_app/emitItemShowEvent.js
  function emitItemShowEvent (line 4) | function emitItemShowEvent (listView, defaultIcon) {

FILE: src/miui_cleaner_app/fetch.js
  function fetchAny (line 7) | function fetchAny (url, options = {}) {
  function fetch (line 31) | function fetch (url, options = {}) {
  class Response (line 81) | class Response {
    method status (line 82) | get status () {
    method ok (line 86) | get ok () {
    method url (line 90) | get url () {
    method redirected (line 94) | get redirected () {
    method statusText (line 98) | get statusText () {
    method headers (line 102) | get headers () {
    method body (line 106) | get body () {
    method blob (line 110) | blob () {
    method arrayBuffer (line 114) | arrayBuffer () {
    method text (line 118) | text () {
    method json (line 122) | json () {
    method clone (line 126) | clone () {
  function hexToArrayUint8Array (line 132) | function hexToArrayUint8Array (input) {
  function wrapResponse (line 140) | function wrapResponse (res, options) {

FILE: src/miui_cleaner_app/findClickableParent.js
  function findClickableParent (line 6) | function findClickableParent (node) {

FILE: src/miui_cleaner_app/getApplicationInfo.js
  function getApplicationInfo (line 5) | function getApplicationInfo (options) {

FILE: src/miui_cleaner_app/getRemoteFileInfo.js
  class Asset (line 5) | class Asset {
    method constructor (line 6) | constructor (data) {
    method getLocation (line 10) | async getLocation () {
  function parseGithubRelease (line 19) | function parseGithubRelease (url, redirect) {
  function parse32r (line 65) | function parse32r (url) {
  function getVersionForFile (line 113) | function getVersionForFile (fileInfo) {
  function getVersion (line 122) | function getVersion (fileInfo) {
  function getOptsFromUrl (line 133) | function getOptsFromUrl (url) {
  function getRemoteFileInfo (line 144) | function getRemoteFileInfo (url) {

FILE: src/miui_cleaner_app/index.js
  function mainMenu (line 59) | function mainMenu () {
  function regBack (line 66) | function regBack () {

FILE: src/miui_cleaner_app/instApk.js
  function instApk (line 4) | function instApk (apk) {

FILE: src/miui_cleaner_app/lanzou.js
  function getFileInfoFromUrl (line 13) | async function getFileInfoFromUrl (url, options) {
  function parseFileInfo (line 111) | function parseFileInfo (fileInfo, url) {
  function getIdByUrl (line 161) | function getIdByUrl (url) {
  function getFileInfoByAjax (line 165) | async function getFileInfoByAjax (url, fileInfo, ajaxConfig, options) {
  function getFileInfo (line 227) | function getFileInfo (url, options) {
  function getRealFile (line 247) | async function getRealFile (fileInfo, redirect) {
  function checkResponse (line 323) | async function checkResponse (res) {
  function parseUrl (line 329) | function parseUrl (url) {
  function getRandomIP (line 336) | function getRandomIP () {
  class FileInfo (line 347) | class FileInfo {
    method constructor (line 348) | constructor (data) {
    method getLocation (line 352) | async getLocation (redirect) {
  function parse (line 359) | async function parse (url, options) {

FILE: src/miui_cleaner_app/multiChoice.js
  function multiChoice (line 4) | function multiChoice (

FILE: src/miui_cleaner_app/offAppAd.js
  function getCleanerList (line 135) | function getCleanerList () {
  function offAppAd (line 145) | function offAppAd () {

FILE: src/miui_cleaner_app/recycle.js
  function launchMarket (line 9) | function launchMarket () {
  function getInstalledPackages (line 27) | function getInstalledPackages () {
  function recycle (line 58) | function recycle () {

FILE: src/miui_cleaner_app/serviceMgr.js
  function sleep (line 2) | function sleep (time) {
  function waitForEngineStart (line 6) | function waitForEngineStart () {
  function waitForEngineStop (line 16) | function waitForEngineStop () {
  function getEngine (line 24) | function getEngine (task) {
  function parseTaskInfo (line 42) | function parseTaskInfo (taskInfo) {
  function start (line 51) | function start (taskList) {

FILE: src/miui_cleaner_app/services.js
  function delay (line 9) | function delay (time) {
  function findByClassName (line 13) | function findByClassName (reg) {
  function findScrollableParent (line 24) | function findScrollableParent (node) {
  function isFrameLayout (line 28) | function isFrameLayout (linear) {
  function getDefaultValue (line 32) | function getDefaultValue (value, defaultValu) {
  function skipConfirmPopup (line 40) | function skipConfirmPopup () {
  function walkListView (line 69) | function walkListView (options = {}) {
  function getDelayTimeByPackageName (line 218) | function getDelayTimeByPackageName (packageName) {
  function startPkg (line 234) | function startPkg ({
  function startAct (line 262) | function startAct ({
  function startIntent (line 294) | function startIntent ({
  function clickButton (line 314) | function clickButton (btnLabelList, text) {
  function skipPopupPage (line 338) | function skipPopupPage () {
  function findOneByRightTopCorner (line 374) | function findOneByRightTopCorner (uiSelector) {
  function findOneByRightBottomCorner (line 387) | function findOneByRightBottomCorner (uiObjList) {
  function openCfgPageByPopupMenu (line 402) | function openCfgPageByPopupMenu (options) {
  function openCfgPageBySubPage (line 419) | function openCfgPageBySubPage (options) {
  function openCfgPage (line 462) | function openCfgPage (options) {
  function clearAnim (line 476) | function clearAnim () {
  function startTask (line 489) | function startTask (options) {
  function switchBroHomePage (line 521) | function switchBroHomePage (listView, options) {
  function click10 (line 556) | function click10 (listView, options) {
  function installApk (line 668) | function installApk (apkPath, resolver) {
  function closeApp (line 683) | function closeApp (pkgName) {
  function selectApp (line 690) | function selectApp (pkgName, appName) {
  function getSettings (line 957) | function getSettings (key) {
  function runTask (line 961) | function runTask (taskInfo) {
  function runTaskList (line 1000) | function runTaskList (taskList) {
  function init (line 1017) | function init () {

FILE: src/miui_cleaner_app/settings.js
  function lazyAction (line 18) | function lazyAction (key, action) {
  function requestSettings (line 80) | function requestSettings (key, expectValue, reason) {
  function tryCmd (line 118) | function tryCmd (cmd, root = true) {
  function pmPermission (line 143) | function pmPermission (key, permission) {
  function getAdbInput (line 217) | function getAdbInput () {
  function enableDependSetting (line 221) | function enableDependSetting (depend, value) {
  function defineSettingProperty (line 228) | function defineSettingProperty ({

FILE: src/miui_cleaner_app/singleChoice.js
  function singleChoice (line 5) | function singleChoice (

FILE: src/miui_cleaner_app/startActivity.js
  function startActivity (line 1) | function startActivity (options) {

FILE: src/miui_cleaner_app/support.js
  function support (line 5) | function support () {

FILE: src/miui_cleaner_app/sysAppRm.js
  function clickButton (line 17) | function clickButton (button, text) {
  function getAppList (line 49) | function getAppList () {
  function installerHelper (line 63) | function installerHelper () {
  function removeByInstaler (line 84) | function removeByInstaler (taskList) {
  function removeByScript (line 112) | function removeByScript (tasks) {
  function sysAppRm (line 176) | function sysAppRm () {

FILE: src/miui_cleaner_app/test/services.js
  function isObject (line 1) | function isObject (val) {
  function isBoolean (line 4) | function isBoolean (value) {
  function compiler (line 191) | function compiler (result, data, options, parent) {

FILE: src/miui_cleaner_app/update.js
  function iec (line 7) | function iec (number, options) {
  function download (line 14) | function download (remote, options) {
  function getFastUrl (line 76) | function getFastUrl (remote) {

FILE: src/miui_cleaner_app/waitForBack.js
  function waitForBack (line 2) | function waitForBack (leave) {

FILE: src/miui_cleaner_app/webView.js
  function toErrorObj (line 2) | function toErrorObj (reason) {
  function webView (line 53) | function webView (options) {

FILE: types/adbkit.d.ts
  type TcpUsbServer (line 7) | interface TcpUsbServer { }
  type Connection (line 9) | interface Connection { }
  type Device (line 11) | interface Device {
  type Client (line 16) | interface Client {
  type ClientOptions (line 24) | interface ClientOptions {

FILE: types/autojs.d.ts
  type Point (line 19) | interface Point {
  type SendEmailOptions (line 86) | interface SendEmailOptions {
  type Intent (line 126) | interface Intent { }
  type IntentOptions (line 131) | interface IntentOptions {
  type GesturePoint (line 314) | type GesturePoint = [number, number];
  type Gesture (line 320) | type Gesture = [number, number, GesturePoint, GesturePoint] | [number, G...
  class RootAutomator (line 331) | class RootAutomator {
  type ScriptEngine (line 804) | interface ScriptEngine {
  type ScriptExecution (line 822) | interface ScriptExecution {
  type ScriptConfig (line 838) | interface ScriptConfig {
  type KeyEvent (line 902) | interface KeyEvent {
  type Keys (line 914) | type Keys = 'volume_up' | 'volume_down' | 'home' | 'back' | 'menu';
  type Toast (line 949) | interface Toast {
  type Notification (line 968) | interface Notification {
  type keys (line 985) | enum keys {
  type EventEmitter (line 993) | interface EventEmitter {
  type FloatyWindow (line 1013) | interface FloatyWindow {
  type byte (line 1028) | type byte = number;
  type ReadableTextFile (line 1058) | interface ReadableTextFile {
  type WritableTextFile (line 1066) | interface WritableTextFile {
  type SensorEventEmitter (line 1090) | interface SensorEventEmitter {
  type delay (line 1099) | enum delay {
  type HttpRequestOptions (line 1132) | interface HttpRequestOptions {
  type Request (line 1138) | interface Request {
  type Response (line 1141) | interface Response {
  type ResponseBody (line 1150) | interface ResponseBody {
  type RequestMultipartBody (line 1160) | interface RequestMultipartBody {
  type Image (line 1171) | interface Image {
  type FindColorOptions (line 1187) | interface FindColorOptions {
  type FindImageOptions (line 1196) | interface FindImageOptions {
  type Storage (line 1248) | interface Storage {
  type UiSelector (line 1277) | interface UiSelector {
  type UiObject (line 1326) | interface UiObject {
  type UiCollection (line 1358) | interface UiCollection {
  type Rect (line 1368) | interface Rect {

FILE: types/modules/app.d.ts
  type SendEmailOptions (line 63) | interface SendEmailOptions {
  type Intent (line 103) | interface Intent { }
  type IntentOptions (line 108) | interface IntentOptions {

FILE: types/modules/console.d.ts
  type Console (line 1) | interface Console {

FILE: types/modules/coordinate.d.ts
  type GesturePoint (line 38) | type GesturePoint = [number, number];
  type Gesture (line 44) | type Gesture = [number, number, GesturePoint, GesturePoint] | [number, G...

FILE: types/modules/engines.d.ts
  type ScriptEngine (line 9) | interface ScriptEngine {
  type ScriptExecution (line 27) | interface ScriptExecution {
  type ScriptConfig (line 43) | interface ScriptConfig {

FILE: types/modules/events.d.ts
  type KeyEvent (line 3) | interface KeyEvent {
  type Keys (line 15) | type Keys = 'volume_up' | 'volume_down' | 'home' | 'back' | 'menu';
  type Toast (line 50) | interface Toast {
  type Notification (line 69) | interface Notification {
  type keys (line 86) | enum keys {
  type EventEmitter (line 94) | interface EventEmitter {

FILE: types/modules/files.d.ts
  type byte (line 2) | type byte = number;
  type ReadableTextFile (line 32) | interface ReadableTextFile {
  type WritableTextFile (line 40) | interface WritableTextFile {

FILE: types/modules/floaty.d.ts
  type FloatyWindow (line 4) | interface FloatyWindow {

FILE: types/modules/global.d.ts
  type Point (line 1) | interface Point {

FILE: types/modules/http.d.ts
  type HttpRequestOptions (line 4) | interface HttpRequestOptions {
  type Request (line 10) | interface Request {
  type Response (line 13) | interface Response {
  type ResponseBody (line 22) | interface ResponseBody {
  type RequestMultipartBody (line 32) | interface RequestMultipartBody {

FILE: types/modules/images.d.ts
  type Image (line 3) | interface Image {
  type FindColorOptions (line 19) | interface FindColorOptions {
  type FindImageOptions (line 28) | interface FindImageOptions {

FILE: types/modules/root.d.ts
  class RootAutomator (line 6) | class RootAutomator {

FILE: types/modules/sensors.d.ts
  type SensorEventEmitter (line 2) | interface SensorEventEmitter {
  type delay (line 11) | enum delay {

FILE: types/modules/storages.d.ts
  type Storage (line 1) | interface Storage {

FILE: types/modules/threads.d.ts
  type ThreadTimerID (line 3) | type ThreadTimerID = number;
  type Thread (line 5) | interface Thread {

FILE: types/modules/ui.d.ts
  type View (line 44) | type View = any;
  type UILike (line 46) | interface UILike {

FILE: types/modules/widgets.d.ts
  type UiSelector (line 17) | interface UiSelector {
  type UiObject (line 66) | interface UiObject {
  type UiCollection (line 98) | interface UiCollection {
  type Rect (line 108) | interface Rect {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (321K chars).
[
  {
    "path": ".editorconfig",
    "chars": 320,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = tab\nin"
  },
  {
    "path": ".eslintrc.js",
    "chars": 1533,
    "preview": "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"
  },
  {
    "path": ".github/workflows/webpack.yml",
    "chars": 2436,
    "preview": "name: NodeJS with Webpack\n\non:\n  push:\n    branches: [ \"*\" ]\n  pull_request:\n    branches: [ \"*\" ]\n\njobs:\n  build:\n    r"
  },
  {
    "path": ".gitignore",
    "chars": 6336,
    "preview": "# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig\n# Created by https://www.t"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2019 Chaos\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 5548,
    "preview": "# MiuiCleaner\n----\nMIUI广告清理工具\n\n## 使用方法\n\n- [点击下载最新版本MiuiCleaner](https://github.com/gucong3000/MiuiCleaner/releases/lates"
  },
  {
    "path": "autojs-deploy.js",
    "chars": 13769,
    "preview": "const { Adb } = require(\"@devicefarmer/adbkit\");\nconst { readFile } = require(\"fs/promises\");\nconst EventEmitter = requi"
  },
  {
    "path": "babel.config.js",
    "chars": 202,
    "preview": "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\tplu"
  },
  {
    "path": "package.json",
    "chars": 1660,
    "preview": "{\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\"t"
  },
  {
    "path": "project.js",
    "chars": 3734,
    "preview": "const fs = require(\"fs/promises\");\nconst { spawnSync } = require(\"node:child_process\");\n\n(async () => {\n\tconst [\n\t\tpacka"
  },
  {
    "path": "src/miui_cleaner_app/123pan.js",
    "chars": 4065,
    "preview": "const jsonParse = require(\"json5/lib/parse\");\nconst fetch = global.fetch || require(\"./fetch\");\nconst atob = global.atob"
  },
  {
    "path": "src/miui_cleaner_app/appDesc.js",
    "chars": 3186,
    "preview": "\n// https://gist.github.com/mcxiaoke/0a4c639d04e94c45eb6c787c0f98940a\n// https://fengooge.blogspot.com/2019/03/taking-AD"
  },
  {
    "path": "src/miui_cleaner_app/appManager.js",
    "chars": 2052,
    "preview": "const startActivity = require(\"./startActivity\");\nconst singleChoice = require(\"./singleChoice\");\n\n// https://blog.unide"
  },
  {
    "path": "src/miui_cleaner_app/dialogs.js",
    "chars": 3871,
    "preview": "const resString = com.stardust.autojs.R.string;\nconst AlertDialog = android.app.AlertDialog;\n\nconst btnLabelMap = {\n\tpos"
  },
  {
    "path": "src/miui_cleaner_app/downApp.js",
    "chars": 11986,
    "preview": "const getApplicationInfo = require(\"./getApplicationInfo\");\nconst getRemoteFileInfo = require(\"./getRemoteFileInfo\");\nco"
  },
  {
    "path": "src/miui_cleaner_app/downFile.js",
    "chars": 7541,
    "preview": "const DownloadManager = android.app.DownloadManager;\nconst Cursor = android.database.Cursor;\nconst Intent = android.cont"
  },
  {
    "path": "src/miui_cleaner_app/emitItemShowEvent.js",
    "chars": 1547,
    "preview": "const debounce = require(\"debounce\");\nconst Rect = android.graphics.Rect;\nconst inNightMode = Boolean(activity.getApplic"
  },
  {
    "path": "src/miui_cleaner_app/fetch.js",
    "chars": 5774,
    "preview": "const okhttp3 = global.Packages?.okhttp3;\nconst Headers = global.Headers || require(\"headers-polyfill\").Headers;\n// cons"
  },
  {
    "path": "src/miui_cleaner_app/findClickableParent.js",
    "chars": 239,
    "preview": "/**\n * 向上查找 UiObject 的父节点,找到可点击的祖先节点\n * @param {UiObject} node 节点\n * @returns\n */\nfunction findClickableParent (node) {\n"
  },
  {
    "path": "src/miui_cleaner_app/getApplicationInfo.js",
    "chars": 2707,
    "preview": "// const PackageManager = android.content.pm.PackageManager;\nconst pm = context.getPackageManager();\n// https://develope"
  },
  {
    "path": "src/miui_cleaner_app/getRemoteFileInfo.js",
    "chars": 4881,
    "preview": "const fetch = require(\"./fetch\");\nconst lanzou = require(\"./lanzou\");\nconst _123pan = require(\"./123pan\");\n\nclass Asset "
  },
  {
    "path": "src/miui_cleaner_app/index.js",
    "chars": 1998,
    "preview": "console.setGlobalLogConfig({\n\tfile: files.join(\n\t\tcontext.getExternalFilesDir(\"logs\"),\n\t\t\"log.txt\",\n\t),\n});\n\ndelete glob"
  },
  {
    "path": "src/miui_cleaner_app/instApk.js",
    "chars": 154,
    "preview": "/**\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 t"
  },
  {
    "path": "src/miui_cleaner_app/lanzou.js",
    "chars": 10819,
    "preview": "const jsonParse = require(\"json5/lib/parse\");\nconst fetch = global.fetch || require(\"./fetch\");\n// const webView = globa"
  },
  {
    "path": "src/miui_cleaner_app/multiChoice.js",
    "chars": 2273,
    "preview": "const emitItemShowEvent = require(\"./emitItemShowEvent\");\nconst project = require(\"./project.json\");\n\nfunction multiChoi"
  },
  {
    "path": "src/miui_cleaner_app/offAppAd.js",
    "chars": 4035,
    "preview": "const getApplicationInfo = require(\"./getApplicationInfo\");\nconst multiChoice = require(\"./multiChoice\");\nconst serviceM"
  },
  {
    "path": "src/miui_cleaner_app/project.json",
    "chars": 499,
    "preview": "{\n\t\"icon\": \"res/drawable/ic_launcher.png\",\n\t\"launchConfig\": {\n\t\t\"displaySplash\": false,\n\t\t\"hideLogs\": false,\n\t\t\"permissi"
  },
  {
    "path": "src/miui_cleaner_app/recycle.js",
    "chars": 2184,
    "preview": "const singleChoice = require(\"./singleChoice\");\nconst serviceMgr = require(\"./serviceMgr\");\nconst settings = require(\"./"
  },
  {
    "path": "src/miui_cleaner_app/serviceMgr.js",
    "chars": 1429,
    "preview": "let scriptEngine;\nfunction sleep (time) {\n\treturn new Promise((resolve) => setTimeout(resolve, time));\n}\n\nfunction waitF"
  },
  {
    "path": "src/miui_cleaner_app/services.js",
    "chars": 24991,
    "preview": "const findClickableParent = require(\"./findClickableParent\");\nconst servicesTest = DEBUG && require(\"./test/services\");\n"
  },
  {
    "path": "src/miui_cleaner_app/settings.js",
    "chars": 9341,
    "preview": "const waitForBack = require(\"./waitForBack\");\nconst serviceMgr = require(\"./serviceMgr\");\nconst project = require(\"./pro"
  },
  {
    "path": "src/miui_cleaner_app/singleChoice.js",
    "chars": 2222,
    "preview": "const emitItemShowEvent = require(\"./emitItemShowEvent\");\nconst project = require(\"./project.json\");\nconst View = androi"
  },
  {
    "path": "src/miui_cleaner_app/startActivity.js",
    "chars": 257,
    "preview": "function startActivity (options) {\n\tif (options.className) {\n\t\toptions.className = options.className.replace(/^(?=\\.)/, "
  },
  {
    "path": "src/miui_cleaner_app/support.js",
    "chars": 2209,
    "preview": "const project = require(\"./project.json\");\nconst View = android.view.View;\nconst url = \"https://support.qq.com/products/"
  },
  {
    "path": "src/miui_cleaner_app/sysAppRm.js",
    "chars": 5299,
    "preview": "const findClickableParent = require(\"./findClickableParent\");\nconst getApplicationInfo = require(\"./getApplicationInfo\")"
  },
  {
    "path": "src/miui_cleaner_app/test/getRemoteFileInfo.js",
    "chars": 5397,
    "preview": "module.exports = (getRemoteFileInfo) => {\n\treturn DEBUG && Promise.all([\n\t\t// getRemoteFileInfo(\"https://www.firepx.com/"
  },
  {
    "path": "src/miui_cleaner_app/test/services.js",
    "chars": 3728,
    "preview": "function isObject (val) {\n\treturn val != null && typeof val === \"object\" && Array.isArray(val) === false;\n};\nfunction is"
  },
  {
    "path": "src/miui_cleaner_app/update.js",
    "chars": 3340,
    "preview": "const prettyBytes = require(\"pretty-bytes\");\nconst project = require(\"./project.json\");\nconst downFile = require(\"./down"
  },
  {
    "path": "src/miui_cleaner_app/waitForBack.js",
    "chars": 299,
    "preview": "\nfunction waitForBack (leave) {\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, 0x200);\n\t\tui.emit"
  },
  {
    "path": "src/miui_cleaner_app/webView.js",
    "chars": 6496,
    "preview": "const asyncFnWrap = (taskId, fn, args) => {\n\tfunction toErrorObj (reason) {\n\t\tif (reason instanceof Error) {\n\t\t\tconst er"
  },
  {
    "path": "src/miui_cleaner_cmd/main.cmd",
    "chars": 2345,
    "preview": "@echo off\nchcp 65001>nul 2>nul\ntitle MiuiCleaner - MIUI广告清理工具\n\n:start\nadb shell pm list packages>\"%temp%\\adb_list_packag"
  },
  {
    "path": "types/adbkit.d.ts",
    "chars": 750,
    "preview": "\n// import * as a from \"adbkit\";\n\ndeclare module \"adbkit\" {\n    import { ReadStream } from \"fs\";\n\n    export interface T"
  },
  {
    "path": "types/auto.d.ts",
    "chars": 1018,
    "preview": "/// <reference path=\"./modules/global.d.ts\" />\n/// <reference path=\"./modules/app.d.ts\" />\n/// <reference path=\"./module"
  },
  {
    "path": "types/autojs.d.ts",
    "chars": 39791,
    "preview": "/* 内置模块 */\n\n/*\n * based on commit \"cf1e602\"\n * 文件结构\n * \n * -模块\n *     -命名空间\n *     -全局\n * \n * 未加入:WidgetsBasedAutomation"
  },
  {
    "path": "types/modules/app.d.ts",
    "chars": 3768,
    "preview": "/**\n * app模块提供一系列函数,用于使用其他应用、与其他应用交互。例如发送意图、打开文件、发送邮件等。\n */\ndeclare namespace app {\n\n    /**\n     * 通过应用名称启动应用。如果该名称对应的应"
  },
  {
    "path": "types/modules/colors.d.ts",
    "chars": 709,
    "preview": "declare namespace colors {\n    function toString(color: number): string;\n    function red(color: number | string): numbe"
  },
  {
    "path": "types/modules/console.d.ts",
    "chars": 1639,
    "preview": "interface Console {\n    /**\n * 显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。\n */\n    show(): void;\n\n    /**\n     * 隐藏控制台悬浮窗。\n     */\n   "
  },
  {
    "path": "types/modules/coordinate.d.ts",
    "chars": 1365,
    "preview": "/* 基于坐标的触摸模拟 */\n\n/**\n * 设置脚本坐标点击所适合的屏幕宽高。如果脚本运行时,屏幕宽度不一致会自动放缩坐标。\n */\ndeclare function setScreenMetrics(width: number, he"
  },
  {
    "path": "types/modules/device.d.ts",
    "chars": 5780,
    "preview": "/**\n * device模块提供了与设备有关的信息与操作,例如获取设备宽高,内存使用率,IMEI,调整设备亮度、音量等。\n * \n * 此模块的部分函数,例如调整音量,需要\"修改系统设置\"的权限。如果没有该权限,会抛出SecurityEx"
  },
  {
    "path": "types/modules/dialogs.d.ts",
    "chars": 4064,
    "preview": "/**\n * dialogs 模块提供了简单的对话框支持,可以通过对话框和用户进行交互。\n */\ndeclare namespace dialogs {\n\n    /**\n     * 显示一个只包含“确定”按钮的提示对话框。直至用户点击确"
  },
  {
    "path": "types/modules/engines.d.ts",
    "chars": 2006,
    "preview": "/**\n * engines模块包含了一些与脚本环境、脚本运行、脚本引擎有关的函数,包括运行其他脚本,关闭脚本等。\n */\ndeclare namespace engines {\n\n    /**\n     * 脚本引擎对象。\n     *"
  },
  {
    "path": "types/modules/events.d.ts",
    "chars": 2909,
    "preview": "declare namespace events {\n\n    interface KeyEvent {\n        getAction();\n        getKeyCode(): number;\n        getEvent"
  },
  {
    "path": "types/modules/files.d.ts",
    "chars": 2070,
    "preview": "declare namespace files {\n    type byte = number;\n    function isFile(path: string): boolean;\n    function isDir(path: s"
  },
  {
    "path": "types/modules/floaty.d.ts",
    "chars": 453,
    "preview": "declare namespace floaty {\n    function window(layout: any): FloatyWindow;\n    function closeAll(): void;\n    interface "
  },
  {
    "path": "types/modules/global.d.ts",
    "chars": 646,
    "preview": "interface Point {\n    x: number;\n    y: number;\n}\n\ndeclare function sleep(n: number): void;\n\ndeclare function currentPac"
  },
  {
    "path": "types/modules/http.d.ts",
    "chars": 1636,
    "preview": "/// <reference path=\"../auto.d.ts\" />\n\ndeclare namespace http {\n    interface HttpRequestOptions {\n        header: { [ke"
  },
  {
    "path": "types/modules/images.d.ts",
    "chars": 1936,
    "preview": "/// <reference path=\"./global.d.ts\" />\n\ninterface Image {\n    getWidth(): number;\n    getHeight(): number;\n    saveTo(pa"
  },
  {
    "path": "types/modules/keys.d.ts",
    "chars": 753,
    "preview": "declare function back(): boolean;\ndeclare function home(): boolean;\ndeclare function powerDialog(): boolean;\ndeclare fun"
  },
  {
    "path": "types/modules/media.d.ts",
    "chars": 419,
    "preview": "declare namespace media {\n    function scanFile(path: string): void;\n    function playMusic(path: string, volume?: numbe"
  },
  {
    "path": "types/modules/root.d.ts",
    "chars": 1224,
    "preview": "/**\n * RootAutomator是一个使用root权限来模拟触摸的对象,用它可以完成触摸与多点触摸,并且这些动作的执行没有延迟。\n * \n * 一个脚本中最好只存在一个RootAutomator,并且保证脚本结束退出他。\n */\nd"
  },
  {
    "path": "types/modules/sensors.d.ts",
    "chars": 622,
    "preview": "declare namespace sensors {\n    interface SensorEventEmitter {\n        on(eventName: 'change', callback: (...args: numbe"
  },
  {
    "path": "types/modules/storages.d.ts",
    "chars": 314,
    "preview": "interface Storage {\n    get<T>(key: string, defaultValue?: T): T;\n    put<T>(key: string, value: T): void;\n    remove(ke"
  },
  {
    "path": "types/modules/threads.d.ts",
    "chars": 861,
    "preview": "declare namespace threads {\n\n    type ThreadTimerID = number;\n\n    interface Thread {\n        interrupt(): void;\n       "
  },
  {
    "path": "types/modules/ui.d.ts",
    "chars": 1632,
    "preview": "// interface View {\n//     w: 'auto' | '*' | number;\n//     h: 'auto' | '*' | number;\n//     id: string;\n//     gravity:"
  },
  {
    "path": "types/modules/widgets.d.ts",
    "chars": 4138,
    "preview": "declare function auto(mode?: 'fast' | 'normal'): void;\ndeclare namespace auto {\n    function waitFor(): void;\n    functi"
  },
  {
    "path": "webpack.config.js",
    "chars": 1557,
    "preview": "// Generated using webpack-cli https://github.com/webpack/webpack-cli\n// http://auto.moly.host/index.html#/template/temp"
  }
]

About this extraction

This page contains the full source code of the gucong3000/MiuiCleaner GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (249.8 KB), approximately 79.8k tokens, and a symbol index with 244 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!