mirror of
https://github.com/ReVanced/revanced-manager-downloaders.git
synced 2026-01-10 21:36:18 +00:00
feat: Add play store downloader (#10)
This commit is contained in:
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -11,18 +11,18 @@ body:
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
@@ -68,8 +68,8 @@ body:
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-manager-apkmirror-downloader/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-apkmirror-downloader/blob/main/CONTRIBUTING.md).
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-manager-downloaders/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-downloaders/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -79,7 +79,7 @@ body:
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Error logs
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
12
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -11,18 +11,18 @@ body:
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-apkmirror-downloader/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/revanced/revanced-manager-downloaders/main/assets/revanced-logo/revanced-logo.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
@@ -68,8 +68,8 @@ body:
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-manager-apkmirror-downloader/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-apkmirror-downloader/blob/main/CONTRIBUTING.md).
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-manager-downloaders/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager-downloaders/blob/main/CONTRIBUTING.md).
|
||||
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
10
.github/workflows/build_pull_request.yml
vendored
10
.github/workflows/build_pull_request.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
@@ -23,9 +23,15 @@ jobs:
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v2
|
||||
uses: burrunan/gradle-cache-action@v3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build --no-daemon
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: revanced-manager-downloaders
|
||||
path: downloaders/*/build/outputs/apk/release/*.apk
|
||||
|
||||
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@@ -12,6 +12,8 @@ jobs:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -20,7 +22,7 @@ jobs:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
@@ -29,12 +31,12 @@ jobs:
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v2
|
||||
uses: burrunan/gradle-cache-action@v3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build --no-daemon
|
||||
run: ./gradlew assembleRelease --no-daemon
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -64,3 +66,10 @@ jobs:
|
||||
KEYSTORE_ENTRY_PASSWORD: ${{ secrets.KEYSTORE_ENTRY_PASSWORD }}
|
||||
run: |
|
||||
npx semantic-release
|
||||
|
||||
- name: Attest
|
||||
if: steps.release.outputs.new_release_published == 'true'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: 'ReVanced Downloader ${{ steps.release.outputs.new_release_git_tag }}'
|
||||
subject-path: downloaders/*/build/outputs/apk/release/*.apk
|
||||
|
||||
140
.gitignore
vendored
140
.gitignore
vendored
@@ -1,130 +1,16 @@
|
||||
### Java template
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/artifacts
|
||||
.idea/compiler.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Gradle template
|
||||
.gradle
|
||||
**/build/
|
||||
!src/**/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Potentially copyrighted test APK
|
||||
*.apk
|
||||
|
||||
# Ignore vscode config
|
||||
.vscode/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Ignore IDEA files
|
||||
.idea/
|
||||
|
||||
.kotlin/
|
||||
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
**/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
.idea/
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "arsclib"]
|
||||
path = arsclib
|
||||
url = https://github.com/REAndroid/ARSCLib
|
||||
@@ -32,7 +32,10 @@
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/outputs/apk/release/*.apk?(.asc)",
|
||||
"path": "play-store-downloader/build/outputs/apk/release/play-store-downloader-*.apk?(.asc)",
|
||||
},
|
||||
{
|
||||
"path": "apkmirror-downloader/build/outputs/apk/release/apkmirror-downloader-*.apk?(.asc)",
|
||||
}
|
||||
],
|
||||
successComment: false
|
||||
|
||||
@@ -60,38 +60,38 @@
|
||||
|
||||
# 👋 Contribution guidelines
|
||||
|
||||
This document describes how to contribute to ReVanced Manager downloader template.
|
||||
This document describes how to contribute to ReVanced Manager Downloaders.
|
||||
|
||||
## 📖 Resources to help you get started
|
||||
|
||||
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
|
||||
* [Issues](https://github.com/ReVanced/revanced-manager-downloader-template/issues) are where we keep track of bugs and feature requests
|
||||
* [Issues](https://github.com/ReVanced/revanced-manager-downloaders/issues) are where we keep track of bugs and feature requests
|
||||
|
||||
## 🙏 Submitting a feature request
|
||||
|
||||
Features can be requested by opening an issue using the
|
||||
[Feature request issue template](https://github.com/ReVanced/revanced-manager-downloader-template/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||
[Feature request issue template](https://github.com/ReVanced/revanced-manager-downloaders/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
|
||||
|
||||
> **Note**
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager downloader template.
|
||||
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Manager Downloaders.
|
||||
> Good motivation has to be provided for a request to be accepted.
|
||||
|
||||
## 🐞 Submitting a bug report
|
||||
|
||||
If you encounter a bug while using ReVanced Manager downloader template, open an issue using the
|
||||
[Bug report issue template](https://github.com/ReVanced/revanced-manager-downloader-template/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||
If you encounter a bug while using ReVanced Manager Downloaders, open an issue using the
|
||||
[Bug report issue template](https://github.com/ReVanced/revanced-manager-downloaders/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
|
||||
|
||||
## 📝 How to contribute
|
||||
|
||||
1. Before contributing, it is recommended to open an issue to discuss your change
|
||||
with the maintainers of ReVanced Manager downloader template. This will help you determine whether your change is acceptable
|
||||
with the maintainers of ReVanced Manager Downloaders. This will help you determine whether your change is acceptable
|
||||
and whether it is worth your time to implement it
|
||||
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`
|
||||
3. Commit your changes
|
||||
4. Submit a pull request to the `dev` branch of the repository and reference issues
|
||||
that your pull request closes in the description of your pull request
|
||||
5. Our team will review your pull request and provide feedback. Once your pull request is approved,
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Manager downloader template
|
||||
it will be merged into the `dev` branch and will be included in the next release of ReVanced Manager Downloaders
|
||||
|
||||
❤️ Thank you for considering contributing to ReVanced Manager downloader template,
|
||||
❤️ Thank you for considering contributing to ReVanced Manager Downloaders,
|
||||
ReVanced
|
||||
|
||||
31
README.md
31
README.md
@@ -58,34 +58,43 @@
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 👋🔌 ReVanced Manager APKMirror downloader
|
||||
# 👋🔌 ReVanced Manager Downloaders
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
An [APKMirror](https://www.apkmirror.com/) plugin for ReVanced Manager.
|
||||
The collection of ReVanced downloaders.
|
||||
|
||||
## 🧑💻 Usage
|
||||
|
||||
- Plugins are managed as Android apps. Download and install the APK file from the releases page.
|
||||
- After installing, restart ReVanced Manager and enable the plugin in the settings.
|
||||
- The plugin will now be usable.
|
||||
- A downloader is managed as Android apps. Download and install the APK file from the releases page.
|
||||
- After installing, restart ReVanced Manager and enable the downloader in the settings.
|
||||
- The downloader will now be usable.
|
||||
|
||||
The plugin works by opening the APKMirror website in an embedded browser. If the search string contains a version, you must select that version. Selecting another version in that situation will cause patching to fail.
|
||||
|
||||
### APMMirror Downloader
|
||||
This downloader will open an [APKMirror](https://www.apkmirror.com/) page where you can download the apk as you would normally do.
|
||||
If the chosen file is a bundle (.apkm file), the downloader will automatically merge it into a .apk file.
|
||||
|
||||
### Play Store Downloader
|
||||
When you get prompted, log in to Google with your account. After that, you will be able to download apps from the Play Store.
|
||||
|
||||
> [!WARNING]
|
||||
> Due to technical limitations, it is only possible to download the latest version of the app. If the ReVanced suggested version differs from the latest, the installation will fail.
|
||||
|
||||
## 📚 Everything else
|
||||
|
||||
### 📙 Contributing
|
||||
|
||||
Thank you for considering contributing to ReVanced Manager APKMirror downloader.
|
||||
Thank you for considering contributing to ReVanced Manager Downloaders.
|
||||
You can find the contribution guidelines [here](CONTRIBUTING.md).
|
||||
|
||||
### 🛠️ Building
|
||||
|
||||
To build ReVanced Manager downloader template, a Java Development Kit (JDK) and Git must be installed.
|
||||
Follow the steps below to build ReVanced Manager downloader template:
|
||||
To build ReVanced Manager Downloaders, a Java Development Kit (JDK) and Git must be installed.
|
||||
Follow the steps below to build ReVanced Manager Downloaders:
|
||||
|
||||
1. Run `git clone git@github.com:ReVanced/revanced-manager-apkmirror-downloader.git` to clone the repository
|
||||
1. Run `git clone git@github.com:ReVanced/revanced-manager-downloaders.git` to clone the repository
|
||||
2. Run `gradlew assembleRelease` to build the project
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
1
arsclib
Submodule
1
arsclib
Submodule
Submodule arsclib added at a44577e73f
182
build.gradle.kts
182
build.gradle.kts
@@ -1,88 +1,110 @@
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||
import org.gradle.plugins.signing.SigningExtension
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
publishing
|
||||
signing
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.parcelize) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.plugin.api)
|
||||
}
|
||||
|
||||
android {
|
||||
val packageName = "app.revanced.manager.plugin.downloader.apkmirror"
|
||||
|
||||
namespace = packageName
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = packageName
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionName = version.toString()
|
||||
versionCode = versionName!!.filter { it.isDigit() }.toInt()
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
|
||||
val keystoreFile = file("keystore.jks")
|
||||
signingConfig =
|
||||
if (keystoreFile.exists()) {
|
||||
signingConfigs.create("release") {
|
||||
storeFile = keystoreFile
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||
}
|
||||
} else {
|
||||
signingConfigs["debug"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
|
||||
|
||||
outputFileName = "${rootProject.name}-$version.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
val assembleReleaseSignApk by registering {
|
||||
dependsOn("assembleRelease")
|
||||
|
||||
val apk = layout.buildDirectory.file("outputs/apk/release/${rootProject.name}-$version.apk")
|
||||
|
||||
inputs.file(apk).withPropertyName("input")
|
||||
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
||||
|
||||
doLast {
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(*inputs.files.files.toTypedArray())
|
||||
subprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = providers.gradleProperty("gpr.user")
|
||||
.getOrElse(System.getenv("GITHUB_ACTOR"))
|
||||
password =
|
||||
providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used by gradle-semantic-release-plugin.
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
||||
publish {
|
||||
dependsOn(assembleReleaseSignApk)
|
||||
if (project.path.endsWith("-downloader")) {
|
||||
apply(plugin = "com.android.application")
|
||||
apply(plugin = "org.jetbrains.kotlin.android")
|
||||
apply(plugin = "maven-publish")
|
||||
apply(plugin = "signing")
|
||||
|
||||
dependencies {
|
||||
"compileOnly"(rootProject.libs.plugin.api)
|
||||
}
|
||||
|
||||
configure<AppExtension> {
|
||||
compileSdkVersion(35)
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionName = version.toString()
|
||||
versionCode = versionName!!.filter { it.isDigit() }.toInt()
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
|
||||
val keystoreFile = file("${rootDir}/keystore.jks")
|
||||
signingConfig =
|
||||
if (keystoreFile.exists()) {
|
||||
signingConfigs.create("release") {
|
||||
storeFile = keystoreFile
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
|
||||
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
|
||||
}
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as ApkVariantOutputImpl
|
||||
outputFileName = "${project.name}-$version.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("assembleReleaseSignApk") {
|
||||
dependsOn("assembleRelease")
|
||||
|
||||
val apk = layout.buildDirectory.file("outputs/apk/release/${project.name}-$version.apk")
|
||||
|
||||
inputs.file(apk).withPropertyName("input")
|
||||
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
||||
|
||||
doLast {
|
||||
project.configure<SigningExtension> {
|
||||
useGpgCmd()
|
||||
sign(*inputs.files.files.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("publish") {
|
||||
dependsOn("assembleReleaseSignApk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
downloaders/apkmirror-downloader/build.gradle.kts
Normal file
16
downloaders/apkmirror-downloader/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
}
|
||||
|
||||
android {
|
||||
val packageName = "app.revanced.manager.plugin.downloader.apkmirror"
|
||||
namespace = packageName
|
||||
defaultConfig {
|
||||
applicationId = packageName
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":arsclib"))
|
||||
implementation(project(":shared"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:name="app.revanced.manager.plugin.downloader" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
tools:targetApi="35">
|
||||
|
||||
<meta-data
|
||||
android:name="app.revanced.manager.plugin.downloader.class"
|
||||
android:value="app.revanced.manager.plugin.downloader.apkmirror.APKMirrorDownloaderKt" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,110 @@
|
||||
@file:Suppress("Unused")
|
||||
|
||||
package app.revanced.manager.plugin.downloader.apkmirror
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import app.revanced.manager.plugin.downloader.DownloadUrl
|
||||
import app.revanced.manager.plugin.downloader.Downloader
|
||||
import app.revanced.manager.plugin.downloader.download
|
||||
import app.revanced.manager.plugin.downloader.webview.runWebView
|
||||
import app.revanced.manager.plugin.utils.Merger
|
||||
import com.reandroid.apk.APKLogger
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
@Parcelize
|
||||
class ApkMirrorApp(
|
||||
val downloadUrl: DownloadUrl,
|
||||
val packageName: String
|
||||
) : Parcelable
|
||||
|
||||
private object ArscLogger : APKLogger {
|
||||
const val TAG = "ARSCLib"
|
||||
|
||||
override fun logMessage(msg: String) {
|
||||
Log.i(TAG, msg)
|
||||
}
|
||||
|
||||
override fun logError(msg: String, tr: Throwable?) {
|
||||
Log.e(TAG, msg, tr)
|
||||
}
|
||||
|
||||
override fun logVerbose(msg: String) {
|
||||
Log.v(TAG, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
val ApkMirrorDownloader = Downloader<ApkMirrorApp> {
|
||||
get { packageName, version ->
|
||||
ApkMirrorApp(
|
||||
downloadUrl = runWebView("APKMirror") {
|
||||
download { url, _, userAgent ->
|
||||
finish(
|
||||
DownloadUrl(
|
||||
url,
|
||||
mapOf("User-Agent" to userAgent)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
with(Uri.Builder()) {
|
||||
scheme("https")
|
||||
authority("www.apkmirror.com")
|
||||
mapOf(
|
||||
"post_type" to "app_release",
|
||||
"searchtype" to "apk",
|
||||
"s" to (version?.let { "$packageName $it" } ?: packageName),
|
||||
"bundles%5B%5D" to "apk_files" // bundles[]
|
||||
).forEach { (key, value) ->
|
||||
appendQueryParameter(key, value)
|
||||
}
|
||||
|
||||
build().toString()
|
||||
}
|
||||
},
|
||||
packageName = packageName
|
||||
) to version
|
||||
}
|
||||
|
||||
download { app, outputStream ->
|
||||
val workingDir = Files.createTempDirectory("apkmirror_dl")
|
||||
val downloadedFile = workingDir.resolve("${app.packageName}.apk").also {
|
||||
it.outputStream().use { output ->
|
||||
app.downloadUrl.toDownloadResult().first.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (URI(app.downloadUrl.url).path.substringAfterLast('/').endsWith(".apk")) {
|
||||
Files.copy(downloadedFile, outputStream)
|
||||
} else {
|
||||
val xapkWorkingDir = workingDir.resolve("xapk").also { it.toFile().mkdirs() }
|
||||
|
||||
ZipFile(downloadedFile.toString()).use { zip ->
|
||||
zip.entries().asSequence().forEach { entry ->
|
||||
xapkWorkingDir.resolve(entry.name).also { it.parent.toFile().mkdirs() }.also { outputFile ->
|
||||
zip.getInputStream(entry).use { input ->
|
||||
File(outputFile.toString()).outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Merger.merge(xapkWorkingDir, ArscLogger).writeApk(outputStream)
|
||||
}
|
||||
} finally {
|
||||
workingDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">ReVanced Manager: APKMirror downloader</string>
|
||||
</resources>
|
||||
34
downloaders/play-store-downloader/build.gradle.kts
Normal file
34
downloaders/play-store-downloader/build.gradle.kts
Normal file
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
android {
|
||||
val packageName = "app.revanced.manager.plugin.downloader.play.store"
|
||||
namespace = packageName
|
||||
defaultConfig {
|
||||
applicationId = packageName
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.gplayapi)
|
||||
implementation(project(":arsclib"))
|
||||
implementation(project(":shared"))
|
||||
|
||||
implementation(libs.ktor.core)
|
||||
implementation(libs.ktor.logging)
|
||||
implementation(libs.ktor.okhttp)
|
||||
|
||||
implementation(libs.compose.activity)
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.ui.tooling)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.webview)
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
aidl = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:name="app.revanced.manager.plugin.downloader" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
tools:targetApi="34">
|
||||
|
||||
<activity
|
||||
android:name=".ui.AuthActivity"
|
||||
android:exported="true"
|
||||
android:permission="app.revanced.manager.permission.PLUGIN_HOST"
|
||||
android:theme="@android:style/Theme.DeviceDefault" />
|
||||
|
||||
<service
|
||||
android:name=".service.CredentialProviderService"
|
||||
android:exported="true"
|
||||
android:permission="app.revanced.manager.permission.PLUGIN_HOST" />
|
||||
|
||||
<meta-data
|
||||
android:name="app.revanced.manager.plugin.downloader.class"
|
||||
android:value="app.revanced.manager.plugin.downloader.play.store.PlayStorePluginKt" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,10 @@
|
||||
// ICredentialProvider.aidl
|
||||
package app.revanced.manager.plugin.downloader.play.store;
|
||||
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.Credentials;
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.ParcelProperties;
|
||||
|
||||
interface ICredentialProvider {
|
||||
@nullable Credentials retrieveCredentials();
|
||||
ParcelProperties getProperties();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Credentials.aidl
|
||||
package app.revanced.manager.plugin.downloader.play.store.data;
|
||||
|
||||
parcelable Credentials;
|
||||
@@ -0,0 +1,4 @@
|
||||
// ParcelProperties.aidl
|
||||
package app.revanced.manager.plugin.downloader.play.store.data;
|
||||
|
||||
parcelable ParcelProperties;
|
||||
@@ -0,0 +1,118 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import app.revanced.manager.plugin.downloader.*
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.Credentials
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.Http
|
||||
import app.revanced.manager.plugin.downloader.play.store.service.CredentialProviderService
|
||||
import app.revanced.manager.plugin.downloader.play.store.ui.AuthActivity
|
||||
import app.revanced.manager.plugin.utils.Merger
|
||||
import com.aurora.gplayapi.data.models.File as GPlayFile
|
||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.reandroid.apk.APKLogger
|
||||
import io.ktor.client.request.url
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.Properties
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
private val allowedFileTypes = arrayOf(GPlayFile.FileType.BASE, GPlayFile.FileType.SPLIT)
|
||||
const val LOG_TAG = "PlayStorePlugin"
|
||||
|
||||
private object ArscLogger : APKLogger {
|
||||
const val TAG = "ARSCLib"
|
||||
|
||||
override fun logMessage(msg: String) {
|
||||
Log.i(TAG, msg)
|
||||
}
|
||||
|
||||
override fun logError(msg: String, tr: Throwable?) {
|
||||
Log.e(TAG, msg, tr)
|
||||
}
|
||||
|
||||
override fun logVerbose(msg: String) {
|
||||
Log.v(TAG, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class GPlayApp(
|
||||
val files: List<GPlayFile>
|
||||
) : Parcelable
|
||||
|
||||
@Suppress("Unused")
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
val playStoreDownloader = Downloader<GPlayApp> {
|
||||
get { packageName, version ->
|
||||
val (credentials, deviceProps) = useService<CredentialProviderService, Pair<Credentials, Properties>> { binder ->
|
||||
val credentialProvider = ICredentialProvider.Stub.asInterface(binder)
|
||||
val props = credentialProvider.properties.value
|
||||
credentialProvider.retrieveCredentials()?.let { return@useService it to props }
|
||||
|
||||
try {
|
||||
requestStartActivity<AuthActivity>()
|
||||
} catch (e: UserInteractionException.Activity.NotCompleted) {
|
||||
if (e.resultCode == AuthActivity.RESULT_FAILED) throw Exception(
|
||||
"Login failed: ${
|
||||
e.intent?.getStringExtra(
|
||||
AuthActivity.FAILURE_MESSAGE_KEY
|
||||
)
|
||||
}"
|
||||
)
|
||||
throw e
|
||||
}
|
||||
|
||||
credentialProvider.retrieveCredentials()?.let { it to props } ?: throw Exception("Could not get credentials")
|
||||
}
|
||||
val authData = credentials.toAuthData(deviceProps)
|
||||
|
||||
val app = try {
|
||||
AppDetailsHelper(authData).using(Http).getAppByPackageName(packageName)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "Got exception while trying to get app", e)
|
||||
null
|
||||
}?.takeUnless { version != null && it.versionName != version } ?: return@get null
|
||||
if (!app.isFree) return@get null
|
||||
|
||||
GPlayApp(
|
||||
app.fileList.filterNot { it.url.isBlank() }.ifEmpty {
|
||||
PurchaseHelper(authData).using(Http).purchase(
|
||||
app.packageName,
|
||||
app.versionCode,
|
||||
app.offerType
|
||||
)
|
||||
}
|
||||
) to app.versionName
|
||||
}
|
||||
|
||||
download { app, outputStream ->
|
||||
val apkDir = Files.createTempDirectory("play_dl")
|
||||
try {
|
||||
if (app.files.isEmpty()) error("No valid files to download")
|
||||
app.files.forEach { file ->
|
||||
if (file.type !in allowedFileTypes) error("${file.name} could not be downloaded because it has an unsupported type: ${file.type.name}")
|
||||
apkDir.resolve(file.name).outputStream(StandardOpenOption.CREATE_NEW)
|
||||
.use { stream ->
|
||||
Http.download(stream) {
|
||||
url(file.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val apkFiles = apkDir.listDirectoryEntries()
|
||||
if (apkFiles.size == 1)
|
||||
Files.copy(apkFiles.first(), outputStream)
|
||||
else
|
||||
Merger.merge(apkDir, ArscLogger).writeApk(outputStream)
|
||||
|
||||
} finally {
|
||||
apkDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Properties
|
||||
|
||||
@Parcelize
|
||||
data class Credentials(val email: String, val aasToken: String) : Parcelable {
|
||||
fun toAuthData(deviceProperties: Properties) =
|
||||
AuthHelper.using(Http)
|
||||
.build(email, aasToken, AuthHelper.Token.AAS, properties = deviceProperties)
|
||||
}
|
||||
|
||||
private fun Context.credentialsSharedPrefs() =
|
||||
getSharedPreferences("credentials", Context.MODE_PRIVATE)
|
||||
|
||||
fun Context.saveCredentials(credentials: Credentials) {
|
||||
with(credentialsSharedPrefs().edit()) {
|
||||
putString(EMAIL_KEY, credentials.email)
|
||||
putString(AAS_TOKEN_KEY, credentials.aasToken)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.readSavedCredentials() = with(credentialsSharedPrefs()) {
|
||||
val email = getString(EMAIL_KEY, null) ?: return@with null
|
||||
val aasToken = getString(AAS_TOKEN_KEY, null) ?: return@with null
|
||||
|
||||
Credentials(email, aasToken)
|
||||
}
|
||||
|
||||
private const val EMAIL_KEY = "email"
|
||||
private const val AAS_TOKEN_KEY = "aas_token"
|
||||
@@ -0,0 +1,110 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.data
|
||||
|
||||
import com.aurora.gplayapi.data.models.PlayResponse
|
||||
import com.aurora.gplayapi.network.IHttpClient
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.prepareGet
|
||||
import io.ktor.client.request.request
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.utils.io.jvm.javaio.copyTo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.OutputStream
|
||||
|
||||
object Http : IHttpClient {
|
||||
val client = HttpClient(OkHttp) {
|
||||
install(HttpTimeout) {
|
||||
socketTimeoutMillis = 10000
|
||||
}
|
||||
}
|
||||
|
||||
private val _responseCode = MutableStateFlow(100)
|
||||
override val responseCode = _responseCode.asStateFlow()
|
||||
|
||||
suspend inline fun download(
|
||||
outputStream: OutputStream,
|
||||
block: HttpRequestBuilder.() -> Unit
|
||||
) = client.prepareGet(block).execute {
|
||||
it.bodyAsChannel().copyTo(outputStream)
|
||||
}
|
||||
|
||||
private fun playRequest(
|
||||
method: HttpMethod,
|
||||
url: String,
|
||||
headers: Map<String, String>,
|
||||
builder: suspend HttpRequestBuilder.() -> Unit = {}
|
||||
) = runBlocking {
|
||||
val response = client.request(url) {
|
||||
this.method = method
|
||||
this.headers {
|
||||
headers.forEach { (name, value) ->
|
||||
append(name, value)
|
||||
}
|
||||
}
|
||||
builder()
|
||||
}
|
||||
|
||||
_responseCode.apply {
|
||||
// State flows will not emit the same value twice
|
||||
value = 0
|
||||
value = response.status.value
|
||||
}
|
||||
|
||||
val success = response.status.value in 200..299
|
||||
PlayResponse(
|
||||
code = response.status.value,
|
||||
isSuccessful = success,
|
||||
responseBytes = response.body(),
|
||||
errorString = if (!success) response.status.description else ""
|
||||
)
|
||||
}
|
||||
|
||||
override fun get(url: String, headers: Map<String, String>) =
|
||||
playRequest(HttpMethod.Get, url, headers)
|
||||
|
||||
override fun get(
|
||||
url: String,
|
||||
headers: Map<String, String>,
|
||||
params: Map<String, String>
|
||||
) = playRequest(HttpMethod.Get, url, headers) {
|
||||
params.forEach { (name, value) ->
|
||||
parameter(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(url: String, headers: Map<String, String>, paramString: String) = playRequest(
|
||||
HttpMethod.Get, url + paramString, headers
|
||||
)
|
||||
|
||||
override fun getAuth(url: String) = PlayResponse(
|
||||
isSuccessful = false,
|
||||
code = 444
|
||||
)
|
||||
|
||||
override fun post(url: String, headers: Map<String, String>, body: ByteArray) = playRequest(
|
||||
HttpMethod.Post, url, headers
|
||||
) {
|
||||
setBody(body)
|
||||
}
|
||||
|
||||
override fun post(
|
||||
url: String,
|
||||
headers: Map<String, String>,
|
||||
params: Map<String, String>
|
||||
) = playRequest(HttpMethod.Post, url, headers) {
|
||||
params.forEach { (name, value) ->
|
||||
parameter(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun postAuth(url: String, body: ByteArray) = getAuth(url)
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.data
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.opengl.EGL14
|
||||
import android.opengl.GLES10
|
||||
import javax.microedition.khronos.egl.EGL10
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.egl.EGLContext
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Properties
|
||||
import javax.microedition.khronos.egl.EGLDisplay
|
||||
|
||||
@Parcelize
|
||||
data class ParcelProperties(val value: Properties) : Parcelable
|
||||
|
||||
object PropertiesProvider {
|
||||
fun createDeviceProperties(context: Context) = with(context) {
|
||||
Properties().apply {
|
||||
//Build Props
|
||||
setProperty("UserReadableName", "${Build.DEVICE}-default")
|
||||
setProperty("Build.HARDWARE", Build.HARDWARE)
|
||||
setProperty("Build.RADIO", Build.getRadioVersion() ?: "unknown")
|
||||
setProperty("Build.FINGERPRINT", Build.FINGERPRINT)
|
||||
setProperty("Build.BRAND", Build.BRAND)
|
||||
setProperty("Build.DEVICE", Build.DEVICE)
|
||||
setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}")
|
||||
setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE)
|
||||
setProperty("Build.MODEL", Build.MODEL)
|
||||
setProperty("Build.MANUFACTURER", Build.MANUFACTURER)
|
||||
setProperty("Build.PRODUCT", Build.PRODUCT)
|
||||
setProperty("Build.ID", Build.ID)
|
||||
setProperty("Build.BOOTLOADER", Build.BOOTLOADER)
|
||||
|
||||
val config = resources.configuration
|
||||
setProperty("TouchScreen", "${config.touchscreen}")
|
||||
setProperty("Keyboard", "${config.keyboard}")
|
||||
setProperty("Navigation", "${config.navigation}")
|
||||
setProperty("ScreenLayout", "${config.screenLayout and 15}")
|
||||
setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}")
|
||||
setProperty(
|
||||
"HasFiveWayNavigation",
|
||||
"${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}"
|
||||
)
|
||||
|
||||
//Display Metrics
|
||||
val metrics = resources.displayMetrics
|
||||
setProperty("Screen.Density", "${metrics.densityDpi}")
|
||||
setProperty("Screen.Width", "${metrics.widthPixels}")
|
||||
setProperty("Screen.Height", "${metrics.heightPixels}")
|
||||
|
||||
|
||||
//Supported Platforms
|
||||
setProperty("Platforms", Build.SUPPORTED_ABIS.commaSeparated())
|
||||
//Supported Features
|
||||
setProperty("Features", features.commaSeparated())
|
||||
//Shared Locales
|
||||
setProperty("Locales", locales.commaSeparated())
|
||||
//Shared Libraries
|
||||
setProperty("SharedLibraries", sharedLibraries.commaSeparated())
|
||||
//GL Extensions
|
||||
setProperty(
|
||||
"GL.Version",
|
||||
getSystemService<ActivityManager>()!!.deviceConfigurationInfo.reqGlEsVersion.toString()
|
||||
)
|
||||
setProperty(
|
||||
"GL.Extensions",
|
||||
getEglExtensions().commaSeparated()
|
||||
)
|
||||
|
||||
//Google Related Props
|
||||
setProperty("Client", "android-google")
|
||||
setProperty("GSF.version", "203615037")
|
||||
setProperty("Vending.version", "82201710")
|
||||
setProperty("Vending.versionString", "22.0.17-21 [0] [PR] 332555730")
|
||||
|
||||
//MISC
|
||||
setProperty("Roaming", "mobile-notroaming")
|
||||
setProperty("TimeZone", "UTC-10")
|
||||
|
||||
//Telephony (USA 3650 AT&T)
|
||||
setProperty("CellOperator", "310")
|
||||
setProperty("SimOperator", "38")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Array<String>.commaSeparated() = joinToString(separator = ",")
|
||||
private fun Iterable<String>.commaSeparated() = joinToString(separator = ",")
|
||||
|
||||
private val Context.features
|
||||
get() = packageManager.systemAvailableFeatures.map { it.name }
|
||||
.filterNot(String::isNullOrEmpty)
|
||||
private val Context.locales
|
||||
get() = assets.locales.filter(String::isNullOrEmpty).map { it.replace("-", "_") }
|
||||
private val Context.sharedLibraries
|
||||
get() = packageManager.systemSharedLibraryNames.orEmpty().filterNotNull()
|
||||
|
||||
private fun getEglExtensions() = buildSet {
|
||||
val egl = EGLContext.getEGL() as EGL10
|
||||
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
|
||||
egl.eglInitialize(display, IntArray(2))
|
||||
try {
|
||||
val numConfigsContainer = IntArray(1)
|
||||
if (!egl.eglGetConfigs(display, null, 0, numConfigsContainer)) return@buildSet
|
||||
val numConfigs = numConfigsContainer[0]
|
||||
val configs = arrayOfNulls<EGLConfig>(numConfigs).apply {
|
||||
if (!egl.eglGetConfigs(
|
||||
display,
|
||||
this,
|
||||
numConfigs,
|
||||
numConfigsContainer
|
||||
)
|
||||
) return@buildSet
|
||||
}.requireNoNulls()
|
||||
|
||||
configs.forEach {
|
||||
val resultContainer = IntArray(1)
|
||||
egl.eglGetConfigAttrib(display, it, EGL10.EGL_CONFIG_CAVEAT, resultContainer)
|
||||
if (resultContainer[0] == EGL10.EGL_SLOW_CONFIG) return@forEach
|
||||
|
||||
egl.eglGetConfigAttrib(display, it, EGL10.EGL_SURFACE_TYPE, resultContainer)
|
||||
if (EGL10.EGL_PBUFFER_BIT and resultContainer[0] == 0) return@forEach
|
||||
|
||||
egl.eglGetConfigAttrib(display, it, EGL10.EGL_RENDERABLE_TYPE, resultContainer)
|
||||
if (EGL14.EGL_OPENGL_ES_BIT and resultContainer[0] != 0) addEglExtensions(
|
||||
egl,
|
||||
display,
|
||||
it,
|
||||
null,
|
||||
this
|
||||
)
|
||||
if (EGL14.EGL_OPENGL_ES2_BIT and resultContainer[0] != 0) addEglExtensions(
|
||||
egl,
|
||||
display,
|
||||
it,
|
||||
openGlEs2AttribList,
|
||||
this
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
egl.eglTerminate(display)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEglExtensions(
|
||||
egl: EGL10,
|
||||
eglDisplay: EGLDisplay,
|
||||
eglConfig: EGLConfig,
|
||||
eglContextAttribList: IntArray?,
|
||||
target: MutableSet<String>
|
||||
) {
|
||||
val eglContext =
|
||||
egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, eglContextAttribList)
|
||||
if (eglContext === EGL10.EGL_NO_CONTEXT) return
|
||||
|
||||
val eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, eglSurfaceAttribList)
|
||||
if (eglSurface === EGL10.EGL_NO_SURFACE) {
|
||||
egl.eglDestroyContext(eglDisplay, eglContext)
|
||||
return
|
||||
}
|
||||
|
||||
egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
|
||||
val extensionsString = GLES10.glGetString(GLES10.GL_EXTENSIONS)
|
||||
if (!extensionsString.isNullOrEmpty()) {
|
||||
val extensions = extensionsString.split(" ".toRegex())
|
||||
target.addAll(extensions)
|
||||
}
|
||||
egl.eglMakeCurrent(
|
||||
eglDisplay,
|
||||
EGL10.EGL_NO_SURFACE,
|
||||
EGL10.EGL_NO_SURFACE,
|
||||
EGL10.EGL_NO_CONTEXT
|
||||
)
|
||||
egl.eglDestroySurface(eglDisplay, eglSurface)
|
||||
egl.eglDestroyContext(eglDisplay, eglContext)
|
||||
}
|
||||
|
||||
private val eglSurfaceAttribList
|
||||
get() = intArrayOf(
|
||||
EGL10.EGL_WIDTH,
|
||||
EGL10.EGL_PBUFFER_BIT,
|
||||
EGL10.EGL_HEIGHT,
|
||||
EGL10.EGL_PBUFFER_BIT,
|
||||
EGL10.EGL_NONE
|
||||
)
|
||||
|
||||
private val openGlEs2AttribList
|
||||
get() = intArrayOf(
|
||||
EGL14.EGL_CONTEXT_CLIENT_VERSION,
|
||||
EGL10.EGL_PIXMAP_BIT,
|
||||
EGL10.EGL_NONE
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import app.revanced.manager.plugin.downloader.play.store.ICredentialProvider
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.ParcelProperties
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.PropertiesProvider
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.readSavedCredentials
|
||||
|
||||
class CredentialProviderService : Service() {
|
||||
private val binder = object : ICredentialProvider.Stub() {
|
||||
override fun retrieveCredentials() = try {
|
||||
readSavedCredentials()
|
||||
} catch (e: Exception) {
|
||||
Log.e("CredentialService", "Got exception while retrieving credentials", e)
|
||||
throw IllegalStateException("An exception was raised when reading credentials")
|
||||
}
|
||||
|
||||
override fun getProperties() =
|
||||
ParcelProperties(PropertiesProvider.createDeviceProperties(this@CredentialProviderService))
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder = binder.asBinder()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kevinnzou.web.WebView
|
||||
import com.kevinnzou.web.rememberWebViewState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AuthActivity : ComponentActivity() {
|
||||
private val vm: AuthActivityViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
val (code, intent) = vm.awaitActivityResultCode()
|
||||
setResult(code, intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
setContent {
|
||||
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
|
||||
|
||||
MaterialTheme(colorScheme) {
|
||||
Scaffold { paddingValues ->
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
val state =
|
||||
rememberWebViewState(url = AuthActivityViewModel.EMBEDDED_SETUP_URL)
|
||||
|
||||
WebView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = state,
|
||||
onCreated = vm::setupWebView,
|
||||
client = vm.webViewClient
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val RESULT_FAILED = RESULT_FIRST_USER + 1
|
||||
const val FAILURE_MESSAGE_KEY = "FAIL_MESSAGE"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package app.revanced.manager.plugin.downloader.play.store.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.plugin.downloader.play.store.LOG_TAG
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.Credentials
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.Http
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.PropertiesProvider
|
||||
import app.revanced.manager.plugin.downloader.play.store.data.saveCredentials
|
||||
import com.aurora.gplayapi.helpers.AuthValidator
|
||||
import com.kevinnzou.web.AccompanistWebViewClient
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.append
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
import java.util.StringTokenizer
|
||||
|
||||
class AuthActivityViewModel(app: Application) : AndroidViewModel(app) {
|
||||
private val cookieManager = CookieManager.getInstance()!!
|
||||
private val activityResultCode =
|
||||
CompletableDeferred<Pair<Int, Intent?>>(parent = viewModelScope.coroutineContext.job)
|
||||
|
||||
val webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
|
||||
val cookieString = cookieManager.getCookie(url) ?: return
|
||||
val cookies = buildMap {
|
||||
putAll(cookiePattern.findAll(cookieString).map {
|
||||
val (key, value) = it.destructured
|
||||
key to value
|
||||
})
|
||||
}
|
||||
cookies[OAUTH_COOKIE]?.let { token ->
|
||||
view.evaluateJavascript(EXTRACT_EMAIL_JS) {
|
||||
val email = it.replace("\"".toRegex(), "")
|
||||
getAndSaveAasToken(email, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
cookieManager.removeAllCookies(null)
|
||||
}
|
||||
|
||||
suspend fun awaitActivityResultCode() = activityResultCode.await()
|
||||
private fun finishActivity(code: Int, intent: Intent? = null) =
|
||||
activityResultCode.complete(code to intent)
|
||||
|
||||
private fun getAndSaveAasToken(email: String, oauthToken: String) = viewModelScope.launch {
|
||||
try {
|
||||
val response = getAC2DMResponse(email, oauthToken)
|
||||
val aasToken = response["Token"] ?: throw Exception("AC2DM did not return a token")
|
||||
val credentials = Credentials(email, aasToken)
|
||||
val context = getApplication<Application>()
|
||||
val deviceProps = PropertiesProvider.createDeviceProperties(context)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val authData = credentials.toAuthData(deviceProps)
|
||||
val validator = AuthValidator(authData).using(Http)
|
||||
|
||||
if (!validator.isValid()) throw Exception("Credential validation failed")
|
||||
context.saveCredentials(credentials)
|
||||
}
|
||||
|
||||
finishActivity(Activity.RESULT_OK)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "Could not get and save token", e)
|
||||
finishActivity(AuthActivity.RESULT_FAILED, Intent().apply {
|
||||
putExtra(AuthActivity.FAILURE_MESSAGE_KEY, e.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAC2DMResponse(email: String, oauthToken: String): Map<String, String> {
|
||||
val locale = Locale.getDefault()
|
||||
val formData = mapOf(
|
||||
"lang" to locale.toString().replace("_", "-"),
|
||||
"google_play_services_version" to PLAY_SERVICES_VERSION_CODE,
|
||||
"sdk_version" to BUILD_VERSION_SDK,
|
||||
"device_country" to locale.country.lowercase(Locale.US),
|
||||
"Email" to email,
|
||||
"service" to "ac2dm",
|
||||
"get_accountid" to 1,
|
||||
"ACCESS_TOKEN" to 1,
|
||||
"callerPkg" to GMS_PACKAGE_NAME,
|
||||
"add_account" to 1,
|
||||
"Token" to oauthToken,
|
||||
"callerSig" to CALLER_SIGNATURE
|
||||
).entries.joinToString(separator = "&") { (key, value) -> "$key=$value" }
|
||||
|
||||
val response = Http.client.post(TOKEN_AUTH_URL) {
|
||||
headers {
|
||||
set("app", GMS_PACKAGE_NAME)
|
||||
set(HttpHeaders.UserAgent, "")
|
||||
append(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded)
|
||||
}
|
||||
|
||||
setBody(formData)
|
||||
}
|
||||
|
||||
val body = response.bodyAsText()
|
||||
return buildMap {
|
||||
val st = StringTokenizer(body, "\n\r")
|
||||
while (st.hasMoreTokens()) {
|
||||
val (key, value) = st.nextToken().split("=", limit = 2)
|
||||
put(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
fun setupWebView(webView: WebView) = webView.apply {
|
||||
cookieManager.acceptThirdPartyCookies(this)
|
||||
cookieManager.setAcceptThirdPartyCookies(this, true)
|
||||
|
||||
settings.apply {
|
||||
safeBrowsingEnabled = false
|
||||
allowContentAccess = true
|
||||
databaseEnabled = true
|
||||
domStorageEnabled = true
|
||||
javaScriptEnabled = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object Constants {
|
||||
private const val OAUTH_COOKIE = "oauth_token"
|
||||
private const val EXTRACT_EMAIL_JS =
|
||||
"(function() { return document.querySelector('[data-profile-identifier]').innerText; })();"
|
||||
const val EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup"
|
||||
private const val TOKEN_AUTH_URL = "https://android.clients.google.com/auth"
|
||||
private const val BUILD_VERSION_SDK = 28
|
||||
private const val PLAY_SERVICES_VERSION_CODE = 19629032
|
||||
private const val GMS_PACKAGE_NAME = "com.google.android.gms"
|
||||
private const val CALLER_SIGNATURE = "38918a453d07199354f8b19af05ec6562ced5788"
|
||||
private val cookiePattern = "([^=]+)=([^;]*);?\\s?".toRegex()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">ReVanced Manager: Play Store downloader</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -1,7 +1,24 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs = -Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX = true
|
||||
android.nonTransitiveRClass = true
|
||||
android.nonFinalResIds = false
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style = official
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
version = 1.0.0
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass = true
|
||||
version = 1.0.0-dev.1
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
[versions]
|
||||
agp = "8.7.3"
|
||||
kotlin = "2.1.10"
|
||||
gplayapi = "3.4.4"
|
||||
arsclib = "1.3.5"
|
||||
kotlinx-coroutines-core = "1.9.0"
|
||||
ktor = "2.3.9"
|
||||
|
||||
compose-activity = "1.10.1"
|
||||
ui-tooling = "1.8.2"
|
||||
compose-bom = "2025.06.00"
|
||||
material3 = "1.3.2"
|
||||
compose-webview = "0.33.6"
|
||||
|
||||
plugin-api = "1.0.0"
|
||||
android-gradle-plugin = "8.7.3"
|
||||
kotlin = "2.1.0"
|
||||
|
||||
[libraries]
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||
plugin-api = { group = "app.revanced", name = "revanced-manager-downloader-api", version.ref = "plugin-api" }
|
||||
gplayapi = { group = "com.auroraoss", name = "gplayapi", version.ref = "gplayapi" }
|
||||
arsclib = { group = "io.github.reandroid", name = "ARSCLib", version.ref = "arsclib" }
|
||||
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||
compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "ui-tooling" }
|
||||
compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
|
||||
compose-webview = { group = "io.github.kevinnzou", name = "compose-webview", version.ref = "compose-webview" }
|
||||
ktor-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
||||
ktor-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" }
|
||||
ktor-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,8 +1,6 @@
|
||||
#Tue Jul 23 17:28:58 CEST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
285
gradlew
vendored
285
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,104 +15,69 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -122,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -133,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
37
gradlew.bat
vendored
37
gradlew.bat
vendored
@@ -13,10 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -27,8 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -43,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -78,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
1408
package-lock.json
generated
1408
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,6 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.10.1",
|
||||
"semantic-release": "^24.1.2"
|
||||
"semantic-release": "^24.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
22
proguard-rules.pro
vendored
22
proguard-rules.pro
vendored
@@ -1 +1,21 @@
|
||||
-keep public class app.revanced.manager.plugin.downloader.apkmirror.*
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -1,19 +1,24 @@
|
||||
rootProject.name = "revanced-manager-apkmirror-downloader"
|
||||
rootProject.name = "revanced-manager-downloaders"
|
||||
|
||||
pluginManagement.repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencyResolutionManagement.repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
||||
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = providers.gradleProperty("gpr.user").getOrElse(System.getenv("GITHUB_ACTOR"))
|
||||
password = providers.gradleProperty("gpr.key").getOrElse(System.getenv("GITHUB_TOKEN"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include(":shared")
|
||||
include(":arsclib")
|
||||
file("downloaders").listFiles()
|
||||
?.forEach {
|
||||
include(":${it.name}")
|
||||
project(":${it.name}").projectDir = file("downloaders/${it.name}")
|
||||
}
|
||||
|
||||
8
shared/build.gradle.kts
Normal file
8
shared/build.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(project(":arsclib"))
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package app.revanced.manager.plugin.utils
|
||||
|
||||
import com.reandroid.apk.APKLogger
|
||||
import com.reandroid.apk.ApkBundle
|
||||
import com.reandroid.apk.ApkModule
|
||||
import com.reandroid.app.AndroidManifest
|
||||
import java.io.Closeable
|
||||
import java.nio.file.Path
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class Merger {
|
||||
companion object Factory {
|
||||
suspend fun merge(apkDir: Path, arscLogger: APKLogger): ApkModule {
|
||||
val closeables = mutableSetOf<Closeable>()
|
||||
try {
|
||||
// Merge splits
|
||||
val merged = withContext(Dispatchers.Default) {
|
||||
with(ApkBundle()) {
|
||||
setAPKLogger(arscLogger)
|
||||
loadApkDirectory(apkDir.toFile())
|
||||
closeables.addAll(modules)
|
||||
mergeModules().also(closeables::add)
|
||||
}
|
||||
}
|
||||
merged.androidManifest.apply {
|
||||
arrayOf(
|
||||
AndroidManifest.ID_isSplitRequired,
|
||||
AndroidManifest.ID_extractNativeLibs
|
||||
).forEach {
|
||||
applicationElement.removeAttributesWithId(it)
|
||||
manifestElement.removeAttributesWithId(it)
|
||||
}
|
||||
|
||||
arrayOf(
|
||||
AndroidManifest.NAME_requiredSplitTypes,
|
||||
AndroidManifest.NAME_splitTypes
|
||||
).forEach(manifestElement::removeAttributesWithName)
|
||||
|
||||
val pattern = "^com\\.android\\.(stamp|vending)\\.".toRegex()
|
||||
applicationElement.removeElementsIf { element ->
|
||||
if (element.name != AndroidManifest.TAG_meta_data) return@removeElementsIf false
|
||||
val nameAttr =
|
||||
element.getAttributes { it.nameId == AndroidManifest.ID_name }
|
||||
.asSequence().single()
|
||||
|
||||
pattern.containsMatchIn(nameAttr.valueString)
|
||||
}
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
return merged
|
||||
} finally {
|
||||
closeables.forEach(Closeable::close)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user