Minor tracking refactors (#3900)
* Make tracker Api classes take their tracker's ID Also made the name consistently `trackerId` since `trackId` can be confusing in the context of Tracks that also carry several IDs. Also made the public constants in TrackerManager entirely redundant, so trackers are all equally getting their ID defined in their constructor call now. * Explicitly mark unused Exceptions as such * Make isExpired method of BGMOAuth data class As opposed to an extension defined in the same file * Kavita: thumbnail_url -> thumbnailUrl With a SerialName annotation of course * Suwayomi: Remove redundant with(json) Both of these requests don't use parseAs so this not required. * Bangumi: Don't recreate OAuth object for storing * Remove unused attribute from tracker OAuth classes Mostly `token_type`. Also removed `user_id` from BGMOAuth and `scope` & `expiresIn` from MangaBakaOAuth (which already provides `expiresAt`). * Use kotlin.time durations for token expiry math Something like `Clock.System.now().plus(1.hours)` is much easier to read than `System.getEpochMillis() + 3600`. Also lets us use the `epochSeconds` attribute where the given timestamps are seconds-resolution. * Bangumi: Remove redundant apply block * Kavita: Misc simplifications * Use parseAs in tracker interceptors * MAL: Use existing setAuth to store refresh token
This commit is contained in:
@@ -20,24 +20,17 @@ import kotlinx.coroutines.flow.combine
|
||||
@SingleIn(AppScope::class)
|
||||
class TrackerManager {
|
||||
|
||||
companion object {
|
||||
const val ANILIST = 2L
|
||||
const val KITSU = 3L
|
||||
const val KAVITA = 8L
|
||||
const val MANGABAKA = 11L
|
||||
}
|
||||
|
||||
val myAnimeList = MyAnimeList(1L)
|
||||
val aniList = Anilist(ANILIST)
|
||||
val kitsu = Kitsu(KITSU)
|
||||
val aniList = Anilist(2L)
|
||||
val kitsu = Kitsu(3L)
|
||||
val shikimori = Shikimori(4L)
|
||||
val bangumi = Bangumi(5L)
|
||||
val komga = Komga(6L)
|
||||
val mangaUpdates = MangaUpdates(7L)
|
||||
val kavita = Kavita(KAVITA)
|
||||
val kavita = Kavita(8L)
|
||||
val suwayomi = Suwayomi(9L)
|
||||
val hikka = Hikka(10L)
|
||||
val mangaBaka = MangaBaka(MANGABAKA)
|
||||
val mangaBaka = MangaBaka(11L)
|
||||
|
||||
val trackers = listOf(
|
||||
myAnimeList,
|
||||
|
||||
@@ -35,7 +35,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
private val interceptor by lazy { AnilistInterceptor(this, getPassword()) }
|
||||
|
||||
private val api by lazy { AnilistApi(client, interceptor) }
|
||||
private val api by lazy { AnilistApi(id, client, interceptor) }
|
||||
|
||||
override val supportsReadingDates: Boolean = true
|
||||
|
||||
@@ -47,7 +47,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
// If the preference is an int from APIv1, logout user to force using APIv2
|
||||
try {
|
||||
scorePreference.get()
|
||||
} catch (e: ClassCastException) {
|
||||
} catch (_: ClassCastException) {
|
||||
logout()
|
||||
scorePreference.delete()
|
||||
}
|
||||
@@ -224,7 +224,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
scorePreference.set(currentUser.mediaListOptions.scoreFormat)
|
||||
saveDisplayUsername(currentUser.name)
|
||||
saveCredentials(currentUser.id.toString(), oauth.accessToken)
|
||||
} catch (e: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
fun loadOAuth(): ALOAuth? {
|
||||
return try {
|
||||
json.decodeFromString<ALOAuth>(trackPreferences.trackToken(this).get())
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,11 @@ import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Instant
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
class AnilistApi(
|
||||
val trackerId: Long,
|
||||
val client: OkHttpClient,
|
||||
interceptor: AnilistInterceptor,
|
||||
) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
@@ -192,7 +196,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
.awaitSuccess()
|
||||
.parseAs<ALSearchResult>()
|
||||
.data.page.media
|
||||
.map { it.toALManga().toTrack() }
|
||||
.map { it.toALManga().toTrack(trackerId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +277,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
.data.page.mediaList
|
||||
.map { it.toALUserManga() }
|
||||
.firstOrNull()
|
||||
?.toTrack()
|
||||
?.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,7 +379,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
.data.page.media
|
||||
.firstOrNull()
|
||||
?.toALManga()
|
||||
?.toTrack()
|
||||
?.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.anilist.dto
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
@@ -21,7 +20,7 @@ data class ALManga(
|
||||
val averageScore: Int,
|
||||
val staff: ALStaff,
|
||||
) {
|
||||
fun toTrack() = TrackSearch.create(TrackerManager.ANILIST).apply {
|
||||
fun toTrack(trackerId: Long) = TrackSearch.create(trackerId).apply {
|
||||
remote_id = remoteId
|
||||
title = this@ALManga.title
|
||||
total_chapters = totalChapters
|
||||
@@ -57,7 +56,7 @@ data class ALUserManga(
|
||||
val manga: ALManga,
|
||||
val private: Boolean,
|
||||
) {
|
||||
fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
|
||||
fun toTrack(trackerId: Long) = Track.create(trackerId).apply {
|
||||
remote_id = manga.remoteId
|
||||
title = manga.title
|
||||
status = toTrackStatus()
|
||||
|
||||
@@ -30,7 +30,7 @@ import tachiyomi.core.common.util.lang.withIOContext
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class BangumiApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: BangumiInterceptor,
|
||||
) {
|
||||
@@ -106,7 +106,7 @@ class BangumiApi(
|
||||
.parseAs<BGMSearchResult>()
|
||||
.data
|
||||
.filter { it.platform == null || it.platform == "漫画" }
|
||||
.map { it.toTrackSearch(trackId) }
|
||||
.map { it.toTrackSearch(trackerId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class BangumiApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<BGMSubject>()
|
||||
.takeIf { it.platform == null || it.platform == "漫画" }
|
||||
?.toTrackSearch(trackId)
|
||||
?.toTrackSearch(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.bangumi
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.BGMOAuth
|
||||
import eu.kanade.tachiyomi.data.track.bangumi.dto.isExpired
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -25,7 +25,9 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
||||
if (currAuth.isExpired()) {
|
||||
val response = chain.proceed(BangumiApi.refreshTokenRequest(currAuth.refreshToken!!))
|
||||
if (response.isSuccessful) {
|
||||
currAuth = json.decodeFromString<BGMOAuth>(response.body.string())
|
||||
currAuth = with(json) {
|
||||
response.parseAs<BGMOAuth>()
|
||||
}
|
||||
newAuth(currAuth)
|
||||
} else {
|
||||
response.close()
|
||||
@@ -37,27 +39,13 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
||||
"User-Agent",
|
||||
"antsylich/Mihon/v${BuildConfig.VERSION_NAME} (Android) (http://github.com/mihonapp/mihon)",
|
||||
)
|
||||
.apply {
|
||||
addHeader("Authorization", "Bearer ${currAuth.accessToken}")
|
||||
}
|
||||
.addHeader("Authorization", "Bearer ${currAuth.accessToken}")
|
||||
.build()
|
||||
.let(chain::proceed)
|
||||
}
|
||||
|
||||
fun newAuth(oauth: BGMOAuth?) {
|
||||
this.oauth = if (oauth == null) {
|
||||
null
|
||||
} else {
|
||||
BGMOAuth(
|
||||
oauth.accessToken,
|
||||
oauth.tokenType,
|
||||
System.currentTimeMillis() / 1000,
|
||||
oauth.expiresIn,
|
||||
oauth.refreshToken,
|
||||
this.oauth?.userId,
|
||||
)
|
||||
}
|
||||
|
||||
this.oauth = oauth
|
||||
bangumi.saveToken(oauth)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,22 @@ package eu.kanade.tachiyomi.data.track.bangumi.dto
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@Serializable
|
||||
// Incomplete DTO with only our needed attributes
|
||||
data class BGMOAuth(
|
||||
@SerialName("access_token")
|
||||
val accessToken: String,
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
@SerialName("created_at")
|
||||
@EncodeDefault
|
||||
val createdAt: Long = System.currentTimeMillis() / 1000,
|
||||
val createdAt: Long = Clock.System.now().epochSeconds,
|
||||
@SerialName("expires_in")
|
||||
val expiresIn: Long,
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String?,
|
||||
@SerialName("user_id")
|
||||
val userId: Long?,
|
||||
)
|
||||
|
||||
) {
|
||||
// Access token refresh before expired
|
||||
fun BGMOAuth.isExpired() = (System.currentTimeMillis() / 1000) > (createdAt + expiresIn - 3600)
|
||||
fun isExpired() = Clock.System.now().plus(1.hours).epochSeconds > (createdAt + expiresIn)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ data class BGMSubject(
|
||||
val rating: BGMSubjectRating?,
|
||||
val platform: String?,
|
||||
) {
|
||||
fun toTrackSearch(trackId: Long): TrackSearch = TrackSearch.create(trackId).apply {
|
||||
fun toTrackSearch(trackerId: Long): TrackSearch = TrackSearch.create(trackerId).apply {
|
||||
remote_id = this@BGMSubject.id
|
||||
title = nameCn.ifBlank { name }
|
||||
cover_url = images?.common.orEmpty()
|
||||
|
||||
@@ -32,7 +32,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class HikkaApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: HikkaInterceptor,
|
||||
) {
|
||||
@@ -95,7 +95,7 @@ class HikkaApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<HKMangaPagination>()
|
||||
.list
|
||||
.map { it.toTrack(trackId) }
|
||||
.map { it.toTrack(trackerId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class HikkaApi(
|
||||
} else {
|
||||
response
|
||||
.parseAs<HKManga>()
|
||||
.toTrack(trackId)
|
||||
.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ class HikkaApi(
|
||||
authClient.newCall(GET(url.toString()))
|
||||
.awaitSuccess()
|
||||
.parseAs<HKManga>()
|
||||
.toTrack(trackId)
|
||||
.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ class HikkaApi(
|
||||
authClient.newCall(PUT(url.toString(), body = payload.toString().toRequestBody(jsonMime)))
|
||||
.awaitSuccess()
|
||||
.parseAs<HKRead>()
|
||||
.toTrack(trackId)
|
||||
.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.track.hikka
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.hikka.dto.HKAuthTokenInfo
|
||||
import eu.kanade.tachiyomi.data.track.hikka.dto.HKOAuth
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -32,7 +33,9 @@ class HikkaInterceptor(private val hikka: Hikka) : Interceptor {
|
||||
throw Exception("Hikka: Auth token info failed")
|
||||
}
|
||||
|
||||
val authTokenInfo = json.decodeFromString<HKAuthTokenInfo>(authTokenInfoResponse.body.string())
|
||||
val authTokenInfo = with(json) {
|
||||
authTokenInfoResponse.parseAs<HKAuthTokenInfo>()
|
||||
}
|
||||
setAuth(HKOAuth(currAuth.accessToken, authTokenInfo.expiration, authTokenInfo.created))
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ data class HKManga(
|
||||
val startDate: Long? = null,
|
||||
val read: List<HKRead>? = emptyList(),
|
||||
) {
|
||||
fun toTrack(trackId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackId).apply {
|
||||
fun toTrack(trackerId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackerId).apply {
|
||||
remote_id = stringToNumber(this@HKManga.slug)
|
||||
title = this@HKManga.titleUa ?: this@HKManga.titleEn ?: this@HKManga.titleOriginal
|
||||
total_chapters = this@HKManga.chapters?.toLong() ?: 0
|
||||
|
||||
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.track.hikka.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@Serializable
|
||||
data class HKOAuth(
|
||||
@@ -10,9 +12,5 @@ data class HKOAuth(
|
||||
val expiration: Long,
|
||||
val created: Long,
|
||||
) {
|
||||
fun isExpired(): Boolean {
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
val buffer = 5 * 60 // safety margin
|
||||
return currentTime >= (expiration - buffer)
|
||||
}
|
||||
fun isExpired() = Clock.System.now().plus(5.minutes).epochSeconds >= expiration
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ data class HKRead(
|
||||
val endDate: Long? = null,
|
||||
val content: HKManga? = null,
|
||||
) {
|
||||
fun toTrack(trackId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackId).apply {
|
||||
fun toTrack(trackerId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackerId).apply {
|
||||
val mangaContent = this@HKRead.content
|
||||
if (mangaContent != null) {
|
||||
title = mangaContent.titleUa ?: mangaContent.titleEn ?: mangaContent.titleOriginal
|
||||
|
||||
@@ -27,7 +27,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
var authentications: OAuth? = null
|
||||
|
||||
private val interceptor by lazy { KavitaInterceptor(this) }
|
||||
val api by lazy { KavitaApi(client, interceptor) }
|
||||
val api by lazy { KavitaApi(id, client, interceptor) }
|
||||
|
||||
private val sourceManager: SourceManager by lazy { appGraph.sourceManager }
|
||||
|
||||
@@ -95,7 +95,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
override suspend fun match(manga: Manga): TrackSearch? =
|
||||
try {
|
||||
api.getTrackSearch(manga.url)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ 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.awaitSuccess
|
||||
import eu.kanade.tachiyomi.network.jsonMime
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Dns
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
@@ -18,7 +18,11 @@ import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
|
||||
class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
|
||||
class KavitaApi(
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: KavitaInterceptor,
|
||||
) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
@@ -40,7 +44,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
fun getNewToken(apiUrl: String, apiKey: String): String? {
|
||||
val request = POST(
|
||||
"$apiUrl/Plugin/authenticate?apiKey=$apiKey&pluginName=Tachiyomi-Kavita",
|
||||
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
|
||||
body = EMPTY_JSON_BODY,
|
||||
)
|
||||
try {
|
||||
with(json) {
|
||||
@@ -54,9 +58,9 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
throw IOException("Unauthorized / api key not valid")
|
||||
}
|
||||
500 -> {
|
||||
logcat(
|
||||
LogPriority.WARN,
|
||||
) { "Error fetching JWT token. API URL: $apiUrl, empty API key: ${apiKey.isEmpty()}" }
|
||||
logcat(LogPriority.WARN) {
|
||||
"Error fetching JWT token. API URL: $apiUrl, empty API key: ${apiKey.isEmpty()}"
|
||||
}
|
||||
throw IOException("Error fetching JWT token")
|
||||
}
|
||||
else -> {}
|
||||
@@ -64,7 +68,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
}
|
||||
}
|
||||
// Not sure which one to catch
|
||||
} catch (e: SocketTimeoutException) {
|
||||
} catch (_: SocketTimeoutException) {
|
||||
logcat(LogPriority.WARN) {
|
||||
"Could not fetch JWT token. Probably due to connectivity issue or URL '$apiUrl' not available, skipping"
|
||||
}
|
||||
@@ -133,10 +137,9 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(
|
||||
LogPriority.WARN,
|
||||
e,
|
||||
) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
|
||||
logcat(LogPriority.WARN, e) {
|
||||
"Exception getting latest chapter read. Could not get itemRequest: $requestUrl"
|
||||
}
|
||||
throw e
|
||||
}
|
||||
return 0.0
|
||||
@@ -150,9 +153,9 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
.parseAs()
|
||||
}
|
||||
|
||||
val track = seriesDto.toTrack()
|
||||
val track = seriesDto.toTrack(trackerId)
|
||||
track.apply {
|
||||
cover_url = seriesDto.thumbnail_url.toString()
|
||||
cover_url = seriesDto.thumbnailUrl.toString()
|
||||
tracking_url = url
|
||||
total_chapters = getTotalChapters(url)
|
||||
|
||||
@@ -177,9 +180,13 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
track.tracking_url,
|
||||
)}&chapterNumber=${track.last_chapter_read}"
|
||||
authClient.newCall(
|
||||
POST(requestUrl, body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())),
|
||||
POST(requestUrl, body = EMPTY_JSON_BODY),
|
||||
)
|
||||
.awaitSuccess()
|
||||
return getTrackSearch(track.tracking_url)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EMPTY_JSON_BODY = "{}".toRequestBody(jsonMime)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.track.kavita
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@@ -9,7 +9,8 @@ data class SeriesDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val originalName: String = "",
|
||||
val thumbnail_url: String? = "",
|
||||
@SerialName("thumbnailUrl")
|
||||
val thumbnailUrl: String? = "",
|
||||
val localizedName: String? = "",
|
||||
val sortName: String? = "",
|
||||
val pages: Int,
|
||||
@@ -22,7 +23,7 @@ data class SeriesDto(
|
||||
val libraryId: Int,
|
||||
val libraryName: String? = "",
|
||||
) {
|
||||
fun toTrack(): TrackSearch = TrackSearch.create(TrackerManager.KAVITA).also {
|
||||
fun toTrack(trackerId: Long): TrackSearch = TrackSearch.create(trackerId).also {
|
||||
it.title = name
|
||||
it.summary = ""
|
||||
}
|
||||
@@ -69,14 +70,7 @@ class OAuth(
|
||||
SourceAuth(3),
|
||||
),
|
||||
) {
|
||||
fun getToken(apiUrl: String): String? {
|
||||
for (authentication in authentications) {
|
||||
if (authentication.apiUrl == apiUrl) {
|
||||
return authentication.jwtToken
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun getToken(apiUrl: String): String? = authentications.find { it.apiUrl == apiUrl }?.jwtToken
|
||||
}
|
||||
|
||||
data class SourceAuth(
|
||||
|
||||
@@ -33,7 +33,7 @@ import kotlin.time.Instant
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class KitsuApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: KitsuInterceptor,
|
||||
) {
|
||||
@@ -292,7 +292,7 @@ class KitsuApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<KitsuSearchByTitleResult>()
|
||||
.data.searchMangaByTitle.nodes
|
||||
.map { it.toTrackSearch(trackId) }
|
||||
.map { it.toTrackSearch(trackerId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +334,7 @@ class KitsuApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<KitsuSearchByIdWithLibraryResult>()
|
||||
.data.findMangaById
|
||||
?.toTrackSearch(trackId)
|
||||
?.toTrackSearch(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,7 +436,7 @@ class KitsuApi(
|
||||
.data.findMangaBySlug
|
||||
}
|
||||
|
||||
kitsuManga?.toTrackSearch(trackId)
|
||||
kitsuManga?.toTrackSearch(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.track.kitsu
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -19,7 +20,7 @@ class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val currAuth = oauth ?: throw Exception("Not authenticated with Kitsu")
|
||||
var currAuth = oauth ?: throw Exception("Not authenticated with Kitsu")
|
||||
|
||||
val refreshToken = currAuth.refreshToken!!
|
||||
|
||||
@@ -27,7 +28,10 @@ class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor {
|
||||
if (currAuth.isExpired()) {
|
||||
val response = chain.proceed(KitsuApi.refreshTokenRequest(refreshToken))
|
||||
if (response.isSuccessful) {
|
||||
newAuth(json.decodeFromString(response.body.string()))
|
||||
currAuth = with(json) {
|
||||
response.parseAs<KitsuOAuth>()
|
||||
}
|
||||
newAuth(currAuth)
|
||||
} else {
|
||||
response.close()
|
||||
}
|
||||
@@ -35,7 +39,7 @@ class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor {
|
||||
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.accessToken}")
|
||||
.addHeader("Authorization", "Bearer ${currAuth.accessToken}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
|
||||
@@ -3,13 +3,12 @@ package eu.kanade.tachiyomi.data.track.kitsu.dto
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@Serializable
|
||||
data class KitsuOAuth(
|
||||
@SerialName("access_token")
|
||||
val accessToken: String,
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
@SerialName("created_at")
|
||||
val createdAt: Long,
|
||||
@SerialName("expires_in")
|
||||
@@ -17,5 +16,5 @@ data class KitsuOAuth(
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String?,
|
||||
) {
|
||||
fun isExpired(): Boolean = (Clock.System.now().toEpochMilliseconds() / 1000) > (createdAt + expiresIn - 3600)
|
||||
fun isExpired() = Clock.System.now().plus(1.hours).epochSeconds > (createdAt + expiresIn)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||
override suspend fun match(manga: Manga): TrackSearch? =
|
||||
try {
|
||||
api.getTrackSearch(manga.url)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
private const val READLIST_API = "/api/v1/readlists"
|
||||
|
||||
class KomgaApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
) {
|
||||
|
||||
@@ -97,13 +97,13 @@ class KomgaApi(
|
||||
return getTrackSearch(track.tracking_url)
|
||||
}
|
||||
|
||||
private fun SeriesDto.toTrack(): TrackSearch = TrackSearch.create(trackId).also {
|
||||
private fun SeriesDto.toTrack(): TrackSearch = TrackSearch.create(trackerId).also {
|
||||
it.title = metadata.title
|
||||
it.summary = metadata.summary
|
||||
it.publishing_status = metadata.status
|
||||
}
|
||||
|
||||
private fun ReadListDto.toTrack(): TrackSearch = TrackSearch.create(trackId).also {
|
||||
private fun ReadListDto.toTrack(): TrackSearch = TrackSearch.create(trackerId).also {
|
||||
it.title = name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.dto.MangaBakaItem
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.dto.MangaBakaItemResult
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.dto.MangaBakaListResult
|
||||
@@ -41,7 +40,7 @@ import java.util.Locale
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class MangaBakaApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
baseClient: OkHttpClient,
|
||||
interceptor: MangaBakaInterceptor,
|
||||
) {
|
||||
@@ -120,7 +119,7 @@ class MangaBakaApi(
|
||||
.parseAs<MangaBakaItemResult>()
|
||||
.data
|
||||
|
||||
Track.create(TrackerManager.MANGABAKA).apply {
|
||||
Track.create(trackerId).apply {
|
||||
remote_id = track.remote_id
|
||||
title = additionalData.chooseBestTitle()
|
||||
status = userData.getStatus()
|
||||
@@ -196,7 +195,7 @@ class MangaBakaApi(
|
||||
}
|
||||
|
||||
private fun parseSearchItem(item: MangaBakaItem): TrackSearch {
|
||||
return TrackSearch.create(trackId).apply {
|
||||
return TrackSearch.create(trackerId).apply {
|
||||
remote_id = item.id
|
||||
title = item.chooseBestTitle()
|
||||
summary = item.description?.trim().orEmpty()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.track.mangabaka
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.mangabaka.dto.MangaBakaOAuth
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -20,7 +21,9 @@ class MangaBakaInterceptor(private val mangaBaka: MangaBaka) : Interceptor {
|
||||
if (currentAuth.isExpired()) {
|
||||
val response = chain.proceed(MangaBakaApi.refreshTokenRequest(currentAuth.refreshToken))
|
||||
if (response.isSuccessful) {
|
||||
currentAuth = json.decodeFromString(response.body.string())
|
||||
currentAuth = with(json) {
|
||||
response.parseAs<MangaBakaOAuth>()
|
||||
}
|
||||
setAuth(currentAuth)
|
||||
} else {
|
||||
response.close()
|
||||
|
||||
@@ -11,13 +11,8 @@ data class MangaBakaOAuth(
|
||||
val accessToken: String,
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String,
|
||||
@SerialName("expires_in")
|
||||
val expiresIn: Long,
|
||||
@SerialName("expires_at")
|
||||
val expiresAt: Long,
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
val scope: String,
|
||||
) {
|
||||
fun isExpired(): Boolean = Clock.System.now().plus(1.minutes).epochSeconds > expiresAt
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
|
||||
private val interceptor by lazy { MangaUpdatesInterceptor(this) }
|
||||
|
||||
private val api by lazy { MangaUpdatesApi(interceptor, client) }
|
||||
private val api by lazy { MangaUpdatesApi(client, interceptor) }
|
||||
|
||||
override fun getLogo(): Int = R.drawable.brand_mangaupdates
|
||||
|
||||
@@ -83,7 +83,7 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
return try {
|
||||
val (series, rating) = api.getSeriesListItem(track)
|
||||
track.copyFrom(series, rating)
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
track.score = 0.0
|
||||
api.addSeriesToList(track, hasReadChapters)
|
||||
track
|
||||
|
||||
@@ -32,8 +32,8 @@ import uy.kohesive.injekt.injectLazy
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class MangaUpdatesApi(
|
||||
interceptor: MangaUpdatesInterceptor,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: MangaUpdatesInterceptor,
|
||||
) {
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
@@ -123,7 +123,7 @@ class MangaUpdatesApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<MURating>()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
val username = api.getCurrentUser()
|
||||
saveDisplayUsername(username)
|
||||
saveCredentials(username, oauth.accessToken)
|
||||
} catch (e: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
fun loadOAuth(): MALOAuth? {
|
||||
return try {
|
||||
json.decodeFromString<MALOAuth>(trackPreferences.trackToken(this).get())
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.Locale
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class MyAnimeListApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: MyAnimeListInterceptor,
|
||||
) {
|
||||
@@ -223,7 +223,7 @@ class MyAnimeListApi(
|
||||
}
|
||||
|
||||
private fun parseSearchItem(searchItem: MALManga): TrackSearch {
|
||||
return TrackSearch.create(trackId).apply {
|
||||
return TrackSearch.create(trackerId).apply {
|
||||
remote_id = searchItem.id
|
||||
title = searchItem.title
|
||||
summary = searchItem.synopsis
|
||||
|
||||
+1
-4
@@ -72,10 +72,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
|
||||
}
|
||||
}
|
||||
.getOrNull()
|
||||
?.also {
|
||||
this.oauth = it
|
||||
myanimelist.saveOAuth(it)
|
||||
}
|
||||
?.also { setAuth(it) }
|
||||
?: throw MALTokenRefreshFailed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.data.track.myanimelist.dto
|
||||
import kotlinx.serialization.EncodeDefault
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@Serializable
|
||||
data class MALOAuth(
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String,
|
||||
@SerialName("access_token")
|
||||
@@ -16,10 +16,8 @@ data class MALOAuth(
|
||||
val expiresIn: Long,
|
||||
@SerialName("created_at")
|
||||
@EncodeDefault
|
||||
val createdAt: Long = System.currentTimeMillis() / 1000,
|
||||
val createdAt: Long = Clock.System.now().epochSeconds,
|
||||
) {
|
||||
// Assumes expired a minute earlier
|
||||
private val adjustedExpiresIn: Long = (expiresIn - 60)
|
||||
|
||||
fun isExpired() = createdAt + adjustedExpiresIn < System.currentTimeMillis() / 1000
|
||||
fun isExpired() = Clock.System.now().plus(1.minutes).epochSeconds <= createdAt + expiresIn
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
val user = api.getCurrentUser()
|
||||
saveDisplayUsername(user.nickname)
|
||||
saveCredentials(user.id, oauth.accessToken)
|
||||
} catch (e: Throwable) {
|
||||
} catch (_: Throwable) {
|
||||
logout()
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
fun restoreToken(): SMOAuth? {
|
||||
return try {
|
||||
json.decodeFromString<SMOAuth>(trackPreferences.trackToken(this).get())
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class ShikimoriApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val client: OkHttpClient,
|
||||
interceptor: ShikimoriInterceptor,
|
||||
) {
|
||||
@@ -145,7 +145,7 @@ class ShikimoriApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<SMSearchResult>()
|
||||
.data.mangas
|
||||
.map { it.toTrack(trackId) }
|
||||
.map { it.toTrack(trackerId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class ShikimoriApi(
|
||||
.parseAs<SMSearchResult>()
|
||||
.data.mangas
|
||||
.firstOrNull()
|
||||
?.toTrack(trackId)
|
||||
?.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ class ShikimoriApi(
|
||||
if (listResult?.userRate == null) {
|
||||
null
|
||||
} else {
|
||||
listResult.toTrack(trackId)
|
||||
listResult.toTrack(trackerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.track.shikimori
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
|
||||
import eu.kanade.tachiyomi.data.track.shikimori.dto.isExpired
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@@ -20,15 +20,16 @@ class ShikimoriInterceptor(private val shikimori: Shikimori) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
val currAuth = oauth ?: throw Exception("Not authenticated with Shikimori")
|
||||
|
||||
val refreshToken = currAuth.refreshToken!!
|
||||
var currAuth = oauth ?: throw Exception("Not authenticated with Shikimori")
|
||||
|
||||
// Refresh access token if expired.
|
||||
if (currAuth.isExpired()) {
|
||||
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
|
||||
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(currAuth.refreshToken!!))
|
||||
if (response.isSuccessful) {
|
||||
newAuth(json.decodeFromString<SMOAuth>(response.body.string()))
|
||||
currAuth = with(json) {
|
||||
response.parseAs<SMOAuth>()
|
||||
}
|
||||
newAuth(currAuth)
|
||||
} else {
|
||||
response.close()
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ data class SMManga(
|
||||
val kind: String?,
|
||||
val personRoles: List<SMPersonRole>?,
|
||||
) {
|
||||
fun toTrack(trackId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackId).apply {
|
||||
fun toTrack(trackerId: Long): TrackSearch {
|
||||
return TrackSearch.create(trackerId).apply {
|
||||
remote_id = this@SMManga.id
|
||||
title = name
|
||||
total_chapters = chapters
|
||||
|
||||
@@ -2,20 +2,19 @@ package eu.kanade.tachiyomi.data.track.shikimori.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@Serializable
|
||||
data class SMOAuth(
|
||||
@SerialName("access_token")
|
||||
val accessToken: String,
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
@SerialName("created_at")
|
||||
val createdAt: Long,
|
||||
@SerialName("expires_in")
|
||||
val expiresIn: Long,
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String?,
|
||||
)
|
||||
|
||||
// Access token lives 1 day
|
||||
fun SMOAuth.isExpired() = (System.currentTimeMillis() / 1000) > (createdAt + expiresIn - 3600)
|
||||
) {
|
||||
fun isExpired() = Clock.System.now().plus(1.hours).epochSeconds > (createdAt + expiresIn)
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ data class SMUserListManga(
|
||||
val totalChapters: Long, // the title's total chapters
|
||||
val userRate: SMUserRate?,
|
||||
) {
|
||||
fun toTrack(trackId: Long): Track {
|
||||
return Track.create(trackId).apply {
|
||||
fun toTrack(trackerId: Long): Track {
|
||||
return Track.create(trackerId).apply {
|
||||
title = name
|
||||
total_chapters = totalChapters
|
||||
tracking_url = url
|
||||
|
||||
@@ -87,7 +87,7 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
|
||||
override suspend fun match(manga: DomainManga): TrackSearch? =
|
||||
try {
|
||||
api.getTrackSearch(manga.url.getMangaId())
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import uy.kohesive.injekt.injectLazy
|
||||
import java.security.MessageDigest
|
||||
|
||||
class SuwayomiApi(
|
||||
private val trackId: Long,
|
||||
private val trackerId: Long,
|
||||
private val sourceManager: SourceManager,
|
||||
) {
|
||||
|
||||
@@ -71,7 +71,7 @@ class SuwayomiApi(
|
||||
.entry
|
||||
}
|
||||
|
||||
TrackSearch.create(trackId).apply {
|
||||
TrackSearch.create(trackerId).apply {
|
||||
remote_id = mangaId
|
||||
title = manga.title
|
||||
cover_url = "$baseUrl/${manga.thumbnailUrl}"
|
||||
@@ -152,7 +152,6 @@ class SuwayomiApi(
|
||||
}
|
||||
}
|
||||
}
|
||||
with(json) {
|
||||
client.newCall(
|
||||
POST(
|
||||
apiUrl,
|
||||
@@ -160,7 +159,6 @@ class SuwayomiApi(
|
||||
),
|
||||
)
|
||||
.awaitSuccess()
|
||||
}
|
||||
|
||||
val trackQuery = $$"""
|
||||
|mutation TrackManga($mangaId: Int!) {
|
||||
@@ -175,7 +173,6 @@ class SuwayomiApi(
|
||||
put("mangaId", mangaId)
|
||||
}
|
||||
}
|
||||
with(json) {
|
||||
client.newCall(
|
||||
POST(
|
||||
apiUrl,
|
||||
@@ -183,7 +180,6 @@ class SuwayomiApi(
|
||||
),
|
||||
)
|
||||
.awaitSuccess()
|
||||
}
|
||||
|
||||
return getTrackSearch(track.remote_id)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user