From ba22641ff14c89348e8033adf364d587bf644c68 Mon Sep 17 00:00:00 2001 From: Andreas Date: Sun, 18 Jul 2021 18:47:40 +0200 Subject: [PATCH] Add support for start/end fields for Kitsu (#5573) Also shifting kitsu api to use kotlinx.serialization.json --- .../tachiyomi/data/track/kitsu/Kitsu.kt | 29 +- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 417 ++++++++++-------- .../data/track/kitsu/KitsuDateHelper.kt | 25 ++ .../data/track/kitsu/KitsuInterceptor.kt | 10 +- .../tachiyomi/data/track/kitsu/KitsuModels.kt | 66 +-- .../tachiyomi/data/track/kitsu/OAuth.kt | 3 + 6 files changed, 315 insertions(+), 235 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuDateHelper.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index 158f5e4e7a..1ad1129658 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -3,13 +3,15 @@ package eu.kanade.tachiyomi.data.track.kitsu import android.content.Context import android.graphics.Color import androidx.annotation.StringRes -import com.google.gson.Gson import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.updateNewTrackInfo +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat @@ -30,19 +32,17 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { @StringRes override fun nameRes() = R.string.kitsu - private val gson: Gson by injectLazy() + override val supportsReadingDates: Boolean = true - private val interceptor by lazy { KitsuInterceptor(this, gson) } + private val json: Json by injectLazy() + + private val interceptor by lazy { KitsuInterceptor(this) } private val api by lazy { KitsuApi(client, interceptor) } - override fun getLogo(): Int { - return R.drawable.ic_tracker_kitsu - } + override fun getLogo() = R.drawable.ic_tracker_kitsu - override fun getLogoColor(): Int { - return Color.rgb(51, 37, 50) - } + override fun getLogoColor() = Color.rgb(51, 37, 50) override fun getStatusList(): List { return listOf(READING, PLAN_TO_READ, COMPLETED, ON_HOLD, DROPPED) @@ -135,15 +135,15 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { } override suspend fun login(username: String, password: String): Boolean { - try { + return try { val oauth = api.login(username, password) interceptor.newAuth(oauth) val userId = api.getCurrentUser() saveCredentials(username, userId) - return true + true } catch (e: Exception) { Timber.e(e) - return false + false } } @@ -157,13 +157,12 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { } fun saveToken(oauth: OAuth?) { - val json = gson.toJson(oauth) - preferences.trackToken(this).set(json) + preferences.trackToken(this).set(json.encodeToString(oauth)) } fun restoreToken(): OAuth? { return try { - gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java) + json.decodeFromString(preferences.trackToken(this).get()) } catch (e: Exception) { null } 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 c0f5bacc3e..daf4a993b7 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 @@ -1,230 +1,269 @@ package eu.kanade.tachiyomi.data.track.kitsu -import com.github.salomonbrys.kotson.array +import androidx.core.net.toUri import com.github.salomonbrys.kotson.get -import com.github.salomonbrys.kotson.int -import com.github.salomonbrys.kotson.jsonObject -import com.github.salomonbrys.kotson.obj -import com.github.salomonbrys.kotson.string -import com.google.gson.GsonBuilder -import com.google.gson.JsonObject import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.await +import eu.kanade.tachiyomi.network.jsonMime +import eu.kanade.tachiyomi.network.parseAs +import eu.kanade.tachiyomi.util.system.withIOContext +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject import okhttp3.FormBody +import okhttp3.Headers.Companion.headersOf +import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.Body -import retrofit2.http.DELETE -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Headers -import retrofit2.http.PATCH -import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Query -import timber.log.Timber +import okhttp3.Request +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) { private val authClient = client.newBuilder().addInterceptor(interceptor).build() - private val rest = Retrofit.Builder() - .baseUrl(baseUrl) - .client(authClient) - .addConverterFactory(GsonConverterFactory.create(GsonBuilder().serializeNulls().create())) - .build() - .create(KitsuApi.Rest::class.java) - - private val searchRest = Retrofit.Builder() - .baseUrl(algoliaKeyUrl) - .client(authClient) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(KitsuApi.SearchKeyRest::class.java) - - private val algoliaRest = Retrofit.Builder() - .baseUrl(algoliaUrl) - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(KitsuApi.AgoliaSearchRest::class.java) - suspend fun addLibManga(track: Track, userId: String): Track { - // @formatter:off - val data = jsonObject( - "type" to "libraryEntries", - "attributes" to jsonObject( - "status" to track.toKitsuStatus(), - "progress" to track.last_chapter_read - ), - "relationships" to jsonObject( - "user" to jsonObject( - "data" to jsonObject( - "id" to userId, - "type" to "users" - ) - ), - "media" to jsonObject( - "data" to jsonObject( - "id" to track.media_id, - "type" to "manga" - ) + return withIOContext { + val data = buildJsonObject { + putJsonObject("data") { + put("type", "libraryEntries") + putJsonObject("attributes") { + put("status", track.toKitsuStatus()) + put("progress", track.last_chapter_read) + } + putJsonObject("relationships") { + putJsonObject("user") { + putJsonObject("data") { + put("id", userId) + put("type", "users") + } + } + putJsonObject("media") { + putJsonObject("data") { + put("id", track.media_id) + put("type", "manga") + } + } + } + } + } + + authClient.newCall( + POST( + "${baseUrl}library-entries", + headers = headersOf( + "Content-Type", + "application/vnd.api+json" + ), + body = data.toString().toRequestBody("application/vnd.api+json".toMediaType()) ) ) - ) - - val json = rest.addLibManga(jsonObject("data" to data)) - track.media_id = json["data"]["id"].int - return track + .await() + .parseAs() + .let { + track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.int + track + } + } } suspend fun updateLibManga(track: Track): Track { - // @formatter:off - val data = jsonObject( - "type" to "libraryEntries", - "id" to track.media_id, - "attributes" to jsonObject( - "status" to track.toKitsuStatus(), - "progress" to track.last_chapter_read, - "ratingTwenty" to track.toKitsuScore() - ) - ) - // @formatter:on + return withIOContext { + val data = buildJsonObject { + putJsonObject("data") { + put("type", "libraryEntries") + put("id", track.media_id) + putJsonObject("attributes") { + put("status", track.toKitsuStatus()) + put("progress", track.last_chapter_read) + put("ratingTwenty", track.toKitsuScore()) + put("startedAt", KitsuDateHelper.convert(track.started_reading_date)) + put("finishedAt", KitsuDateHelper.convert(track.finished_reading_date)) + } + } + } - rest.updateLibManga(track.media_id, jsonObject("data" to data)) - return track + 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() + ) + .await() + .parseAs() + .let { + val manga = it["data"]?.jsonObject + if (manga != null) { + val startedAt = manga["attributes"]!!.jsonObject["startedAt"]?.jsonPrimitive?.contentOrNull + val finishedAt = manga["attributes"]!!.jsonObject["finishedAt"]?.jsonPrimitive?.contentOrNull + val startedDate = KitsuDateHelper.parse(startedAt) + if (track.started_reading_date <= 0L || startedDate > 0) { + track.started_reading_date = startedDate + } + val finishedDate = KitsuDateHelper.parse(finishedAt) + if (track.finished_reading_date <= 0L || finishedDate > 0) { + track.finished_reading_date = finishedDate + } + } + track.apply { + } + } + } } suspend fun remove(track: Track): Boolean { - try { - rest.deleteLibManga(track.media_id) - return true - } catch (e: Exception) { - Timber.w(e) + return withIOContext { + val data = buildJsonObject { + putJsonObject("data") { + put("type", "libraryEntries") + put("id", track.media_id) + } + } + + authClient.newCall( + Request.Builder() + .url("${baseUrl}library-entries/${track.media_id}") + .headers( + headersOf( + "Content-Type", + "application/vnd.api+json" + ) + ) + .delete(data.toString().toRequestBody("application/vnd.api+json".toMediaType())) + .build() + ) + .await() + .let { + true + } } - return false } suspend fun search(query: String): List { - val key = searchRest.getKey()["media"].asJsonObject["key"].string - return algoliaSearch(key, query) + return withIOContext { + authClient.newCall(GET(algoliaKeyUrl)) + .await() + .parseAs() + .let { + val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content + algoliaSearch(key, query) + } + } } private suspend fun algoliaSearch(key: String, query: String): List { - val jsonObject = jsonObject("params" to "query=$query$algoliaFilter") - val json = algoliaRest.getSearchQuery(algoliaAppId, key, jsonObject) - val data = json["hits"].array - return data.map { KitsuSearchManga(it.obj) } - .filter { it.subType != "novel" } - .map { it.toTrack() } + return withIOContext { + val jsonObject = buildJsonObject { + put("params", "query=$query$algoliaFilter") + } + + client.newCall( + POST( + algoliaUrl, + headers = headersOf( + "X-Algolia-Application-Id", + algoliaAppId, + "X-Algolia-API-Key", + key, + ), + body = jsonObject.toString().toRequestBody(jsonMime) + ) + ) + .await() + .parseAs() + .let { + it["hits"]!!.jsonArray + .map { KitsuSearchManga(it.jsonObject) } + .filter { it.subType != "novel" } + .map { it.toTrack() } + } + } } suspend fun findLibManga(track: Track, userId: String): Track? { - val json = rest.findLibManga(track.media_id, userId) - val data = json["data"].array - return if (data.size() > 0) { - val manga = json["included"].array[0].obj - KitsuLibManga(data[0].obj, manga).toTrack() - } else { - null + return withIOContext { + val url = "${baseUrl}library-entries".toUri().buildUpon() + .encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId") + .appendQueryParameter("include", "manga") + .build() + authClient.newCall(GET(url.toString())) + .await() + .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 + } + } } } suspend fun getLibManga(track: Track): Track { - val json = rest.getLibManga(track.media_id) - val data = json["data"].array - if (data.size() > 0) { - val manga = json["included"].array[0].obj - return KitsuLibManga(data[0].obj, manga).toTrack() - } else { - throw Exception("Could not find manga") + return withIOContext { + val url = "${baseUrl}library-entries".toUri().buildUpon() + .encodedQuery("filter[id]=${track.media_id}") + .appendQueryParameter("include", "manga") + .build() + authClient.newCall(GET(url.toString())) + .await() + .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") + } + } } } suspend fun login(username: String, password: String): OAuth { - return Retrofit.Builder() - .baseUrl(loginUrl) - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(KitsuApi.LoginRest::class.java) - .requestAccessToken(username, password) + return withIOContext { + val formBody: RequestBody = FormBody.Builder() + .add("username", username) + .add("password", password) + .add("grant_type", "password") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .build() + client.newCall(POST(loginUrl, body = formBody)) + .await() + .parseAs() + } } suspend fun getCurrentUser(): String { - val currentUser = rest.getCurrentUser() - return currentUser["data"].array[0]["id"].string - } - - private interface Rest { - - @Headers("Content-Type: application/vnd.api+json") - @POST("library-entries") - suspend fun addLibManga( - @Body data: JsonObject - ): JsonObject - - @Headers("Content-Type: application/vnd.api+json") - @DELETE("library-entries/{id}") - suspend fun deleteLibManga( - @Path("id") remoteId: Int - ): JsonObject - - @Headers("Content-Type: application/vnd.api+json") - @PATCH("library-entries/{id}") - suspend fun updateLibManga( - @Path("id") remoteId: Int, - @Body data: JsonObject - ): JsonObject - - @GET("library-entries") - suspend fun findLibManga( - @Query("filter[manga_id]", encoded = true) remoteId: Int, - @Query("filter[user_id]", encoded = true) userId: String, - @Query("include") includes: String = "manga" - ): JsonObject - - @GET("library-entries") - suspend fun getLibManga( - @Query("filter[id]", encoded = true) remoteId: Int, - @Query("include") includes: String = "manga" - ): JsonObject - - @GET("users") - suspend fun getCurrentUser( - @Query("filter[self]", encoded = true) self: Boolean = true - ): JsonObject - } - - private interface SearchKeyRest { - @GET("media/") - suspend fun getKey(): JsonObject - } - - private interface AgoliaSearchRest { - @POST("query/") - suspend fun getSearchQuery( - @Header("X-Algolia-Application-Id") appid: String, - @Header("X-Algolia-API-Key") key: String, - @Body json: JsonObject - ): JsonObject - } - - private interface LoginRest { - - @FormUrlEncoded - @POST("oauth/token") - suspend fun requestAccessToken( - @Field("username") username: String, - @Field("password") password: String, - @Field("grant_type") grantType: String = "password", - @Field("client_id") client_id: String = clientId, - @Field("client_secret") client_secret: String = clientSecret - ): OAuth + return withIOContext { + val url = "${baseUrl}users".toUri().buildUpon() + .encodedQuery("filter[self]=true") + .build() + authClient.newCall(GET(url.toString())) + .await() + .parseAs() + .let { + it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content + } + } } companion object { @@ -232,12 +271,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) "dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd" private const val clientSecret = "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151" + private const val baseUrl = "https://kitsu.io/api/edge/" - private const val loginUrl = "https://kitsu.io/api/" + private const val loginUrl = "https://kitsu.io/api/oauth/token" private const val baseMangaUrl = "https://kitsu.io/manga/" - private const val algoliaKeyUrl = "https://kitsu.io/api/edge/algolia-keys/" + private const val algoliaKeyUrl = "https://kitsu.io/api/edge/algolia-keys/media/" + private const val algoliaUrl = - "https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/" + "https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/" private const val algoliaAppId = "AWQO5J657S" private const val algoliaFilter = "&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" @@ -247,12 +288,12 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } fun refreshTokenRequest(token: String) = POST( - "${loginUrl}oauth/token", + loginUrl, body = FormBody.Builder() .add("grant_type", "refresh_token") + .add("refresh_token", token) .add("client_id", clientId) .add("client_secret", clientSecret) - .add("refresh_token", token) .build() ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuDateHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuDateHelper.kt new file mode 100644 index 0000000000..3e473746d1 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuDateHelper.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.data.track.kitsu + +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +object KitsuDateHelper { + + private const val pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + private val formatter = SimpleDateFormat(pattern, Locale.ENGLISH) + + fun convert(dateValue: Long): String? { + if (dateValue <= 0L) return null + + return formatter.format(Date(dateValue)) + } + + fun parse(dateString: String?): Long { + if (dateString == null) return 0L + + val dateValue = formatter.parse(dateString) + + return dateValue?.time ?: return 0 + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt index 0e4dc070cb..5f698731b0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt @@ -1,10 +1,14 @@ package eu.kanade.tachiyomi.data.track.kitsu -import com.google.gson.Gson +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.Interceptor import okhttp3.Response +import uy.kohesive.injekt.injectLazy -class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor { +class KitsuInterceptor(val kitsu: Kitsu) : Interceptor { + + private val json: Json by injectLazy() /** * OAuth object used for authenticated requests. @@ -22,7 +26,7 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor { if (currAuth.isExpired()) { val response = chain.proceed(KitsuApi.refreshTokenRequest(refreshToken)) if (response.isSuccessful) { - newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java)) + newAuth(json.decodeFromString(response.body!!.string())) } else { response.close() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt index 9664d87924..d5ec925414 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt @@ -1,32 +1,36 @@ package eu.kanade.tachiyomi.data.track.kitsu import androidx.annotation.CallSuper -import com.github.salomonbrys.kotson.byInt -import com.github.salomonbrys.kotson.byString -import com.github.salomonbrys.kotson.nullInt -import com.github.salomonbrys.kotson.nullObj -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.google.gson.JsonObject import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.model.TrackSearch +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import java.text.SimpleDateFormat import java.util.Date import java.util.Locale class KitsuSearchManga(obj: JsonObject) { - val id by obj.byInt - private val canonicalTitle by obj.byString - private val chapterCount = obj.get("chapterCount").nullInt - val subType = obj.get("subtype").nullString - val original = obj.get("posterImage").nullObj?.get("original")?.asString - private val synopsis by obj.byString - private var startDate = obj.get("startDate").nullString?.let { + val id = obj["id"]!!.jsonPrimitive.int + private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content + private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull + val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull + val original = try { + obj["posterImage"]?.jsonObject?.get("original")?.jsonPrimitive?.content + } catch (e: IllegalArgumentException) { + // posterImage is sometimes a jsonNull object instead + null + } + private val synopsis = obj["synopsis"]!!.jsonPrimitive.content + private var startDate = obj["startDate"]?.jsonPrimitive?.contentOrNull?.let { val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) outputDf.format(Date(it.toLong() * 1000)) } - private val endDate = obj.get("endDate").nullString + private val endDate = obj["endDate"]?.jsonPrimitive?.contentOrNull @CallSuper fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { @@ -36,10 +40,10 @@ class KitsuSearchManga(obj: JsonObject) { cover_url = original ?: "" summary = synopsis tracking_url = KitsuApi.mangaUrl(media_id) - if (endDate == null) { - publishing_status = "Publishing" + publishing_status = if (endDate == null) { + "Publishing" } else { - publishing_status = "Finished" + "Finished" } publishing_type = subType ?: "" start_date = startDate ?: "" @@ -47,17 +51,19 @@ class KitsuSearchManga(obj: JsonObject) { } class KitsuLibManga(obj: JsonObject, manga: JsonObject) { - val id by manga.byInt - private val canonicalTitle by manga["attributes"].byString - private val chapterCount = manga["attributes"].obj.get("chapterCount").nullInt - val type = manga["attributes"].obj.get("mangaType").nullString.orEmpty() - val original by manga["attributes"].obj["posterImage"].byString - private val synopsis by manga["attributes"].byString - private val startDate = manga["attributes"].obj.get("startDate").nullString.orEmpty() - private val libraryId by obj.byInt("id") - val status by obj["attributes"].byString - private val ratingTwenty = obj["attributes"].obj.get("ratingTwenty").nullString - val progress by obj["attributes"].byInt + val id = manga["id"]!!.jsonPrimitive.int + private val canonicalTitle = manga["attributes"]!!.jsonObject["canonicalTitle"]!!.jsonPrimitive.content + private val chapterCount = manga["attributes"]!!.jsonObject["chapterCount"]?.jsonPrimitive?.intOrNull + val type = manga["attributes"]!!.jsonObject["mangaType"]?.jsonPrimitive?.contentOrNull.orEmpty() + val original = manga["attributes"]!!.jsonObject["posterImage"]!!.jsonObject["original"]!!.jsonPrimitive.content + private val synopsis = manga["attributes"]!!.jsonObject["synopsis"]!!.jsonPrimitive.content + private val startDate = manga["attributes"]!!.jsonObject["startDate"]?.jsonPrimitive?.contentOrNull.orEmpty() + private val startedAt = obj["attributes"]!!.jsonObject["startedAt"]?.jsonPrimitive?.contentOrNull + private val finishedAt = obj["attributes"]!!.jsonObject["finishedAt"]?.jsonPrimitive?.contentOrNull + private val libraryId = obj["id"]!!.jsonPrimitive.int + val status = obj["attributes"]!!.jsonObject["status"]!!.jsonPrimitive.content + private val ratingTwenty = obj["attributes"]!!.jsonObject["ratingTwenty"]?.jsonPrimitive?.contentOrNull + val progress = obj["attributes"]!!.jsonObject["progress"]!!.jsonPrimitive.int fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { media_id = libraryId @@ -69,6 +75,8 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) { publishing_status = this@KitsuLibManga.status publishing_type = type start_date = startDate + started_reading_date = KitsuDateHelper.parse(startedAt) + finished_reading_date = KitsuDateHelper.parse(finishedAt) status = toTrackStatus() score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f last_chapter_read = progress diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt index a10981c51e..5cd28c4962 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.data.track.kitsu +import kotlinx.serialization.Serializable + +@Serializable data class OAuth( val access_token: String, val token_type: String,