Refactor tracker response parsing

This commit is contained in:
arkon 2020-12-27 17:46:14 -05:00
parent 0e2b8b10d1
commit 2e8791a101
5 changed files with 313 additions and 255 deletions

View file

@ -6,7 +6,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import kotlinx.serialization.decodeFromString
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
@ -33,6 +35,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun addLibManga(track: Track): Track {
return withContext(Dispatchers.IO) {
val query =
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
@ -50,18 +53,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("status", track.toAnilistStatus())
}
}
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonObject>(responseBody)
track.library_id = response["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let {
track.library_id =
it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
track
}
}
}
suspend fun updateLibManga(track: Track): Track {
return withContext(Dispatchers.IO) {
val query =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
@ -81,11 +90,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("score", track.score.toInt())
}
}
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await()
return track
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
.await()
track
}
}
suspend fun search(search: String): List<TrackSearch> {
return withContext(Dispatchers.IO) {
val query =
"""
|query Search(${'$'}query: String) {
@ -117,12 +129,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", search)
}
}
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonObject>(responseBody)
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let { response ->
val data = response["data"]!!.jsonObject
val page = data["Page"]!!.jsonObject
val media = page["media"]!!.jsonArray
@ -130,8 +145,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
entries.map { it.toTrack() }
}
}
}
suspend fun findLibManga(track: Track, userid: Int): Track? {
return withContext(Dispatchers.IO) {
val query =
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
@ -170,12 +187,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("manga_id", track.media_id)
}
}
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonObject>(responseBody)
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let { response ->
val data = response["data"]!!.jsonObject
val page = data["Page"]!!.jsonObject
val media = page["mediaList"]!!.jsonArray
@ -183,6 +203,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
entries.firstOrNull()?.toTrack()
}
}
}
suspend fun getLibManga(track: Track, userid: Int): Track {
return findLibManga(track, userid) ?: throw Exception("Could not find manga")
@ -193,6 +214,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
suspend fun getCurrentUser(): Pair<Int, String> {
return withContext(Dispatchers.IO) {
val query =
"""
|query User {
@ -207,13 +229,16 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
val payload = buildJsonObject {
put("query", query)
}
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonObject>(responseBody)
val data = response["data"]!!.jsonObject
authClient.newCall(
POST(
apiUrl,
body = payload.toString().toRequestBody(jsonMime)
)
)
.await()
.parseAs<JsonObject>()
.let {
val data = it["data"]!!.jsonObject
val viewer = data["Viewer"]!!.jsonObject
Pair(
viewer["id"]!!.jsonPrimitive.int,
@ -221,6 +246,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
)
}
}
}
private fun jsonToALManga(struct: JsonObject): ALManga {
val date = try {

View file

@ -8,6 +8,9 @@ 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.parseAs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
@ -30,37 +33,51 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun addLibManga(track: Track): Track {
return withContext(Dispatchers.IO) {
val body = FormBody.Builder()
.add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus())
.build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body)).await()
return track
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body))
.await()
track
}
}
suspend fun updateLibManga(track: Track): Track {
return withContext(Dispatchers.IO) {
// read status update
val sbody = FormBody.Builder()
.add("status", track.toBangumiStatus())
.build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody)).await()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody))
.await()
// chapter update
val body = FormBody.Builder()
.add("watched_eps", track.last_chapter_read.toString())
.build()
authClient.newCall(POST("$apiUrl/subject/${track.media_id}/update/watched_eps", body = body)).await()
authClient.newCall(
POST(
"$apiUrl/subject/${track.media_id}/update/watched_eps",
body = body
)
).await()
return track
track
}
}
suspend fun search(search: String): List<TrackSearch> {
return withContext(Dispatchers.IO) {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}"
.toUri()
.buildUpon()
.appendQueryParameter("max_results", "20")
.build()
return authClient.newCall(GET(url.toString())).await().use {
authClient.newCall(GET(url.toString()))
.await()
.use {
var responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
@ -69,7 +86,9 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
responseBody = "{\"results\":0,\"list\":[]}"
}
val response = json.decodeFromString<JsonObject>(responseBody)["list"]?.jsonArray
response?.filter { it.jsonObject["type"]?.jsonPrimitive?.int == 1 }?.map { jsonToSearch(it.jsonObject) }.orEmpty()
response?.filter { it.jsonObject["type"]?.jsonPrimitive?.int == 1 }
?.map { jsonToSearch(it.jsonObject) }.orEmpty()
}
}
}
@ -98,14 +117,16 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
suspend fun findLibManga(track: Track): Track? {
return authClient.newCall(GET("$apiUrl/subject/${track.media_id}")).await().use {
// get comic info
val responseBody = it.body?.string().orEmpty()
jsonToTrack(json.decodeFromString(responseBody))
return withContext(Dispatchers.IO) {
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
.await()
.parseAs<JsonObject>()
.let { jsonToSearch(it) }
}
}
suspend fun statusLibManga(track: Track): Track? {
return withContext(Dispatchers.IO) {
val urlUserRead = "$apiUrl/collection/${track.media_id}"
val requestUserRead = Request.Builder()
.url(urlUserRead)
@ -114,22 +135,22 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.build()
// TODO: get user readed chapter here
return authClient.newCall(requestUserRead).await().use {
val resp = it.body?.string()
val coll = json.decodeFromString<Collection>(resp!!)
track.status = coll.status?.id!!
track.last_chapter_read = coll.ep_status!!
authClient.newCall(requestUserRead)
.await()
.parseAs<Collection>()
.let {
track.status = it.status?.id!!
track.last_chapter_read = it.ep_status!!
track
}
}
}
suspend fun accessToken(code: String): OAuth {
return client.newCall(accessTokenRequest(code)).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
json.decodeFromString<OAuth>(responseBody)
return withContext(Dispatchers.IO) {
client.newCall(accessTokenRequest(code))
.await()
.parseAs()
}
}

View file

@ -8,13 +8,12 @@ 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.parseAs
import eu.kanade.tachiyomi.util.PkceUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.int
@ -25,15 +24,11 @@ import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
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 {
@ -44,10 +39,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("code_verifier", codeVerifier)
.add("grant_type", "authorization_code")
.build()
client.newCall(POST("$baseOAuthUrl/token", body = formBody)).await().use {
val responseBody = it.body?.string().orEmpty()
json.decodeFromString(responseBody)
}
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
.await()
.parseAs()
}
}
@ -57,11 +51,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$baseApiUrl/users/@me")
.get()
.build()
authClient.newCall(request).await().use {
val responseBody = it.body?.string().orEmpty()
val response = json.decodeFromString<JsonObject>(responseBody)
response["name"]!!.jsonPrimitive.content
}
authClient.newCall(request)
.await()
.parseAs<JsonObject>()
.let { it["name"]!!.jsonPrimitive.content }
}
}
@ -70,10 +63,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
val url = "$baseApiUrl/manga".toUri().buildUpon()
.appendQueryParameter("q", query)
.build()
authClient.newCall(GET(url.toString())).await().use {
val responseBody = it.body?.string().orEmpty()
val response = json.decodeFromString<JsonObject>(responseBody)
response["data"]!!.jsonArray
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonObject>()
.let {
it["data"]!!.jsonArray
.map { data -> data.jsonObject["node"]!!.jsonObject }
.map { node ->
val id = node["id"]!!.jsonPrimitive.int
@ -91,10 +85,11 @@ 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()
authClient.newCall(GET(url.toString())).await().use {
val responseBody = it.body?.string().orEmpty()
val response = json.decodeFromString<JsonObject>(responseBody)
val obj = response.jsonObject
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonObject>()
.let {
val obj = it.jsonObject
TrackSearch.create(TrackManager.MYANIMELIST).apply {
media_id = obj["id"]!!.jsonPrimitive.int
title = obj["title"]!!.jsonPrimitive.content
@ -124,9 +119,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBody)
.build()
authClient.newCall(request).await().use {
parseMangaItem(it, track)
}
authClient.newCall(request)
.await()
.parseAs<JsonObject>()
.let { parseMangaItem(it, track) }
}
}
@ -140,9 +136,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBody)
.build()
authClient.newCall(request).await().use {
parseMangaItem(it, track)
}
authClient.newCall(request)
.await()
.parseAs<JsonObject>()
.let { parseMangaItem(it, track) }
}
}
@ -158,15 +155,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBody)
.build()
authClient.newCall(request).await().use {
parseMangaItem(it, track)
}
authClient.newCall(request)
.await()
.parseAs<JsonObject>()
.let { parseMangaItem(it, track) }
}
}
private fun parseMangaItem(response: Response, track: Track): Track {
val responseBody = response.body?.string().orEmpty()
val obj = json.decodeFromString<JsonObject>(responseBody).jsonObject
private fun parseMangaItem(response: JsonObject, track: Track): Track {
val obj = response.jsonObject
return track.apply {
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]!!.jsonPrimitive.content)

View file

@ -7,8 +7,10 @@ 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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
@ -22,16 +24,14 @@ import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import uy.kohesive.injekt.injectLazy
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
private val json: Json by injectLazy()
private val jsonMime = "application/json; charset=utf-8".toMediaType()
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun addLibManga(track: Track, user_id: String): Track {
return withContext(Dispatchers.IO) {
val payload = buildJsonObject {
putJsonObject("user_rate") {
put("user_id", user_id)
@ -42,25 +42,33 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
put("status", track.toShikimoriStatus())
}
}
authClient.newCall(POST("$apiUrl/v2/user_rates", body = payload.toString().toRequestBody(jsonMime))).await()
return track
authClient.newCall(
POST(
"$apiUrl/v2/user_rates",
body = payload.toString().toRequestBody(jsonMime)
)
).await()
track
}
}
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
suspend fun search(search: String): List<TrackSearch> {
return withContext(Dispatchers.IO) {
val url = "$apiUrl/mangas".toUri().buildUpon()
.appendQueryParameter("order", "popularity")
.appendQueryParameter("search", search)
.appendQueryParameter("limit", "20")
.build()
return authClient.newCall(GET(url.toString())).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonArray>()
.let { response ->
response.map {
jsonToSearch(it.jsonObject)
}
}
val response = json.decodeFromString<JsonArray>(responseBody)
response.map { jsonToSearch(it.jsonObject) }
}
}
@ -91,25 +99,23 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
}
suspend fun findLibManga(track: Track, user_id: String): Track? {
return withContext(Dispatchers.IO) {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString())
.build()
val mangas = authClient.newCall(GET(urlMangas.toString())).await().use {
val responseBody = it.body?.string().orEmpty()
json.decodeFromString<JsonObject>(responseBody)
}
val mangas = authClient.newCall(GET(urlMangas.toString()))
.await()
.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()
return authClient.newCall(GET(url.toString())).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = json.decodeFromString<JsonArray>(responseBody)
authClient.newCall(GET(url.toString()))
.await()
.parseAs<JsonArray>()
.let { response ->
if (response.size > 1) {
throw Exception("Too much mangas in response")
}
@ -119,19 +125,24 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
entry.firstOrNull()
}
}
}
fun getCurrentUser(): Int {
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body?.string()!!
return json.decodeFromString<JsonObject>(user)["id"]!!.jsonPrimitive.int
return runBlocking {
authClient.newCall(GET("$apiUrl/users/whoami"))
.await()
.parseAs<JsonObject>()
.let {
it["id"]!!.jsonPrimitive.int
}
}
}
suspend fun accessToken(code: String): OAuth {
return client.newCall(accessTokenRequest(code)).await().use {
val responseBody = it.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
json.decodeFromString(responseBody)
return withContext(Dispatchers.IO) {
client.newCall(accessTokenRequest(code))
.await()
.parseAs()
}
}

View file

@ -12,6 +12,7 @@ import rx.Observable
import rx.Producer
import rx.Subscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.fullType
import uy.kohesive.injekt.api.get
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
@ -111,8 +112,10 @@ fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListene
}
inline fun <reified T> Response.parseAs(): T {
// Avoiding Injekt.get<Json>() due to compiler issues
val json = Injekt.getInstance<Json>(fullType<Json>().type)
this.use {
val responseBody = it.body?.string().orEmpty()
return Injekt.get<Json>().decodeFromString<T>(responseBody)
return json.decodeFromString(responseBody)
}
}