Merge pull request #348 from chrisk325/patch-5

This commit is contained in:
Nayif 2026-01-03 10:36:11 +05:30 committed by GitHub
commit 6cb115ed74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 94 additions and 60 deletions

View file

@ -36,6 +36,9 @@ interface ContinueWatchingItem extends StreamingContent {
episode?: number; episode?: number;
episodeTitle?: string; episodeTitle?: string;
addonId?: string; addonId?: string;
addonPoster?: string;
addonName?: string;
addonDescription?: string;
} }
// Define the ref interface // Define the ref interface
@ -212,9 +215,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
const metadataCache = useRef<Record<string, { metadata: any; basicContent: StreamingContent | null; timestamp: number }>>({}); const metadataCache = useRef<Record<string, { metadata: any; basicContent: StreamingContent | null; timestamp: number }>>({});
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
// Helper function to get cached or fetch metadata const getCachedMetadata = useCallback(async (type: string, id: string, addonId?: string) => {
const getCachedMetadata = useCallback(async (type: string, id: string) => { const cacheKey = `${type}:${id}:${addonId || 'default'}`;
const cacheKey = `${type}:${id}`;
const cached = metadataCache.current[cacheKey]; const cached = metadataCache.current[cacheKey];
const now = Date.now(); const now = Date.now();
@ -224,60 +226,67 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
try { try {
const shouldFetchMeta = await stremioService.isValidContentId(type, id); 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), 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 finalContent = basicContent ? {
const result = { metadata, basicContent, timestamp: now }; ...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; metadataCache.current[cacheKey] = result;
return result; return result;
} }
return null; return null;
} catch (error: any) { } catch (error: any) {
// Skip logging 404 errors to reduce noise
return null; return null;
} }
}, []); }, []);
// Helper function to find the next episode const findNextEpisode = useCallback((
const findNextEpisode = useCallback((currentSeason: number, currentEpisode: number, videos: any[]) => { currentSeason: number,
currentEpisode: number,
videos: any[],
watchedSet?: Set<string>,
showId?: string
) => {
if (!videos || !Array.isArray(videos)) return null; if (!videos || !Array.isArray(videos)) return null;
// Sort videos to ensure correct order
const sortedVideos = [...videos].sort((a, b) => { const sortedVideos = [...videos].sort((a, b) => {
if (a.season !== b.season) return a.season - b.season; if (a.season !== b.season) return a.season - b.season;
return a.episode - b.episode; return a.episode - b.episode;
}); });
// Strategy 1: Look for next episode in the same season const isAlreadyWatched = (season: number, episode: number): boolean => {
let nextEp = sortedVideos.find(v => v.season === currentSeason && v.episode === currentEpisode + 1); 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 for (const video of sortedVideos) {
if (!nextEp) { if (video.season < currentSeason) continue;
nextEp = sortedVideos.find(v => v.season === currentSeason + 1 && v.episode === 1); if (video.season === currentSeason && video.episode <= currentEpisode) continue;
}
if (isAlreadyWatched(video.season, video.episode)) continue;
// 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 (isEpisodeReleased(video)) {
if (!nextEp) { return video;
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;
} }
} }
// Verify the found episode is released
if (nextEp && isEpisodeReleased(nextEp)) {
return nextEp;
}
return null; return null;
}, []); }, []);
// Modified loadContinueWatching to render incrementally // Modified loadContinueWatching to render incrementally
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => { const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
@ -461,7 +470,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
return; 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; if (!cachedData?.basicContent) return;
const { metadata, basicContent } = cachedData; const { metadata, basicContent } = cachedData;
@ -660,7 +669,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
const movieKey = `movie:${imdbId}`; const movieKey = `movie:${imdbId}`;
if (recentlyRemovedRef.current.has(movieKey)) continue; if (recentlyRemovedRef.current.has(movieKey)) continue;
const cachedData = await getCachedMetadata('movie', imdbId); const cachedData = await getCachedMetadata('movie', imdbId, item.addonId);
if (!cachedData?.basicContent) continue; if (!cachedData?.basicContent) continue;
const pausedAt = new Date(item.paused_at).getTime(); const pausedAt = new Date(item.paused_at).getTime();
@ -692,7 +701,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
continue; continue;
} }
const cachedData = await getCachedMetadata('series', showImdb); const cachedData = await getCachedMetadata('series', showImdb, item.addonId);
if (!cachedData?.basicContent) continue; if (!cachedData?.basicContent) continue;
traktBatch.push({ traktBatch.push({

View file

@ -559,15 +559,23 @@ export function useTraktIntegration() {
return undefined; return undefined;
})(); })();
updatePromises.push( // Merge with local progress
storageService.mergeWithTraktProgress( await storageService.mergeWithTraktProgress(
id, id,
type, type,
item.progress, item.progress,
item.paused_at, item.paused_at,
episodeId, episodeId,
exactTime 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) { } catch (error) {
logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error); logger.error('[useTraktIntegration] Error preparing Trakt progress update:', error);
@ -581,19 +589,27 @@ export function useTraktIntegration() {
const id = movie.movie.ids.imdb; const id = movie.movie.ids.imdb;
const watchedAt = movie.last_watched_at; const watchedAt = movie.last_watched_at;
updatePromises.push( await storageService.mergeWithTraktProgress(
storageService.mergeWithTraktProgress( id,
id, 'movie',
'movie', 100,
100, // 100% progress for watched items watchedAt
watchedAt );
)
// FIX: Mark as already synced
await storageService.updateTraktSyncStatus(
id,
'movie',
true,
100
); );
} }
} catch (error) { } 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) { for (const show of watchedShows) {
try { try {
if (show.show?.ids?.imdb && show.seasons) { if (show.show?.ids?.imdb && show.seasons) {
@ -602,14 +618,22 @@ export function useTraktIntegration() {
for (const season of show.seasons) { for (const season of show.seasons) {
for (const episode of season.episodes) { for (const episode of season.episodes) {
const episodeId = `${showImdbId}:${season.number}:${episode.number}`; const episodeId = `${showImdbId}:${season.number}:${episode.number}`;
updatePromises.push(
storageService.mergeWithTraktProgress( await storageService.mergeWithTraktProgress(
showImdbId, showImdbId,
'series', 'series',
100, 100,
episode.last_watched_at, episode.last_watched_at,
episodeId 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); logger.error('[useTraktIntegration] Error preparing watched show update:', error);
} }
} }
// Execute all updates in parallel // Execute all updates in parallel
await Promise.all(updatePromises); await Promise.all(updatePromises);