diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt index e15a77c6442..4536df830f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/KitsuApi.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.syncproviders.providers - import androidx.annotation.StringRes import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey @@ -22,6 +21,8 @@ import com.lagradost.cloudstream3.ui.SyncWatchType import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.txt +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import okhttp3.Interceptor import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody @@ -67,13 +68,9 @@ class KitsuApi: SyncAPI() { val request: Request = chain.request() try { - val response = chain.proceed(request); - if (response.isSuccessful) return response - response.close() - } catch (_: Exception) { } @@ -82,7 +79,6 @@ class KitsuApi: SyncAPI() { .build() return chain.proceed(fallbackRequest) - } } @@ -182,9 +178,9 @@ class KitsuApi: SyncAPI() { return null } + @Serializable data class KitsuResponse( - @field:JsonProperty(value = "data") - val data: KitsuNode, + @SerialName("data") val data: KitsuNode, ) val url = @@ -204,7 +200,7 @@ class KitsuApi: SyncAPI() { publicScore = Score.from(anime.ratingTwenty, 20), duration = anime.episodeLength, synopsis = anime.synopsis, - airStatus = when(anime.status) { + airStatus = when (anime.status) { "finished" -> ShowStatus.Completed "current" -> ShowStatus.Ongoing else -> null @@ -220,7 +216,6 @@ class KitsuApi: SyncAPI() { prevSeason = null, actors = null, ) - } override suspend fun status(auth : AuthData?, id: String): AbstractSyncStatus? { @@ -255,15 +250,13 @@ class KitsuApi: SyncAPI() { watchedEpisodes = anime.progress, ) } - suspend fun getAnimeIdByTitle(title: String): String? { + suspend fun getAnimeIdByTitle(title: String): String? { val animeSelectedFields = arrayOf("titles","canonicalTitle") val url = "$apiUrl/anime?filter[text]=$title&page[limit]=$KITSU_MAX_SEARCH_LIMIT&fields[anime]=${animeSelectedFields.joinToString(",")}" val res = app.get(url, interceptor = apiFallbackInterceptor).parsed() - return res.data.firstOrNull()?.id - } override fun urlToId(url: String): String? = @@ -274,7 +267,6 @@ class KitsuApi: SyncAPI() { id: String, newStatus: AbstractSyncStatus ): Boolean { - return setScoreRequest( auth ?: return false, id.toIntOrNull() ?: return false, @@ -285,21 +277,18 @@ class KitsuApi: SyncAPI() { } private suspend fun setScoreRequest( - auth : AuthData, + auth: AuthData, id: Int, status: KitsuStatusType? = null, score: Int? = null, numWatchedEpisodes: Int? = null, ): Boolean { - val libraryEntryId = getAnimeLibraryEntryId(auth, id) // Exists entry for anime in library if (libraryEntryId != null) { - // Delete anime from library if (status == null || status == KitsuStatusType.None) { - val res = app.delete( "$apiUrl/library-entries/$libraryEntryId", headers = mapOf( @@ -308,9 +297,7 @@ class KitsuApi: SyncAPI() { interceptor = apiFallbackInterceptor ) - return res.isSuccessful - } return setScoreRequest( @@ -320,7 +307,6 @@ class KitsuApi: SyncAPI() { score, numWatchedEpisodes ) - } val data = mapOf( @@ -359,7 +345,6 @@ class KitsuApi: SyncAPI() { ) return res.isSuccessful - } @Suppress("UNCHECKED_CAST") @@ -392,15 +377,11 @@ class KitsuApi: SyncAPI() { interceptor = apiFallbackInterceptor ) - return res.isSuccessful - } private suspend fun getAnimeLibraryEntryId(auth: AuthData, id: Int): Int? { - val userId = auth.user.id - val res = app.get( "$apiUrl/library-entries?filter[userId]=$userId&filter[animeId]=$id", headers = mapOf( @@ -410,7 +391,6 @@ class KitsuApi: SyncAPI() { ).parsed().data.firstOrNull() ?: return null return res.id.toInt() - } override suspend fun library(auth : AuthData?): LibraryMetadata? { @@ -452,7 +432,6 @@ class KitsuApi: SyncAPI() { } private suspend fun getKitsuAnimeList(token: AuthToken, userId: Int): Array { - val animeSelectedFields = arrayOf("titles","canonicalTitle","posterImage","synopsis","startDate","endDate","episodeCount") val libraryEntriesSelectedFields = arrayOf("progress","ratingTwenty","updatedAt", "status") val limit = 500 @@ -461,49 +440,44 @@ class KitsuApi: SyncAPI() { val fullList = mutableListOf() while (true) { - val data: KitsuResponse = getKitsuAnimeListSlice(token, url) - data.data.forEachIndexed { index, value -> value.anime = data.included?.get(index) } fullList.addAll(data.data) - url = data.links?.next ?: break } - return fullList.toTypedArray() } private suspend fun getKitsuAnimeListSlice(token: AuthToken, url: String): KitsuResponse { - val res = app.get( + return app.get( url, headers = mapOf( "Authorization" to "Bearer ${token.accessToken}", ), interceptor = apiFallbackInterceptor ).parsed() - return res } - + @Serializable data class ResponseToken( - @JsonProperty("token_type") val tokenType: String, - @JsonProperty("expires_in") val expiresIn: Int, - @JsonProperty("access_token") val accessToken: String, - @JsonProperty("refresh_token") val refreshToken: String, + @JsonProperty("token_type") @SerialName("token_type") val tokenType: String, + @JsonProperty("expires_in") @SerialName("expires_in") val expiresIn: Int, + @JsonProperty("access_token") @SerialName("access_token") val accessToken: String, + @JsonProperty("refresh_token") @SerialName("refresh_token") val refreshToken: String, ) + @Serializable data class KitsuNode( - @JsonProperty("id") val id: String, - @JsonProperty("attributes") val attributes: KitsuNodeAttributes, + @SerialName("id") val id: String, + @SerialName("attributes") val attributes: KitsuNodeAttributes, /* User list anime node */ - @JsonProperty("relationships") val relationships: KitsuRelationships?, - var anime: KitsuAnimeData? + @SerialName("relationships") val relationships: KitsuRelationships?, + @SerialName("anime") var anime: KitsuAnimeData?, ) { fun toLibraryItem(): LibraryItem { - val animeItem = this.anime val numEpisodes = animeItem?.attributes?.episodeCount @@ -545,93 +519,100 @@ class KitsuApi: SyncAPI() { } + @Serializable data class KitsuAnimeAttributes( - @JsonProperty("titles") val titles: KitsuTitles?, - @JsonProperty("canonicalTitle") val canonicalTitle: String?, - @JsonProperty("posterImage") val posterImage: KitsuPosterImage?, - @JsonProperty("synopsis") val synopsis: String?, - @JsonProperty("startDate") val startDate: String?, - @JsonProperty("endDate") val endDate: String?, - @JsonProperty("episodeCount") val episodeCount: Int?, - @JsonProperty("episodeLength") val episodeLength: Int?, + @SerialName("titles") val titles: KitsuTitles?, + @SerialName("canonicalTitle") val canonicalTitle: String?, + @SerialName("posterImage") val posterImage: KitsuPosterImage?, + @SerialName("synopsis") val synopsis: String?, + @SerialName("startDate") val startDate: String?, + @SerialName("endDate") val endDate: String?, + @SerialName("episodeCount") val episodeCount: Int?, + @SerialName("episodeLength") val episodeLength: Int?, ) + @Serializable data class KitsuAnimeData( - @JsonProperty("id") val id: String, - @JsonProperty("attributes") val attributes: KitsuAnimeAttributes, + @SerialName("id") val id: String, + @SerialName("attributes") val attributes: KitsuAnimeAttributes, ) - + @Serializable data class KitsuNodeAttributes( /* General attributes */ - @JsonProperty("titles") val titles: KitsuTitles?, - @JsonProperty("canonicalTitle") val canonicalTitle: String?, - @JsonProperty("posterImage") val posterImage: KitsuPosterImage?, - @JsonProperty("synopsis") val synopsis: String?, - @JsonProperty("startDate") val startDate: String?, - @JsonProperty("endDate") val endDate: String?, - @JsonProperty("episodeCount") val episodeCount: Int?, - @JsonProperty("episodeLength") val episodeLength: Int?, + @SerialName("titles") val titles: KitsuTitles?, + @SerialName("canonicalTitle") val canonicalTitle: String?, + @SerialName("posterImage") val posterImage: KitsuPosterImage?, + @SerialName("synopsis") val synopsis: String?, + @SerialName("startDate") val startDate: String?, + @SerialName("endDate") val endDate: String?, + @SerialName("episodeCount") val episodeCount: Int?, + @SerialName("episodeLength") val episodeLength: Int?, /* User attributes */ - @JsonProperty("name") val name: String?, - @JsonProperty("location") val location: String?, - @JsonProperty("createdAt") val createdAt: String?, - @JsonProperty("avatar") val avatar: KitsuUserAvatar?, + @SerialName("name") val name: String?, + @SerialName("location") val location: String?, + @SerialName("createdAt") val createdAt: String?, + @SerialName("avatar") val avatar: KitsuUserAvatar?, /* User list anime attributes */ - @JsonProperty("progress") val progress: Int?, - @JsonProperty("ratingTwenty") val ratingTwenty: Int?, - @JsonProperty("updatedAt") val updatedAt: String?, - @JsonProperty("status") val status: String?, + @SerialName("progress") val progress: Int?, + @SerialName("ratingTwenty") val ratingTwenty: Int?, + @SerialName("updatedAt") val updatedAt: String?, + @SerialName("status") val status: String?, ) + @Serializable data class KitsuRelationships( - @JsonProperty("anime") val anime: KitsuRelationshipsAnime? + @SerialName("anime") val anime: KitsuRelationshipsAnime?, ) + @Serializable data class KitsuRelationshipsAnime( - @JsonProperty("links") val links: KitsuLinks? + @SerialName("links") val links: KitsuLinks?, ) + @Serializable data class KitsuPosterImage( - @JsonProperty("large") val large: String?, - @JsonProperty("medium") val medium: String?, + @SerialName("large") val large: String?, + @SerialName("medium") val medium: String?, ) + @Serializable data class KitsuTitles( - @JsonProperty("en_jp") val enJp: String?, - @JsonProperty("ja_jp") val jaJp: String? + @JsonProperty("en_jp") @SerialName("en_jp") val enJp: String?, + @JsonProperty("ja_jp") @SerialName("ja_jp") val jaJp: String?, ) + @Serializable data class KitsuUserAvatar( - @JsonProperty("original") val original: String? + @SerialName("original") val original: String?, ) + @Serializable data class KitsuLinks( /* Pagination */ - @JsonProperty("first") val first: String?, - @JsonProperty("next") val next: String?, - @JsonProperty("last") val last: String?, + @SerialName("first") val first: String?, + @SerialName("next") val next: String?, + @SerialName("last") val last: String?, /* Relationships */ - @JsonProperty("related") val related: String? + @SerialName("related") val related: String?, ) + @Serializable data class KitsuResponse( - @JsonProperty("links") val links: KitsuLinks?, - @JsonProperty("data") val data: List, + @SerialName("links") val links: KitsuLinks?, + @SerialName("data") val data: List, /* When requesting related info (User library entry -> anime) */ - @JsonProperty("included") val included: List?, + @SerialName("included") val included: List?, ) - companion object { - const val KITSU_CACHED_LIST: String = "kitsu_cached_list" private fun parseDateLong(string: String?): Long? { return try { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse( string ?: return null )?.time?.div(1000) - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -640,13 +621,13 @@ class KitsuApi: SyncAPI() { arrayOf("current", "completed", "on_hold", "dropped", "planned") private fun fromIntToAnimeStatus(inp: SyncWatchType): KitsuStatusType { return when (inp) { - SyncWatchType.NONE -> KitsuStatusType.None - SyncWatchType.WATCHING -> KitsuStatusType.Watching - SyncWatchType.COMPLETED -> KitsuStatusType.Completed - SyncWatchType.ONHOLD -> KitsuStatusType.OnHold - SyncWatchType.DROPPED -> KitsuStatusType.Dropped - SyncWatchType.PLANTOWATCH -> KitsuStatusType.PlanToWatch - SyncWatchType.REWATCHING -> KitsuStatusType.Watching + SyncWatchType.NONE -> KitsuStatusType.None + SyncWatchType.WATCHING -> KitsuStatusType.Watching + SyncWatchType.COMPLETED -> KitsuStatusType.Completed + SyncWatchType.ONHOLD -> KitsuStatusType.OnHold + SyncWatchType.DROPPED -> KitsuStatusType.Dropped + SyncWatchType.PLANTOWATCH -> KitsuStatusType.PlanToWatch + SyncWatchType.REWATCHING -> KitsuStatusType.Watching } } @@ -661,12 +642,12 @@ class KitsuApi: SyncAPI() { private fun convertToStatus(string: String): KitsuStatusType { return when (string) { - "current" -> KitsuStatusType.Watching - "completed" -> KitsuStatusType.Completed - "on_hold" -> KitsuStatusType.OnHold - "dropped" -> KitsuStatusType.Dropped - "planned" -> KitsuStatusType.PlanToWatch - else -> KitsuStatusType.None + "current" -> KitsuStatusType.Watching + "completed" -> KitsuStatusType.Completed + "on_hold" -> KitsuStatusType.OnHold + "dropped" -> KitsuStatusType.Dropped + "planned" -> KitsuStatusType.PlanToWatch + else -> KitsuStatusType.None } } } @@ -688,7 +669,7 @@ object Kitsu { "https://kitsu.io/api/graphql", headers = headers, data = mapOf("query" to query) - ).parsed() + ).parsed() } private val cache: MutableMap, Map> = @@ -770,44 +751,52 @@ query { return map } + @Serializable data class KitsuResponse( - val data: Data? = null + @SerialName("data") val data: Data? = null, ) { + @Serializable data class Data( - val lookupMapping: LookupMapping? = null + @SerialName("lookupMapping") val lookupMapping: LookupMapping? = null, ) + @Serializable data class LookupMapping( - val id: String? = null, - val episodes: Episodes? = null + @SerialName("id") val id: String? = null, + @SerialName("episodes") val episodes: Episodes? = null, ) + @Serializable data class Episodes( - val nodes: List? = null + @SerialName("nodes") val nodes: List? = null, ) + @Serializable data class Node( - @JsonProperty("number") - val num: Int? = null, - val titles: Titles? = null, - val description: Description? = null, - val thumbnail: Thumbnail? = null + @JsonProperty("number") @SerialName("number") val num: Int? = null, + @SerialName("titles") val titles: Titles? = null, + @SerialName("description") val description: Description? = null, + @SerialName("thumbnail") val thumbnail: Thumbnail? = null, ) + @Serializable data class Description( - val en: String? = null + @SerialName("en") val en: String? = null, ) + @Serializable data class Thumbnail( - val original: Original? = null + @SerialName("original") val original: Original? = null, ) + @Serializable data class Original( - val url: String? = null + @SerialName("url") val url: String? = null, ) + @Serializable data class Titles( - val canonical: String? = null + @SerialName("canonical") val canonical: String? = null, ) } }