mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
Convert Shikimori to kotlinx.serialization
with this, it is over. now for the final cleanup
This commit is contained in:
parent
d2c1582a48
commit
33135f766d
6 changed files with 191 additions and 177 deletions
|
@ -0,0 +1,16 @@
|
||||||
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OAuth(
|
||||||
|
val access_token: String,
|
||||||
|
val token_type: String,
|
||||||
|
val created_at: Long,
|
||||||
|
val expires_in: Long,
|
||||||
|
val refresh_token: String?,
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Access token lives 1 day
|
||||||
|
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
||||||
|
}
|
|
@ -3,23 +3,36 @@ package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.google.gson.Gson
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
|
||||||
import eu.kanade.tachiyomi.data.track.updateNewTrackInfo
|
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 timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val READING = 1
|
||||||
|
const val COMPLETED = 2
|
||||||
|
const val ON_HOLD = 3
|
||||||
|
const val DROPPED = 4
|
||||||
|
const val PLAN_TO_READ = 5
|
||||||
|
const val REREADING = 6
|
||||||
|
|
||||||
|
const val DEFAULT_STATUS = READING
|
||||||
|
const val DEFAULT_SCORE = 0
|
||||||
|
}
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
override fun nameRes() = R.string.shikimori
|
override fun nameRes() = R.string.shikimori
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val interceptor by lazy { ShikimoriInterceptor(this, gson) }
|
private val interceptor by lazy { ShikimoriInterceptor(this) }
|
||||||
|
|
||||||
private val api by lazy { ShikimoriApi(client, interceptor) }
|
private val api by lazy { ShikimoriApi(client, interceptor) }
|
||||||
|
|
||||||
|
@ -28,14 +41,14 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||||
|
|
||||||
override fun getStatusList(): List<Int> {
|
override fun getStatusList(): List<Int> {
|
||||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLANNING, REPEATING)
|
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isCompletedStatus(index: Int) = getStatusList()[index] == COMPLETED
|
override fun isCompletedStatus(index: Int) = getStatusList()[index] == COMPLETED
|
||||||
|
|
||||||
override fun completedStatus(): Int = MyAnimeList.COMPLETED
|
override fun completedStatus(): Int = COMPLETED
|
||||||
override fun readingStatus() = READING
|
override fun readingStatus() = READING
|
||||||
override fun planningStatus() = PLANNING
|
override fun planningStatus() = PLAN_TO_READ
|
||||||
|
|
||||||
override fun getStatus(status: Int): String = with(context) {
|
override fun getStatus(status: Int): String = with(context) {
|
||||||
when (status) {
|
when (status) {
|
||||||
|
@ -43,23 +56,13 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
COMPLETED -> getString(R.string.completed)
|
COMPLETED -> getString(R.string.completed)
|
||||||
ON_HOLD -> getString(R.string.on_hold)
|
ON_HOLD -> getString(R.string.on_hold)
|
||||||
DROPPED -> getString(R.string.dropped)
|
DROPPED -> getString(R.string.dropped)
|
||||||
PLANNING -> getString(R.string.plan_to_read)
|
PLAN_TO_READ -> getString(R.string.plan_to_read)
|
||||||
REPEATING -> getString(R.string.rereading)
|
REREADING -> getString(R.string.rereading)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGlobalStatus(status: Int): String = with(context) {
|
override fun getGlobalStatus(status: Int): String = getStatus(status)
|
||||||
when (status) {
|
|
||||||
READING -> getString(R.string.reading)
|
|
||||||
PLANNING -> getString(R.string.plan_to_read)
|
|
||||||
COMPLETED -> getString(R.string.completed)
|
|
||||||
ON_HOLD -> getString(R.string.on_hold)
|
|
||||||
DROPPED -> getString(R.string.dropped)
|
|
||||||
REPEATING -> getString(R.string.rereading)
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getScoreList(): List<String> {
|
override fun getScoreList(): List<String> {
|
||||||
return IntRange(0, 10).map(Int::toString)
|
return IntRange(0, 10).map(Int::toString)
|
||||||
|
@ -77,12 +80,11 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
override suspend fun add(track: Track): Track {
|
override suspend fun add(track: Track): Track {
|
||||||
track.score = DEFAULT_SCORE.toFloat()
|
track.score = DEFAULT_SCORE.toFloat()
|
||||||
track.status = DEFAULT_STATUS
|
track.status = DEFAULT_STATUS
|
||||||
updateNewTrackInfo(track, PLANNING)
|
updateNewTrackInfo(track, PLAN_TO_READ)
|
||||||
return api.addLibManga(track, getUsername())
|
return api.addLibManga(track, getUsername())
|
||||||
}
|
}
|
||||||
override suspend fun bind(track: Track): Track {
|
override suspend fun bind(track: Track): Track {
|
||||||
val remoteTrack = api.findLibManga(track, getUsername())
|
val remoteTrack = api.findLibManga(track, getUsername())
|
||||||
|
|
||||||
return if (remoteTrack != null) {
|
return if (remoteTrack != null) {
|
||||||
track.copyPersonalFrom(remoteTrack)
|
track.copyPersonalFrom(remoteTrack)
|
||||||
track.library_id = remoteTrack.library_id
|
track.library_id = remoteTrack.library_id
|
||||||
|
@ -94,9 +96,7 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
override fun canRemoveFromService(): Boolean = true
|
override fun canRemoveFromService(): Boolean = true
|
||||||
|
|
||||||
override suspend fun removeFromService(track: Track): Boolean {
|
override suspend fun removeFromService(track: Track) = api.remove(track, getUsername())
|
||||||
return api.remove(track, getUsername())
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun search(query: String) = api.search(query)
|
override suspend fun search(query: String) = api.search(query)
|
||||||
|
|
||||||
|
@ -115,7 +115,6 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
suspend fun login(code: String): Boolean {
|
suspend fun login(code: String): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val oauth = api.accessToken(code)
|
val oauth = api.accessToken(code)
|
||||||
|
|
||||||
interceptor.newAuth(oauth)
|
interceptor.newAuth(oauth)
|
||||||
val user = api.getCurrentUser()
|
val user = api.getCurrentUser()
|
||||||
saveCredentials(user.toString(), oauth.access_token)
|
saveCredentials(user.toString(), oauth.access_token)
|
||||||
|
@ -128,13 +127,12 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToken(oauth: OAuth?) {
|
fun saveToken(oauth: OAuth?) {
|
||||||
val json = gson.toJson(oauth)
|
preferences.trackToken(this).set(json.encodeToString(oauth))
|
||||||
preferences.trackToken(this).set(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreToken(): OAuth? {
|
fun restoreToken(): OAuth? {
|
||||||
return try {
|
return try {
|
||||||
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
|
json.decodeFromString<OAuth>(preferences.trackToken(this).get())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -145,16 +143,4 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
preferences.trackToken(this).delete()
|
preferences.trackToken(this).delete()
|
||||||
interceptor.newAuth(null)
|
interceptor.newAuth(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val READING = 1
|
|
||||||
const val COMPLETED = 2
|
|
||||||
const val ON_HOLD = 3
|
|
||||||
const val DROPPED = 4
|
|
||||||
const val PLANNING = 5
|
|
||||||
const val REPEATING = 6
|
|
||||||
|
|
||||||
const val DEFAULT_STATUS = READING
|
|
||||||
const val DEFAULT_SCORE = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,115 +1,115 @@
|
||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.github.salomonbrys.kotson.array
|
|
||||||
import com.github.salomonbrys.kotson.get
|
|
||||||
import com.github.salomonbrys.kotson.jsonObject
|
|
||||||
import com.github.salomonbrys.kotson.nullString
|
|
||||||
import com.github.salomonbrys.kotson.obj
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import com.google.gson.JsonParser
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.network.DELETE
|
||||||
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 kotlinx.coroutines.Dispatchers
|
import eu.kanade.tachiyomi.network.await
|
||||||
import kotlinx.coroutines.withContext
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import eu.kanade.tachiyomi.util.system.withIOContext
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.contentOrNull
|
||||||
|
import kotlinx.serialization.json.float
|
||||||
|
import kotlinx.serialization.json.int
|
||||||
|
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.FormBody
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
|
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
|
||||||
private val jsonime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun updateLibManga(track: Track, user_id: String): Track = addLibManga(track, user_id)
|
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track, user_id: String): Track {
|
suspend fun addLibManga(track: Track, user_id: String): Track {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
val payload = jsonObject(
|
val payload = buildJsonObject {
|
||||||
"user_rate" to jsonObject(
|
putJsonObject("user_rate") {
|
||||||
"user_id" to user_id,
|
put("user_id", user_id)
|
||||||
"target_id" to track.media_id,
|
put("target_id", track.media_id)
|
||||||
"target_type" to "Manga",
|
put("target_type", "Manga")
|
||||||
"chapters" to track.last_chapter_read.toInt(),
|
put("chapters", track.last_chapter_read.toInt())
|
||||||
"score" to track.score.toInt(),
|
put("score", track.score.toInt())
|
||||||
"status" to track.toShikimoriStatus()
|
put("status", track.toShikimoriStatus())
|
||||||
)
|
}
|
||||||
)
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonime)
|
authClient.newCall(
|
||||||
val request = Request.Builder().url("$apiUrl/v2/user_rates").post(body).build()
|
POST(
|
||||||
authClient.newCall(request).execute()
|
"$apiUrl/v2/user_rates",
|
||||||
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
|
),
|
||||||
|
).await()
|
||||||
track
|
track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
val url =
|
val url = "$apiUrl/mangas".toUri().buildUpon()
|
||||||
"$apiUrl/mangas".toUri().buildUpon().appendQueryParameter("order", "popularity")
|
.appendQueryParameter("order", "popularity")
|
||||||
.appendQueryParameter("search", search).appendQueryParameter("limit", "20")
|
.appendQueryParameter("search", search)
|
||||||
.build()
|
.appendQueryParameter("limit", "20")
|
||||||
val request = Request.Builder().url(url.toString()).get().build()
|
.build()
|
||||||
val netResponse = authClient.newCall(request).execute()
|
authClient.newCall(GET(url.toString()))
|
||||||
|
.await()
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
.parseAs<JsonArray>()
|
||||||
if (responseBody.isEmpty()) {
|
.let { response ->
|
||||||
throw Exception("Null Response")
|
response.map {
|
||||||
}
|
jsonToSearch(it.jsonObject)
|
||||||
val response = JsonParser.parseString(responseBody).array
|
}
|
||||||
|
}
|
||||||
response.map { jsonToSearch(it.obj) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun remove(track: Track, user_id: String): Boolean {
|
suspend fun remove(track: Track, user_id: String): Boolean {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
try {
|
try {
|
||||||
val rates = getUserRates(track, user_id)
|
val rates = getUserRates(track, user_id)
|
||||||
val id = rates.last()["id"]
|
val id = rates.last().jsonObject["id"]!!.jsonPrimitive.content
|
||||||
val url = "$apiUrl/v2/user_rates/$id"
|
val url = "$apiUrl/v2/user_rates/$id"
|
||||||
val request = Request.Builder().url(url).delete().build()
|
authClient.newCall(DELETE(url)).await()
|
||||||
authClient.newCall(request).execute()
|
true
|
||||||
return@withContext true
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
|
false
|
||||||
}
|
}
|
||||||
return@withContext false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
||||||
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
|
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
|
||||||
media_id = obj["id"].asInt
|
media_id = obj["id"]!!.jsonPrimitive.int
|
||||||
title = obj["name"].asString
|
title = obj["name"]!!.jsonPrimitive.content
|
||||||
total_chapters = obj["chapters"].asInt
|
total_chapters = obj["chapters"]!!.jsonPrimitive.int
|
||||||
cover_url = baseUrl + obj["image"].obj["preview"].asString
|
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
||||||
summary = ""
|
summary = ""
|
||||||
tracking_url = baseUrl + obj["url"].asString
|
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
||||||
publishing_status = obj["status"].asString
|
publishing_status = obj["status"]!!.jsonPrimitive.content
|
||||||
publishing_type = obj["kind"].asString
|
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
||||||
start_date = obj.get("aired_on").nullString.orEmpty()
|
start_date = obj["aired_on"]?.jsonPrimitive?.contentOrNull ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
|
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
|
||||||
return Track.create(TrackManager.SHIKIMORI).apply {
|
return Track.create(TrackManager.SHIKIMORI).apply {
|
||||||
title = mangas["name"].asString
|
title = mangas["name"]!!.jsonPrimitive.content
|
||||||
media_id = obj["id"].asInt
|
media_id = obj["id"]!!.jsonPrimitive.int
|
||||||
total_chapters = mangas["chapters"].asInt
|
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
||||||
last_chapter_read = obj["chapters"].asFloat
|
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
||||||
score = (obj["score"].asInt).toFloat()
|
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
||||||
status = toTrackStatus(obj["status"].asString)
|
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
||||||
tracking_url = baseUrl + mangas["url"].asString
|
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,70 +117,70 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
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").build()
|
.appendQueryParameter("target_type", "Manga")
|
||||||
val request = Request.Builder().url(url.toString()).get().build()
|
.build()
|
||||||
|
return authClient.newCall(GET(url.toString()))
|
||||||
val requestResponse = authClient.newCall(request).execute()
|
.execute()
|
||||||
val requestResponseBody = requestResponse.body?.string().orEmpty()
|
.parseAs()
|
||||||
|
|
||||||
if (requestResponseBody.isEmpty()) {
|
|
||||||
throw Exception("Null Response")
|
|
||||||
}
|
|
||||||
return JsonParser.parseString(requestResponseBody).array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun findLibManga(track: Track, user_id: String): Track? {
|
suspend fun findLibManga(track: Track, user_id: String): Track? {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
val urlMangas = "$apiUrl/mangas".toUri().buildUpon().appendPath(track.media_id.toString())
|
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
|
||||||
|
.appendPath(track.media_id.toString())
|
||||||
.build()
|
.build()
|
||||||
val requestMangas = Request.Builder().url(urlMangas.toString()).get().build()
|
val mangas = authClient.newCall(GET(urlMangas.toString()))
|
||||||
|
.await()
|
||||||
val requestMangasResponse = authClient.newCall(requestMangas).execute()
|
.parseAs<JsonObject>()
|
||||||
val requestMangasBody = requestMangasResponse.body?.string().orEmpty()
|
|
||||||
val mangas = JsonParser.parseString(requestMangasBody).obj
|
|
||||||
|
|
||||||
val entry = getUserRates(track, user_id)
|
val entry = getUserRates(track, user_id)
|
||||||
return@withContext entry.map {
|
if (entry.size > 1) {
|
||||||
jsonToTrack(it.obj, mangas)
|
throw Exception("Too much mangas in response")
|
||||||
|
}
|
||||||
|
entry.map {
|
||||||
|
jsonToTrack(it.jsonObject, mangas)
|
||||||
}.firstOrNull()
|
}.firstOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentUser(): Int {
|
suspend fun getCurrentUser(): Int {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body?.string()
|
authClient.newCall(GET("$apiUrl/users/whoami"))
|
||||||
JsonParser.parseString(user).obj["id"].asInt
|
.await()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
.let {
|
||||||
|
it["id"]!!.jsonPrimitive.int
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): OAuth {
|
suspend fun accessToken(code: String): OAuth {
|
||||||
return withContext(Dispatchers.IO) {
|
return withIOContext {
|
||||||
val netResponse = client.newCall(accessTokenRequest(code)).execute()
|
client.newCall(accessTokenRequest(code))
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
.await()
|
||||||
if (responseBody.isEmpty()) {
|
.parseAs()
|
||||||
throw Exception("Null Response")
|
|
||||||
}
|
|
||||||
gson.fromJson(responseBody, OAuth::class.java)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun accessTokenRequest(code: String) = POST(
|
private fun accessTokenRequest(code: String) = POST(
|
||||||
oauthUrl,
|
oauthUrl,
|
||||||
body = FormBody.Builder().add("grant_type", "authorization_code").add("client_id", clientId)
|
body = FormBody.Builder()
|
||||||
.add("client_secret", clientSecret).add("code", code).add("redirect_uri", redirectUrl)
|
.add("grant_type", "authorization_code")
|
||||||
.build()
|
.add("client_id", clientId)
|
||||||
|
.add("client_secret", clientSecret)
|
||||||
|
.add("code", code)
|
||||||
|
.add("redirect_uri", redirectUrl)
|
||||||
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId =
|
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
||||||
"1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
||||||
private const val clientSecret =
|
|
||||||
"229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
|
||||||
|
|
||||||
private const val baseUrl = "https://shikimori.one"
|
private const val baseUrl = "https://shikimori.one"
|
||||||
private const val apiUrl = "https://shikimori.one/api"
|
private const val apiUrl = "$baseUrl/api"
|
||||||
private const val oauthUrl = "https://shikimori.one/oauth/token"
|
private const val oauthUrl = "$baseUrl/oauth/token"
|
||||||
private const val loginUrl = "https://shikimori.one/oauth/authorize"
|
private const val loginUrl = "$baseUrl/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
||||||
private const val baseMangaUrl = "$apiUrl/mangas"
|
private const val baseMangaUrl = "$apiUrl/mangas"
|
||||||
|
@ -189,14 +189,21 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
||||||
return "$baseMangaUrl/$remoteId"
|
return "$baseMangaUrl/$remoteId"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun authUrl() = loginUrl.toUri().buildUpon().appendQueryParameter("client_id", clientId)
|
fun authUrl() =
|
||||||
.appendQueryParameter("redirect_uri", redirectUrl)
|
loginUrl.toUri().buildUpon()
|
||||||
.appendQueryParameter("response_type", "code").build()
|
.appendQueryParameter("client_id", clientId)
|
||||||
|
.appendQueryParameter("redirect_uri", redirectUrl)
|
||||||
|
.appendQueryParameter("response_type", "code")
|
||||||
|
.build()
|
||||||
|
|
||||||
fun refreshTokenRequest(token: String) = POST(
|
fun refreshTokenRequest(token: String) = POST(
|
||||||
oauthUrl,
|
oauthUrl,
|
||||||
body = FormBody.Builder().add("grant_type", "refresh_token").add("client_id", clientId)
|
body = FormBody.Builder()
|
||||||
.add("client_secret", clientSecret).add("refresh_token", token).build()
|
.add("grant_type", "refresh_token")
|
||||||
|
.add("client_id", clientId)
|
||||||
|
.add("client_secret", clientSecret)
|
||||||
|
.add("refresh_token", token)
|
||||||
|
.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package eu.kanade.tachiyomi.data.track.shikimori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Interceptor {
|
class ShikimoriInterceptor(val shikimori: Shikimori) : Interceptor {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth object used for authenticated requests.
|
* OAuth object used for authenticated requests.
|
||||||
|
@ -22,7 +26,7 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept
|
||||||
if (currAuth.isExpired()) {
|
if (currAuth.isExpired()) {
|
||||||
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
|
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
newAuth(gson.fromJson(response.body!!.string(), OAuth::class.java))
|
newAuth(json.decodeFromString<OAuth>(response.body!!.string()))
|
||||||
} else {
|
} else {
|
||||||
response.close()
|
response.close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ fun Track.toShikimoriStatus() = when (status) {
|
||||||
Shikimori.COMPLETED -> "completed"
|
Shikimori.COMPLETED -> "completed"
|
||||||
Shikimori.ON_HOLD -> "on_hold"
|
Shikimori.ON_HOLD -> "on_hold"
|
||||||
Shikimori.DROPPED -> "dropped"
|
Shikimori.DROPPED -> "dropped"
|
||||||
Shikimori.PLANNING -> "planned"
|
Shikimori.PLAN_TO_READ -> "planned"
|
||||||
Shikimori.REPEATING -> "rewatching"
|
Shikimori.REREADING -> "rewatching"
|
||||||
else -> throw NotImplementedError("Unknown status")
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTrackStatus(status: String) = when (status) {
|
fun toTrackStatus(status: String) = when (status) {
|
||||||
|
@ -17,20 +17,7 @@ fun toTrackStatus(status: String) = when (status) {
|
||||||
"completed" -> Shikimori.COMPLETED
|
"completed" -> Shikimori.COMPLETED
|
||||||
"on_hold" -> Shikimori.ON_HOLD
|
"on_hold" -> Shikimori.ON_HOLD
|
||||||
"dropped" -> Shikimori.DROPPED
|
"dropped" -> Shikimori.DROPPED
|
||||||
"planned" -> Shikimori.PLANNING
|
"planned" -> Shikimori.PLAN_TO_READ
|
||||||
"rewatching" -> Shikimori.REPEATING
|
"rewatching" -> Shikimori.REREADING
|
||||||
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
else -> throw Exception("Unknown status")
|
|
||||||
}
|
|
||||||
|
|
||||||
data class OAuth(
|
|
||||||
val access_token: String,
|
|
||||||
val token_type: String,
|
|
||||||
val created_at: Long,
|
|
||||||
val expires_in: Long,
|
|
||||||
val refresh_token: String?
|
|
||||||
) {
|
|
||||||
|
|
||||||
// Access token lives 1 day
|
|
||||||
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
|
||||||
fun GET(
|
fun GET(
|
||||||
url: String,
|
url: String,
|
||||||
headers: Headers = DEFAULT_HEADERS,
|
headers: Headers = DEFAULT_HEADERS,
|
||||||
cache: CacheControl = DEFAULT_CACHE_CONTROL
|
cache: CacheControl = DEFAULT_CACHE_CONTROL,
|
||||||
): Request {
|
): Request {
|
||||||
return Request.Builder()
|
return Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -27,7 +27,7 @@ fun POST(
|
||||||
url: String,
|
url: String,
|
||||||
headers: Headers = DEFAULT_HEADERS,
|
headers: Headers = DEFAULT_HEADERS,
|
||||||
body: RequestBody = DEFAULT_BODY,
|
body: RequestBody = DEFAULT_BODY,
|
||||||
cache: CacheControl = DEFAULT_CACHE_CONTROL
|
cache: CacheControl = DEFAULT_CACHE_CONTROL,
|
||||||
): Request {
|
): Request {
|
||||||
return Request.Builder()
|
return Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
|
@ -36,3 +36,17 @@ fun POST(
|
||||||
.cacheControl(cache)
|
.cacheControl(cache)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun DELETE(
|
||||||
|
url: String,
|
||||||
|
headers: Headers = DEFAULT_HEADERS,
|
||||||
|
body: RequestBody = DEFAULT_BODY,
|
||||||
|
cache: CacheControl = DEFAULT_CACHE_CONTROL,
|
||||||
|
): Request {
|
||||||
|
return Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.delete(body)
|
||||||
|
.headers(headers)
|
||||||
|
.cacheControl(cache)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue