mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-10 12:01:55 +00:00
added back up next
This commit is contained in:
parent
6ff5aa9e02
commit
1627928fb2
1 changed files with 169 additions and 4 deletions
|
|
@ -511,8 +511,54 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
const { episodeId, progress, progressPercent } = episode;
|
const { episodeId, progress, progressPercent } = episode;
|
||||||
|
|
||||||
if (group.type === 'series' && progressPercent >= 85) {
|
if (group.type === 'series' && progressPercent >= 85) {
|
||||||
// Skip completed episodes - don't add "next episode" here
|
// Episode is completed - find the next unwatched episode
|
||||||
// The Trakt playback endpoint handles in-progress items
|
let completedSeason: number | undefined;
|
||||||
|
let completedEpisode: number | undefined;
|
||||||
|
|
||||||
|
if (episodeId) {
|
||||||
|
const match = episodeId.match(/s(\d+)e(\d+)/i);
|
||||||
|
if (match) {
|
||||||
|
completedSeason = parseInt(match[1], 10);
|
||||||
|
completedEpisode = parseInt(match[2], 10);
|
||||||
|
} else {
|
||||||
|
const parts = episodeId.split(':');
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const seasonPart = parts[parts.length - 2];
|
||||||
|
const episodePart = parts[parts.length - 1];
|
||||||
|
const seasonNum = parseInt(seasonPart, 10);
|
||||||
|
const episodeNum = parseInt(episodePart, 10);
|
||||||
|
if (!isNaN(seasonNum) && !isNaN(episodeNum)) {
|
||||||
|
completedSeason = seasonNum;
|
||||||
|
completedEpisode = episodeNum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have valid season/episode info, find the next episode
|
||||||
|
if (completedSeason !== undefined && completedEpisode !== undefined && metadata?.videos) {
|
||||||
|
const watchedEpisodesSet = await traktShowsSetPromise;
|
||||||
|
const nextEpisode = findNextEpisode(
|
||||||
|
completedSeason,
|
||||||
|
completedEpisode,
|
||||||
|
metadata.videos,
|
||||||
|
watchedEpisodesSet,
|
||||||
|
group.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextEpisode) {
|
||||||
|
logger.log(`📺 [ContinueWatching] Found next episode: S${nextEpisode.season}E${nextEpisode.episode} for ${basicContent.name}`);
|
||||||
|
batch.push({
|
||||||
|
...basicContent,
|
||||||
|
progress: 0, // Up next - no progress yet
|
||||||
|
lastUpdated: progress.lastUpdated, // Keep the timestamp from completed episode
|
||||||
|
season: nextEpisode.season,
|
||||||
|
episode: nextEpisode.episode,
|
||||||
|
episodeTitle: nextEpisode.title || `Episode ${nextEpisode.episode}`,
|
||||||
|
addonId: progress.addonId,
|
||||||
|
} as ContinueWatchingItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -627,13 +673,14 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
try {
|
try {
|
||||||
// Skip items with < 2% progress (accidental clicks)
|
// Skip items with < 2% progress (accidental clicks)
|
||||||
if (item.progress < 2) continue;
|
if (item.progress < 2) continue;
|
||||||
// Skip items with >= 85% progress (completed)
|
|
||||||
if (item.progress >= 85) continue;
|
|
||||||
// Skip items older than 30 days
|
// Skip items older than 30 days
|
||||||
const pausedAt = new Date(item.paused_at).getTime();
|
const pausedAt = new Date(item.paused_at).getTime();
|
||||||
if (pausedAt < thirtyDaysAgo) continue;
|
if (pausedAt < thirtyDaysAgo) continue;
|
||||||
|
|
||||||
if (item.type === 'movie' && item.movie?.ids?.imdb) {
|
if (item.type === 'movie' && item.movie?.ids?.imdb) {
|
||||||
|
// Skip completed movies
|
||||||
|
if (item.progress >= 85) continue;
|
||||||
|
|
||||||
const imdbId = item.movie.ids.imdb.startsWith('tt')
|
const imdbId = item.movie.ids.imdb.startsWith('tt')
|
||||||
? item.movie.ids.imdb
|
? item.movie.ids.imdb
|
||||||
: `tt${item.movie.ids.imdb}`;
|
: `tt${item.movie.ids.imdb}`;
|
||||||
|
|
@ -672,6 +719,37 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
const cachedData = await getCachedMetadata('series', showImdb);
|
const cachedData = await getCachedMetadata('series', showImdb);
|
||||||
if (!cachedData?.basicContent) continue;
|
if (!cachedData?.basicContent) continue;
|
||||||
|
|
||||||
|
// If episode is completed (>= 85%), find next episode
|
||||||
|
if (item.progress >= 85) {
|
||||||
|
const metadata = cachedData.metadata;
|
||||||
|
if (metadata?.videos) {
|
||||||
|
const nextEpisode = findNextEpisode(
|
||||||
|
item.episode.season,
|
||||||
|
item.episode.number,
|
||||||
|
metadata.videos,
|
||||||
|
undefined, // No watched set needed, findNextEpisode handles it
|
||||||
|
showImdb
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextEpisode) {
|
||||||
|
logger.log(`📺 [TraktPlayback] Episode completed, adding next: S${nextEpisode.season}E${nextEpisode.episode} for ${item.show.title}`);
|
||||||
|
traktBatch.push({
|
||||||
|
...cachedData.basicContent,
|
||||||
|
id: showImdb,
|
||||||
|
type: 'series',
|
||||||
|
progress: 0, // Up next - no progress yet
|
||||||
|
lastUpdated: pausedAt,
|
||||||
|
season: nextEpisode.season,
|
||||||
|
episode: nextEpisode.episode,
|
||||||
|
episodeTitle: nextEpisode.title || `Episode ${nextEpisode.episode}`,
|
||||||
|
addonId: undefined,
|
||||||
|
traktPlaybackId: item.id,
|
||||||
|
} as ContinueWatchingItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
traktBatch.push({
|
traktBatch.push({
|
||||||
...cachedData.basicContent,
|
...cachedData.basicContent,
|
||||||
id: showImdb,
|
id: showImdb,
|
||||||
|
|
@ -692,6 +770,93 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STEP 2: Get watched shows and find "Up Next" episodes
|
||||||
|
// This handles cases where episodes are fully completed and removed from playback progress
|
||||||
|
try {
|
||||||
|
const watchedShows = await traktService.getWatchedShows();
|
||||||
|
const thirtyDaysAgoForShows = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
for (const watchedShow of watchedShows) {
|
||||||
|
try {
|
||||||
|
if (!watchedShow.show?.ids?.imdb) continue;
|
||||||
|
|
||||||
|
// Skip shows that haven't been watched recently
|
||||||
|
const lastWatchedAt = new Date(watchedShow.last_watched_at).getTime();
|
||||||
|
if (lastWatchedAt < thirtyDaysAgoForShows) continue;
|
||||||
|
|
||||||
|
const showImdb = watchedShow.show.ids.imdb.startsWith('tt')
|
||||||
|
? watchedShow.show.ids.imdb
|
||||||
|
: `tt${watchedShow.show.ids.imdb}`;
|
||||||
|
|
||||||
|
// Check if recently removed
|
||||||
|
const showKey = `series:${showImdb}`;
|
||||||
|
if (recentlyRemovedRef.current.has(showKey)) continue;
|
||||||
|
|
||||||
|
// Find the last watched episode
|
||||||
|
let lastWatchedSeason = 0;
|
||||||
|
let lastWatchedEpisode = 0;
|
||||||
|
let latestEpisodeTimestamp = 0;
|
||||||
|
|
||||||
|
if (watchedShow.seasons) {
|
||||||
|
for (const season of watchedShow.seasons) {
|
||||||
|
for (const episode of season.episodes) {
|
||||||
|
const episodeTimestamp = new Date(episode.last_watched_at).getTime();
|
||||||
|
if (episodeTimestamp > latestEpisodeTimestamp) {
|
||||||
|
latestEpisodeTimestamp = episodeTimestamp;
|
||||||
|
lastWatchedSeason = season.number;
|
||||||
|
lastWatchedEpisode = episode.number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastWatchedSeason === 0 && lastWatchedEpisode === 0) continue;
|
||||||
|
|
||||||
|
// Get metadata with episode list
|
||||||
|
const cachedData = await getCachedMetadata('series', showImdb);
|
||||||
|
if (!cachedData?.basicContent || !cachedData?.metadata?.videos) continue;
|
||||||
|
|
||||||
|
// Build a set of watched episodes for this show
|
||||||
|
const watchedEpisodeSet = new Set<string>();
|
||||||
|
if (watchedShow.seasons) {
|
||||||
|
for (const season of watchedShow.seasons) {
|
||||||
|
for (const episode of season.episodes) {
|
||||||
|
watchedEpisodeSet.add(`${showImdb}:${season.number}:${episode.number}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the next unwatched episode
|
||||||
|
const nextEpisode = findNextEpisode(
|
||||||
|
lastWatchedSeason,
|
||||||
|
lastWatchedEpisode,
|
||||||
|
cachedData.metadata.videos,
|
||||||
|
watchedEpisodeSet,
|
||||||
|
showImdb
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextEpisode) {
|
||||||
|
logger.log(`📺 [TraktWatched] Found Up Next: ${watchedShow.show.title} S${nextEpisode.season}E${nextEpisode.episode}`);
|
||||||
|
traktBatch.push({
|
||||||
|
...cachedData.basicContent,
|
||||||
|
id: showImdb,
|
||||||
|
type: 'series',
|
||||||
|
progress: 0, // Up next - no progress yet
|
||||||
|
lastUpdated: latestEpisodeTimestamp,
|
||||||
|
season: nextEpisode.season,
|
||||||
|
episode: nextEpisode.episode,
|
||||||
|
episodeTitle: nextEpisode.title || `Episode ${nextEpisode.episode}`,
|
||||||
|
addonId: undefined,
|
||||||
|
} as ContinueWatchingItem);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Continue with other shows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('[TraktSync] Error fetching watched shows for Up Next:', err);
|
||||||
|
}
|
||||||
|
|
||||||
// Set Trakt playback items as state (replace, don't merge with local storage)
|
// Set Trakt playback items as state (replace, don't merge with local storage)
|
||||||
if (traktBatch.length > 0) {
|
if (traktBatch.length > 0) {
|
||||||
// Dedupe: for series, keep only the latest episode per show
|
// Dedupe: for series, keep only the latest episode per show
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue