Compare commits

..

10 Commits

Author SHA1 Message Date
semantic-release-bot
2d7b4e7b7f chore(release): 1.2.0-dev.1 [skip ci]
# [1.2.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.1.0...v1.2.0-dev.1) (2024-08-16)

### Features

* Move /latest routes to parent ([4e8e83d](4e8e83db1a))
2024-08-16 23:03:43 +00:00
oSumAtrIX
4e8e83db1a feat: Move /latest routes to parent
There is only ever current releases, so a latest route doesn't make sense.
2024-08-17 01:01:33 +02:00
oSumAtrIX
e113daa089 build: Bump dependencies to correctly serialize patches as JSON 2024-08-17 00:53:13 +02:00
oSumAtrIX
170edd3157 build: Bump Gradle 2024-08-17 00:20:07 +02:00
semantic-release-bot
d18e09cba3 chore(release): 1.1.0 [skip ci]
# [1.1.0](https://github.com/ReVanced/revanced-api/compare/v1.0.0...v1.1.0) (2024-07-15)

### Bug Fixes

* Don't encode public keys & instead send them raw ([435beae](435beae383))

### Features

* Add static file paths to remove env specific files in resources ([39d0b78](39d0b78c79))
* Convert static about file to documented route & add key parameter to about route ([dfe6df3](dfe6df3ef6))
2024-07-15 18:20:46 +00:00
oSumAtrIX
bc919b85f5 chore: Merge branch dev to main (#183) 2024-07-15 20:18:36 +02:00
semantic-release-bot
665de7bcd0 chore(release): 1.1.0-dev.1 [skip ci]
# [1.1.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.0.0...v1.1.0-dev.1) (2024-07-15)

### Bug Fixes

* Don't encode public keys & instead send them raw ([435beae](435beae383))

### Features

* Add static file paths to remove env specific files in resources ([39d0b78](39d0b78c79))
* Convert static about file to documented route & add key parameter to about route ([dfe6df3](dfe6df3ef6))
2024-07-15 01:15:03 +00:00
oSumAtrIX
dfe6df3ef6 feat: Convert static about file to documented route & add key parameter to about route 2024-07-15 03:12:39 +02:00
oSumAtrIX
39d0b78c79 feat: Add static file paths to remove env specific files in resources 2024-07-15 02:30:21 +02:00
oSumAtrIX
435beae383 fix: Don't encode public keys & instead send them raw 2024-07-15 01:52:16 +02:00
21 changed files with 271 additions and 78 deletions

4
.gitignore vendored
View File

@@ -42,4 +42,6 @@ configuration.toml
docker-compose.yml docker-compose.yml
patches-public-key.asc patches-public-key.asc
integrations-public-key.asc integrations-public-key.asc
node_modules/ node_modules/
static/
about.json

View File

@@ -1,3 +1,36 @@
# [1.2.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.1.0...v1.2.0-dev.1) (2024-08-16)
### Features
* Move /latest routes to parent ([4e8e83d](https://github.com/ReVanced/revanced-api/commit/4e8e83db1a20c76a81967af4e7e3a8634649790a))
# [1.1.0](https://github.com/ReVanced/revanced-api/compare/v1.0.0...v1.1.0) (2024-07-15)
### Bug Fixes
* Don't encode public keys & instead send them raw ([435beae](https://github.com/ReVanced/revanced-api/commit/435beae3831fc8ce161aec676ff20f253b1caf66))
### Features
* Add static file paths to remove env specific files in resources ([39d0b78](https://github.com/ReVanced/revanced-api/commit/39d0b78c7919f684439b6f052ab3f064159c2a70))
* Convert static about file to documented route & add key parameter to about route ([dfe6df3](https://github.com/ReVanced/revanced-api/commit/dfe6df3ef6006d06681673bcfaf87c44c40ad446))
# [1.1.0-dev.1](https://github.com/ReVanced/revanced-api/compare/v1.0.0...v1.1.0-dev.1) (2024-07-15)
### Bug Fixes
* Don't encode public keys & instead send them raw ([435beae](https://github.com/ReVanced/revanced-api/commit/435beae3831fc8ce161aec676ff20f253b1caf66))
### Features
* Add static file paths to remove env specific files in resources ([39d0b78](https://github.com/ReVanced/revanced-api/commit/39d0b78c7919f684439b6f052ab3f064159c2a70))
* Convert static about file to documented route & add key parameter to about route ([dfe6df3](https://github.com/ReVanced/revanced-api/commit/dfe6df3ef6006d06681673bcfaf87c44c40ad446))
# 1.0.0 (2024-07-13) # 1.0.0 (2024-07-13)

View File

@@ -1,6 +1,7 @@
{ {
"name": "ReVanced", "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.", "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": { "branding": {
"logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg" "logo": "https://raw.githubusercontent.com/ReVanced/revanced-branding/main/assets/revanced-logo/revanced-logo.svg"
}, },

View File

@@ -1,6 +1,6 @@
organization = "revanced" organization = "revanced"
patches = { repository = "revanced-patches", asset-regex = "jar$", signature-asset-regex = "asc$", public-key-file = "patches-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" } 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$" } manager = { repository = "revanced-manager", asset-regex = "apk$" }
contributors-repositories = [ contributors-repositories = [
"revanced-patcher", "revanced-patcher",
@@ -17,3 +17,6 @@ cors-allowed-hosts = [
] ]
endpoint = "https://api.revanced.app" endpoint = "https://api.revanced.app"
old-api-endpoint = "https://old-api.revanced.app" old-api-endpoint = "https://old-api.revanced.app"
static-files-path = "static/root"
versioned-static-files-path = "static/versioned"
about-json-file-path = "about.json"

View File

@@ -8,6 +8,8 @@ services:
- /data/revanced-api/configuration.toml:/app/configuration.toml - /data/revanced-api/configuration.toml:/app/configuration.toml
- /data/revanced-api/patches-public-key.asc:/app/patches-public-key.asc - /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/integrations-public-key.asc:/app/integrations-public-key.asc
- /data/revanced-api/static:/app/static
- /data/revanced-api/about.json:/app/about.json
environment: environment:
- COMMAND=start - COMMAND=start
ports: ports:

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 1.0.0 version = 1.2.0-dev.1

View File

@@ -10,8 +10,8 @@ ktor = "2.3.7"
ktoml = "0.5.2" ktoml = "0.5.2"
picocli = "4.7.6" picocli = "4.7.6"
datetime = "0.6.0" datetime = "0.6.0"
revanced-patcher = "19.3.1" revanced-patcher = "20.0.0"
revanced-library = "2.3.0" revanced-library = "3.0.1-dev.1"
caffeine = "3.1.8" caffeine = "3.1.8"
bouncy-castle = "1.78.1" bouncy-castle = "1.78.1"

Binary file not shown.

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

5
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

2
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################

View File

@@ -4,8 +4,12 @@ import io.bkbn.kompendium.core.plugin.NotarizedRoute
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.content.* import io.ktor.http.content.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.cachingheaders.* import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.io.File
import java.nio.file.Path
import kotlin.time.Duration import kotlin.time.Duration
internal suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound) internal suspend fun ApplicationCall.respondOrNotFound(value: Any?) = respond(value ?: HttpStatusCode.NotFound)
@@ -25,3 +29,14 @@ internal fun ApplicationCallPipeline.installCache(cacheControl: CacheControl) =
internal fun ApplicationCallPipeline.installNotarizedRoute(configure: NotarizedRoute.Config.() -> Unit = {}) = internal fun ApplicationCallPipeline.installNotarizedRoute(configure: NotarizedRoute.Config.() -> Unit = {}) =
install(NotarizedRoute(), configure) install(NotarizedRoute(), configure)
internal fun Route.staticFiles(
remotePath: String,
dir: Path,
block: StaticContentConfig<File>.() -> Unit = {
contentType {
ContentType.Application.Json
}
extensions("json")
},
) = staticFiles(remotePath, dir.toFile(), null, block)

View File

@@ -10,7 +10,6 @@ import io.bkbn.kompendium.core.routes.redoc
import io.bkbn.kompendium.core.routes.swagger import io.bkbn.kompendium.core.routes.swagger
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
import org.koin.ktor.ext.get as koinGet import org.koin.ktor.ext.get as koinGet
@@ -27,9 +26,31 @@ internal fun Application.configureRouting() = routing {
apiRoute() apiRoute()
} }
staticResources("/", "/app/revanced/api/static/root") { staticFiles("/", configuration.staticFilesPath) {
contentType { ContentType.Application.Json } contentType {
extensions("json") 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 = "/") swagger(pageTitle = "ReVanced API", path = "/")

View File

@@ -1,7 +1,9 @@
package app.revanced.api.configuration.repository 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.ManagerService
import app.revanced.api.configuration.services.PatchesService import app.revanced.api.configuration.services.PatchesService
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -10,7 +12,11 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder 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.io.File
import java.nio.file.Path
/** /**
* The repository storing the configuration for the API. * The repository storing the configuration for the API.
@@ -24,6 +30,10 @@ import java.io.File
* @property corsAllowedHosts The hosts allowed to make requests to the API. * @property corsAllowedHosts The hosts allowed to make requests to the API.
* @property endpoint The endpoint of the API. * @property endpoint The endpoint of the API.
* @property oldApiEndpoint The endpoint of the old API to proxy requests to. * @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 @Serializable
internal class ConfigurationRepository( internal class ConfigurationRepository(
@@ -40,6 +50,15 @@ internal class ConfigurationRepository(
val endpoint: String, val endpoint: String,
@SerialName("old-api-endpoint") @SerialName("old-api-endpoint")
val oldApiEndpoint: String, val oldApiEndpoint: String,
@Serializable(with = PathSerializer::class)
@SerialName("static-files-path")
val staticFilesPath: Path,
@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. * Am asset configuration whose asset is signed.
@@ -108,3 +127,23 @@ private object FileSerializer : KSerializer<File> {
override fun deserialize(decoder: Decoder) = File(decoder.decodeString()) override fun deserialize(decoder: Decoder) = File(decoder.decodeString())
} }
private object PathSerializer : KSerializer<Path> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Path", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Path) = encoder.encodeString(value.toString())
override fun deserialize(decoder: Decoder): Path = Path.of(decoder.decodeString())
}
private object AboutSerializer : KSerializer<APIAbout> {
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())
}

View File

@@ -1,9 +1,11 @@
package app.revanced.api.configuration.routes package app.revanced.api.configuration.routes
import app.revanced.api.configuration.*
import app.revanced.api.configuration.installCache import app.revanced.api.configuration.installCache
import app.revanced.api.configuration.installNoCache import app.revanced.api.configuration.installNoCache
import app.revanced.api.configuration.installNotarizedRoute import app.revanced.api.configuration.installNotarizedRoute
import app.revanced.api.configuration.respondOrNotFound 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.APIContributable
import app.revanced.api.configuration.schema.APIMember import app.revanced.api.configuration.schema.APIMember
import app.revanced.api.configuration.schema.APIRateLimit import app.revanced.api.configuration.schema.APIRateLimit
@@ -13,7 +15,6 @@ import io.bkbn.kompendium.core.metadata.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.ratelimit.* import io.ktor.server.plugins.ratelimit.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
@@ -56,6 +57,16 @@ internal fun Route.apiRoute() {
} }
} }
route("about") {
installCache(1.days)
installAboutRouteDocumentation()
get {
call.respond(apiService.about)
}
}
route("ping") { route("ping") {
installNoCache() installNoCache()
@@ -75,9 +86,21 @@ internal fun Route.apiRoute() {
} }
} }
staticResources("/", "/app/revanced/api/static/versioned") { staticFiles("/", apiService.versionedStaticFilesPath)
contentType { ContentType.Application.Json } }
extensions("json") }
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<APIAbout>()
} }
} }
} }

View File

@@ -16,31 +16,29 @@ import org.koin.ktor.ext.get as koinGet
internal fun Route.managerRoute() = route("manager") { internal fun Route.managerRoute() = route("manager") {
val managerService = koinGet<ManagerService>() val managerService = koinGet<ManagerService>()
route("latest") { installManagerRouteDocumentation()
installLatestManagerRouteDocumentation()
rateLimit(RateLimitName("weak")) {
get {
call.respond(managerService.latestRelease())
}
route("version") {
installManagerVersionRouteDocumentation()
rateLimit(RateLimitName("weak")) {
get { get {
call.respond(managerService.latestRelease()) call.respond(managerService.latestVersion())
}
route("version") {
installLatestManagerVersionRouteDocumentation()
get {
call.respond(managerService.latestVersion())
}
} }
} }
} }
} }
private fun Route.installLatestManagerRouteDocumentation() = installNotarizedRoute { private fun Route.installManagerRouteDocumentation() = installNotarizedRoute {
tags = setOf("Manager") tags = setOf("Manager")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the latest manager release") description("Get the current manager release")
summary("Get latest manager release") summary("Get current manager release")
response { response {
description("The latest manager release") description("The latest manager release")
mediaTypes("application/json") mediaTypes("application/json")
@@ -50,14 +48,14 @@ private fun Route.installLatestManagerRouteDocumentation() = installNotarizedRou
} }
} }
private fun Route.installLatestManagerVersionRouteDocumentation() = installNotarizedRoute { private fun Route.installManagerVersionRouteDocumentation() = installNotarizedRoute {
tags = setOf("Manager") tags = setOf("Manager")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the latest manager release version") description("Get the current manager release version")
summary("Get latest manager release version") summary("Get current manager release version")
response { response {
description("The latest manager release version") description("The current manager release version")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<APIReleaseVersion>() responseType<APIReleaseVersion>()

View File

@@ -19,30 +19,28 @@ import org.koin.ktor.ext.get as koinGet
internal fun Route.patchesRoute() = route("patches") { internal fun Route.patchesRoute() = route("patches") {
val patchesService = koinGet<PatchesService>() val patchesService = koinGet<PatchesService>()
route("latest") { installPatchesRouteDocumentation()
installLatestPatchesRouteDocumentation()
rateLimit(RateLimitName("weak")) { rateLimit(RateLimitName("weak")) {
get { get {
call.respond(patchesService.latestRelease()) call.respond(patchesService.latestRelease())
}
route("version") {
installLatestPatchesVersionRouteDocumentation()
get {
call.respond(patchesService.latestVersion())
}
}
} }
rateLimit(RateLimitName("strong")) { route("version") {
route("list") { installPatchesVersionRouteDocumentation()
installLatestPatchesListRouteDocumentation()
get { get {
call.respondBytes(ContentType.Application.Json) { patchesService.list() } call.respond(patchesService.latestVersion())
} }
}
}
rateLimit(RateLimitName("strong")) {
route("list") {
installPatchesListRouteDocumentation()
get {
call.respondBytes(ContentType.Application.Json) { patchesService.list() }
} }
} }
} }
@@ -60,14 +58,14 @@ internal fun Route.patchesRoute() = route("patches") {
} }
} }
private fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRoute { private fun Route.installPatchesRouteDocumentation() = installNotarizedRoute {
tags = setOf("Patches") tags = setOf("Patches")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the latest patches release") description("Get the current patches release")
summary("Get latest patches release") summary("Get current patches release")
response { response {
description("The latest patches release") description("The current patches release")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<APIRelease<APIPatchesAsset>>() responseType<APIRelease<APIPatchesAsset>>()
@@ -75,14 +73,14 @@ private fun Route.installLatestPatchesRouteDocumentation() = installNotarizedRou
} }
} }
private fun Route.installLatestPatchesVersionRouteDocumentation() = installNotarizedRoute { private fun Route.installPatchesVersionRouteDocumentation() = installNotarizedRoute {
tags = setOf("Patches") tags = setOf("Patches")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the latest patches release version") description("Get the current patches release version")
summary("Get latest patches release version") summary("Get current patches release version")
response { response {
description("The latest patches release version") description("The current patches release version")
mediaTypes("application/json") mediaTypes("application/json")
responseCode(HttpStatusCode.OK) responseCode(HttpStatusCode.OK)
responseType<APIReleaseVersion>() responseType<APIReleaseVersion>()
@@ -90,12 +88,12 @@ private fun Route.installLatestPatchesVersionRouteDocumentation() = installNotar
} }
} }
private fun Route.installLatestPatchesListRouteDocumentation() = installNotarizedRoute { private fun Route.installPatchesListRouteDocumentation() = installNotarizedRoute {
tags = setOf("Patches") tags = setOf("Patches")
get = GetInfo.builder { get = GetInfo.builder {
description("Get the list of patches from the latest patches release") description("Get the list of patches from the current patches release")
summary("Get list of patches from latest patches release") summary("Get list of patches from current patches release")
response { response {
description("The list of patches") description("The list of patches")
mediaTypes("application/json") mediaTypes("application/json")

View File

@@ -120,3 +120,55 @@ class APIAssetPublicKeys(
val patchesPublicKey: String, val patchesPublicKey: String,
val integrationsPublicKey: 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<Social>?,
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<Wallet>?,
// Using a list instead of a set because set semantics are unnecessary here.
val links: List<Link>?,
)
}

View File

@@ -12,6 +12,9 @@ internal class ApiService(
private val backendRepository: BackendRepository, private val backendRepository: BackendRepository,
private val configurationRepository: ConfigurationRepository, private val configurationRepository: ConfigurationRepository,
) { ) {
val versionedStaticFilesPath = configurationRepository.versionedStaticFilesPath
val about = configurationRepository.about
suspend fun contributors() = withContext(Dispatchers.IO) { suspend fun contributors() = withContext(Dispatchers.IO) {
configurationRepository.contributorsRepositoryNames.map { configurationRepository.contributorsRepositoryNames.map {
async { async {

View File

@@ -4,10 +4,9 @@ import app.revanced.api.configuration.repository.BackendRepository
import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first import app.revanced.api.configuration.repository.BackendRepository.BackendOrganization.BackendRepository.BackendRelease.Companion.first
import app.revanced.api.configuration.repository.ConfigurationRepository import app.revanced.api.configuration.repository.ConfigurationRepository
import app.revanced.api.configuration.schema.* import app.revanced.api.configuration.schema.*
import app.revanced.library.PatchUtils import app.revanced.library.serializeTo
import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.patch.loadPatchesFromJar
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import io.ktor.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@@ -95,7 +94,7 @@ internal class PatchesService(
configurationRepository.patches.publicKeyId, configurationRepository.patches.publicKeyId,
) )
) { ) {
PatchBundleLoader.Jar(patchesFile) loadPatchesFromJar(setOf(patchesFile))
} else { } else {
// Use an empty set of patches if the signature is invalid. // Use an empty set of patches if the signature is invalid.
emptySet() emptySet()
@@ -104,7 +103,7 @@ internal class PatchesService(
patchesFile.delete() patchesFile.delete()
ByteArrayOutputStream().use { stream -> ByteArrayOutputStream().use { stream ->
PatchUtils.Json.serialize(patches, outputStream = stream) patches.serializeTo(outputStream = stream)
stream.toByteArray() stream.toByteArray()
} }
@@ -113,12 +112,13 @@ internal class PatchesService(
} }
fun publicKeys(): APIAssetPublicKeys { fun publicKeys(): APIAssetPublicKeys {
fun publicKeyBase64(getSignedAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.SignedAssetConfiguration) = fun readPublicKey(
configurationRepository.getSignedAssetConfiguration().publicKeyFile.readBytes().encodeBase64() getSignedAssetConfiguration: ConfigurationRepository.() -> ConfigurationRepository.SignedAssetConfiguration,
) = configurationRepository.getSignedAssetConfiguration().publicKeyFile.readText()
return APIAssetPublicKeys( return APIAssetPublicKeys(
publicKeyBase64 { patches }, readPublicKey { patches },
publicKeyBase64 { integrations }, readPublicKey { integrations },
) )
} }
} }

View File

@@ -1,2 +0,0 @@
User-agent: *
Disallow: /