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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await 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.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
@ -33,8 +35,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun addLibManga(track: Track): Track { suspend fun addLibManga(track: Track): Track {
val query = return withContext(Dispatchers.IO) {
""" val query =
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
@ -42,28 +45,34 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = buildJsonObject { val payload = buildJsonObject {
put("query", query) put("query", query)
putJsonObject("variables") { putJsonObject("variables") {
put("mangaId", track.media_id) put("mangaId", track.media_id)
put("progress", track.last_chapter_read) put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus()) put("status", track.toAnilistStatus())
}
} }
} authClient.newCall(
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use { POST(
val responseBody = it.body?.string().orEmpty() apiUrl,
if (responseBody.isEmpty()) { body = payload.toString().toRequestBody(jsonMime)
throw Exception("Null Response") )
} )
val response = json.decodeFromString<JsonObject>(responseBody) .await()
track.library_id = response["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long .parseAs<JsonObject>()
track .let {
track.library_id =
it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
track
}
} }
} }
suspend fun updateLibManga(track: Track): Track { suspend fun updateLibManga(track: Track): Track {
val query = return withContext(Dispatchers.IO) {
""" val query =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id |id
@ -72,22 +81,25 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = buildJsonObject { val payload = buildJsonObject {
put("query", query) put("query", query)
putJsonObject("variables") { putJsonObject("variables") {
put("listId", track.library_id) put("listId", track.library_id)
put("progress", track.last_chapter_read) put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus()) put("status", track.toAnilistStatus())
put("score", track.score.toInt()) put("score", track.score.toInt())
}
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
.await()
track
} }
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await()
return track
} }
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
val query = return withContext(Dispatchers.IO) {
""" val query =
"""
|query Search(${'$'}query: String) { |query Search(${'$'}query: String) {
|Page (perPage: 50) { |Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) { |media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@ -111,29 +123,34 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = buildJsonObject { val payload = buildJsonObject {
put("query", query) put("query", query)
putJsonObject("variables") { putJsonObject("variables") {
put("query", search) put("query", search)
}
} }
} authClient.newCall(
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use { POST(
val responseBody = it.body?.string().orEmpty() apiUrl,
if (responseBody.isEmpty()) { body = payload.toString().toRequestBody(jsonMime)
throw Exception("Null Response") )
} )
val response = json.decodeFromString<JsonObject>(responseBody) .await()
val data = response["data"]!!.jsonObject .parseAs<JsonObject>()
val page = data["Page"]!!.jsonObject .let { response ->
val media = page["media"]!!.jsonArray val data = response["data"]!!.jsonObject
val entries = media.map { jsonToALManga(it.jsonObject) } val page = data["Page"]!!.jsonObject
entries.map { it.toTrack() } val media = page["media"]!!.jsonArray
val entries = media.map { jsonToALManga(it.jsonObject) }
entries.map { it.toTrack() }
}
} }
} }
suspend fun findLibManga(track: Track, userid: Int): Track? { suspend fun findLibManga(track: Track, userid: Int): Track? {
val query = return withContext(Dispatchers.IO) {
""" val query =
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) { |query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page { |Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) { |mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@ -163,24 +180,28 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = buildJsonObject { val payload = buildJsonObject {
put("query", query) put("query", query)
putJsonObject("variables") { putJsonObject("variables") {
put("id", userid) put("id", userid)
put("manga_id", track.media_id) put("manga_id", track.media_id)
}
} }
} authClient.newCall(
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime))).await().use { POST(
val responseBody = it.body?.string().orEmpty() apiUrl,
if (responseBody.isEmpty()) { body = payload.toString().toRequestBody(jsonMime)
throw Exception("Null Response") )
} )
val response = json.decodeFromString<JsonObject>(responseBody) .await()
val data = response["data"]!!.jsonObject .parseAs<JsonObject>()
val page = data["Page"]!!.jsonObject .let { response ->
val media = page["mediaList"]!!.jsonArray val data = response["data"]!!.jsonObject
val entries = media.map { jsonToALUserManga(it.jsonObject) } val page = data["Page"]!!.jsonObject
entries.firstOrNull()?.toTrack() val media = page["mediaList"]!!.jsonArray
val entries = media.map { jsonToALUserManga(it.jsonObject) }
entries.firstOrNull()?.toTrack()
}
} }
} }
@ -193,8 +214,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
suspend fun getCurrentUser(): Pair<Int, String> { suspend fun getCurrentUser(): Pair<Int, String> {
val query = return withContext(Dispatchers.IO) {
""" val query =
"""
|query User { |query User {
|Viewer { |Viewer {
|id |id
@ -204,21 +226,25 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|} |}
|} |}
|""".trimMargin() |""".trimMargin()
val payload = buildJsonObject { val payload = buildJsonObject {
put("query", query) 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) authClient.newCall(
val data = response["data"]!!.jsonObject POST(
val viewer = data["Viewer"]!!.jsonObject apiUrl,
Pair( body = payload.toString().toRequestBody(jsonMime)
viewer["id"]!!.jsonPrimitive.int, )
viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content
) )
.await()
.parseAs<JsonObject>()
.let {
val data = it["data"]!!.jsonObject
val viewer = data["Viewer"]!!.jsonObject
Pair(
viewer["id"]!!.jsonPrimitive.int,
viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content
)
}
} }
} }

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

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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.PkceUtil import eu.kanade.tachiyomi.util.PkceUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.int import kotlinx.serialization.json.int
@ -25,15 +24,11 @@ import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
private val json: Json by injectLazy()
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun getAccessToken(authCode: String): OAuth { suspend fun getAccessToken(authCode: String): OAuth {
@ -44,10 +39,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("code_verifier", codeVerifier) .add("code_verifier", codeVerifier)
.add("grant_type", "authorization_code") .add("grant_type", "authorization_code")
.build() .build()
client.newCall(POST("$baseOAuthUrl/token", body = formBody)).await().use { client.newCall(POST("$baseOAuthUrl/token", body = formBody))
val responseBody = it.body?.string().orEmpty() .await()
json.decodeFromString(responseBody) .parseAs()
}
} }
} }
@ -57,11 +51,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$baseApiUrl/users/@me") .url("$baseApiUrl/users/@me")
.get() .get()
.build() .build()
authClient.newCall(request).await().use { authClient.newCall(request)
val responseBody = it.body?.string().orEmpty() .await()
val response = json.decodeFromString<JsonObject>(responseBody) .parseAs<JsonObject>()
response["name"]!!.jsonPrimitive.content .let { it["name"]!!.jsonPrimitive.content }
}
} }
} }
@ -70,18 +63,19 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
val url = "$baseApiUrl/manga".toUri().buildUpon() val url = "$baseApiUrl/manga".toUri().buildUpon()
.appendQueryParameter("q", query) .appendQueryParameter("q", query)
.build() .build()
authClient.newCall(GET(url.toString())).await().use { authClient.newCall(GET(url.toString()))
val responseBody = it.body?.string().orEmpty() .await()
val response = json.decodeFromString<JsonObject>(responseBody) .parseAs<JsonObject>()
response["data"]!!.jsonArray .let {
.map { data -> data.jsonObject["node"]!!.jsonObject } it["data"]!!.jsonArray
.map { node -> .map { data -> data.jsonObject["node"]!!.jsonObject }
val id = node["id"]!!.jsonPrimitive.int .map { node ->
async { getMangaDetails(id) } val id = node["id"]!!.jsonPrimitive.int
} async { getMangaDetails(id) }
.awaitAll() }
.filter { trackSearch -> trackSearch.publishing_type != "novel" } .awaitAll()
} .filter { trackSearch -> trackSearch.publishing_type != "novel" }
}
} }
} }
@ -91,27 +85,28 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendPath(id.toString()) .appendPath(id.toString())
.appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date") .appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
.build() .build()
authClient.newCall(GET(url.toString())).await().use { authClient.newCall(GET(url.toString()))
val responseBody = it.body?.string().orEmpty() .await()
val response = json.decodeFromString<JsonObject>(responseBody) .parseAs<JsonObject>()
val obj = response.jsonObject .let {
TrackSearch.create(TrackManager.MYANIMELIST).apply { val obj = it.jsonObject
media_id = obj["id"]!!.jsonPrimitive.int TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = obj["title"]!!.jsonPrimitive.content media_id = obj["id"]!!.jsonPrimitive.int
summary = obj["synopsis"]?.jsonPrimitive?.content ?: "" title = obj["title"]!!.jsonPrimitive.content
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: "" total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
tracking_url = "https://myanimelist.net/manga/$media_id" cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: ""
publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ") tracking_url = "https://myanimelist.net/manga/$media_id"
publishing_type = obj["media_type"]!!.jsonPrimitive.content.replace("_", " ") publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ")
start_date = try { publishing_type = obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) start_date = try {
outputDf.format(obj["start_date"]!!) val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
} catch (e: Exception) { outputDf.format(obj["start_date"]!!)
"" } catch (e: Exception) {
""
}
} }
} }
}
} }
} }
@ -124,9 +119,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString()) .url(mangaUrl(track.media_id).toString())
.put(formBody) .put(formBody)
.build() .build()
authClient.newCall(request).await().use { authClient.newCall(request)
parseMangaItem(it, track) .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()) .url(mangaUrl(track.media_id).toString())
.put(formBody) .put(formBody)
.build() .build()
authClient.newCall(request).await().use { authClient.newCall(request)
parseMangaItem(it, track) .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()) .url(mangaUrl(track.media_id).toString())
.put(formBody) .put(formBody)
.build() .build()
authClient.newCall(request).await().use { authClient.newCall(request)
parseMangaItem(it, track) .await()
} .parseAs<JsonObject>()
.let { parseMangaItem(it, track) }
} }
} }
private fun parseMangaItem(response: Response, track: Track): Track { private fun parseMangaItem(response: JsonObject, track: Track): Track {
val responseBody = response.body?.string().orEmpty() val obj = response.jsonObject
val obj = json.decodeFromString<JsonObject>(responseBody).jsonObject
return track.apply { return track.apply {
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]!!.jsonPrimitive.content) 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.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import kotlinx.serialization.decodeFromString import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.json.Json import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
@ -22,45 +24,51 @@ import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import uy.kohesive.injekt.injectLazy
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) { 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 jsonMime = "application/json; charset=utf-8".toMediaType()
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
suspend fun addLibManga(track: Track, user_id: String): Track { suspend fun addLibManga(track: Track, user_id: String): Track {
val payload = buildJsonObject { return withContext(Dispatchers.IO) {
putJsonObject("user_rate") { val payload = buildJsonObject {
put("user_id", user_id) putJsonObject("user_rate") {
put("target_id", track.media_id) put("user_id", user_id)
put("target_type", "Manga") put("target_id", track.media_id)
put("chapters", track.last_chapter_read) put("target_type", "Manga")
put("score", track.score.toInt()) put("chapters", track.last_chapter_read)
put("status", track.toShikimoriStatus()) put("score", track.score.toInt())
put("status", track.toShikimoriStatus())
}
} }
authClient.newCall(
POST(
"$apiUrl/v2/user_rates",
body = payload.toString().toRequestBody(jsonMime)
)
).await()
track
} }
authClient.newCall(POST("$apiUrl/v2/user_rates", body = payload.toString().toRequestBody(jsonMime))).await()
return track
} }
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id) suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
val url = "$apiUrl/mangas".toUri().buildUpon() return withContext(Dispatchers.IO) {
.appendQueryParameter("order", "popularity") val url = "$apiUrl/mangas".toUri().buildUpon()
.appendQueryParameter("search", search) .appendQueryParameter("order", "popularity")
.appendQueryParameter("limit", "20") .appendQueryParameter("search", search)
.build() .appendQueryParameter("limit", "20")
return authClient.newCall(GET(url.toString())).await().use { .build()
val responseBody = it.body?.string().orEmpty() authClient.newCall(GET(url.toString()))
if (responseBody.isEmpty()) { .await()
throw Exception("Null Response") .parseAs<JsonArray>()
} .let { response ->
val response = json.decodeFromString<JsonArray>(responseBody) response.map {
response.map { jsonToSearch(it.jsonObject) } jsonToSearch(it.jsonObject)
}
}
} }
} }
@ -91,47 +99,50 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
} }
suspend fun findLibManga(track: Track, user_id: String): Track? { suspend fun findLibManga(track: Track, user_id: String): Track? {
val urlMangas = "$apiUrl/mangas".toUri().buildUpon() return withContext(Dispatchers.IO) {
.appendPath(track.media_id.toString()) val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
.build() .appendPath(track.media_id.toString())
val mangas = authClient.newCall(GET(urlMangas.toString())).await().use { .build()
val responseBody = it.body?.string().orEmpty() val mangas = authClient.newCall(GET(urlMangas.toString()))
json.decodeFromString<JsonObject>(responseBody) .await()
} .parseAs<JsonObject>()
val url = "$apiUrl/v2/user_rates".toUri().buildUpon() val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", user_id) .appendQueryParameter("user_id", user_id)
.appendQueryParameter("target_id", track.media_id.toString()) .appendQueryParameter("target_id", track.media_id.toString())
.appendQueryParameter("target_type", "Manga") .appendQueryParameter("target_type", "Manga")
.build() .build()
return authClient.newCall(GET(url.toString())).await().use { authClient.newCall(GET(url.toString()))
val responseBody = it.body?.string().orEmpty() .await()
if (responseBody.isEmpty()) { .parseAs<JsonArray>()
throw Exception("Null Response") .let { response ->
} if (response.size > 1) {
val response = json.decodeFromString<JsonArray>(responseBody) throw Exception("Too much mangas in response")
if (response.size > 1) { }
throw Exception("Too much mangas in response") val entry = response.map {
} jsonToTrack(it.jsonObject, mangas)
val entry = response.map { }
jsonToTrack(it.jsonObject, mangas) entry.firstOrNull()
} }
entry.firstOrNull()
} }
} }
fun getCurrentUser(): Int { fun getCurrentUser(): Int {
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body?.string()!! return runBlocking {
return json.decodeFromString<JsonObject>(user)["id"]!!.jsonPrimitive.int authClient.newCall(GET("$apiUrl/users/whoami"))
.await()
.parseAs<JsonObject>()
.let {
it["id"]!!.jsonPrimitive.int
}
}
} }
suspend fun accessToken(code: String): OAuth { suspend fun accessToken(code: String): OAuth {
return client.newCall(accessTokenRequest(code)).await().use { return withContext(Dispatchers.IO) {
val responseBody = it.body?.string().orEmpty() client.newCall(accessTokenRequest(code))
if (responseBody.isEmpty()) { .await()
throw Exception("Null Response") .parseAs()
}
json.decodeFromString(responseBody)
} }
} }

View file

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