diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 2649d3ad..35d9605a 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -28,13 +28,6 @@ dependencies { implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion") implementation("com.squareup.okio:okio:2.10.0") - // Retrofit, used in `ExtensionGithubService` - val retrofitVersion = "2.9.0" - implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") - implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0") - implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion") - implementation("com.squareup.retrofit2:adapter-rxjava:$retrofitVersion") - // Javalin api implementation("io.javalin:javalin:3.13.6") // jackson version is tied to javalin, ref: `io.javalin.core.util.OptionalDependency` @@ -54,9 +47,6 @@ dependencies { implementation("com.dorkbox:SystemTray:4.1") implementation("com.dorkbox:Utilities:1.9") - // misc - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") - // dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference implementation("com.github.inorichi.injekt:injekt-core:65b0440") diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 1608a337..6eb0f337 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -7,13 +7,12 @@ package eu.kanade.tachiyomi.extension.api * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import com.github.salomonbrys.kotson.int +import com.github.salomonbrys.kotson.string +import com.google.gson.JsonArray import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.util.ExtensionLoader import ir.armor.tachidesk.model.dataclass.ExtensionDataClass -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive object ExtensionGithubApi { const val BASE_URL = "https://raw.githubusercontent.com" @@ -21,19 +20,20 @@ object ExtensionGithubApi { private fun parseResponse(json: JsonArray): List { return json + .map { it.asJsonObject } .filter { element -> - val versionName = element.jsonObject["version"]!!.jsonPrimitive.content + val versionName = element["version"].string val libVersion = versionName.substringBeforeLast('.').toDouble() libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX } .map { element -> - val name = element.jsonObject["name"]!!.jsonPrimitive.content.substringAfter("Tachiyomi: ") - val pkgName = element.jsonObject["pkg"]!!.jsonPrimitive.content - val apkName = element.jsonObject["apk"]!!.jsonPrimitive.content - val versionName = element.jsonObject["version"]!!.jsonPrimitive.content - val versionCode = element.jsonObject["code"]!!.jsonPrimitive.int - val lang = element.jsonObject["lang"]!!.jsonPrimitive.content - val nsfw = element.jsonObject["nsfw"]!!.jsonPrimitive.int == 1 + val name = element["name"].string.substringAfter("Tachiyomi: ") + val pkgName = element["pkg"].string + val apkName = element["apk"].string + val versionName = element["version"].string + val versionCode = element["code"].int + val lang = element["lang"].string + val nsfw = element["nsfw"].int == 1 val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}" Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon) @@ -41,9 +41,8 @@ object ExtensionGithubApi { } suspend fun findExtensions(): List { - val service: ExtensionGithubService = ExtensionGithubService.create() - val response = service.getRepo() + val response = ExtensionGithubService.getRepo() return parseResponse(response) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt index 077301bb..fc072c57 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt @@ -1,46 +1,74 @@ package eu.kanade.tachiyomi.extension.api -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.NetworkHelper -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import okhttp3.MediaType.Companion.toMediaType -import retrofit2.Retrofit -import retrofit2.http.GET +import okhttp3.Headers +import okhttp3.Interceptor +import okhttp3.Interceptor.Chain +import okhttp3.Request +import okhttp3.Response +import okhttp3.internal.http.RealResponseBody +import okio.GzipSource +import okio.buffer import uy.kohesive.injekt.injectLazy +import java.io.IOException /** * Used to get the extension repo listing from GitHub. */ -interface ExtensionGithubService { - - companion object { - private val client by lazy { - val network: NetworkHelper by injectLazy() - network.client.newBuilder() - .addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .header("Content-Encoding", "gzip") - .header("Content-Type", "application/json") - .build() - } - .build() - } - - @ExperimentalSerializationApi - fun create(): ExtensionGithubService { - val adapter = Retrofit.Builder() // TODO: rewrite in order to not depend on retrofit2 - .baseUrl(ExtensionGithubApi.BASE_URL) - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) - .client(client) - .build() - - return adapter.create(ExtensionGithubService::class.java) - } +object ExtensionGithubService { + private val client by lazy { + val network: NetworkHelper by injectLazy() + network.client.newBuilder() + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .header("Content-Encoding", "gzip") + .header("Content-Type", "application/json") + .build() + } + .addInterceptor(UnzippingInterceptor()) + .build() } - @GET("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json.gz") - suspend fun getRepo(): JsonArray + suspend fun getRepo(): com.google.gson.JsonArray { + val request = Request.Builder() + .url("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json.gz") + .build() + + val response = client.newCall(request).execute().use { response -> response.body!!.string() } + return JsonParser.parseString(response).asJsonArray + } +} + +private class UnzippingInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Chain): Response { + val response: Response = chain.proceed(chain.request()) + return unzip(response) + } + + // ref: https://stackoverflow.com/questions/51901333/okhttp-3-how-to-decompress-gzip-deflate-response-manually-using-java-android + @Throws(IOException::class) + private fun unzip(response: Response): Response { + if (response.body == null) { + return response + } + + // check if we have gzip response + val contentEncoding: String? = response.headers["Content-Encoding"] + + // this is used to decompress gzipped responses + return if (contentEncoding != null && contentEncoding == "gzip") { + val body = response.body!! + val contentLength: Long = body.contentLength() + val responseBody = GzipSource(body.source()) + val strippedHeaders: Headers = response.headers.newBuilder().build() + response.newBuilder().headers(strippedHeaders) + .body(RealResponseBody(body.contentType().toString(), contentLength, responseBody.buffer())) + .build() + } else { + response + } + } }