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)
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
@@ -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)
}