From 9f60bb8f3eca8237454d7702f57056ebfd591aa1 Mon Sep 17 00:00:00 2001 From: Constantin Piber <59023762+cpiber@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:54:49 +0100 Subject: [PATCH] Fix Kitsu to use library_id properly (#1843) Same as mihonapp/mihon#2609 --- .../manga/impl/track/tracker/kitsu/Kitsu.kt | 1 + .../impl/track/tracker/kitsu/KitsuApi.kt | 10 +-- .../tracker/kitsu/dto/KitsuListSearch.kt | 3 +- .../migration/M0053_TrackersFixIds.kt | 65 +++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0053_TrackersFixIds.kt diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/Kitsu.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/Kitsu.kt index 8111bf9d..327510f6 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/Kitsu.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/Kitsu.kt @@ -100,6 +100,7 @@ class Kitsu( return if (remoteTrack != null) { track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false) track.remote_id = remoteTrack.remote_id + track.library_id = remoteTrack.library_id if (track.status != COMPLETED) { track.status = if (hasReadChapters) READING else track.status diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/KitsuApi.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/KitsuApi.kt index 40345a1e..bc5a269c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/KitsuApi.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/KitsuApi.kt @@ -80,7 +80,7 @@ class KitsuApi( ).awaitSuccess() .parseAs() .let { - track.remote_id = it.data.id + track.library_id = it.data.id track } } @@ -92,7 +92,7 @@ class KitsuApi( buildJsonObject { putJsonObject("data") { put("type", "libraryEntries") - put("id", track.remote_id) + put("id", track.library_id) putJsonObject("attributes") { put("status", track.toApiStatus()) put("progress", track.last_chapter_read.toInt()) @@ -108,7 +108,7 @@ class KitsuApi( .newCall( Request .Builder() - .url("${BASE_URL}library-entries/${track.remote_id}") + .url("${BASE_URL}library-entries/${track.library_id}") .headers( headersOf("Content-Type", VND_API_JSON), ).patch(data.toString().toRequestBody(VND_JSON_MEDIA_TYPE)) @@ -123,7 +123,7 @@ class KitsuApi( authClient .newCall( DELETE( - "${BASE_URL}library-entries/${track.remote_id}", + "${BASE_URL}library-entries/${track.library_id}", headers = headersOf("Content-Type", VND_API_JSON), ), ).awaitSuccess() @@ -208,7 +208,7 @@ class KitsuApi( "${BASE_URL}library-entries" .toUri() .buildUpon() - .encodedQuery("filter[id]=${track.remote_id}") + .encodedQuery("filter[id]=${track.library_id}") .appendQueryParameter("include", "manga") .build() with(json) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/dto/KitsuListSearch.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/dto/KitsuListSearch.kt index 5c481acb..0ff1811a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/dto/KitsuListSearch.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/track/tracker/kitsu/dto/KitsuListSearch.kt @@ -21,7 +21,8 @@ data class KitsuListSearchResult( val manga = included[0].attributes return TrackSearch.create(TrackerManager.KITSU).apply { - remote_id = userData.id + remote_id = included[0].id + library_id = userData.id title = manga.canonicalTitle total_chapters = manga.chapterCount ?: 0 cover_url = manga.posterImage?.original ?: "" diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0053_TrackersFixIds.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0053_TrackersFixIds.kt new file mode 100644 index 00000000..8ad83881 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0053_TrackersFixIds.kt @@ -0,0 +1,65 @@ +@file:Suppress("ktlint:standard:property-naming") + +package suwayomi.tachidesk.server.database.migration + +import de.neonew.exposed.migrations.helpers.SQLMigration +import suwayomi.tachidesk.graphql.types.DatabaseType +import suwayomi.tachidesk.server.database.migration.helpers.MAYBE_TYPE_PREFIX +import suwayomi.tachidesk.server.database.migration.helpers.UNLIMITED_TEXT +import suwayomi.tachidesk.server.database.migration.helpers.toSqlName +import suwayomi.tachidesk.server.serverConfig + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +@Suppress("ClassName", "unused") +class M0053_TrackersFixIds : SQLMigration() { + private val TrackRecordTable by lazy { "TrackRecord".toSqlName() } + private val SyncIdColumn by lazy { "sync_id".toSqlName() } + private val LibraryIdColumn by lazy { "library_id".toSqlName() } + private val RemoteIdColumn by lazy { "remote_id".toSqlName() } + private val RemoteUrlColumn by lazy { "remote_url".toSqlName() } + + override val sql by lazy { + """ + -- Save the current remote_id as library_id, since old Kitsu tracker did not use this correctly + UPDATE $TrackRecordTable SET $LibraryIdColumn = $RemoteIdColumn WHERE $SyncIdColumn = 3; + + -- Kitsu isn't using the remote_id field properly, but the ID is present in the URL + -- This parses a url and gets the ID from the trailing path part, e.g. https://kitsu.app/manga/ + UPDATE $TrackRecordTable SET $RemoteIdColumn = ${toNumber(rightMost(RemoteUrlColumn, '/'))} WHERE $SyncIdColumn = 3; + """.trimIndent() + } + + fun h2RightMost( + field: String, + sep: Char, + ): String = "SUBSTRING($field, LOCATE('$sep', $field, -1) + 1)" + + fun postgresRightMost( + field: String, + sep: Char, + ): String = "SUBSTRING(SUBSTRING($field FROM '$sep[^$sep]*$') FROM 2)" + + fun h2ToNumber(expr: String): String = expr + + fun postgresToNumber(expr: String): String = "TO_NUMBER($expr, '0000000000')" + + fun rightMost( + field: String, + sep: Char, + ) = when (serverConfig.databaseType.value) { + DatabaseType.H2 -> h2RightMost(field, sep) + DatabaseType.POSTGRESQL -> postgresRightMost(field, sep) + } + + fun toNumber(expr: String) = + when (serverConfig.databaseType.value) { + DatabaseType.H2 -> h2ToNumber(expr) + DatabaseType.POSTGRESQL -> postgresToNumber(expr) + } +}