Support for private tracking with AniList and Bangumi (#1736)

Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
NarwhalHorns
2025-02-25 11:01:13 +00:00
committed by GitHub
parent badc229a23
commit 49b2b346b6
27 changed files with 214 additions and 25 deletions

View File

@@ -25,6 +25,7 @@ data class BackupTracking(
@ProtoNumber(10) var startedReadingDate: Long = 0,
// finishedReadingDate is called endReadTime in 1.x
@ProtoNumber(11) var finishedReadingDate: Long = 0,
@ProtoNumber(12) var private: Boolean = false,
@ProtoNumber(100) var mediaId: Long = 0,
) {
@@ -48,6 +49,7 @@ data class BackupTracking(
startDate = this@BackupTracking.startedReadingDate,
finishDate = this@BackupTracking.finishedReadingDate,
remoteUrl = this@BackupTracking.trackingUrl,
private = this@BackupTracking.private,
)
}
}
@@ -66,6 +68,7 @@ val backupTrackMapper = {
remoteUrl: String,
startDate: Long,
finishDate: Long,
private: Boolean,
->
BackupTracking(
syncId = syncId.toInt(),
@@ -80,5 +83,6 @@ val backupTrackMapper = {
startedReadingDate = startDate,
finishedReadingDate = finishDate,
trackingUrl = remoteUrl,
private = private,
)
}

View File

@@ -404,6 +404,7 @@ class MangaRestorer(
track.remoteUrl,
track.startDate,
track.finishDate,
track.private,
track.id,
)
}

View File

@@ -32,12 +32,15 @@ interface Track : Serializable {
var tracking_url: String
fun copyPersonalFrom(other: Track) {
var private: Boolean
fun copyPersonalFrom(other: Track, copyRemotePrivate: Boolean = true) {
last_chapter_read = other.last_chapter_read
score = other.score
status = other.status
started_reading_date = other.started_reading_date
finished_reading_date = other.finished_reading_date
if (copyRemotePrivate) private = other.private
}
companion object {

View File

@@ -29,4 +29,6 @@ class TrackImpl : Track {
override var finished_reading_date: Long = 0
override var tracking_url: String = ""
override var private: Boolean = false
}

View File

@@ -37,6 +37,8 @@ abstract class BaseTracker(
// Application and remote support for reading dates
override val supportsReadingDates: Boolean = false
override val supportsPrivateTracking: Boolean = false
// TODO: Store all scores as 10 point in the future maybe?
override fun get10PointScore(track: DomainTrack): Double {
return track.score
@@ -120,6 +122,11 @@ abstract class BaseTracker(
updateRemote(track)
}
override suspend fun setRemotePrivate(track: Track, private: Boolean) {
track.private = private
updateRemote(track)
}
private suspend fun updateRemote(track: Track): Unit = withIOContext {
try {
update(track)

View File

@@ -22,6 +22,8 @@ interface Tracker {
// Application and remote support for reading dates
val supportsReadingDates: Boolean
val supportsPrivateTracking: Boolean
@ColorInt
fun getLogoColor(): Int
@@ -82,4 +84,6 @@ interface Tracker {
suspend fun setRemoteStartDate(track: Track, epochMillis: Long)
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)
suspend fun setRemotePrivate(track: Track, private: Boolean)
}

View File

@@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
@@ -43,6 +42,8 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
override val supportsReadingDates: Boolean = true
override val supportsPrivateTracking: Boolean = true
private val scorePreference = trackPreferences.anilistScoreType()
init {
@@ -183,7 +184,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val remoteTrack = api.findLibManga(track, getUsername().toInt())
return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false)
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {

View File

@@ -42,8 +42,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun addLibManga(track: Track): Track {
return withIOContext {
val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private) {
| id
| status
|}
@@ -56,6 +56,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("mangaId", track.remote_id)
put("progress", track.last_chapter_read.toInt())
put("status", track.toApiStatus())
put("private", track.private)
}
}
with(json) {
@@ -79,11 +80,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
return withIOContext {
val query = """
|mutation UpdateManga(
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus,
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean,
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|) {
|SaveMediaListEntry(
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status,
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private,
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|) {
|id
@@ -102,6 +103,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("score", track.score.toInt())
put("startedAt", createDate(track.started_reading_date))
put("completedAt", createDate(track.finished_reading_date))
put("private", track.private)
}
}
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
@@ -190,6 +192,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|status
|scoreRaw: score(format: POINT_100)
|progress
|private
|startedAt {
|year
|month

View File

@@ -49,6 +49,7 @@ data class ALUserManga(
val startDateFuzzy: Long,
val completedDateFuzzy: Long,
val manga: ALManga,
val private: Boolean,
) {
fun toTrack() = Track.create(TrackerManager.ANILIST).apply {
remote_id = manga.remoteId
@@ -60,6 +61,7 @@ data class ALUserManga(
last_chapter_read = chaptersRead.toDouble()
library_id = libraryId
total_chapters = manga.totalChapters
private = this@ALUserManga.private
}
private fun toTrackStatus() = when (listStatus) {

View File

@@ -28,6 +28,7 @@ data class ALUserListItem(
val startedAt: ALFuzzyDate,
val completedAt: ALFuzzyDate,
val media: ALSearchItem,
val private: Boolean,
) {
fun toALUserManga(): ALUserManga {
return ALUserManga(
@@ -38,6 +39,7 @@ data class ALUserListItem(
startDateFuzzy = startedAt.toEpochMilli(),
completedDateFuzzy = completedAt.toEpochMilli(),
manga = media.toALManga(),
private = private,
)
}
}

View File

@@ -22,6 +22,8 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
private val api by lazy { BangumiApi(id, client, interceptor) }
override val supportsPrivateTracking: Boolean = true
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
override fun displayScore(track: DomainTrack): String {
@@ -49,7 +51,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
val statusTrack = api.statusLibManga(track, getUsername())
return if (statusTrack != null) {
track.copyPersonalFrom(statusTrack)
track.copyPersonalFrom(statusTrack, copyRemotePrivate = false)
track.library_id = statusTrack.library_id
track.score = statusTrack.score
track.last_chapter_read = statusTrack.last_chapter_read

View File

@@ -45,6 +45,7 @@ class BangumiApi(
put("type", track.toApiStatus())
put("rate", track.score.toInt().coerceIn(0, 10))
put("ep_status", track.last_chapter_read.toInt())
put("private", track.private)
}
.toString()
.toRequestBody()
@@ -62,6 +63,7 @@ class BangumiApi(
put("type", track.toApiStatus())
put("rate", track.score.toInt().coerceIn(0, 10))
put("ep_status", track.last_chapter_read.toInt())
put("private", track.private)
}
.toString()
.toRequestBody()

View File

@@ -30,6 +30,8 @@ class TrackSearch : Track {
override var finished_reading_date: Long = 0
override var private: Boolean = false
override lateinit var tracking_url: String
var cover_url: String = ""

View File

@@ -172,6 +172,7 @@ data class TrackInfoDialogHomeScreen(
)
},
onCopyLink = { context.copyTrackerLink(it) },
onTogglePrivate = screenModel::togglePrivate,
)
}
@@ -247,6 +248,12 @@ data class TrackInfoDialogHomeScreen(
}
}
fun togglePrivate(item: TrackItem) {
screenModelScope.launchNonCancellable {
item.tracker.setRemotePrivate(item.track!!.toDbTrack(), !item.track.private)
}
}
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
@@ -673,11 +680,14 @@ data class TrackerSearchScreen(
queryResult = state.queryResult,
selected = state.selected,
onSelectedChange = screenModel::updateSelection,
onConfirmSelection = {
screenModel.registerTracking(state.selected!!)
onConfirmSelection = f@{ private: Boolean ->
val selected = state.selected ?: return@f
selected.private = private
screenModel.registerTracking(selected)
navigator.pop()
},
onDismissRequest = navigator::pop,
supportsPrivateTracking = screenModel.supportsPrivateTracking,
)
}
@@ -688,6 +698,8 @@ data class TrackerSearchScreen(
private val tracker: Tracker,
) : StateScreenModel<Model.State>(State()) {
val supportsPrivateTracking = tracker.supportsPrivateTracking
init {
// Run search on first launch
if (initialQuery.isNotBlank()) {