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款应用的卸载:
点击查看详细名单
- APP 外置开屏广告
- 广告分析
- 小米系统广告解决方案(智能服务)
- 桌面广告 APP
- 智能助理(负一屏)
- 信息助手(负一屏)
- 智能出行
- 内容中心(趣看看)
- 百度搜索框
- 桌面搜索框(搜索/全局搜索)
- 桌面搜索框(Google)
- 过时的 APP
- 悬浮球
- 小米闻声
- 智慧生活
- 影音类 APP
- 音乐
- Mi Video
- 小米视频
- 腾讯视频小米版
- 爱奇艺播放器
- 天气
- 小米天气
- 支付、电商、理财类 APP
- 小米商城
- 小米商城系统组件(电商助手)
- 小米钱包
- 米币支付
- 小米支付
- 小米卡包
- 小米金融(天星金融)
- 小米金融(天星金融)- 安全组件
- 小米金服安全组件
- 银联可信服务安全组件小米版
- 低使用频率 APP
- 小米换机
- 小米社区
- 用户反馈
- KLO bug反馈
- 服务与反馈
- 我的服务
- 小米画报
- 动态壁纸
- 动态壁纸获取
- 壁纸备份
- 壁纸编辑器
- 收音机(蜻蜓FM)
- WPS Office Lite
- 阅读(番茄免费小说)
- 阅读(多看阅读器)
- 小米运动健康
- 浏览器
- 小米浏览器
- 小米浏览器(国际版)
- Chrome
- 内置输入法
- 百度输入法-小米版
- 搜狗输入法-小米版
- 讯飞输入法-小米版
- 小米安全键盘
- 小米游戏中心
- 游戏中心(旧版)
- 游戏中心
- 游戏服务
- 游戏中心 - pad 版
- Joyose
- SIM 卡应用
- 小米移动
- 全球上网
- 小米云流量
- 全球上网工具插件
- SIM卡应用
- 快应用
- 快应用中心
- 快应用服务框架
- 语音助手
- 语音唤醒
- 小爱语音(小爱同学)
- 小爱视觉(扫一扫)
- 小爱翻译
- 小爱通话(AI虚拟助手)
### 去广告应用
内置多款去广告应用的下载链接:
点击查看详细名单
- [李跳跳](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
### 关闭各应用广告
支持在以下17款应用中,自动搜索50多个广告开关的具体位置,并自动给予处置。
点击查看详细名单
- 小米帐号
- 关于小米帐号
- 系统广告
- 系统工具广告:`关闭`
- 系统安全
- 加入“用户体验改进计划”:`关闭`
- 自动发送诊断数据:`关闭`
- 广告服务
- 个性化广告推荐:`关闭`
- 网页链接调用服务
- 网页链接调用服务:`关闭`
- 手机管家
- 在通知栏显示:`关闭`
- 在线服务:`关闭`
- 隐私设置
- 仅在WLAN下推荐:`打开`
- 个性化推荐:`关闭`
- 应用管理
- 资源推荐:`关闭`
- 垃圾清理
- 扫描内存:`关闭`
- 推荐内容:`关闭`
- 仅在WLAN下推荐:`打开`
- 应用商店
- 通知设置
- 新手帮助:`关闭`
- 应用更新通知:`关闭`
- 点赞消息:`关闭`
- 评论消息:`关闭`
- 通知栏快捷入口:`关闭`
- 隐私设置
- 个性化服务
- 个性化服务:`关闭`
- 功能设置
- 显示福利活动:`关闭`
- 下载管理
- 信息流设置
- 仅在WLAN下加载:`打开`
- 资源推荐:`关闭`
- 热榜推荐:`关闭`
- 日历
- 功能设置
- 显示天气服务:`关闭`
- 用户体验计划
- 内容推广:`关闭`
- 时钟
- 更多闹钟设置
- 显示生活早报:`关闭`
- 小米社区
- 隐私管理
- 详情页相似推荐:`关闭`
- 个性化广告:`关闭`
- 信息流推荐:`关闭`
- 关闭私信消息提醒:`打开`
- 小米天气
- 用户体验计划
- 天气视频卡片:`关闭`
- 内容推广:`关闭`
- 小米视频
- 隐私设置
- 个性化内容推荐:`关闭`
- 个性化广告推荐:`关闭`
- 消息与推送
- 未读消息提醒:`关闭`
- 接收小米推送:`关闭`
- 其他
- 在线服务:`关闭`
- 音乐
- 在线内容服务:`关闭`
- 小爱语音
- 隐私管理
- 隐私设置
- 加入用户体验改进计划:`关闭`
- 小爱技巧推送服务:`关闭`
- 个性化推荐:`关闭`
- 个性化广告推荐:`关闭`
- 搜索
- 搜索快捷方式
- 桌面搜索框:`关闭`
- 首页展示模块
- 热搜榜单
- 热搜榜s:`关闭`
- 搜索提示词:`关闭`
- 搜索项
- 搜索精选:`关闭`
- 网站广告过滤:`打开`
- 浏览器
- 主页设置
- 简洁版:`打开`
- 宫格位推送:`关闭`
- 隐私防护
- 广告过滤
- 广告过滤:`打开`
- 消息通知管理
- 接收消息通知:`关闭`
- 小米浏览器
- 首页设置
- 简洁版:`打开`
- 隐私保护
- 广告过滤
- 广告过滤:`打开`
- 高级
- 浏览器广告:`关闭`
- 通知栏快捷入口:`关闭`
- Facebook快捷通知:`关闭`
[演示视频: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("");
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(``);
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(`
`, 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(/