From a079649563e8d1e02cdc5f68afa774e9ab105c38 Mon Sep 17 00:00:00 2001 From: chrisk325 Date: Sat, 3 Jan 2026 01:23:40 +0530 Subject: [PATCH 1/3] fix trakt syncing watched shows/movies back to trakt's recent history --- src/hooks/useTraktIntegration.ts | 75 +++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/hooks/useTraktIntegration.ts b/src/hooks/useTraktIntegration.ts index 2a992b3..a0fdc6d 100644 --- a/src/hooks/useTraktIntegration.ts +++ b/src/hooks/useTraktIntegration.ts @@ -559,15 +559,23 @@ export function useTraktIntegration() { return undefined; })(); - updatePromises.push( - storageService.mergeWithTraktProgress( - id, - type, - item.progress, - item.paused_at, - episodeId, - exactTime - ) + // Merge with local progress + await storageService.mergeWithTraktProgress( + id, + type, + item.progress, + item.paused_at, + episodeId, + exactTime + ); + + // FIX: Mark as already synced so it won't be re-uploaded to Trakt + await storageService.updateTraktSyncStatus( + id, + type, + true, // synced = true + item.progress, + episodeId ); } catch (error) { logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error); @@ -581,19 +589,27 @@ export function useTraktIntegration() { const id = movie.movie.ids.imdb; const watchedAt = movie.last_watched_at; - updatePromises.push( - storageService.mergeWithTraktProgress( - id, - 'movie', - 100, // 100% progress for watched items - watchedAt - ) + await storageService.mergeWithTraktProgress( + id, + 'movie', + 100, + watchedAt + ); + + // FIX: Mark as already synced + await storageService.updateTraktSyncStatus( + id, + 'movie', + true, + 100 ); } } catch (error) { - logger.error('[useTraktIntegration] Error preparing watched movie update:', error); + logger.error('[useTraktIntegration] Error preparing watched movie update:', error); } } + + // Process watched shows (100% completed episodes) for (const show of watchedShows) { try { if (show.show?.ids?.imdb && show.seasons) { @@ -602,14 +618,22 @@ export function useTraktIntegration() { for (const season of show.seasons) { for (const episode of season.episodes) { const episodeId = `${showImdbId}:${season.number}:${episode.number}`; - updatePromises.push( - storageService.mergeWithTraktProgress( - showImdbId, - 'series', - 100, - episode.last_watched_at, - episodeId - ) + + await storageService.mergeWithTraktProgress( + showImdbId, + 'series', + 100, + episode.last_watched_at, + episodeId + ); + + // FIX: Mark as already synced + await storageService.updateTraktSyncStatus( + showImdbId, + 'series', + true, + 100, + episodeId ); } } @@ -618,6 +642,7 @@ export function useTraktIntegration() { logger.error('[useTraktIntegration] Error preparing watched show update:', error); } } + // Execute all updates in parallel await Promise.all(updatePromises); From faa4f341e634a2fa4ebd1b254a92c3e0f398da56 Mon Sep 17 00:00:00 2001 From: chrisk325 Date: Sat, 3 Jan 2026 01:50:43 +0530 Subject: [PATCH 2/3] fix up next yet again (final fix probably) --- .../home/ContinueWatchingSection.tsx | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index 120ec53..b758db0 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -241,43 +241,41 @@ const ContinueWatchingSection = React.forwardRef((props, re } }, []); - // Helper function to find the next episode - const findNextEpisode = useCallback((currentSeason: number, currentEpisode: number, videos: any[]) => { + const findNextEpisode = useCallback(( + currentSeason: number, + currentEpisode: number, + videos: any[], + watchedSet?: Set, + showId?: string + ) => { if (!videos || !Array.isArray(videos)) return null; - // Sort videos to ensure correct order const sortedVideos = [...videos].sort((a, b) => { if (a.season !== b.season) return a.season - b.season; return a.episode - b.episode; }); - // Strategy 1: Look for next episode in the same season - let nextEp = sortedVideos.find(v => v.season === currentSeason && v.episode === currentEpisode + 1); + const isAlreadyWatched = (season: number, episode: number): boolean => { + if (!watchedSet || !showId) return false; + const cleanShowId = showId.startsWith('tt') ? showId : `tt${showId}`; + return watchedSet.has(`${cleanShowId}:${season}:${episode}`) || + watchedSet.has(`${showId}:${season}:${episode}`); + }; - // Strategy 2: If not found, look for the first episode of the next season - if (!nextEp) { - nextEp = sortedVideos.find(v => v.season === currentSeason + 1 && v.episode === 1); - } - - // Strategy 3: Just find the very next video in the list after the current one - // This handles cases where episode numbering isn't sequential or S+1 E1 isn't the standard start - if (!nextEp) { - const currentIndex = sortedVideos.findIndex(v => v.season === currentSeason && v.episode === currentEpisode); - if (currentIndex !== -1 && currentIndex + 1 < sortedVideos.length) { - const candidate = sortedVideos[currentIndex + 1]; - // Ensure we didn't just jump to a random special; check reasonable bounds if needed, - // but generally taking the next sorted item is correct for sequential viewing. - nextEp = candidate; + for (const video of sortedVideos) { + if (video.season < currentSeason) continue; + if (video.season === currentSeason && video.episode <= currentEpisode) continue; + + if (isAlreadyWatched(video.season, video.episode)) continue; + + if (isEpisodeReleased(video)) { + return video; } } - // Verify the found episode is released - if (nextEp && isEpisodeReleased(nextEp)) { - return nextEp; - } - return null; }, []); + // Modified loadContinueWatching to render incrementally const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => { From 0149068126ebc5cb89d4266f5065d31750a72468 Mon Sep 17 00:00:00 2001 From: chrisk325 Date: Sat, 3 Jan 2026 02:25:15 +0530 Subject: [PATCH 3/3] fix continue watching metadata --- .../home/ContinueWatchingSection.tsx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index b758db0..4d19e3c 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -36,6 +36,9 @@ interface ContinueWatchingItem extends StreamingContent { episode?: number; episodeTitle?: string; addonId?: string; + addonPoster?: string; + addonName?: string; + addonDescription?: string; } // Define the ref interface @@ -212,9 +215,8 @@ const ContinueWatchingSection = React.forwardRef((props, re const metadataCache = useRef>({}); const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes - // Helper function to get cached or fetch metadata - const getCachedMetadata = useCallback(async (type: string, id: string) => { - const cacheKey = `${type}:${id}`; + const getCachedMetadata = useCallback(async (type: string, id: string, addonId?: string) => { + const cacheKey = `${type}:${id}:${addonId || 'default'}`; const cached = metadataCache.current[cacheKey]; const now = Date.now(); @@ -224,22 +226,31 @@ const ContinueWatchingSection = React.forwardRef((props, re try { const shouldFetchMeta = await stremioService.isValidContentId(type, id); - const [metadata, basicContent] = await Promise.all([ + + const [metadata, basicContent, addonContent] = await Promise.all([ shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null), - catalogService.getBasicContentDetails(type, id) + catalogService.getBasicContentDetails(type, id), + addonId ? stremioService.getMetaDetails(type, id, addonId).catch(() => null) : Promise.resolve(null) ]); - if (basicContent) { - const result = { metadata, basicContent, timestamp: now }; + const finalContent = basicContent ? { + ...basicContent, + ...(addonContent?.name && { name: addonContent.name }), + ...(addonContent?.poster && { poster: addonContent.poster }), + ...(addonContent?.description && { description: addonContent.description }), + } : null; + + if (finalContent) { + const result = { metadata, basicContent: finalContent, addonContent, timestamp: now }; metadataCache.current[cacheKey] = result; return result; } return null; } catch (error: any) { - // Skip logging 404 errors to reduce noise return null; } }, []); + const findNextEpisode = useCallback(( currentSeason: number, @@ -459,7 +470,7 @@ const ContinueWatchingSection = React.forwardRef((props, re return; } } - const cachedData = await getCachedMetadata(group.type, group.id); + const cachedData = await getCachedMetadata(group.type, group.id, group.episodes[0]?.progress?.addonId); if (!cachedData?.basicContent) return; const { metadata, basicContent } = cachedData; @@ -658,7 +669,7 @@ const ContinueWatchingSection = React.forwardRef((props, re const movieKey = `movie:${imdbId}`; if (recentlyRemovedRef.current.has(movieKey)) continue; - const cachedData = await getCachedMetadata('movie', imdbId); + const cachedData = await getCachedMetadata('movie', imdbId, item.addonId); if (!cachedData?.basicContent) continue; const pausedAt = new Date(item.paused_at).getTime(); @@ -690,7 +701,7 @@ const ContinueWatchingSection = React.forwardRef((props, re continue; } - const cachedData = await getCachedMetadata('series', showImdb); + const cachedData = await getCachedMetadata('series', showImdb, item.addonId); if (!cachedData?.basicContent) continue; traktBatch.push({