mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-29 20:33:42 +00:00
Merge pull request #350 from chrisk325/patch-6
Complete fix for trakt up next thanx to @oceanm8 on discord for the idea
This commit is contained in:
commit
b42401a909
3 changed files with 170 additions and 65 deletions
|
|
@ -227,21 +227,34 @@ 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, addonContent] = await Promise.all([
|
const [metadata, basicContent, addonSpecificMeta] = 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)
|
|
||||||
|
addonId
|
||||||
|
? stremioService.getMetaDetails(type, id, addonId).catch(() => null)
|
||||||
|
: Promise.resolve(null)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const preferredAddonMeta = addonSpecificMeta || metadata;
|
||||||
|
|
||||||
|
|
||||||
const finalContent = basicContent ? {
|
const finalContent = basicContent ? {
|
||||||
...basicContent,
|
...basicContent,
|
||||||
...(addonContent?.name && { name: addonContent.name }),
|
...(preferredAddonMeta?.name && { name: preferredAddonMeta.name }),
|
||||||
...(addonContent?.poster && { poster: addonContent.poster }),
|
...(preferredAddonMeta?.poster && { poster: preferredAddonMeta.poster }),
|
||||||
...(addonContent?.description && { description: addonContent.description }),
|
...(preferredAddonMeta?.description && { description: preferredAddonMeta.description }),
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
|
|
||||||
if (finalContent) {
|
if (finalContent) {
|
||||||
const result = { metadata, basicContent: finalContent, addonContent, timestamp: now };
|
const result = {
|
||||||
|
metadata,
|
||||||
|
basicContent: finalContent,
|
||||||
|
addonContent: preferredAddonMeta,
|
||||||
|
timestamp: now
|
||||||
|
};
|
||||||
|
|
||||||
metadataCache.current[cacheKey] = result;
|
metadataCache.current[cacheKey] = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -500,23 +513,46 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSeason !== undefined && currentEpisode !== undefined && metadata?.videos) {
|
if (currentSeason !== undefined && currentEpisode !== undefined) {
|
||||||
const nextEpisodeVideo = findNextEpisode(currentSeason, currentEpisode, metadata.videos);
|
const traktService = TraktService.getInstance();
|
||||||
|
let nextEpisode: any = null;
|
||||||
|
|
||||||
if (nextEpisodeVideo) {
|
try {
|
||||||
|
const isAuthed = await traktService.isAuthenticated();
|
||||||
|
if (isAuthed && typeof (traktService as any).getShowWatchedProgress === 'function') {
|
||||||
|
const showProgress = await (traktService as any).getShowWatchedProgress(group.id);
|
||||||
|
|
||||||
|
if (showProgress && !showProgress.completed && showProgress.next_episode) {
|
||||||
|
nextEpisode = showProgress.next_episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextEpisode && metadata?.videos) {
|
||||||
|
nextEpisode = findNextEpisode(
|
||||||
|
currentSeason,
|
||||||
|
currentEpisode,
|
||||||
|
metadata.videos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEpisode) {
|
||||||
batch.push({
|
batch.push({
|
||||||
...basicContent,
|
...basicContent,
|
||||||
id: group.id,
|
id: group.id,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
lastUpdated: progress.lastUpdated,
|
lastUpdated: progress.lastUpdated,
|
||||||
season: nextEpisodeVideo.season,
|
season: nextEpisode.season,
|
||||||
episode: nextEpisodeVideo.episode,
|
episode: nextEpisode.number ?? nextEpisode.episode,
|
||||||
episodeTitle: `Episode ${nextEpisodeVideo.episode}`,
|
episodeTitle: nextEpisode.title || `Episode ${nextEpisode.number ?? nextEpisode.episode}`,
|
||||||
addonId: progress.addonId,
|
addonId: progress.addonId,
|
||||||
} as ContinueWatchingItem);
|
} as ContinueWatchingItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -769,24 +805,36 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
if (!cachedData?.basicContent) continue;
|
if (!cachedData?.basicContent) continue;
|
||||||
const { metadata, basicContent } = cachedData;
|
const { metadata, basicContent } = cachedData;
|
||||||
|
|
||||||
if (metadata?.videos) {
|
const traktService = TraktService.getInstance();
|
||||||
const nextEpisodeVideo = findNextEpisode(info.season, info.episode, metadata.videos);
|
let showProgress: any = null;
|
||||||
if (nextEpisodeVideo) {
|
|
||||||
logger.log(`➕ [TraktSync] Adding next episode for ${showId}: S${nextEpisodeVideo.season}E${nextEpisodeVideo.episode}`);
|
try {
|
||||||
traktBatch.push({
|
showProgress = await (traktService as any).getShowWatchedProgress?.(showId);
|
||||||
...basicContent,
|
} catch {
|
||||||
id: showId,
|
showProgress = null;
|
||||||
type: 'series',
|
|
||||||
progress: 0, // Next episode, not started
|
|
||||||
lastUpdated: info.watchedAt,
|
|
||||||
season: nextEpisodeVideo.season,
|
|
||||||
episode: nextEpisodeVideo.episode,
|
|
||||||
episodeTitle: `Episode ${nextEpisodeVideo.episode}`,
|
|
||||||
addonId: undefined,
|
|
||||||
} as ContinueWatchingItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!showProgress || showProgress.completed || !showProgress.next_episode) {
|
||||||
|
logger.log(`🚫 [TraktSync] Skipping completed show: ${showId}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextEp = showProgress.next_episode;
|
||||||
|
|
||||||
|
logger.log(`➕ [TraktSync] Adding next episode for ${showId}: S${nextEp.season}E${nextEp.number}`);
|
||||||
|
|
||||||
|
traktBatch.push({
|
||||||
|
...basicContent,
|
||||||
|
id: showId,
|
||||||
|
type: 'series',
|
||||||
|
progress: 0,
|
||||||
|
lastUpdated: info.watchedAt,
|
||||||
|
season: nextEp.season,
|
||||||
|
episode: nextEp.number,
|
||||||
|
episodeTitle: nextEp.title || `Episode ${nextEp.number}`,
|
||||||
|
addonId: undefined,
|
||||||
|
} as ContinueWatchingItem);
|
||||||
|
|
||||||
// Persist "watched" progress for the episode that Trakt reported
|
// Persist "watched" progress for the episode that Trakt reported
|
||||||
if (!recentlyRemovedRef.current.has(showKey)) {
|
if (!recentlyRemovedRef.current.has(showKey)) {
|
||||||
const watchedEpisodeId = `${showId}:${info.season}:${info.episode}`;
|
const watchedEpisodeId = `${showId}:${info.season}:${info.episode}`;
|
||||||
|
|
|
||||||
|
|
@ -1099,6 +1099,55 @@ export class TraktService {
|
||||||
return this.apiRequest<TraktWatchedItem[]>('/sync/watched/shows');
|
return this.apiRequest<TraktWatchedItem[]>('/sync/watched/shows');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async isMovieWatchedAccurate(imdbId: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const history = await this.client.get(
|
||||||
|
`/sync/history/movies/${imdbId}?limit=1`
|
||||||
|
);
|
||||||
|
|
||||||
|
const history = response.data;
|
||||||
|
return Array.isArray(history) && history.length > 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isEpisodeWatchedAccurate(
|
||||||
|
showId: string,
|
||||||
|
season: number,
|
||||||
|
episode: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const history = await this.client.get(
|
||||||
|
`/sync/history/episodes/${showId}`,
|
||||||
|
{ params: { limit: 20 } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const history = response.data;
|
||||||
|
if (!Array.isArray(history)) return false;
|
||||||
|
|
||||||
|
for (const entry of history) {
|
||||||
|
if (
|
||||||
|
entry.episode?.season === season &&
|
||||||
|
entry.episode?.number === episode
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (entry.reset_at) {
|
||||||
|
const watchedAt = new Date(entry.watched_at).getTime();
|
||||||
|
const resetAt = new Date(entry.reset_at).getTime();
|
||||||
|
if (watchedAt < resetAt) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user's watchlist movies
|
* Get the user's watchlist movies
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -301,50 +301,58 @@ class WatchedService {
|
||||||
* Check if a movie is marked as watched (locally)
|
* Check if a movie is marked as watched (locally)
|
||||||
*/
|
*/
|
||||||
public async isMovieWatched(imdbId: string): Promise<boolean> {
|
public async isMovieWatched(imdbId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// First check local watched flag
|
const isAuthed = await this.traktService.isAuthenticated();
|
||||||
const localWatched = await mmkvStorage.getItem(`watched:movie:${imdbId}`);
|
|
||||||
if (localWatched === 'true') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check local progress
|
if (isAuthed) {
|
||||||
const progress = await storageService.getWatchProgress(imdbId, 'movie');
|
const traktWatched =
|
||||||
if (progress) {
|
await this.traktService.isMovieWatchedAccurate(imdbId);
|
||||||
const progressPercent = (progress.currentTime / progress.duration) * 100;
|
if (traktWatched) return true;
|
||||||
if (progressPercent >= 85) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[WatchedService] Error checking movie watched status:', error);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const local = await mmkvStorage.getItem(`watched:movie:${imdbId}`);
|
||||||
|
return local === 'true';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an episode is marked as watched (locally)
|
* Check if an episode is marked as watched (locally)
|
||||||
*/
|
*/
|
||||||
public async isEpisodeWatched(showId: string, season: number, episode: number): Promise<boolean> {
|
public async isEpisodeWatched(
|
||||||
try {
|
showId: string,
|
||||||
const episodeId = `${showId}:${season}:${episode}`;
|
season: number,
|
||||||
|
episode: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const isAuthed = await this.traktService.isAuthenticated();
|
||||||
|
|
||||||
// Check local progress
|
if (isAuthed) {
|
||||||
const progress = await storageService.getWatchProgress(showId, 'series', episodeId);
|
const traktWatched =
|
||||||
if (progress) {
|
await this.traktService.isEpisodeWatchedAccurate(
|
||||||
const progressPercent = (progress.currentTime / progress.duration) * 100;
|
showId,
|
||||||
if (progressPercent >= 85) {
|
season,
|
||||||
return true;
|
episode
|
||||||
}
|
);
|
||||||
}
|
if (traktWatched) return true;
|
||||||
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[WatchedService] Error checking episode watched status:', error);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const episodeId = `${showId}:${season}:${episode}`;
|
||||||
|
const progress = await storageService.getWatchProgress(
|
||||||
|
showId,
|
||||||
|
'series',
|
||||||
|
episodeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!progress) return false;
|
||||||
|
|
||||||
|
const pct = (progress.currentTime / progress.duration) * 100;
|
||||||
|
return pct >= 99;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue