Use Shikimori's update endpoint (#3810)

No idea why it was originally going via the
addition endpoint but Shikimori's v1 and v2 APIs
have a dedicated endpoint to update a list entry
via PATCH or PUT (interchangeable, both allow
partial payloads).

Splitting off the update path revealed that
findLibManga would return a non-null track, which
in turn causes the update call to fail with a 404
due to `library_id` being null.

After further investigation, I realised that
findLibManga should always return null if the
title is not in the user's library, not just in
refreshes.
This commit is contained in:
MajorTanya
2026-08-20 20:12:48 +02:00
committed by GitHub
parent 01bfaa99ad
commit 0974e787ad
3 changed files with 41 additions and 14 deletions
@@ -55,7 +55,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
}
}
return api.updateLibManga(track, getUsername())
return api.updateLibManga(track)
}
override suspend fun delete(track: DomainTrack) {
@@ -93,11 +93,10 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
}
override suspend fun refresh(track: Track): Track {
api.findLibManga(track, isRefresh = true)?.let { remoteTrack ->
track.library_id = remoteTrack.library_id
track.copyPersonalFrom(remoteTrack)
track.total_chapters = remoteTrack.total_chapters
} ?: throw Exception("Could not find manga")
val remoteTrack = api.findLibManga(track) ?: throw Exception("Could not find manga")
track.library_id = remoteTrack.library_id
track.copyPersonalFrom(remoteTrack)
track.total_chapters = remoteTrack.total_chapters
return track
}
@@ -4,7 +4,7 @@ import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMLibraryIdResponse
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMSearchResult
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListResult
import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserResult
import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.PUT
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
@@ -55,7 +56,7 @@ class ShikimoriApi(
body = payload.toString().toRequestBody(jsonMime),
),
).awaitSuccess()
.parseAs<SMAddMangaResponse>()
.parseAs<SMLibraryIdResponse>()
.let {
// save id of the entry for possible future delete request
track.library_id = it.id
@@ -65,7 +66,32 @@ class ShikimoriApi(
}
}
suspend fun updateLibManga(track: Track, userId: String): Track = addLibManga(track, userId)
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
val payload = buildJsonObject {
putJsonObject("user_rate") {
put("chapters", track.last_chapter_read.toInt())
put("score", track.score.toInt())
put("status", track.toShikimoriStatus())
}
}
with(json) {
authClient.newCall(
PUT(
"$API_URL/v2/user_rates/${track.library_id}",
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<SMLibraryIdResponse>()
.let {
track.library_id = it.id
}
track
}
}
}
suspend fun deleteLibManga(track: DomainTrack) {
withIOContext {
@@ -175,7 +201,7 @@ class ShikimoriApi(
}
}
suspend fun findLibManga(track: Track, isRefresh: Boolean = false): Track? {
suspend fun findLibManga(track: Track): Track? {
return withIOContext {
val query = $$"""
|query($id: String) {
@@ -216,9 +242,11 @@ class ShikimoriApi(
// userRate data which will be null if the title is not in the user's list.
// If it was removed on Shikimori and is still linked in the app, notify user via returning null here
// which throws an exception at the Shikimori.refresh call
if (isRefresh && listResult?.userRate == null) return@with null
listResult?.toTrack(trackId)
if (listResult?.userRate == null) {
null
} else {
listResult.toTrack(trackId)
}
}
}
}
@@ -3,6 +3,6 @@ package eu.kanade.tachiyomi.data.track.shikimori.dto
import kotlinx.serialization.Serializable
@Serializable
data class SMAddMangaResponse(
data class SMLibraryIdResponse(
val id: Long,
)