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

(cherry picked from commit 93523ef50b80ef294866bfb0da54e236cdf2d9f6)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt
#	app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
#	core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
#	core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
#	domain/build.gradle.kts
#	source-api/build.gradle.kts
This commit is contained in:
arkon 2023-02-20 19:02:38 -05:00 committed by Jobobby04
parent 2e1c83442e
commit 6563490513
42 changed files with 864 additions and 657 deletions

View File

@ -296,6 +296,7 @@ tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf(
"-Xcontext-receivers",
"-opt-in=coil.annotation.ExperimentalCoilApi",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",

View File

@ -281,7 +281,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)
},
),
@ -339,7 +339,6 @@ object SettingsAdvancedScreen : SearchableSettings {
context.toast(R.string.error_user_agent_string_invalid)
return@EditTextPreference false
}
context.toast(R.string.requires_app_restart)
true
},
),

View File

@ -134,7 +134,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()) }

View File

@ -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,6 +57,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("status", track.toAnilistStatus())
}
}
with(json) {
authClient.newCall(
POST(
apiUrl,
@ -68,6 +73,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
}
}
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
@ -137,6 +143,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", search)
}
}
with(json) {
authClient.newCall(
POST(
apiUrl,
@ -154,6 +161,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
}
}
suspend fun findLibManga(track: Track, userid: Int): Track? {
return withIOContext {
@ -205,6 +213,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("manga_id", track.media_id)
}
}
with(json) {
authClient.newCall(
POST(
apiUrl,
@ -222,6 +231,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
}
}
suspend fun getLibManga(track: Track, userid: Int): Track {
return findLibManga(track, userid) ?: throw Exception("Could not find manga")
@ -247,6 +257,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
val payload = buildJsonObject {
put("query", query)
}
with(json) {
authClient.newCall(
POST(
apiUrl,
@ -265,6 +276,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
}
}
private fun jsonToALManga(struct: JsonObject): ALManga {
return ALManga(

View File

@ -118,12 +118,14 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
suspend fun findLibManga(track: Track): Track? {
return withIOContext {
with(json) {
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
.awaitSuccess()
.parseAs<JsonObject>()
.let { jsonToSearch(it) }
}
}
}
suspend fun statusLibManga(track: Track): Track? {
return withIOContext {
@ -155,11 +157,13 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
suspend fun accessToken(code: String): OAuth {
return withIOContext {
with(json) {
client.newCall(accessTokenRequest(code))
.awaitSuccess()
.parseAs()
}
}
}
private fun accessTokenRequest(code: String) = POST(
oauthUrl,
@ -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()

View File

@ -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,6 +43,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
)
try {
with(json) {
client.newCall(request).execute().use {
when (it.code) {
200 -> return it.parseAs<AuthenticationDto>().token
@ -53,6 +58,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
else -> {}
}
}
}
// Not sure which one to catch
} catch (e: SocketTimeoutException) {
logcat(LogPriority.WARN) {
@ -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))
val listVolumeDto = with(json) {
authClient.newCall(GET(requestUrl))
.execute()
.parseAs<List<VolumeDto>>()
}
var volumeNumber = 0
var maxChapterNumber = 0
for (volume in listVolumeDto) {
@ -110,6 +118,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
val serieId = getIdFromUrl(url)
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
try {
with(json) {
authClient.newCall(GET(requestUrl)).execute().use {
if (it.code == 200) {
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
@ -118,6 +127,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
return 0F
}
}
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
throw e
@ -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))
val serieDto: SeriesDto = with(json) {
authClient.newCall(GET(url))
.awaitSuccess()
.parseAs()
}
val track = serieDto.toTrack()
track.apply {

View File

@ -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,6 +61,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
with(json) {
authClient.newCall(
POST(
"${baseUrl}library-entries",
@ -64,7 +69,8 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
"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()
@ -75,6 +81,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
@ -92,6 +99,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
with(json) {
authClient.newCall(
Request.Builder()
.url("${baseUrl}library-entries/${track.media_id}")
@ -101,7 +109,9 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
"application/vnd.api+json",
),
)
.patch(data.toString().toRequestBody("application/vnd.api+json".toMediaType()))
.patch(
data.toString().toRequestBody("application/vnd.api+json".toMediaType()),
)
.build(),
)
.awaitSuccess()
@ -111,9 +121,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
with(json) {
authClient.newCall(GET(algoliaKeyUrl))
.awaitSuccess()
.parseAs<JsonObject>()
@ -123,6 +135,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
return withIOContext {
@ -130,6 +143,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter")
}
with(json) {
client.newCall(
POST(
algoliaUrl,
@ -152,6 +166,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext {
@ -159,6 +174,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -173,6 +189,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun getLibManga(track: Track): Track {
return withIOContext {
@ -180,6 +197,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[id]=${track.media_id}")
.appendQueryParameter("include", "manga")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -194,6 +212,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun login(username: String, password: String): OAuth {
return withIOContext {
@ -204,17 +223,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("client_id", clientId)
.add("client_secret", clientSecret)
.build()
with(json) {
client.newCall(POST(loginUrl, body = formBody))
.awaitSuccess()
.parseAs()
}
}
}
suspend fun getCurrentUser(): String {
return withIOContext {
val url = "${baseUrl}users".toUri().buildUpon()
.encodedQuery("filter[self]=true")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -223,6 +245,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
companion object {
private const val clientId =

View File

@ -26,7 +26,8 @@ class KomgaApi(private val client: OkHttpClient) {
suspend fun getTrackSearch(url: String): TrackSearch =
withIOContext {
try {
val track = if (url.contains(READLIST_API)) {
val track = with(json) {
if (url.contains(READLIST_API)) {
client.newCall(GET(url))
.awaitSuccess()
.parseAs<ReadListDto>()
@ -37,16 +38,19 @@ class KomgaApi(private val client: OkHttpClient) {
.parseAs<SeriesDto>()
.toTrack()
}
}
val progress = client
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
.awaitSuccess().let {
with(json) {
if (url.contains("/api/v1/series/")) {
it.parseAs<ReadProgressV2Dto>()
} else {
it.parseAs<ReadProgressDto>().toV2()
}
}
}
track.apply {
cover_url = "$url/thumbnail"

View File

@ -47,7 +47,7 @@ class MangaUpdatesApi(
}
suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
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<ListItem>()
}
val rating = getSeriesRating(track)
@ -111,6 +112,7 @@ class MangaUpdatesApi(
suspend fun getSeriesRating(track: Track): Rating? {
return try {
with(json) {
authClient.newCall(
GET(
url = "$baseUrl/v1/series/${track.media_id}/rating",
@ -118,6 +120,7 @@ class MangaUpdatesApi(
)
.awaitSuccess()
.parseAs<Rating>()
}
} catch (e: Exception) {
null
}
@ -156,7 +159,8 @@ class MangaUpdatesApi(
},
)
}
return client.newCall(
return with(json) {
client.newCall(
POST(
url = "$baseUrl/v1/series/search",
body = body.toString().toRequestBody(contentType),
@ -171,13 +175,15 @@ class MangaUpdatesApi(
}
.orEmpty()
}
}
suspend fun authenticate(username: String, password: String): Context? {
val body = buildJsonObject {
put("username", username)
put("password", password)
}
return client.newCall(
return with(json) {
client.newCall(
PUT(
url = "$baseUrl/v1/account/login",
body = body.toString().toRequestBody(contentType),
@ -195,3 +201,4 @@ class MangaUpdatesApi(
}
}
}
}

View File

@ -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,11 +46,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("code_verifier", codeVerifier)
.add("grant_type", "authorization_code")
.build()
with(json) {
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
.awaitSuccess()
.parseAs()
}
}
}
suspend fun getCurrentUser(): String {
return withIOContext {
@ -54,12 +60,14 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$baseApiUrl/users/@me")
.get()
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs<JsonObject>()
.let { it["name"]!!.jsonPrimitive.content }
}
}
}
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
@ -68,6 +76,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendQueryParameter("q", query.take(64))
.appendQueryParameter("nsfw", "true")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -83,6 +92,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
}
}
suspend fun getMangaDetails(id: Int): TrackSearch {
return withIOContext {
@ -90,6 +100,7 @@ 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()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -100,10 +111,14 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
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 ?: ""
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("_", " ")
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"]!!)
@ -114,6 +129,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
}
}
suspend fun updateItem(track: Track): Track {
return withIOContext {
@ -133,12 +149,14 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBodyBuilder.build())
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs<JsonObject>()
.let { parseMangaItem(it, track) }
}
}
}
suspend fun findListItem(track: Track): Track? {
return withIOContext {
@ -146,6 +164,7 @@ 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()
with(json) {
authClient.newCall(GET(uri.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
@ -157,6 +176,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
}
}
suspend fun findListItems(query: String, offset: Int = 0): List<TrackSearch> {
return withIOContext {
@ -198,11 +218,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(urlBuilder.build().toString())
.get()
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs()
}
}
}
private fun parseMangaItem(response: JsonObject, track: Track): Track {
val obj = response.jsonObject

View File

@ -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<OAuth>()
with(json) { oauthResponse.parseAs<OAuth>() }
} else {
oauthResponse.close()
null

View File

@ -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,6 +64,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
.appendQueryParameter("search", search)
.appendQueryParameter("limit", "20")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonArray>()
@ -70,6 +75,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
}
}
}
}
private fun jsonToSearch(obj: JsonObject): TrackSearch {
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
@ -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,15 +108,18 @@ 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()))
val mangas = with(json) {
authClient.newCall(GET(urlMangas.toString()))
.awaitSuccess()
.parseAs<JsonObject>()
}
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()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<JsonArray>()
@ -125,23 +134,28 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
}
}
}
}
suspend fun getCurrentUser(): Int {
return authClient.newCall(GET("$apiUrl/users/whoami"))
return with(json) {
authClient.newCall(GET("$apiUrl/users/whoami"))
.awaitSuccess()
.parseAs<JsonObject>()
.let {
it["id"]!!.jsonPrimitive.int
}
}
}
suspend fun accessToken(code: String): OAuth {
return withIOContext {
with(json) {
client.newCall(accessTokenRequest(code))
.awaitSuccess()
.parseAs()
}
}
}
private fun accessTokenRequest(code: String) = POST(
oauthUrl,
@ -164,14 +178,8 @@ 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()
fun authUrl() = loginUrl.toUri().buildUpon()
.appendQueryParameter("client_id", clientId)
.appendQueryParameter("redirect_uri", redirectUrl)
.appendQueryParameter("response_type", "code")

View File

@ -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<NetworkHelper>()
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<MangaDataClass>()
val manga = with(json) {
client.newCall(GET("$url/full", headers))
.awaitSuccess()
.parseAs<MangaDataClass>()
}
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<List<ChapterDataClass>>()
val chapters = with(json) {
client.newCall(GET("$url/chapters", headers))
.awaitSuccess()
.parseAs<List<ChapterDataClass>>()
}
val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
client.newCall(

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import exh.syDebugVersion
import kotlinx.serialization.json.Json
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
@ -21,6 +22,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<Long> by lazy {
preferenceStore.getLong("last_app_check", 0)
}
@ -32,7 +35,8 @@ class AppUpdateChecker {
}
return withIOContext {
val result = networkService.client
val result = with(json) {
networkService.client
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
.awaitSuccess()
.parseAs<GithubRelease>()
@ -50,6 +54,7 @@ class AppUpdateChecker {
AppUpdateResult.NoNewUpdate
}
}
}
when (result) {
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)

View File

@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import exh.source.BlacklistedSources
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
@ -27,10 +28,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<Long> by lazy {
preferenceStore.getLong("last_ext_check", 0)
}
private val extensionManager: ExtensionManager by injectLazy()
// SY -->
private val sourcePreferences: SourcePreferences by injectLazy()
@ -61,7 +64,8 @@ internal class ExtensionGithubApi {
.awaitSuccess()
}
val extensions = response
val extensions = with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toExtensions() /* SY --> */ + unsortedPreferences.extensionRepos()
.get()
@ -78,6 +82,7 @@ internal class ExtensionGithubApi {
.toExtensions(url, repoSource = true)
}
// SY <--
}
// Sanity check - a small number of extensions probably means something broke
// with the repo generator

View File

@ -112,16 +112,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
MangaPlusHandler(network.client)
}
private val comikeyHandler by lazy {
ComikeyHandler(network.cloudflareClient, network.defaultUserAgent)
ComikeyHandler(network.cloudflareClient, network.defaultUserAgentProvider())
}
private val bilibiliHandler by lazy {
BilibiliHandler(network.cloudflareClient)
}
private val azukHandler by lazy {
AzukiHandler(network.client, network.defaultUserAgent)
AzukiHandler(network.client, network.defaultUserAgentProvider())
}
private val mangaHotHandler by lazy {
MangaHotHandler(network.client, network.defaultUserAgent)
MangaHotHandler(network.client, network.defaultUserAgentProvider())
}
private val pageHandler by lazy {
PageHandler(

View File

@ -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

View File

@ -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" }
}

View File

@ -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" }
}
}

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import exh.log.xLogD
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
@ -21,6 +22,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
class BilibiliHandler(currentClient: OkHttpClient) {
@ -35,6 +37,8 @@ class BilibiliHandler(currentClient: OkHttpClient) {
.rateLimit(1, 1, TimeUnit.SECONDS)
.build()
val json by injectLazy<Json>()
suspend fun fetchPageList(externalUrl: String, chapterNumber: String): List<Page> {
// Sometimes the urls direct it to the manga page instead, so we try to find the correct chapter
// Though these seem to be older chapters, so maybe remove this later
@ -97,7 +101,7 @@ class BilibiliHandler(currentClient: OkHttpClient) {
}
fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<BilibiliResultDto<BilibiliComicDto>>()
val result = with(json) { response.parseAs<BilibiliResultDto<BilibiliComicDto>>() }
if (result.code != 0) {
return emptyList()
@ -140,7 +144,7 @@ class BilibiliHandler(currentClient: OkHttpClient) {
}
private fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<BilibiliResultDto<BilibiliReader>>()
val result = with(json) { response.parseAs<BilibiliResultDto<BilibiliReader>>() }
if (result.code != 0) {
return emptyList()
@ -177,7 +181,9 @@ class BilibiliHandler(currentClient: OkHttpClient) {
}
private fun imageUrlParse(response: Response): String {
val result = response.parseAs<BilibiliResultDto<List<BilibiliPageDto>>>()
val result = with(json) {
response.parseAs<BilibiliResultDto<List<BilibiliPageDto>>>()
}
val page = result.data!![0]
return "${page.url}?token=${page.token}"

View File

@ -80,7 +80,7 @@ class MangaDexAuthInterceptor(
val oauthResponse = chain.proceed(MdUtil.refreshTokenRequest(oauth!!))
if (oauthResponse.isSuccessful) {
oauthResponse.parseAs<OAuth>()
with(MdUtil.jsonParser) { oauthResponse.parseAs<OAuth>() }
} else {
oauthResponse.close()
null

View File

@ -35,7 +35,11 @@ class MangaDexLoginHelper(
.build()
val error = kotlin.runCatching {
val data = client.newCall(POST(MdApi.baseAuthUrl + MdApi.token, body = loginFormBody)).awaitSuccess().parseAs<OAuth>()
val data = with(MdUtil.jsonParser) {
client.newCall(
POST(MdApi.baseAuthUrl + MdApi.token, body = loginFormBody),
).awaitSuccess().parseAs<OAuth>()
}
mangaDexAuthInterceptor.setAuth(data)
}.exceptionOrNull()

View File

@ -26,135 +26,160 @@ class MangaDexAuthService(
) {
suspend fun userFollowList(offset: Int): MangaListDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
"${MdApi.userFollows}?limit=100&offset=$offset&includes[]=${MdConstants.Types.coverArt}",
headers,
CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun readingStatusForManga(mangaId: String): ReadingStatusDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
"${MdApi.manga}/$mangaId/status",
headers,
CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun readChaptersForManga(mangaId: String): ReadChapterDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
"${MdApi.manga}/$mangaId/read",
headers,
CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun updateReadingStatusForManga(
mangaId: String,
readingStatusDto: ReadingStatusDto,
): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
POST(
"${MdApi.manga}/$mangaId/status",
headers,
body = MdUtil.encodeToBody(readingStatusDto),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun readingStatusAllManga(): ReadingStatusMapDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.readingStatusForAllManga,
headers,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun readingStatusByType(status: String): ReadingStatusMapDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
"${MdApi.readingStatusForAllManga}?status=$status",
headers,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun markChapterRead(chapterId: String): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
POST(
"${MdApi.chapter}/$chapterId/read",
headers,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun markChapterUnRead(chapterId: String): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
Request.Builder()
.url("${MdApi.chapter}/$chapterId/read")
.delete()
.headers(headers)
.cacheControl(CacheControl.FORCE_NETWORK)
.build(),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun followManga(mangaId: String): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
POST(
"${MdApi.manga}/$mangaId/follow",
headers,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun unfollowManga(mangaId: String): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
Request.Builder()
.url("${MdApi.manga}/$mangaId/follow")
.delete()
.headers(headers)
.cacheControl(CacheControl.FORCE_NETWORK)
.build(),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun updateMangaRating(mangaId: String, rating: Int): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
POST(
"${MdApi.rating}/$mangaId",
headers,
body = MdUtil.encodeToBody(RatingDto(rating)),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun deleteMangaRating(mangaId: String): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
Request.Builder()
.delete()
.url("${MdApi.rating}/$mangaId")
.headers(headers)
.cacheControl(CacheControl.FORCE_NETWORK)
.build(),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun mangasRating(vararg mangaIds: String): RatingResponseDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.rating.toHttpUrl()
.newBuilder()
@ -167,6 +192,7 @@ class MangaDexAuthService(
headers,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
}

View File

@ -31,7 +31,8 @@ class MangaDexService(
suspend fun viewMangas(
ids: List<String>,
): MangaListDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.manga.toHttpUrl()
.newBuilder()
@ -45,13 +46,15 @@ class MangaDexService(
.build(),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun viewManga(
id: String,
): MangaDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.manga.toHttpUrl()
.newBuilder()
@ -64,13 +67,15 @@ class MangaDexService(
.build(),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun mangasRating(
vararg ids: String,
): StatisticsDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.statistics.toHttpUrl()
.newBuilder()
@ -82,14 +87,16 @@ class MangaDexService(
.build(),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun aggregateChapters(
id: String,
translatedLanguage: String,
): AggregateDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.manga.toHttpUrl()
.newBuilder()
@ -101,7 +108,8 @@ class MangaDexService(
.build(),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
private fun String.splitString() = replace("\n", "").split(',').trimAll().dropEmpty()
@ -137,47 +145,58 @@ class MangaDexService(
}
.build()
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
url,
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun viewChapter(id: String): ChapterDto {
return client.newCall(GET("${MdApi.chapter}/$id", cache = CacheControl.FORCE_NETWORK))
return with(MdUtil.jsonParser) {
client.newCall(GET("${MdApi.chapter}/$id", cache = CacheControl.FORCE_NETWORK))
.awaitSuccess()
.parseAs(MdUtil.jsonParser)
.parseAs()
}
}
suspend fun randomManga(): MangaDto {
return client.newCall(GET("${MdApi.manga}/random", cache = CacheControl.FORCE_NETWORK))
return with(MdUtil.jsonParser) {
client.newCall(GET("${MdApi.manga}/random", cache = CacheControl.FORCE_NETWORK))
.awaitSuccess()
.parseAs(MdUtil.jsonParser)
.parseAs()
}
}
suspend fun atHomeImageReport(atHomeImageReportDto: AtHomeImageReportDto): ResultDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
POST(
MdConstants.atHomeReportUrl,
body = MdUtil.encodeToBody(atHomeImageReportDto),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
suspend fun getAtHomeServer(
atHomeRequestUrl: String,
headers: Headers,
): AtHomeDto {
return client.newCall(GET(atHomeRequestUrl, headers, CacheControl.FORCE_NETWORK))
return with(MdUtil.jsonParser) {
client.newCall(GET(atHomeRequestUrl, headers, CacheControl.FORCE_NETWORK))
.awaitSuccess()
.parseAs(MdUtil.jsonParser)
.parseAs()
}
}
suspend fun relatedManga(id: String): RelationListDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
MdApi.manga.toHttpUrl().newBuilder()
.apply {
@ -187,6 +206,7 @@ class MangaDexService(
.build(),
cache = CacheControl.FORCE_NETWORK,
),
).awaitSuccess().parseAs(MdUtil.jsonParser)
).awaitSuccess().parseAs()
}
}
}

View File

@ -11,10 +11,12 @@ class SimilarService(
private val client: OkHttpClient,
) {
suspend fun getSimilarManga(mangaId: String): SimilarMangaDto {
return client.newCall(
return with(MdUtil.jsonParser) {
client.newCall(
GET(
"${MdUtil.similarBaseApi}$mangaId.json",
),
).awaitSuccess().parseAs()
}
}
}

View File

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.util.MangaType
import exh.util.mangaType
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
@ -37,6 +38,7 @@ abstract class API(val endpoint: String) {
val client by lazy {
Injekt.get<NetworkHelper>().client
}
val json by injectLazy<Json>()
abstract suspend fun getRecsBySearch(search: String): List<SManga>
@ -52,7 +54,7 @@ class MyAnimeList : API("https://api.jikan.moe/v4/") {
.addPathSegment("recommendations")
.build()
val data = client.newCall(GET(apiUrl)).awaitSuccess().parseAs<JsonObject>()
val data = with(json) { client.newCall(GET(apiUrl)).awaitSuccess().parseAs<JsonObject>() }
return data["data"]!!.jsonArray
.map { it.jsonObject["entry"]!!.jsonObject }
.map { rec ->
@ -88,8 +90,10 @@ class MyAnimeList : API("https://api.jikan.moe/v4/") {
.addQueryParameter("q", search)
.build()
val data = client.newCall(GET(url)).awaitSuccess()
val data = with(json) {
client.newCall(GET(url)).awaitSuccess()
.parseAs<JsonObject>()
}
return getRecsById(data["data"]!!.jsonArray.first().jsonObject["mal_id"]!!.jsonPrimitive.content)
}
}
@ -137,8 +141,10 @@ class Anilist : API("https://graphql.anilist.co/") {
}
val payloadBody = payload.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
val data = client.newCall(POST(endpoint, body = payloadBody)).awaitSuccess()
val data = with(json) {
client.newCall(POST(endpoint, body = payloadBody)).awaitSuccess()
.parseAs<JsonObject>()
}
val media = data["data"]!!
.jsonObject["Page"]!!

View File

@ -7,6 +7,13 @@ plugins {
android {
namespace = "eu.kanade.tachiyomi.core"
kotlinOptions {
freeCompilerArgs += listOf(
"-Xcontext-receivers",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}
dependencies {
@ -25,12 +32,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)

View File

@ -7,28 +7,32 @@ 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
/* SY --> */
open /* SY <-- */ class NetworkHelper(context: Context) {
private val preferences: NetworkPreferences by injectLazy()
open /* SY <-- */ class NetworkHelper(
context: Context,
private val preferences: NetworkPreferences,
) {
private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
/* SY --> */
open /* SY <-- */val cookieManager = AndroidCookieJar()
open /* SY <-- */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)
@ -69,7 +73,5 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
.build()
}
val defaultUserAgent by lazy {
preferences.defaultUserAgent().get().trim()
}
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
}

View File

@ -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)
}
inline fun <reified T> Response.parseAs(/* SY --> */json: Json = Injekt.get()/* SY <-- */): T {
return decodeFromJsonResponse(serializer(), this, /* SY --> */ json /* SY <-- */)
context(Json)
inline fun <reified T> Response.parseAs(): T {
return decodeFromJsonResponse(serializer(), this)
}
@OptIn(ExperimentalSerializationApi::class)
fun <T> decodeFromJsonResponse(deserializer: DeserializationStrategy<T>, response: Response, /* SY --> */ json: Json /* SY <-- */): T {
context(Json)
fun <T> decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>,
response: Response,
): T {
return response.body.source().use {
json.decodeFromBufferedSource(deserializer, it)
decodeFromBufferedSource(deserializer, it)
}
}

View File

@ -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 }
}

View File

@ -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 {

View File

@ -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()
}
}
}

View File

@ -24,6 +24,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)

View File

@ -8,9 +8,19 @@ 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<UpdatesWithRelations> {
return databaseHandler.awaitList {
updatesViewQueries.getUpdatesByReadStatus(
read = read,
after = after,
mapper = updateWithRelationMapper,
)
}
}
override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
return databaseHandler.subscribeToList {
updatesViewQueries.updates(after, updateWithRelationMapper)
@ -19,4 +29,14 @@ class UpdatesRepositoryImpl(
.map(updatesViewMapper)
}
}
override fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
return databaseHandler.subscribeToList {
updatesViewQueries.getUpdatesByReadStatus(
read = read,
after = after,
mapper = updateWithRelationMapper,
)
}
}
}

View File

@ -13,12 +13,12 @@ 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)
// SY -->
implementation(libs.injekt.core)
// SY <--

View File

@ -9,9 +9,17 @@ class GetUpdates(
private val repository: UpdatesRepository,
) {
suspend fun await(read: Boolean, after: Long): List<UpdatesWithRelations> {
return repository.awaitWithRead(read, after)
}
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> = subscribe(calendar.time.time)
fun subscribe(after: Long): Flow<List<UpdatesWithRelations>> {
return repository.subscribeAll(after)
}
fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
return repository.subscribeWithRead(read, after)
}
}

View File

@ -5,5 +5,9 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
interface UpdatesRepository {
suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations>
fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>>
fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>>
}

View File

@ -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)
}

View File

@ -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(
getUpdates.subscribe(
read = false,
after = UpdatesGridGlanceWidget.DateLimit.timeInMillis,
)
}
.drop(1)
.distinctUntilChanged()
.onEach {

View File

@ -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<UpdatesView>? = null) {
fun loadData(list: List<UpdatesWithRelations>? = 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<DatabaseHandler>()
.awaitList {
updatesViewQueries.getUpdatesByReadStatus(
?: Injekt.get<GetUpdates>().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<UpdatesView>, take: Int): List<Pair<Long, Bitmap?>> {
private fun prepareList(processList: List<UpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
// 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)

View File

@ -11,23 +11,17 @@ 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)
// SY -->
implementation(project(":i18n"))
implementation(kotlinx.reflect)

View File

@ -38,7 +38,7 @@ abstract class HttpSource : CatalogueSource {
// SY -->
protected val network: NetworkHelper by lazy {
val network = Injekt.get<NetworkHelper>()
object : NetworkHelper(Injekt.get<Application>()) {
object : NetworkHelper(Injekt.get<Application>(), Injekt.get()) {
override val client: OkHttpClient
get() = delegate?.networkHttpClient ?: network.client
.newBuilder()
@ -51,8 +51,8 @@ abstract class HttpSource : CatalogueSource {
.maybeInjectEHLogger()
.build()
override val cookieManager: AndroidCookieJar
get() = network.cookieManager
override val cookieJar: AndroidCookieJar
get() = network.cookieJar
}
}
// SY <--
@ -97,7 +97,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())
}
/**