mirror of
https://github.com/ReVanced/revanced-manager-downloaders.git
synced 2026-01-11 13:56:17 +00:00
fix: Refactor of shared module + minor fixes
This commit is contained in:
@@ -67,20 +67,21 @@ The collection of ReVanced downloaders.
|
||||
|
||||
## 🧑💻 Usage
|
||||
|
||||
- A downloader is managed as Android apps. Download and install the APK file from the releases page.
|
||||
- Downloaders are managed as Android apps. Download and install the APK file from the [releases page](https://github.com/ReVanced/revanced-manager-downloaders/releases/).
|
||||
- After installing, restart ReVanced Manager and enable the downloader in the settings.
|
||||
- The downloader will now be usable.
|
||||
|
||||
|
||||
### APKMirror 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.
|
||||
If the chosen file is a bundle (`apkm` file), the downloader will automatically merge it into an `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 suggested version differs from the latest, the installation will fail.
|
||||
> This plugin is not officially endorsed by or affiliated with Google. All usage is at your own risk.
|
||||
|
||||
## 📚 Everything else
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
alias(libs.plugins.kotlin.parcelize) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.compose.compiler) apply false
|
||||
@@ -26,7 +27,7 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
if (project.path.endsWith("-downloader")) {
|
||||
if (project.path.startsWith(":downloaders:")) {
|
||||
apply(plugin = "com.android.application")
|
||||
apply(plugin = "org.jetbrains.kotlin.android")
|
||||
apply(plugin = "maven-publish")
|
||||
@@ -76,7 +77,7 @@ subprojects {
|
||||
applicationVariants.all {
|
||||
outputs.all {
|
||||
this as ApkVariantOutputImpl
|
||||
outputFileName = "${project.name}-$version.apk"
|
||||
outputFileName = "${project.name}-downloader-$version.apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +91,7 @@ subprojects {
|
||||
tasks.register("assembleReleaseSignApk") {
|
||||
dependsOn("assembleRelease")
|
||||
|
||||
val apk = layout.buildDirectory.file("outputs/apk/release/${project.name}-$version.apk")
|
||||
val apk = layout.buildDirectory.file("outputs/apk/release/${project.name}-downloader-$version.apk")
|
||||
|
||||
inputs.file(apk).withPropertyName("input")
|
||||
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
@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.UUID
|
||||
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
|
||||
) : 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)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority("www.apkmirror.com")
|
||||
.appendQueryParameter("post_type", "app_release")
|
||||
.appendQueryParameter("searchtype", "apk")
|
||||
.appendQueryParameter("s", version?.let { "$packageName $it" } ?: packageName)
|
||||
.appendQueryParameter("bundles%5B%5D" /* bundles[] */, "apk_files")
|
||||
.toString()
|
||||
}
|
||||
) to version
|
||||
}
|
||||
|
||||
download { app, outputStream ->
|
||||
val workingDir = Files.createTempDirectory("apkmirror_dl")
|
||||
try {
|
||||
if (URI(app.downloadUrl.url).path.substringAfterLast('/').endsWith(".apk")) {
|
||||
val (inputStream, size) = app.downloadUrl.toDownloadResult()
|
||||
inputStream.use {
|
||||
if (size != null) reportSize(size)
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
} else {
|
||||
val downloadedFile = workingDir.resolve(UUID.randomUUID().toString()).also {
|
||||
it.outputStream().use { output ->
|
||||
app.downloadUrl.toDownloadResult().first.copyTo(output)
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":arsclib"))
|
||||
implementation(project(":shared"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@file:Suppress("Unused")
|
||||
|
||||
package app.revanced.manager.plugin.downloader.apkmirror
|
||||
|
||||
import android.net.Uri
|
||||
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.shared.Merger
|
||||
import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import java.util.UUID
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
val ApkMirrorDownloader = Downloader<DownloadUrl> {
|
||||
get { packageName, version ->
|
||||
runWebView("APKMirror") {
|
||||
download { url, _, userAgent ->
|
||||
finish(
|
||||
DownloadUrl(
|
||||
url,
|
||||
mapOf("User-Agent" to userAgent)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority("www.apkmirror.com")
|
||||
.appendQueryParameter("post_type", "app_release")
|
||||
.appendQueryParameter("searchtype", "apk")
|
||||
.appendQueryParameter("s", version?.let { "$packageName $it" } ?: packageName)
|
||||
.appendQueryParameter("bundles%5B%5D" /* bundles[] */, "apk_files")
|
||||
.toString()
|
||||
} to version
|
||||
}
|
||||
|
||||
download { downloadUrl, outputStream ->
|
||||
val workingDir = Files.createTempDirectory("apkmirror_dl")
|
||||
try {
|
||||
if (URI(downloadUrl.url).path.substringAfterLast('/').endsWith(".apk")) {
|
||||
val (inputStream, size) = downloadUrl.toDownloadResult()
|
||||
inputStream.use {
|
||||
if (size != null) reportSize(size)
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
} else {
|
||||
val downloadedFile = workingDir.resolve(UUID.randomUUID().toString()).also {
|
||||
it.outputStream().use { output ->
|
||||
downloadUrl.toDownloadResult().first.copyTo(output)
|
||||
}
|
||||
}
|
||||
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 ->
|
||||
Files.copy(input, outputFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Merger.merge(xapkWorkingDir).writeApk(outputStream)
|
||||
}
|
||||
} finally {
|
||||
workingDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation(libs.gplayapi)
|
||||
implementation(project(":arsclib"))
|
||||
implementation(project(":shared"))
|
||||
|
||||
implementation(libs.ktor.core)
|
||||
@@ -7,11 +7,10 @@ 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 app.revanced.manager.plugin.shared.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
|
||||
@@ -25,22 +24,6 @@ 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>
|
||||
@@ -109,7 +92,7 @@ val playStoreDownloader = Downloader<GPlayApp> {
|
||||
if (apkFiles.size == 1)
|
||||
Files.copy(apkFiles.first(), outputStream)
|
||||
else
|
||||
Merger.merge(apkDir, ArscLogger).writeApk(outputStream)
|
||||
Merger.merge(apkDir).writeApk(outputStream)
|
||||
|
||||
} finally {
|
||||
apkDir.deleteRecursively()
|
||||
@@ -2,7 +2,6 @@
|
||||
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"
|
||||
|
||||
@@ -18,7 +17,6 @@ plugin-api = "1.0.0"
|
||||
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" }
|
||||
@@ -31,6 +29,7 @@ ktor-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "k
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.21" }
|
||||
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
||||
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
@@ -19,6 +19,6 @@ include(":shared")
|
||||
include(":arsclib")
|
||||
file("downloaders").listFiles()
|
||||
?.forEach {
|
||||
include(":${it.name}")
|
||||
project(":${it.name}").projectDir = file("downloaders/${it.name}")
|
||||
include(":downloaders:${it.name}")
|
||||
project(":downloaders:${it.name}").projectDir = file("downloaders/${it.name}")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.manager.plugin.shared"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(project(":arsclib"))
|
||||
}
|
||||
api(project(":arsclib"))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.manager.plugin.utils
|
||||
package app.revanced.manager.plugin.shared
|
||||
|
||||
import android.util.Log
|
||||
import com.reandroid.apk.APKLogger
|
||||
import com.reandroid.apk.ApkBundle
|
||||
import com.reandroid.apk.ApkModule
|
||||
@@ -9,15 +10,31 @@ import java.nio.file.Path
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
class Merger {
|
||||
companion object Factory {
|
||||
suspend fun merge(apkDir: Path, arscLogger: APKLogger): ApkModule {
|
||||
suspend fun merge(apkDir: Path): ApkModule {
|
||||
val closeables = mutableSetOf<Closeable>()
|
||||
try {
|
||||
// Merge splits
|
||||
val merged = withContext(Dispatchers.Default) {
|
||||
with(ApkBundle()) {
|
||||
setAPKLogger(arscLogger)
|
||||
setAPKLogger(ArscLogger)
|
||||
loadApkDirectory(apkDir.toFile())
|
||||
closeables.addAll(modules)
|
||||
mergeModules().also(closeables::add)
|
||||
Reference in New Issue
Block a user