From 93523ef50b80ef294866bfb0da54e236cdf2d9f6 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 20 Feb 2023 19:02:38 -0500 Subject: [PATCH] Remove dependency injection from core module and data module from presentation-widget module Includes side effects: - No longer need to restart app for user agent string change to take effect - parseAs extension function requires a Json instance in the calling context, which doesn't necessarily need to be the default one provided by Injekt --- app/build.gradle.kts | 1 + .../settings/screen/SettingsAdvancedScreen.kt | 3 +- .../java/eu/kanade/tachiyomi/AppModule.kt | 2 +- .../data/track/anilist/AnilistApi.kt | 130 ++++++----- .../data/track/bangumi/BangumiApi.kt | 19 +- .../tachiyomi/data/track/kavita/KavitaApi.kt | 58 +++-- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 201 ++++++++++-------- .../tachiyomi/data/track/komga/KomgaApi.kt | 32 +-- .../track/mangaupdates/MangaUpdatesApi.kt | 79 +++---- .../data/track/myanimelist/MyAnimeListApi.kt | 132 +++++++----- .../myanimelist/MyAnimeListInterceptor.kt | 6 +- .../data/track/shikimori/ShikimoriApi.kt | 92 ++++---- .../data/track/suwayomi/TachideskApi.kt | 19 +- .../data/updater/AppUpdateChecker.kt | 33 +-- .../extension/api/ExtensionGithubApi.kt | 13 +- .../details/ExtensionDetailsScreenModel.kt | 2 +- .../tachiyomi/ui/webview/WebViewActivity.kt | 2 +- .../ui/webview/WebViewScreenModel.kt | 2 +- core/build.gradle.kts | 11 +- .../kanade/tachiyomi/network/NetworkHelper.kt | 24 ++- .../tachiyomi/network/OkHttpExtensions.kt | 13 +- .../interceptor/CloudflareInterceptor.kt | 17 +- .../interceptor/UserAgentInterceptor.kt | 10 +- .../network/interceptor/WebViewInterceptor.kt | 11 +- data/build.gradle.kts | 1 + .../data/updates/UpdatesRepositoryImpl.kt | 22 +- domain/build.gradle.kts | 6 +- .../domain/updates/interactor/GetUpdates.kt | 8 + .../updates/repository/UpdatesRepository.kt | 4 + presentation-widget/build.gradle.kts | 2 +- .../widget/TachiyomiWidgetManager.kt | 16 +- .../widget/UpdatesGridGlanceWidget.kt | 28 ++- source-api/build.gradle.kts | 8 +- .../tachiyomi/source/online/HttpSource.kt | 2 +- 34 files changed, 576 insertions(+), 433 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c234fffde5..be2da8e9db 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -283,6 +283,7 @@ tasks { // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) withType { kotlinOptions.freeCompilerArgs += listOf( + "-Xcontext-receivers", "-opt-in=coil.annotation.ExperimentalCoilApi", "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 547687c043..2afc3f59d4 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -222,7 +222,7 @@ object SettingsAdvancedScreen : SearchableSettings { Preference.PreferenceItem.TextPreference( title = stringResource(R.string.pref_clear_cookies), onClick = { - networkHelper.cookieManager.removeAll() + networkHelper.cookieJar.removeAll() context.toast(R.string.cookies_cleared) }, ), @@ -280,7 +280,6 @@ object SettingsAdvancedScreen : SearchableSettings { context.toast(R.string.error_user_agent_string_invalid) return@EditTextPreference false } - context.toast(R.string.requires_app_restart) true }, ), diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index fac18baf2d..9cb8b5b067 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -118,7 +118,7 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { ChapterCache(app) } addSingletonFactory { CoverCache(app) } - addSingletonFactory { NetworkHelper(app) } + addSingletonFactory { NetworkHelper(app, get()) } addSingletonFactory { JavaScriptEngine(app) } addSingletonFactory { SourceManager(app, get(), get()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 0cbd72c170..ca48444ca2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject @@ -24,11 +25,14 @@ import kotlinx.serialization.json.putJsonObject import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext +import uy.kohesive.injekt.injectLazy import java.util.Calendar import java.util.concurrent.TimeUnit class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { + private val json: Json by injectLazy() + private val authClient = client.newBuilder() .addInterceptor(interceptor) .rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES) @@ -53,19 +57,21 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("status", track.toAnilistStatus()) } } - authClient.newCall( - POST( - apiUrl, - body = payload.toString().toRequestBody(jsonMime), - ), - ) - .awaitSuccess() - .parseAs() - .let { - track.library_id = - it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long - track - } + with(json) { + authClient.newCall( + POST( + apiUrl, + body = payload.toString().toRequestBody(jsonMime), + ), + ) + .awaitSuccess() + .parseAs() + .let { + track.library_id = + it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long + track + } + } } } @@ -137,21 +143,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("query", search) } } - authClient.newCall( - POST( - apiUrl, - body = payload.toString().toRequestBody(jsonMime), - ), - ) - .awaitSuccess() - .parseAs() - .let { response -> - val data = response["data"]!!.jsonObject - val page = data["Page"]!!.jsonObject - val media = page["media"]!!.jsonArray - val entries = media.map { jsonToALManga(it.jsonObject) } - entries.map { it.toTrack() } - } + with(json) { + authClient.newCall( + POST( + apiUrl, + body = payload.toString().toRequestBody(jsonMime), + ), + ) + .awaitSuccess() + .parseAs() + .let { response -> + val data = response["data"]!!.jsonObject + val page = data["Page"]!!.jsonObject + val media = page["media"]!!.jsonArray + val entries = media.map { jsonToALManga(it.jsonObject) } + entries.map { it.toTrack() } + } + } } } @@ -205,21 +213,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("manga_id", track.media_id) } } - authClient.newCall( - POST( - apiUrl, - body = payload.toString().toRequestBody(jsonMime), - ), - ) - .awaitSuccess() - .parseAs() - .let { response -> - val data = response["data"]!!.jsonObject - val page = data["Page"]!!.jsonObject - val media = page["mediaList"]!!.jsonArray - val entries = media.map { jsonToALUserManga(it.jsonObject) } - entries.firstOrNull()?.toTrack() - } + with(json) { + authClient.newCall( + POST( + apiUrl, + body = payload.toString().toRequestBody(jsonMime), + ), + ) + .awaitSuccess() + .parseAs() + .let { response -> + val data = response["data"]!!.jsonObject + val page = data["Page"]!!.jsonObject + val media = page["mediaList"]!!.jsonArray + val entries = media.map { jsonToALUserManga(it.jsonObject) } + entries.firstOrNull()?.toTrack() + } + } } } @@ -247,22 +257,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { val payload = buildJsonObject { put("query", query) } - authClient.newCall( - POST( - apiUrl, - body = payload.toString().toRequestBody(jsonMime), - ), - ) - .awaitSuccess() - .parseAs() - .let { - val data = it["data"]!!.jsonObject - val viewer = data["Viewer"]!!.jsonObject - Pair( - viewer["id"]!!.jsonPrimitive.int, - viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content, - ) - } + with(json) { + authClient.newCall( + POST( + apiUrl, + body = payload.toString().toRequestBody(jsonMime), + ), + ) + .awaitSuccess() + .parseAs() + .let { + val data = it["data"]!!.jsonObject + val viewer = data["Viewer"]!!.jsonObject + Pair( + viewer["id"]!!.jsonPrimitive.int, + viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content, + ) + } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index 6d10b9d2c7..9b032d9e8f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -118,10 +118,12 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept suspend fun findLibManga(track: Track): Track? { return withIOContext { - authClient.newCall(GET("$apiUrl/subject/${track.media_id}")) - .awaitSuccess() - .parseAs() - .let { jsonToSearch(it) } + with(json) { + authClient.newCall(GET("$apiUrl/subject/${track.media_id}")) + .awaitSuccess() + .parseAs() + .let { jsonToSearch(it) } + } } } @@ -155,9 +157,11 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept suspend fun accessToken(code: String): OAuth { return withIOContext { - client.newCall(accessTokenRequest(code)) - .awaitSuccess() - .parseAs() + with(json) { + client.newCall(accessTokenRequest(code)) + .awaitSuccess() + .parseAs() + } } } @@ -181,7 +185,6 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept private const val loginUrl = "https://bgm.tv/oauth/authorize" private const val redirectUrl = "tachiyomi://bangumi-auth" - private const val baseMangaUrl = "$apiUrl/mangas" fun authUrl(): Uri = loginUrl.toUri().buildUpon() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt index f58b705f9c..cadea435a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import logcat.LogPriority import okhttp3.Dns import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -13,11 +14,14 @@ import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat +import uy.kohesive.injekt.injectLazy import java.io.IOException import java.net.SocketTimeoutException class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) { + private val json: Json by injectLazy() + private val authClient = client.newBuilder() .dns(Dns.SYSTEM) .addInterceptor(interceptor) @@ -39,18 +43,20 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()), ) try { - client.newCall(request).execute().use { - when (it.code) { - 200 -> return it.parseAs().token - 401 -> { - logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } - throw IOException("Unauthorized / api key not valid") + with(json) { + client.newCall(request).execute().use { + when (it.code) { + 200 -> return it.parseAs().token + 401 -> { + logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } + throw IOException("Unauthorized / api key not valid") + } + 500 -> { + logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } + throw IOException("Error fetching JWT token") + } + else -> {} } - 500 -> { - logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } - throw IOException("Error fetching JWT token") - } - else -> {} } } // Not sure which one to catch @@ -86,9 +92,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor private fun getTotalChapters(url: String): Int { val requestUrl = getApiVolumesUrl(url) try { - val listVolumeDto = authClient.newCall(GET(requestUrl)) - .execute() - .parseAs>() + val listVolumeDto = with(json) { + authClient.newCall(GET(requestUrl)) + .execute() + .parseAs>() + } var volumeNumber = 0 var maxChapterNumber = 0 for (volume in listVolumeDto) { @@ -110,12 +118,14 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor val serieId = getIdFromUrl(url) val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId" try { - authClient.newCall(GET(requestUrl)).execute().use { - if (it.code == 200) { - return it.parseAs().number!!.replace(",", ".").toFloat() - } - if (it.code == 204) { - return 0F + with(json) { + authClient.newCall(GET(requestUrl)).execute().use { + if (it.code == 200) { + return it.parseAs().number!!.replace(",", ".").toFloat() + } + if (it.code == 204) { + return 0F + } } } } catch (e: Exception) { @@ -127,9 +137,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor suspend fun getTrackSearch(url: String): TrackSearch = withIOContext { try { - val serieDto: SeriesDto = authClient.newCall(GET(url)) - .awaitSuccess() - .parseAs() + val serieDto: SeriesDto = with(json) { + authClient.newCall(GET(url)) + .awaitSuccess() + .parseAs() + } val track = serieDto.toTrack() track.apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 917c50a56d..96b952b97c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonArray @@ -24,11 +25,14 @@ import okhttp3.Request import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext +import uy.kohesive.injekt.injectLazy import java.net.URLEncoder import java.nio.charset.StandardCharsets class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) { + private val json: Json by injectLazy() + private val authClient = client.newBuilder().addInterceptor(interceptor).build() suspend fun addLibManga(track: Track, userId: String): Track { @@ -57,22 +61,25 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - authClient.newCall( - POST( - "${baseUrl}library-entries", - headers = headersOf( - "Content-Type", - "application/vnd.api+json", + with(json) { + authClient.newCall( + POST( + "${baseUrl}library-entries", + headers = headersOf( + "Content-Type", + "application/vnd.api+json", + ), + body = data.toString() + .toRequestBody("application/vnd.api+json".toMediaType()), ), - body = data.toString().toRequestBody("application/vnd.api+json".toMediaType()), - ), - ) - .awaitSuccess() - .parseAs() - .let { - track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long - track - } + ) + .awaitSuccess() + .parseAs() + .let { + track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long + track + } + } } } @@ -92,35 +99,41 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - authClient.newCall( - Request.Builder() - .url("${baseUrl}library-entries/${track.media_id}") - .headers( - headersOf( - "Content-Type", - "application/vnd.api+json", - ), - ) - .patch(data.toString().toRequestBody("application/vnd.api+json".toMediaType())) - .build(), - ) - .awaitSuccess() - .parseAs() - .let { - track - } + with(json) { + authClient.newCall( + Request.Builder() + .url("${baseUrl}library-entries/${track.media_id}") + .headers( + headersOf( + "Content-Type", + "application/vnd.api+json", + ), + ) + .patch( + data.toString().toRequestBody("application/vnd.api+json".toMediaType()), + ) + .build(), + ) + .awaitSuccess() + .parseAs() + .let { + track + } + } } } suspend fun search(query: String): List { return withIOContext { - authClient.newCall(GET(algoliaKeyUrl)) - .awaitSuccess() - .parseAs() - .let { - val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content - algoliaSearch(key, query) - } + with(json) { + authClient.newCall(GET(algoliaKeyUrl)) + .awaitSuccess() + .parseAs() + .let { + val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content + algoliaSearch(key, query) + } + } } } @@ -130,26 +143,28 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter") } - client.newCall( - POST( - algoliaUrl, - headers = headersOf( - "X-Algolia-Application-Id", - algoliaAppId, - "X-Algolia-API-Key", - key, + with(json) { + client.newCall( + POST( + algoliaUrl, + headers = headersOf( + "X-Algolia-Application-Id", + algoliaAppId, + "X-Algolia-API-Key", + key, + ), + body = jsonObject.toString().toRequestBody(jsonMime), ), - body = jsonObject.toString().toRequestBody(jsonMime), - ), - ) - .awaitSuccess() - .parseAs() - .let { - it["hits"]!!.jsonArray - .map { KitsuSearchManga(it.jsonObject) } - .filter { it.subType != "novel" } - .map { it.toTrack() } - } + ) + .awaitSuccess() + .parseAs() + .let { + it["hits"]!!.jsonArray + .map { KitsuSearchManga(it.jsonObject) } + .filter { it.subType != "novel" } + .map { it.toTrack() } + } + } } } @@ -159,18 +174,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId") .appendQueryParameter("include", "manga") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - val data = it["data"]!!.jsonArray - if (data.size > 0) { - val manga = it["included"]!!.jsonArray[0].jsonObject - KitsuLibManga(data[0].jsonObject, manga).toTrack() - } else { - null + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + val data = it["data"]!!.jsonArray + if (data.size > 0) { + val manga = it["included"]!!.jsonArray[0].jsonObject + KitsuLibManga(data[0].jsonObject, manga).toTrack() + } else { + null + } } - } + } } } @@ -180,18 +197,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .encodedQuery("filter[id]=${track.media_id}") .appendQueryParameter("include", "manga") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - val data = it["data"]!!.jsonArray - if (data.size > 0) { - val manga = it["included"]!!.jsonArray[0].jsonObject - KitsuLibManga(data[0].jsonObject, manga).toTrack() - } else { - throw Exception("Could not find manga") + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + val data = it["data"]!!.jsonArray + if (data.size > 0) { + val manga = it["included"]!!.jsonArray[0].jsonObject + KitsuLibManga(data[0].jsonObject, manga).toTrack() + } else { + throw Exception("Could not find manga") + } } - } + } } } @@ -204,9 +223,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .add("client_id", clientId) .add("client_secret", clientSecret) .build() - client.newCall(POST(loginUrl, body = formBody)) - .awaitSuccess() - .parseAs() + with(json) { + client.newCall(POST(loginUrl, body = formBody)) + .awaitSuccess() + .parseAs() + } } } @@ -215,12 +236,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) val url = "${baseUrl}users".toUri().buildUpon() .encodedQuery("filter[self]=true") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content - } + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content + } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt index 149d2903ba..313f94cceb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt @@ -26,25 +26,29 @@ class KomgaApi(private val client: OkHttpClient) { suspend fun getTrackSearch(url: String): TrackSearch = withIOContext { try { - val track = if (url.contains(READLIST_API)) { - client.newCall(GET(url)) - .awaitSuccess() - .parseAs() - .toTrack() - } else { - client.newCall(GET(url)) - .awaitSuccess() - .parseAs() - .toTrack() + val track = with(json) { + if (url.contains(READLIST_API)) { + client.newCall(GET(url)) + .awaitSuccess() + .parseAs() + .toTrack() + } else { + client.newCall(GET(url)) + .awaitSuccess() + .parseAs() + .toTrack() + } } val progress = client .newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi")) .awaitSuccess().let { - if (url.contains("/api/v1/series/")) { - it.parseAs() - } else { - it.parseAs().toV2() + with(json) { + if (url.contains("/api/v1/series/")) { + it.parseAs() + } else { + it.parseAs().toV2() + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt index 929bd1b864..45d33a9bdd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt @@ -47,7 +47,7 @@ class MangaUpdatesApi( } suspend fun getSeriesListItem(track: Track): Pair { - val listItem = + val listItem = with(json) { authClient.newCall( GET( url = "$baseUrl/v1/lists/series/${track.media_id}", @@ -55,6 +55,7 @@ class MangaUpdatesApi( ) .awaitSuccess() .parseAs() + } val rating = getSeriesRating(track) @@ -111,13 +112,15 @@ class MangaUpdatesApi( suspend fun getSeriesRating(track: Track): Rating? { return try { - authClient.newCall( - GET( - url = "$baseUrl/v1/series/${track.media_id}/rating", - ), - ) - .awaitSuccess() - .parseAs() + with(json) { + authClient.newCall( + GET( + url = "$baseUrl/v1/series/${track.media_id}/rating", + ), + ) + .awaitSuccess() + .parseAs() + } } catch (e: Exception) { null } @@ -156,20 +159,22 @@ class MangaUpdatesApi( }, ) } - return client.newCall( - POST( - url = "$baseUrl/v1/series/search", - body = body.toString().toRequestBody(contentType), - ), - ) - .awaitSuccess() - .parseAs() - .let { obj -> - obj["results"]?.jsonArray?.map { element -> - json.decodeFromJsonElement(element.jsonObject["record"]!!) + return with(json) { + client.newCall( + POST( + url = "$baseUrl/v1/series/search", + body = body.toString().toRequestBody(contentType), + ), + ) + .awaitSuccess() + .parseAs() + .let { obj -> + obj["results"]?.jsonArray?.map { element -> + json.decodeFromJsonElement(element.jsonObject["record"]!!) + } } - } - .orEmpty() + .orEmpty() + } } suspend fun authenticate(username: String, password: String): Context? { @@ -177,21 +182,23 @@ class MangaUpdatesApi( put("username", username) put("password", password) } - return client.newCall( - PUT( - url = "$baseUrl/v1/account/login", - body = body.toString().toRequestBody(contentType), - ), - ) - .awaitSuccess() - .parseAs() - .let { obj -> - try { - json.decodeFromJsonElement(obj["context"]!!) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - null + return with(json) { + client.newCall( + PUT( + url = "$baseUrl/v1/account/login", + body = body.toString().toRequestBody(contentType), + ), + ) + .awaitSuccess() + .parseAs() + .let { obj -> + try { + json.decodeFromJsonElement(obj["context"]!!) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + null + } } - } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 51c4113576..2333519cc8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.PkceUtil import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean import kotlinx.serialization.json.contentOrNull @@ -27,11 +28,14 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import tachiyomi.core.util.lang.withIOContext +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { + private val json: Json by injectLazy() + private val authClient = client.newBuilder().addInterceptor(interceptor).build() suspend fun getAccessToken(authCode: String): OAuth { @@ -42,9 +46,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .add("code_verifier", codeVerifier) .add("grant_type", "authorization_code") .build() - client.newCall(POST("$baseOAuthUrl/token", body = formBody)) - .awaitSuccess() - .parseAs() + with(json) { + client.newCall(POST("$baseOAuthUrl/token", body = formBody)) + .awaitSuccess() + .parseAs() + } } } @@ -54,10 +60,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url("$baseApiUrl/users/@me") .get() .build() - authClient.newCall(request) - .awaitSuccess() - .parseAs() - .let { it["name"]!!.jsonPrimitive.content } + with(json) { + authClient.newCall(request) + .awaitSuccess() + .parseAs() + .let { it["name"]!!.jsonPrimitive.content } + } } } @@ -68,19 +76,21 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendQueryParameter("q", query.take(64)) .appendQueryParameter("nsfw", "true") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - it["data"]!!.jsonArray - .map { data -> data.jsonObject["node"]!!.jsonObject } - .map { node -> - val id = node["id"]!!.jsonPrimitive.int - async { getMangaDetails(id) } - } - .awaitAll() - .filter { trackSearch -> !trackSearch.publishing_type.contains("novel") } - } + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + it["data"]!!.jsonArray + .map { data -> data.jsonObject["node"]!!.jsonObject } + .map { node -> + val id = node["id"]!!.jsonPrimitive.int + async { getMangaDetails(id) } + } + .awaitAll() + .filter { trackSearch -> !trackSearch.publishing_type.contains("novel") } + } + } } } @@ -90,28 +100,34 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendPath(id.toString()) .appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - val obj = it.jsonObject - TrackSearch.create(TrackManager.MYANIMELIST).apply { - media_id = obj["id"]!!.jsonPrimitive.long - title = obj["title"]!!.jsonPrimitive.content - summary = obj["synopsis"]?.jsonPrimitive?.content ?: "" - total_chapters = obj["num_chapters"]!!.jsonPrimitive.int - cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: "" - tracking_url = "https://myanimelist.net/manga/$media_id" - publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ") - publishing_type = obj["media_type"]!!.jsonPrimitive.content.replace("_", " ") - start_date = try { - val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) - outputDf.format(obj["start_date"]!!) - } catch (e: Exception) { - "" + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + val obj = it.jsonObject + TrackSearch.create(TrackManager.MYANIMELIST).apply { + media_id = obj["id"]!!.jsonPrimitive.long + title = obj["title"]!!.jsonPrimitive.content + summary = obj["synopsis"]?.jsonPrimitive?.content ?: "" + total_chapters = obj["num_chapters"]!!.jsonPrimitive.int + cover_url = + obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content + ?: "" + tracking_url = "https://myanimelist.net/manga/$media_id" + publishing_status = + obj["status"]!!.jsonPrimitive.content.replace("_", " ") + publishing_type = + obj["media_type"]!!.jsonPrimitive.content.replace("_", " ") + start_date = try { + val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) + outputDf.format(obj["start_date"]!!) + } catch (e: Exception) { + "" + } } } - } + } } } @@ -133,10 +149,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url(mangaUrl(track.media_id).toString()) .put(formBodyBuilder.build()) .build() - authClient.newCall(request) - .awaitSuccess() - .parseAs() - .let { parseMangaItem(it, track) } + with(json) { + authClient.newCall(request) + .awaitSuccess() + .parseAs() + .let { parseMangaItem(it, track) } + } } } @@ -146,15 +164,17 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendPath(track.media_id.toString()) .appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}") .build() - authClient.newCall(GET(uri.toString())) - .awaitSuccess() - .parseAs() - .let { obj -> - track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int - obj.jsonObject["my_list_status"]?.jsonObject?.let { - parseMangaItem(it, track) + with(json) { + authClient.newCall(GET(uri.toString())) + .awaitSuccess() + .parseAs() + .let { obj -> + track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int + obj.jsonObject["my_list_status"]?.jsonObject?.let { + parseMangaItem(it, track) + } } - } + } } } @@ -198,9 +218,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url(urlBuilder.build().toString()) .get() .build() - authClient.newCall(request) - .awaitSuccess() - .parseAs() + with(json) { + authClient.newCall(request) + .awaitSuccess() + .parseAs() + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index 16313520c9..a8ec377086 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -1,12 +1,16 @@ package eu.kanade.tachiyomi.data.track.myanimelist import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import okhttp3.Interceptor import okhttp3.Response +import uy.kohesive.injekt.injectLazy import java.io.IOException class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor { + private val json: Json by injectLazy() + private var oauth: OAuth? = null override fun intercept(chain: Interceptor.Chain): Response { @@ -69,7 +73,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!)) if (oauthResponse.isSuccessful) { - oauthResponse.parseAs() + with(json) { oauthResponse.parseAs() } } else { oauthResponse.close() null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 2d16a10dc4..55980bce5c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject @@ -24,9 +25,12 @@ import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext +import uy.kohesive.injekt.injectLazy class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) { + private val json: Json by injectLazy() + private val authClient = client.newBuilder().addInterceptor(interceptor).build() suspend fun addLibManga(track: Track, user_id: String): Track { @@ -60,14 +64,16 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter .appendQueryParameter("search", search) .appendQueryParameter("limit", "20") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { response -> - response.map { - jsonToSearch(it.jsonObject) + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { response -> + response.map { + jsonToSearch(it.jsonObject) + } } - } + } } } @@ -81,7 +87,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content publishing_status = obj["status"]!!.jsonPrimitive.content publishing_type = obj["kind"]!!.jsonPrimitive.content - start_date = obj.get("aired_on")!!.jsonPrimitive.contentOrNull ?: "" + start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: "" } } @@ -102,44 +108,52 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter val urlMangas = "$apiUrl/mangas".toUri().buildUpon() .appendPath(track.media_id.toString()) .build() - val mangas = authClient.newCall(GET(urlMangas.toString())) - .awaitSuccess() - .parseAs() + val mangas = with(json) { + authClient.newCall(GET(urlMangas.toString())) + .awaitSuccess() + .parseAs() + } val url = "$apiUrl/v2/user_rates".toUri().buildUpon() .appendQueryParameter("user_id", user_id) .appendQueryParameter("target_id", track.media_id.toString()) .appendQueryParameter("target_type", "Manga") .build() - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { response -> - if (response.size > 1) { - throw Exception("Too much mangas in response") + with(json) { + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { response -> + if (response.size > 1) { + throw Exception("Too much mangas in response") + } + val entry = response.map { + jsonToTrack(it.jsonObject, mangas) + } + entry.firstOrNull() } - val entry = response.map { - jsonToTrack(it.jsonObject, mangas) - } - entry.firstOrNull() - } + } } } suspend fun getCurrentUser(): Int { - return authClient.newCall(GET("$apiUrl/users/whoami")) - .awaitSuccess() - .parseAs() - .let { - it["id"]!!.jsonPrimitive.int - } + return with(json) { + authClient.newCall(GET("$apiUrl/users/whoami")) + .awaitSuccess() + .parseAs() + .let { + it["id"]!!.jsonPrimitive.int + } + } } suspend fun accessToken(code: String): OAuth { return withIOContext { - client.newCall(accessTokenRequest(code)) - .awaitSuccess() - .parseAs() + with(json) { + client.newCall(accessTokenRequest(code)) + .awaitSuccess() + .parseAs() + } } } @@ -164,18 +178,12 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter private const val loginUrl = "$baseUrl/oauth/authorize" private const val redirectUrl = "tachiyomi://shikimori-auth" - private const val baseMangaUrl = "$apiUrl/mangas" - fun mangaUrl(remoteId: Int): String { - return "$baseMangaUrl/$remoteId" - } - - fun authUrl() = - loginUrl.toUri().buildUpon() - .appendQueryParameter("client_id", clientId) - .appendQueryParameter("redirect_uri", redirectUrl) - .appendQueryParameter("response_type", "code") - .build() + fun authUrl() = loginUrl.toUri().buildUpon() + .appendQueryParameter("client_id", clientId) + .appendQueryParameter("redirect_uri", redirectUrl) + .appendQueryParameter("response_type", "code") + .build() fun refreshTokenRequest(token: String) = POST( oauthUrl, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt index 4107cd8046..66d7088cce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.PUT import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs +import kotlinx.serialization.json.Json import okhttp3.Credentials import okhttp3.Dns import okhttp3.FormBody @@ -23,11 +24,15 @@ import java.nio.charset.Charset import java.security.MessageDigest class TachideskApi { - private val network by injectLazy() + + private val network: NetworkHelper by injectLazy() + private val json: Json by injectLazy() + val client: OkHttpClient = network.client.newBuilder() .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing .build() + fun headersBuilder(): Headers.Builder = Headers.Builder().apply { if (basePassword.isNotEmpty() && baseLogin.isNotEmpty()) { val credentials = Credentials.basic(baseLogin, basePassword) @@ -50,7 +55,11 @@ class TachideskApi { trackUrl } - val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs() + val manga = with(json) { + client.newCall(GET("$url/full", headers)) + .awaitSuccess() + .parseAs() + } TrackSearch.create(TrackManager.SUWAYOMI).apply { title = manga.title @@ -70,7 +79,11 @@ class TachideskApi { suspend fun updateProgress(track: Track): Track { val url = track.tracking_url - val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs>() + val chapters = with(json) { + client.newCall(GET("$url/chapters", headers)) + .awaitSuccess() + .parseAs>() + } val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index client.newCall( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index c853490a0d..7f6f7d9d68 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid +import kotlinx.serialization.json.Json import tachiyomi.core.preference.Preference import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.util.lang.withIOContext @@ -18,6 +19,8 @@ class AppUpdateChecker { private val networkService: NetworkHelper by injectLazy() private val preferenceStore: PreferenceStore by injectLazy() + private val json: Json by injectLazy() + private val lastAppCheck: Preference by lazy { preferenceStore.getLong("last_app_check", 0) } @@ -29,24 +32,26 @@ class AppUpdateChecker { } return withIOContext { - val result = networkService.client - .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest")) - .awaitSuccess() - .parseAs() - .let { - lastAppCheck.set(Date().time) + val result = with(json) { + networkService.client + .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest")) + .awaitSuccess() + .parseAs() + .let { + lastAppCheck.set(Date().time) - // Check if latest version is different from current version - if (isNewVersion(it.version)) { - if (context.isInstalledFromFDroid()) { - AppUpdateResult.NewUpdateFdroidInstallation + // Check if latest version is different from current version + if (isNewVersion(it.version)) { + if (context.isInstalledFromFDroid()) { + AppUpdateResult.NewUpdateFdroidInstallation + } else { + AppUpdateResult.NewUpdate(it) + } } else { - AppUpdateResult.NewUpdate(it) + AppUpdateResult.NoNewUpdate } - } else { - AppUpdateResult.NoNewUpdate } - } + } when (result) { is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 99017344be..5ccd503b2b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import logcat.LogPriority import tachiyomi.core.preference.Preference import tachiyomi.core.preference.PreferenceStore @@ -24,10 +25,12 @@ internal class ExtensionGithubApi { private val networkService: NetworkHelper by injectLazy() private val preferenceStore: PreferenceStore by injectLazy() + private val extensionManager: ExtensionManager by injectLazy() + private val json: Json by injectLazy() + private val lastExtCheck: Preference by lazy { preferenceStore.getLong("last_ext_check", 0) } - private val extensionManager: ExtensionManager by injectLazy() private var requiresFallbackSource = false @@ -53,9 +56,11 @@ internal class ExtensionGithubApi { .awaitSuccess() } - val extensions = response - .parseAs>() - .toExtensions() + val extensions = with(json) { + response + .parseAs>() + .toExtensions() + } // Sanity check - a small number of extensions probably means something broke // with the repo generator diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt index 1cdf9c16dc..ceace82ce6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt @@ -120,7 +120,7 @@ class ExtensionDetailsScreenModel( val cleared = urls.sumOf { try { - network.cookieManager.remove(it.toHttpUrl()) + network.cookieJar.remove(it.toHttpUrl()) } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to clear cookies for $it" } 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt index 773e63d313..76b157c78f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt @@ -92,7 +92,7 @@ class WebViewActivity : BaseActivity() { } private fun clearCookies(url: String) { - val cleared = network.cookieManager.remove(url.toHttpUrl()) + val cleared = network.cookieJar.remove(url.toHttpUrl()) logcat { "Cleared $cleared cookies for: $url" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt index bd78fefc75..12e022b189 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt @@ -47,7 +47,7 @@ class WebViewScreenModel( } fun clearCookies(url: String) { - val cleared = network.cookieManager.remove(url.toHttpUrl()) + val cleared = network.cookieJar.remove(url.toHttpUrl()) logcat { "Cleared $cleared cookies for: $url" } } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2c2f64a6ba..30a5985173 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,6 +6,13 @@ plugins { android { namespace = "eu.kanade.tachiyomi.core" + + kotlinOptions { + freeCompilerArgs += listOf( + "-Xcontext-receivers", + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", + ) + } } dependencies { @@ -24,12 +31,8 @@ dependencies { api(kotlinx.serialization.json) api(kotlinx.serialization.json.okio) - api(libs.injekt.core) - api(libs.preferencektx) - implementation(androidx.corektx) - // JavaScript engine implementation(libs.bundles.js.engine) } diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index d81b666d6b..9cc103589b 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -6,26 +6,30 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import uy.kohesive.injekt.injectLazy import java.io.File import java.util.concurrent.TimeUnit -class NetworkHelper(context: Context) { - - private val preferences: NetworkPreferences by injectLazy() +class NetworkHelper( + context: Context, + private val preferences: NetworkPreferences, +) { private val cacheDir = File(context.cacheDir, "network_cache") private val cacheSize = 5L * 1024 * 1024 // 5 MiB - val cookieManager = AndroidCookieJar() + val cookieJar = AndroidCookieJar() - private val userAgentInterceptor by lazy { UserAgentInterceptor() } - private val cloudflareInterceptor by lazy { CloudflareInterceptor(context) } + private val userAgentInterceptor by lazy { + UserAgentInterceptor(::defaultUserAgentProvider) + } + private val cloudflareInterceptor by lazy { + CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider) + } private val baseClientBuilder: OkHttpClient.Builder get() { val builder = OkHttpClient.Builder() - .cookieJar(cookieManager) + .cookieJar(cookieJar) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .callTimeout(2, TimeUnit.MINUTES) @@ -65,7 +69,5 @@ class NetworkHelper(context: Context) { .build() } - val defaultUserAgent by lazy { - preferences.defaultUserAgent().get().trim() - } + fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim() } diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index f651317dc9..240f64a92a 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.okio.decodeFromBufferedSource import kotlinx.serialization.serializer @@ -16,8 +15,6 @@ import okhttp3.Response import rx.Observable import rx.Producer import rx.Subscription -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.resumeWithException @@ -131,14 +128,18 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre return progressClient.newCall(request) } +context(Json) inline fun Response.parseAs(): T { return decodeFromJsonResponse(serializer(), this) } -@OptIn(ExperimentalSerializationApi::class) -fun decodeFromJsonResponse(deserializer: DeserializationStrategy, response: Response): T { +context(Json) +fun decodeFromJsonResponse( + deserializer: DeserializationStrategy, + response: Response, +): T { return response.body.source().use { - Injekt.get().decodeFromBufferedSource(deserializer, it) + decodeFromBufferedSource(deserializer, it) } } diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index d8914b23a2..3814319011 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -6,7 +6,7 @@ import android.webkit.WebView import android.widget.Toast import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.core.R -import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.AndroidCookieJar import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.toast @@ -15,16 +15,17 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response -import uy.kohesive.injekt.injectLazy import java.io.IOException import java.util.concurrent.CountDownLatch -class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(context) { +class CloudflareInterceptor( + private val context: Context, + private val cookieManager: AndroidCookieJar, + defaultUserAgentProvider: () -> String, +) : WebViewInterceptor(context, defaultUserAgentProvider) { private val executor = ContextCompat.getMainExecutor(context) - private val networkHelper: NetworkHelper by injectLazy() - override fun shouldIntercept(response: Response): Boolean { // Check if Cloudflare anti-bot is on return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK @@ -33,8 +34,8 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response { try { response.close() - networkHelper.cookieManager.remove(request.url, COOKIE_NAMES, 0) - val oldCookie = networkHelper.cookieManager.get(request.url) + cookieManager.remove(request.url, COOKIE_NAMES, 0) + val oldCookie = cookieManager.get(request.url) .firstOrNull { it.name == "cf_clearance" } resolveWithWebView(request, oldCookie) @@ -70,7 +71,7 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c webview?.webViewClient = object : WebViewClientCompat() { override fun onPageFinished(view: WebView, url: String) { fun isCloudFlareBypassed(): Boolean { - return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl()) + return cookieManager.get(origRequestUrl.toHttpUrl()) .firstOrNull { it.name == "cf_clearance" } .let { it != null && it != oldCookie } } diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt index e5d1c26566..b085ece3c1 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt @@ -1,13 +1,11 @@ package eu.kanade.tachiyomi.network.interceptor -import eu.kanade.tachiyomi.network.NetworkHelper import okhttp3.Interceptor import okhttp3.Response -import uy.kohesive.injekt.injectLazy -class UserAgentInterceptor : Interceptor { - - private val networkHelper: NetworkHelper by injectLazy() +class UserAgentInterceptor( + private val defaultUserAgentProvider: () -> String, +) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() @@ -16,7 +14,7 @@ class UserAgentInterceptor : Interceptor { val newRequest = originalRequest .newBuilder() .removeHeader("User-Agent") - .addHeader("User-Agent", networkHelper.defaultUserAgent) + .addHeader("User-Agent", defaultUserAgentProvider()) .build() chain.proceed(newRequest) } else { diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index 70436ad275..c71deaf3e7 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -6,7 +6,6 @@ import android.webkit.WebSettings import android.webkit.WebView import android.widget.Toast import eu.kanade.tachiyomi.core.R -import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.setDefaultSettings @@ -16,14 +15,14 @@ import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import tachiyomi.core.util.lang.launchUI -import uy.kohesive.injekt.injectLazy import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -abstract class WebViewInterceptor(private val context: Context) : Interceptor { - - private val networkHelper: NetworkHelper by injectLazy() +abstract class WebViewInterceptor( + private val context: Context, + private val defaultUserAgentProvider: () -> String, +) : Interceptor { /** * When this is called, it initializes the WebView if it wasn't already. We use this to avoid @@ -85,7 +84,7 @@ abstract class WebViewInterceptor(private val context: Context) : Interceptor { return WebView(context).apply { setDefaultSettings() // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty - settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent + settings.userAgentString = request.header("User-Agent") ?: defaultUserAgentProvider() } } } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 24cddd932c..621a7445e1 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(project(":source-api")) implementation(project(":domain")) implementation(project(":core")) + api(libs.sqldelight.android.driver) api(libs.sqldelight.coroutines) api(libs.sqldelight.android.paging) diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index c206bd654a..877eb6f1bb 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -6,12 +6,32 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.repository.UpdatesRepository class UpdatesRepositoryImpl( - val databaseHandler: DatabaseHandler, + private val databaseHandler: DatabaseHandler, ) : UpdatesRepository { + override suspend fun awaitWithRead(read: Boolean, after: Long): List { + return databaseHandler.awaitList { + updatesViewQueries.getUpdatesByReadStatus( + read = read, + after = after, + mapper = updateWithRelationMapper, + ) + } + } + override fun subscribeAll(after: Long): Flow> { return databaseHandler.subscribeToList { updatesViewQueries.updates(after, updateWithRelationMapper) } } + + override fun subscribeWithRead(read: Boolean, after: Long): Flow> { + return databaseHandler.subscribeToList { + updatesViewQueries.getUpdatesByReadStatus( + read = read, + after = after, + mapper = updateWithRelationMapper, + ) + } + } } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 3209e11b32..f51488f72f 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -13,11 +13,11 @@ android { } dependencies { - implementation(platform(kotlinx.coroutines.bom)) - implementation(kotlinx.bundles.coroutines) - implementation(project(":source-api")) implementation(project(":core")) + implementation(platform(kotlinx.coroutines.bom)) + implementation(kotlinx.bundles.coroutines) + testImplementation(libs.junit) } diff --git a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt index 3412250dcf..988b860c96 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt @@ -9,9 +9,17 @@ class GetUpdates( private val repository: UpdatesRepository, ) { + suspend fun await(read: Boolean, after: Long): List { + return repository.awaitWithRead(read, after) + } + fun subscribe(calendar: Calendar): Flow> = subscribe(calendar.time.time) fun subscribe(after: Long): Flow> { return repository.subscribeAll(after) } + + fun subscribe(read: Boolean, after: Long): Flow> { + return repository.subscribeWithRead(read, after) + } } diff --git a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt index 23d45e409b..8c879d3233 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt @@ -5,5 +5,9 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations interface UpdatesRepository { + suspend fun awaitWithRead(read: Boolean, after: Long): List + fun subscribeAll(after: Long): Flow> + + fun subscribeWithRead(read: Boolean, after: Long): Flow> } diff --git a/presentation-widget/build.gradle.kts b/presentation-widget/build.gradle.kts index 18972c3885..a468f8523e 100644 --- a/presentation-widget/build.gradle.kts +++ b/presentation-widget/build.gradle.kts @@ -22,11 +22,11 @@ android { dependencies { implementation(project(":core")) - implementation(project(":data")) implementation(project(":domain")) implementation(project(":presentation-core")) implementation(androidx.glance) implementation(libs.coil.core) + api(libs.injekt.core) } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt index ca33d4b315..89a8a7fcd1 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt @@ -7,21 +7,17 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import tachiyomi.data.DatabaseHandler -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import tachiyomi.domain.updates.interactor.GetUpdates class TachiyomiWidgetManager( - private val database: DatabaseHandler = Injekt.get(), + private val getUpdates: GetUpdates, ) { fun Context.init(scope: LifecycleCoroutineScope) { - database.subscribeToList { - updatesViewQueries.getUpdatesByReadStatus( - read = false, - after = UpdatesGridGlanceWidget.DateLimit.timeInMillis, - ) - } + getUpdates.subscribe( + read = false, + after = UpdatesGridGlanceWidget.DateLimit.timeInMillis, + ) .drop(1) .distinctUntilChanged() .onEach { diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt index 280c5d6797..03b50d8483 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt @@ -25,13 +25,13 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.util.system.dpToPx import kotlinx.coroutines.MainScope import tachiyomi.core.util.lang.launchIO -import tachiyomi.data.DatabaseHandler import tachiyomi.domain.manga.model.MangaCover +import tachiyomi.domain.updates.interactor.GetUpdates +import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.presentation.widget.components.CoverHeight import tachiyomi.presentation.widget.components.CoverWidth import tachiyomi.presentation.widget.components.LockedWidget import tachiyomi.presentation.widget.components.UpdatesWidget -import tachiyomi.view.UpdatesView import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -39,6 +39,7 @@ import java.util.Calendar import java.util.Date class UpdatesGridGlanceWidget : GlanceAppWidget() { + private val app: Application by injectLazy() private val preferences: SecurityPreferences by injectLazy() @@ -58,7 +59,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { UpdatesWidget(data) } - fun loadData(list: List? = null) { + fun loadData(list: List? = null) { coroutineScope.launchIO { // Don't show anything when lock is active if (preferences.useAuthenticator().get()) { @@ -71,13 +72,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { if (ids.isEmpty()) return@launchIO val processList = list - ?: Injekt.get() - .awaitList { - updatesViewQueries.getUpdatesByReadStatus( - read = false, - after = DateLimit.timeInMillis, - ) - } + ?: Injekt.get().await( + read = false, + after = DateLimit.timeInMillis, + ) val (rowCount, columnCount) = ids .flatMap { manager.getAppWidgetSizes(it) } .maxBy { it.height.value * it.width.value } @@ -88,7 +86,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { } } - private fun prepareList(processList: List, take: Int): List> { + private fun prepareList(processList: List, take: Int): List> { // Resize to cover size val widthPx = CoverWidth.value.toInt().dpToPx val heightPx = CoverHeight.value.toInt().dpToPx @@ -101,10 +99,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { .data( MangaCover( mangaId = updatesView.mangaId, - sourceId = updatesView.source, - isMangaFavorite = updatesView.favorite, - url = updatesView.thumbnailUrl, - lastModified = updatesView.coverLastModified, + sourceId = updatesView.sourceId, + isMangaFavorite = true, + url = updatesView.coverData.url, + lastModified = updatesView.coverData.lastModified, ), ) .memoryCachePolicy(CachePolicy.DISABLED) diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index 7034078b6b..86dc1aab5d 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -10,20 +10,14 @@ android { defaultConfig { consumerProguardFile("consumer-proguard.pro") } - } dependencies { - implementation(project(":core")) api(kotlinx.serialization.json) - + api(libs.injekt.core) api(libs.rxjava) - api(libs.preferencektx) - api(libs.jsoup) - - implementation(androidx.corektx) } diff --git a/source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt index 10612d410b..f9e616dcdf 100644 --- a/source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -68,7 +68,7 @@ abstract class HttpSource : CatalogueSource { * Headers builder for requests. Implementations can override this method for custom headers. */ protected open fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", network.defaultUserAgent) + add("User-Agent", network.defaultUserAgentProvider()) } /**