diff --git a/src/services/supabaseSyncService.ts b/src/services/supabaseSyncService.ts index 09816061..6d511ed5 100644 --- a/src/services/supabaseSyncService.ts +++ b/src/services/supabaseSyncService.ts @@ -1038,27 +1038,55 @@ class SupabaseSyncService { videoId: string; progressKey: string; } | null { - const parts = key.split(':'); - if (parts.length < 2) return null; + // Key format from buildWpKeyString: "{type}:{contentId}" or "{type}:{contentId}:{episodeId}" + // contentId may contain colons (e.g., "tmdb:1399", "kitsu:12345") + // episodeId ends with ":{season}:{episode}" digits + const typeIdx = key.indexOf(':'); + if (typeIdx < 0) return null; - const contentType: 'movie' | 'series' = parts[0] === 'movie' ? 'movie' : 'series'; - const contentId = parts[1]; - const episodeId = parts.length > 2 ? parts.slice(2).join(':') : ''; + const typePart = key.substring(0, typeIdx); + if (typePart !== 'movie' && typePart !== 'series') return null; + const contentType: 'movie' | 'series' = typePart; + + const rest = key.substring(typeIdx + 1); + if (!rest) return null; + + // Extract content ID: detect known prefixed patterns (tmdb:NNN, kitsu:NNN), + // otherwise take the first colon-free segment (e.g., tt12345). + const cidPrefixMatch = rest.match(/^((?:tmdb|kitsu):\d+)/); + const contentId = cidPrefixMatch ? cidPrefixMatch[1] : rest.split(':')[0]; + if (!contentId) return null; + + const afterContentId = rest.substring(contentId.length); + + if (!afterContentId || afterContentId === ':') { + // No episode info (movie or series-level) + return { + contentType, + contentId, + season: null, + episode: null, + videoId: contentId, + progressKey: contentId, + }; + } + + // Strip leading ":" to get episodeId + const episodeId = afterContentId.substring(1); + + // Extract season:episode from the end of episodeId let season: number | null = null; let episode: number | null = null; - - if (episodeId) { - const match = episodeId.match(/:(\d+):(\d+)$/); - if (match) { - season = Number(match[1]); - episode = Number(match[2]); - } + const seMatch = episodeId.match(/:(\d+):(\d+)$/); + if (seMatch) { + season = Number(seMatch[1]); + episode = Number(seMatch[2]); } const videoId = episodeId || contentId; - const progressKey = contentType === 'movie' - ? contentId - : (season != null && episode != null ? `${contentId}_s${season}e${episode}` : `${contentId}_${videoId}`); + const progressKey = season != null && episode != null + ? `${contentId}_s${season}e${episode}` + : `${contentId}_${episodeId}`; return { contentType, @@ -1365,7 +1393,7 @@ class SupabaseSyncService { const season = row.season == null ? null : Number(row.season); const episode = row.episode == null ? null : Number(row.episode); const episodeId = type === 'series' && season != null && episode != null - ? `${row.content_id}:${season}:${episode}` + ? (row.video_id && row.video_id !== row.content_id ? row.video_id : `${row.content_id}:${season}:${episode}`) : undefined; remoteSet.add(this.buildLocalWatchProgressKey(type, row.content_id, episodeId));