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