From dfe6df3ef6006d06681673bcfaf87c44c40ad446 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 15 Jul 2024 03:12:39 +0200 Subject: [PATCH] feat: Convert static about file to documented route & add key parameter to about route --- .gitignore | 3 +- about.example.json | 84 +++++++++++++++++++ configuration.example.toml | 7 +- docker-compose.example.yml | 1 + .../app/revanced/api/configuration/Routing.kt | 28 ++++++- .../repository/ConfigurationRepository.kt | 24 +++++- .../api/configuration/routes/ApiRoute.kt | 26 ++++++ .../api/configuration/schema/APISchema.kt | 52 ++++++++++++ .../api/configuration/services/ApiService.kt | 1 + 9 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 about.example.json diff --git a/.gitignore b/.gitignore index 39008635..d376259d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ docker-compose.yml patches-public-key.asc integrations-public-key.asc node_modules/ -static/ \ No newline at end of file +static/ +about.json \ No newline at end of file diff --git a/about.example.json b/about.example.json new file mode 100644 index 00000000..f452fe48 --- /dev/null +++ b/about.example.json @@ -0,0 +1,84 @@ +{ + "name": "ReVanced", + "about": "ReVanced was born out of Vanced's discontinuation and it is our goal to continue the legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create long-lasting patches for nearly any Android app. ReVanced's patching system is designed to allow patches to work on new versions of the apps automatically with bare minimum maintenance.", + "keys": "https://api.revanced.app/keys", + "branding": { + "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg" + }, + "contact": { + "email": "contact@revanced.app" + }, + "socials": [ + { + "name": "Website", + "url": "https://revanced.app", + "preferred": true + }, + { + "name": "GitHub", + "url": "https://github.com/revanced" + }, + { + "name": "Twitter", + "url": "https://twitter.com/revancedapp" + }, + { + "name": "Discord", + "url": "https://revanced.app/discord", + "preferred": true + }, + { + "name": "Reddit", + "url": "https://www.reddit.com/r/revancedapp" + }, + { + "name": "Telegram", + "url": "https://t.me/app_revanced" + }, + { + "name": "YouTube", + "url": "https://www.youtube.com/@ReVanced" + } + ], + "donations": { + "wallets": [ + { + "network": "Bitcoin", + "currency_code": "BTC", + "address": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f" + }, + { + "network": "Dogecoin", + "currency_code": "DOGE", + "address": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp", + "preferred": true + }, + { + "network": "Ethereum", + "currency_code": "ETH", + "address": "0x7ab4091e00363654bf84B34151225742cd92FCE5" + }, + { + "network": "Litecoin", + "currency_code": "LTC", + "address": "LbJi8EuoDcwaZvykcKmcrM74jpjde23qJ2" + }, + { + "network": "Monero", + "currency_code": "XMR", + "address": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh" + } + ], + "links": [ + { + "name": "Open Collective", + "url": "https://opencollective.com/revanced", + "preferred": true + }, + { + "name": "GitHub Sponsors", + "url": "https://github.com/sponsors/ReVanced" + } + ] + } +} diff --git a/configuration.example.toml b/configuration.example.toml index c380e156..fec352cc 100644 --- a/configuration.example.toml +++ b/configuration.example.toml @@ -1,6 +1,6 @@ organization = "revanced" -patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc" } -integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc" } +patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-public-key.asc", public-key-id = 0 } +integrations = { repository = "revanced-integrations", asset-regex = "apk$", signature-asset-regex = "asc$", public-key-file = "integrations-public-key.asc", public-key-id = 0 } manager = { repository = "revanced-manager", asset-regex = "apk$" } contributors-repositories = [ "revanced-patcher", @@ -18,4 +18,5 @@ cors-allowed-hosts = [ endpoint = "https://api.revanced.app" old-api-endpoint = "https://old-api.revanced.app" static-files-path = "static/root" -versioned-static-files-path = "static/versioned" \ No newline at end of file +versioned-static-files-path = "static/versioned" +about-json-file-path = "about.json" diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 49b9d546..8034e332 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -9,6 +9,7 @@ services: - /data/revanced-api/patches-public-key.asc:/app/patches-public-key.asc - /data/revanced-api/integrations-public-key.asc:/app/integrations-public-key.asc - /data/revanced-api/static:/app/static + - /data/revanced-api/about.json:/app/about.json environment: - COMMAND=start ports: diff --git a/src/main/kotlin/app/revanced/api/configuration/Routing.kt b/src/main/kotlin/app/revanced/api/configuration/Routing.kt index 91018a97..1e0f4679 100644 --- a/src/main/kotlin/app/revanced/api/configuration/Routing.kt +++ b/src/main/kotlin/app/revanced/api/configuration/Routing.kt @@ -8,6 +8,7 @@ import app.revanced.api.configuration.routes.oldApiRoute import app.revanced.api.configuration.routes.patchesRoute import io.bkbn.kompendium.core.routes.redoc import io.bkbn.kompendium.core.routes.swagger +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.routing.* import kotlin.time.Duration.Companion.minutes @@ -25,7 +26,32 @@ internal fun Application.configureRouting() = routing { apiRoute() } - staticFiles("/", configuration.staticFilesPath) + staticFiles("/", configuration.staticFilesPath) { + contentType { + when (it.extension) { + "json" -> ContentType.Application.Json + "asc" -> ContentType.Text.Plain + "ico" -> ContentType.Image.XIcon + "svg" -> ContentType.Image.SVG + "jpg", "jpeg" -> ContentType.Image.JPEG + "png" -> ContentType.Image.PNG + "gif" -> ContentType.Image.GIF + "mp4" -> ContentType.Video.MP4 + "ogg" -> ContentType.Video.OGG + "mp3" -> ContentType.Audio.MPEG + "css" -> ContentType.Text.CSS + "js" -> ContentType.Application.JavaScript + "html" -> ContentType.Text.Html + "xml" -> ContentType.Application.Xml + "pdf" -> ContentType.Application.Pdf + "zip" -> ContentType.Application.Zip + "gz" -> ContentType.Application.GZip + else -> ContentType.Application.OctetStream + } + } + + extensions("json", "asc") + } swagger(pageTitle = "ReVanced API", path = "/") redoc(pageTitle = "ReVanced API", path = "/redoc") diff --git a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt index 6325b3cc..22a654e5 100644 --- a/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt +++ b/src/main/kotlin/app/revanced/api/configuration/repository/ConfigurationRepository.kt @@ -1,7 +1,9 @@ package app.revanced.api.configuration.repository +import app.revanced.api.configuration.schema.APIAbout import app.revanced.api.configuration.services.ManagerService import app.revanced.api.configuration.services.PatchesService +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -10,6 +12,9 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNamingStrategy +import kotlinx.serialization.json.decodeFromStream import java.io.File import java.nio.file.Path @@ -27,6 +32,8 @@ import java.nio.file.Path * @property oldApiEndpoint The endpoint of the old API to proxy requests to. * @property staticFilesPath The path to the static files to be served under the root path. * @property versionedStaticFilesPath The path to the static files to be served under a versioned path. + * @property about The path to the json file deserialized to [APIAbout] + * (because com.akuleshov7.ktoml.Toml does not support nested tables). */ @Serializable internal class ConfigurationRepository( @@ -49,6 +56,9 @@ internal class ConfigurationRepository( @Serializable(with = PathSerializer::class) @SerialName("versioned-static-files-path") val versionedStaticFilesPath: Path, + @Serializable(with = AboutSerializer::class) + @SerialName("about-json-file-path") + val about: APIAbout, ) { /** * Am asset configuration whose asset is signed. @@ -123,5 +133,17 @@ private object PathSerializer : KSerializer { override fun serialize(encoder: Encoder, value: Path) = encoder.encodeString(value.toString()) - override fun deserialize(decoder: Decoder) = Path.of(decoder.decodeString()) + override fun deserialize(decoder: Decoder): Path = Path.of(decoder.decodeString()) +} + +private object AboutSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("APIAbout", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: APIAbout) = error("Serializing APIAbout is not supported") + + @OptIn(ExperimentalSerializationApi::class) + val json = Json { namingStrategy = JsonNamingStrategy.SnakeCase } + + override fun deserialize(decoder: Decoder): APIAbout = + json.decodeFromStream(File(decoder.decodeString()).inputStream()) } diff --git a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt index 7c6c5809..834f8573 100644 --- a/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt +++ b/src/main/kotlin/app/revanced/api/configuration/routes/ApiRoute.kt @@ -5,6 +5,7 @@ import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installNoCache import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.respondOrNotFound +import app.revanced.api.configuration.schema.APIAbout import app.revanced.api.configuration.schema.APIContributable import app.revanced.api.configuration.schema.APIMember import app.revanced.api.configuration.schema.APIRateLimit @@ -56,6 +57,16 @@ internal fun Route.apiRoute() { } } + route("about") { + installCache(1.days) + + installAboutRouteDocumentation() + + get { + call.respond(apiService.about) + } + } + route("ping") { installNoCache() @@ -79,6 +90,21 @@ internal fun Route.apiRoute() { } } +private fun Route.installAboutRouteDocumentation() = installNotarizedRoute { + tags = setOf("API") + + get = GetInfo.builder { + description("Get information about the API") + summary("Get about") + response { + description("Information about the API") + mediaTypes("application/json") + responseCode(HttpStatusCode.OK) + responseType() + } + } +} + private fun Route.installRateLimitRouteDocumentation() = installNotarizedRoute { tags = setOf("API") diff --git a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt index d6d28ef3..b48af9af 100644 --- a/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt +++ b/src/main/kotlin/app/revanced/api/configuration/schema/APISchema.kt @@ -120,3 +120,55 @@ class APIAssetPublicKeys( val patchesPublicKey: String, val integrationsPublicKey: String, ) + +@Serializable +class APIAbout( + val name: String, + val about: String, + val keys: String, + val branding: Branding?, + val contact: Contact?, + // Using a list instead of a set because set semantics are unnecessary here. + val socials: List?, + val donations: Donations?, +) { + @Serializable + class Branding( + val logo: String, + ) + + @Serializable + class Contact( + val email: String, + ) + + @Serializable + class Social( + val name: String, + val url: String, + val preferred: Boolean? = false, + ) + + @Serializable + class Wallet( + val network: String, + val currencyCode: String, + val address: String, + val preferred: Boolean? = false, + ) + + @Serializable + class Link( + val name: String, + val url: String, + val preferred: Boolean? = false, + ) + + @Serializable + class Donations( + // Using a list instead of a set because set semantics are unnecessary here. + val wallets: List?, + // Using a list instead of a set because set semantics are unnecessary here. + val links: List?, + ) +} diff --git a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt index cac63e61..5dc9f432 100644 --- a/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt +++ b/src/main/kotlin/app/revanced/api/configuration/services/ApiService.kt @@ -13,6 +13,7 @@ internal class ApiService( private val configurationRepository: ConfigurationRepository, ) { val versionedStaticFilesPath = configurationRepository.versionedStaticFilesPath + val about = configurationRepository.about suspend fun contributors() = withContext(Dispatchers.IO) { configurationRepository.contributorsRepositoryNames.map {