feat: Add /list route

This commit is contained in:
oSumAtrIX 2024-02-07 01:16:35 +01:00
parent e798a4c070
commit 6c930fff9a
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
5 changed files with 90 additions and 27 deletions

View File

@ -21,7 +21,7 @@ tasks {
Because semantic-release is not designed to handle this case, we need to hack it.
RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
*/
*/
register<DefaultTask>("publish") {
group = "publishing"
description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced API"
@ -41,6 +41,9 @@ ktor {
repositories {
mavenCentral()
google()
maven { url = uri("https://jitpack.io") }
mavenLocal()
}
dependencies {
@ -72,6 +75,9 @@ dependencies {
implementation(libs.ktoml.file)
implementation(libs.picocli)
implementation(libs.kotlinx.datetime)
implementation(libs.revanced.patcher)
implementation(libs.revanced.library)
implementation(libs.caffeine)
testImplementation(libs.mockk)
testImplementation(libs.ktor.server.tests)

View File

@ -10,6 +10,9 @@ ktoml = "0.5.1"
picocli = "4.7.3"
datetime = "0.5.0"
mockk = "1.13.9"
revanced-patcher = "19.2.0"
revanced-library = "1.5.0"
caffeine = "3.1.8"
[libraries]
ktor-client-core = { module = "io.ktor:ktor-client-core" }
@ -43,6 +46,9 @@ ktoml-file = { module = "com.akuleshov7:ktoml-file", version.ref = "ktoml" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" }
[plugins]
serilization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

View File

@ -19,8 +19,6 @@ fun Application.configureHTTP() {
anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
}
install(CachingHeaders) {
options { _, _ ->
CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt()))
}
options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 5.minutes.inWholeSeconds.toInt())) }
}
}

View File

@ -2,6 +2,9 @@ package app.revanced.api.modules
import app.revanced.api.backend.Backend
import app.revanced.api.schema.*
import app.revanced.library.PatchUtils
import app.revanced.patcher.PatchBundleLoader
import com.github.benmanes.caffeine.cache.Caffeine
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
@ -12,6 +15,8 @@ import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import java.io.File
import java.net.URL
import org.koin.ktor.ext.get as koinGet
fun Application.configureRouting() {
@ -116,34 +121,70 @@ fun Application.configureRouting() {
route("/patches") {
route("latest") {
get {
val patches = backend.getRelease(configuration.organization, configuration.patchesRepository)
val integrations =
configuration.integrationsRepositoryNames.map {
async { backend.getRelease(configuration.organization, it) }
}.awaitAll()
val patchesRelease =
backend.getRelease(configuration.organization, configuration.patchesRepository)
val integrationsReleases = configuration.integrationsRepositoryNames.map {
async { backend.getRelease(configuration.organization, it) }
}.awaitAll()
val assets =
(patches.assets + integrations.flatMap { it.assets }).filter {
it.downloadUrl.endsWith(".apk") || it.downloadUrl.endsWith(".jar")
}.map { APIAsset(it.downloadUrl) }.toSet()
val assets = (patchesRelease.assets + integrationsReleases.flatMap { it.assets })
.map { APIAsset(it.downloadUrl) }
.filter { it.type != APIAsset.Type.UNKNOWN }
.toSet()
val release =
APIRelease(
patches.tag,
patches.createdAt,
patches.releaseNote,
assets,
)
val apiRelease = APIRelease(
patchesRelease.tag,
patchesRelease.createdAt,
patchesRelease.releaseNote,
assets,
)
call.respond(release)
call.respond(apiRelease)
}
get("/version") {
val patches = backend.getRelease(configuration.organization, configuration.patchesRepository)
val patchesRelease =
backend.getRelease(configuration.organization, configuration.patchesRepository)
val release = APIReleaseVersion(patches.tag)
val apiPatchesRelease = APIReleaseVersion(patchesRelease.tag)
call.respond(release)
call.respond(apiPatchesRelease)
}
val fileCache = Caffeine
.newBuilder()
.evictionListener<String, File> { _, value, _ -> value?.delete() }
.maximumSize(1)
.build<String, File>()
get("/list") {
val patchesRelease =
backend.getRelease(configuration.organization, configuration.patchesRepository)
// Get the cached patches file or download and cache a new one.
// The old file is deleted on eviction.
val patchesFile = fileCache.getIfPresent(patchesRelease.tag) ?: run {
val downloadUrl = patchesRelease.assets
.map { APIAsset(it.downloadUrl) }
.find { it.type == APIAsset.Type.PATCHES }
?.downloadUrl
kotlin.io.path.createTempFile().toFile().apply {
outputStream().use { URL(downloadUrl).openStream().copyTo(it) }
}.also {
fileCache.put(patchesRelease.tag, it)
it.deleteOnExit()
}
}
call.respondOutputStream(
contentType = ContentType.Application.Json,
) {
PatchUtils.Json.serialize(
PatchBundleLoader.Jar(patchesFile),
outputStream = this,
)
}
}
}
}

View File

@ -1,6 +1,7 @@
package app.revanced.api.schema
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@ -44,9 +45,20 @@ class APIAsset(
val downloadUrl: String,
) {
val type = when {
downloadUrl.endsWith(".jar") -> "patches"
downloadUrl.endsWith(".apk") -> "integrations"
else -> "unknown"
downloadUrl.endsWith(".jar") -> Type.PATCHES
downloadUrl.endsWith(".apk") -> Type.INTEGRATIONS
else -> Type.UNKNOWN
}
enum class Type {
@SerialName("patches")
PATCHES,
@SerialName("integrations")
INTEGRATIONS,
@SerialName("unknown")
UNKNOWN,
}
}