mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 11:11:51 +01:00
Add support for start/end fields for Kitsu (#5573)
Also shifting kitsu api to use kotlinx.serialization.json
This commit is contained in:
parent
db82cdedcb
commit
ba22641ff1
@ -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<Int> {
|
||||
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<OAuth>(preferences.trackToken(this).get())
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
@ -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<JsonObject>()
|
||||
.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<JsonObject>()
|
||||
.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<TrackSearch> {
|
||||
val key = searchRest.getKey()["media"].asJsonObject["key"].string
|
||||
return algoliaSearch(key, query)
|
||||
return withIOContext {
|
||||
authClient.newCall(GET(algoliaKeyUrl))
|
||||
.await()
|
||||
.parseAs<JsonObject>()
|
||||
.let {
|
||||
val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content
|
||||
algoliaSearch(key, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
|
||||
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<JsonObject>()
|
||||
.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<JsonObject>()
|
||||
.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<JsonObject>()
|
||||
.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<JsonObject>()
|
||||
.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()
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user