mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-31 08:39:24 +00:00
Merge pull request #1314 from skoruppa/port/trakt-non-standard-ids
Fix Trakt scrobble for non-standard IDs
This commit is contained in:
commit
a0a97325e4
3 changed files with 92 additions and 5 deletions
|
|
@ -54,3 +54,49 @@ internal fun extractTraktYear(value: String?): Int? {
|
|||
|
||||
internal fun TraktExternalIds.hasAnyId(): Boolean =
|
||||
trakt != null || !imdb.isNullOrBlank() || tmdb != null || !slug.isNullOrBlank()
|
||||
|
||||
/**
|
||||
* Returns true if the given contentId uses a Trakt-compatible prefix
|
||||
* (IMDB, TMDB, or Trakt numeric). IDs from other sources (kitsu:, mal:,
|
||||
* anilist:, anidb:, tvdb:, etc.) are NOT Trakt-resolvable and should be
|
||||
* preserved locally rather than being discarded when absent from Trakt
|
||||
* remote responses.
|
||||
*/
|
||||
internal fun isTraktCompatibleId(contentId: String?): Boolean {
|
||||
if (contentId.isNullOrBlank()) return false
|
||||
val raw = contentId.trim()
|
||||
if (raw.startsWith("tt")) return true
|
||||
if (raw.startsWith("tmdb:", ignoreCase = true)) return true
|
||||
if (raw.startsWith("trakt:", ignoreCase = true)) return true
|
||||
// Pure numeric IDs are treated as Trakt IDs
|
||||
val beforeColon = raw.substringBefore(':')
|
||||
if (beforeColon.toIntOrNull() != null) return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* If [contentId] is not Trakt-resolvable but [videoId] contains a valid
|
||||
* IMDB or TMDB prefix, extract and return the resolved ID from videoId.
|
||||
* This handles addons that wrap real IDs in non-standard content IDs
|
||||
* (e.g. contentId = "tun_tt7821582", videoId = "tt7821582:3:7").
|
||||
*
|
||||
* Returns the original [contentId] if it's already valid or if no
|
||||
* better ID can be extracted from [videoId].
|
||||
*/
|
||||
internal fun resolveEffectiveContentId(contentId: String, videoId: String?): String {
|
||||
val parsedContent = parseTraktContentIds(contentId)
|
||||
if (parsedContent.hasAnyId()) return contentId
|
||||
|
||||
if (videoId.isNullOrBlank() || videoId == contentId) return contentId
|
||||
|
||||
val parsedVideo = parseTraktContentIds(videoId)
|
||||
if (!parsedVideo.hasAnyId()) return contentId
|
||||
|
||||
// Rebuild a canonical content ID from the resolved video IDs
|
||||
return when {
|
||||
!parsedVideo.imdb.isNullOrBlank() -> parsedVideo.imdb
|
||||
parsedVideo.tmdb != null -> "tmdb:${parsedVideo.tmdb}"
|
||||
parsedVideo.trakt != null -> parsedVideo.trakt.toString()
|
||||
else -> contentId
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,19 @@ internal object TraktScrobbleRepository {
|
|||
releaseInfo: String? = null,
|
||||
): TraktScrobbleItem? {
|
||||
val normalizedType = contentType.trim().lowercase()
|
||||
val ids = parseTraktContentIds(parentMetaId)
|
||||
var ids = parseTraktContentIds(parentMetaId)
|
||||
|
||||
// Fallback: if parentMetaId doesn't resolve to valid Trakt IDs, try videoId.
|
||||
// Some addons use non-standard contentId (e.g. "tun_tt7821582") but set a
|
||||
// valid IMDB/TMDB videoId (e.g. "tt7821582:3:7").
|
||||
if (!ids.hasAnyId() && !videoId.isNullOrBlank() && videoId != parentMetaId) {
|
||||
ids = parseTraktContentIds(videoId)
|
||||
}
|
||||
|
||||
// Don't send scrobble if we still have no valid Trakt IDs — would cause
|
||||
// title-based fuzzy match on Trakt API resulting in wrong show matched.
|
||||
if (!ids.hasAnyId()) return null
|
||||
|
||||
val parsedYear = extractTraktYear(releaseInfo)
|
||||
|
||||
return if (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import com.nuvio.app.features.profiles.ProfileRepository
|
|||
import com.nuvio.app.features.trakt.TraktAuthRepository
|
||||
import com.nuvio.app.features.trakt.TraktProgressRepository
|
||||
import com.nuvio.app.features.trakt.TraktSettingsRepository
|
||||
import com.nuvio.app.features.trakt.isTraktCompatibleId
|
||||
import com.nuvio.app.features.trakt.resolveEffectiveContentId
|
||||
import com.nuvio.app.features.trakt.shouldUseTraktProgress as shouldUseTraktProgressSource
|
||||
import com.nuvio.app.features.watching.application.WatchingActions
|
||||
import com.nuvio.app.features.watching.sync.ProgressDeltaEvent
|
||||
|
|
@ -806,9 +808,20 @@ object WatchProgressRepository {
|
|||
return
|
||||
}
|
||||
|
||||
val useTraktProgress = shouldUseTraktProgress()
|
||||
|
||||
// If Trakt is the active CW source and parentMetaId is not Trakt-resolvable
|
||||
// but videoId contains a valid IMDB/TMDB, use the resolved ID to avoid
|
||||
// duplicate CW entries (one local with garbage ID, one from Trakt with real ID).
|
||||
val effectiveParentMetaId = if (useTraktProgress) {
|
||||
resolveEffectiveContentId(session.parentMetaId, session.videoId)
|
||||
} else {
|
||||
session.parentMetaId
|
||||
}
|
||||
|
||||
val entry = WatchProgressEntry(
|
||||
contentType = session.contentType,
|
||||
parentMetaId = session.parentMetaId,
|
||||
parentMetaId = effectiveParentMetaId,
|
||||
parentMetaType = session.parentMetaType,
|
||||
videoId = session.videoId,
|
||||
title = session.title,
|
||||
|
|
@ -835,8 +848,6 @@ object WatchProgressRepository {
|
|||
ContinueWatchingPreferencesRepository.removeDismissedNextUpKeysForContent(entry.parentMetaId)
|
||||
}
|
||||
|
||||
val useTraktProgress = shouldUseTraktProgress()
|
||||
|
||||
entriesByVideoId[session.videoId] = entry
|
||||
if (useTraktProgress) {
|
||||
TraktProgressRepository.applyOptimisticProgress(entry)
|
||||
|
|
@ -925,7 +936,25 @@ object WatchProgressRepository {
|
|||
|
||||
private fun currentEntries(): List<WatchProgressEntry> {
|
||||
return if (shouldUseTraktProgress()) {
|
||||
TraktProgressRepository.uiState.value.entries
|
||||
// Merge Trakt remote progress with local-only entries that use
|
||||
// non-Trakt-compatible IDs (kitsu:, mal:, anilist:, etc.).
|
||||
// Trakt will never return these IDs, so they must come from local storage.
|
||||
val traktItems = TraktProgressRepository.uiState.value.entries
|
||||
val localNonTraktItems = entriesByVideoId.values.filter {
|
||||
!isTraktCompatibleId(it.parentMetaId)
|
||||
}
|
||||
if (localNonTraktItems.isEmpty()) {
|
||||
traktItems
|
||||
} else {
|
||||
val traktKeys = traktItems.map { it.videoId }.toSet()
|
||||
val merged = traktItems.toMutableList()
|
||||
localNonTraktItems.forEach { localItem ->
|
||||
if (localItem.videoId !in traktKeys) {
|
||||
merged.add(localItem)
|
||||
}
|
||||
}
|
||||
merged
|
||||
}
|
||||
} else {
|
||||
entriesByVideoId.values.toList()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue