feat(Anilist): Parse GraphQL error responses for downtime and token expiry (#1591)
* Create ALError.kt * Update AnilistApi.kt * Update AnilistApi.kt * Update AnilistApi.kt * Add error handling for AniList API responses * Update AnilistApi.kt * Update ALError.kt * Update AnilistApi.kt * fix: improve error handling and resource management in AniList API calls * Fix interpolation sign & add comment --------- Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
This commit is contained in:
parent
9a7d1b6143
commit
ca26501aef
2 changed files with 67 additions and 8 deletions
|
|
@ -5,6 +5,7 @@ import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
|
||||||
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALError
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALIdSearchResult
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALIdSearchResult
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||||
|
|
@ -26,6 +27,7 @@ import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
@ -43,6 +45,25 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
.rateLimit(permits = 85, period = 1.minutes)
|
.rateLimit(permits = 85, period = 1.minutes)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
private fun Response.parseALError() {
|
||||||
|
val bodyString = peekBody(1024 * 1024).string()
|
||||||
|
val errorObj = try {
|
||||||
|
json.decodeFromString<ALError>(bodyString)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
errorObj?.errors?.firstOrNull()?.let {
|
||||||
|
val msg = it.message
|
||||||
|
if (msg.contains("Invalid token") || it.status == 401) {
|
||||||
|
throw Exception("AniList token expired, please login again")
|
||||||
|
}
|
||||||
|
throw Exception(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track): Track {
|
suspend fun addLibManga(track: Track): Track {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = $$"""
|
val query = $$"""
|
||||||
|
|
@ -71,6 +92,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALAddMangaResult>()
|
.parseAs<ALAddMangaResult>()
|
||||||
.let {
|
.let {
|
||||||
track.library_id = it.data.entry.id
|
track.library_id = it.data.entry.id
|
||||||
|
|
@ -112,6 +136,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
}
|
}
|
||||||
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.use { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +161,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
}
|
}
|
||||||
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.use { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +222,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALSearchResult>()
|
.parseAs<ALSearchResult>()
|
||||||
.data.page.media
|
.data.page.media
|
||||||
.map { it.toALManga().toTrack() }
|
.map { it.toALManga().toTrack() }
|
||||||
|
|
@ -271,6 +304,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALUserListMangaQueryResult>()
|
.parseAs<ALUserListMangaQueryResult>()
|
||||||
.data.page.mediaList
|
.data.page.mediaList
|
||||||
.map { it.toALUserManga() }
|
.map { it.toALUserManga() }
|
||||||
|
|
@ -312,6 +348,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALCurrentUserResult>()
|
.parseAs<ALCurrentUserResult>()
|
||||||
.let {
|
.let {
|
||||||
val viewer = it.data.viewer
|
val viewer = it.data.viewer
|
||||||
|
|
@ -323,9 +362,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
|
|
||||||
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = """
|
val query = $$"""
|
||||||
|query (${'$'}mangaId: Int!) {
|
|query ($mangaId: Int!) {
|
||||||
|Media (id: ${'$'}mangaId) {
|
|Media (id: $mangaId) {
|
||||||
|id
|
|id
|
||||||
|title {
|
|title {
|
||||||
|userPreferred
|
|userPreferred
|
||||||
|
|
@ -365,9 +404,12 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALMangaMetadata>()
|
.parseAs<ALMangaMetadata>()
|
||||||
.let {
|
.let { metadata ->
|
||||||
val media = it.data.media
|
val media = metadata.data.media
|
||||||
TrackMangaMetadata(
|
TrackMangaMetadata(
|
||||||
remoteId = media.id,
|
remoteId = media.id,
|
||||||
title = media.title.userPreferred,
|
title = media.title.userPreferred,
|
||||||
|
|
@ -392,9 +434,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
// SY -->
|
// SY -->
|
||||||
suspend fun searchById(id: String): TrackSearch {
|
suspend fun searchById(id: String): TrackSearch {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val query = """
|
val query = $$"""
|
||||||
|query (${'$'}mangaId: Int!) {
|
|query ($mangaId: Int!) {
|
||||||
|Media (id: ${'$'}mangaId) {
|
|Media (id: $mangaId) {
|
||||||
|id
|
|id
|
||||||
|title {
|
|title {
|
||||||
|userPreferred
|
|userPreferred
|
||||||
|
|
@ -430,6 +472,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
|
// KMK -->
|
||||||
|
.also { it.parseALError() }
|
||||||
|
// KMK <--
|
||||||
.parseAs<ALIdSearchResult>()
|
.parseAs<ALIdSearchResult>()
|
||||||
.data.media
|
.data.media
|
||||||
.toALManga()
|
.toALManga()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.anilist.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALError(
|
||||||
|
val errors: List<ALErrorItem>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ALErrorItem(
|
||||||
|
val message: String,
|
||||||
|
val status: Int? = null,
|
||||||
|
)
|
||||||
Loading…
Reference in a new issue