From eaf239953970ec4269a8e9945b3585eec6c70740 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:34:04 -0600 Subject: [PATCH] new trakt /discover endpoint for featured carousel --- src/backend/metadata/traktApi.ts | 14 +- .../discover/components/FeaturedCarousel.tsx | 142 ++++++++++++------ 2 files changed, 108 insertions(+), 48 deletions(-) diff --git a/src/backend/metadata/traktApi.ts b/src/backend/metadata/traktApi.ts index b0449ded..e7ec854f 100644 --- a/src/backend/metadata/traktApi.ts +++ b/src/backend/metadata/traktApi.ts @@ -29,6 +29,12 @@ export type TraktContentType = "movie" | "episode"; export const TRAKT_BASE_URL = "https://fed-airdate.pstream.org"; +export interface TraktDiscoverResponse { + movie_tmdb_ids: number[]; + tv_tmdb_ids: number[]; + count: number; +} + // Pagination utility export function paginateResults( results: TraktLatestResponse, @@ -47,7 +53,9 @@ export function paginateResults( } // Base function to fetch from Trakt API -async function fetchFromTrakt(endpoint: string): Promise { +async function fetchFromTrakt( + endpoint: string, +): Promise { const response = await fetch(`${TRAKT_BASE_URL}${endpoint}`); if (!response.ok) { throw new Error(`Failed to fetch from ${endpoint}: ${response.statusText}`); @@ -94,6 +102,10 @@ export const getDramaReleases = () => fetchFromTrakt("/drama"); export const getPopularTVShows = () => fetchFromTrakt("/populartv"); export const getPopularMovies = () => fetchFromTrakt("/popularmovies"); +// Discovery content +export const getDiscoverContent = () => + fetchFromTrakt("/discover"); + // Type conversion utilities export function convertToMediaType(type: TraktContentType): MWMediaType { return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES; diff --git a/src/pages/discover/components/FeaturedCarousel.tsx b/src/pages/discover/components/FeaturedCarousel.tsx index 0a63f203..960a7cbc 100644 --- a/src/pages/discover/components/FeaturedCarousel.tsx +++ b/src/pages/discover/components/FeaturedCarousel.tsx @@ -8,6 +8,7 @@ import { isExtensionActive } from "@/backend/extension/messaging"; import { get, getMediaLogo } from "@/backend/metadata/tmdb"; import { TraktReleaseResponse, + getDiscoverContent, getReleaseDetails, } from "@/backend/metadata/traktApi"; import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; @@ -202,60 +203,107 @@ export function FeaturedCarousel({ logoFetchController.current.abort(); // Cancel any in-progress logo fetches } try { - if (effectiveCategory === "movies") { - // First get the list of popular movies - const listData = await get("/movie/popular", { - api_key: conf().TMDB_READ_API_KEY, - language: formattedLanguage, - }); + if (effectiveCategory === "movies" || effectiveCategory === "tvshows") { + // First try to get IDs from Trakt discover endpoint + try { + const discoverData = await getDiscoverContent(); - // Then fetch full details for each movie to get external_ids - const moviePromises = listData.results - .slice(0, FETCH_QUANTITY) - .map((movie: any) => - get(`/movie/${movie.id}`, { - api_key: conf().TMDB_READ_API_KEY, - language: formattedLanguage, - append_to_response: "external_ids", - }), + let tmdbIds: number[] = []; + if (effectiveCategory === "movies") { + tmdbIds = discoverData.movie_tmdb_ids; + } else { + tmdbIds = discoverData.tv_tmdb_ids; + } + + // Then fetch full details for each movie/show to get external_ids + const detailPromises = tmdbIds.map((id) => + get( + `/${effectiveCategory === "movies" ? "movie" : "tv"}/${id}`, + { + api_key: conf().TMDB_READ_API_KEY, + language: formattedLanguage, + append_to_response: "external_ids", + }, + ), ); - const movieDetails = await Promise.all(moviePromises); - const allMovies = movieDetails.map((movie) => ({ - ...movie, - type: "movie" as const, - })); + const details = await Promise.all(detailPromises); + const mediaItems = details.map((item) => ({ + ...item, + type: + effectiveCategory === "movies" ? "movie" : ("show" as const), + })); - // Shuffle - const shuffledMovies = [...allMovies].sort(() => 0.5 - Math.random()); - setMedia(shuffledMovies.slice(0, SLIDE_QUANTITY)); - } else if (effectiveCategory === "tvshows") { - // First get the list of popular shows - const listData = await get("/tv/popular", { - api_key: conf().TMDB_READ_API_KEY, - language: formattedLanguage, - }); - - // Then fetch full details for each show to get external_ids - const showPromises = listData.results - .slice(0, FETCH_QUANTITY) - .map((show: any) => - get(`/tv/${show.id}`, { - api_key: conf().TMDB_READ_API_KEY, - language: formattedLanguage, - append_to_response: "external_ids", - }), + // Take the first SLIDE_QUANTITY items + setMedia(mediaItems.slice(0, SLIDE_QUANTITY)); + } catch (traktError) { + console.error( + "Falling back to TMDB method", + "Error fetching from Trakt discover:", + traktError, ); - const showDetails = await Promise.all(showPromises); - const allShows = showDetails.map((show) => ({ - ...show, - type: "show" as const, - })); + // Fallback to TMDB method + if (effectiveCategory === "movies") { + // First get the list of popular movies + const listData = await get("/movie/popular", { + api_key: conf().TMDB_READ_API_KEY, + language: formattedLanguage, + }); - // Shuffle - const shuffledShows = [...allShows].sort(() => 0.5 - Math.random()); - setMedia(shuffledShows.slice(0, SLIDE_QUANTITY)); + // Then fetch full details for each movie to get external_ids + const moviePromises = listData.results + .slice(0, FETCH_QUANTITY) + .map((movie: any) => + get(`/movie/${movie.id}`, { + api_key: conf().TMDB_READ_API_KEY, + language: formattedLanguage, + append_to_response: "external_ids", + }), + ); + + const movieDetails = await Promise.all(moviePromises); + const allMovies = movieDetails.map((movie) => ({ + ...movie, + type: "movie" as const, + })); + + // Shuffle + const shuffledMovies = [...allMovies].sort( + () => 0.5 - Math.random(), + ); + setMedia(shuffledMovies.slice(0, SLIDE_QUANTITY)); + } else if (effectiveCategory === "tvshows") { + // First get the list of popular shows + const listData = await get("/tv/popular", { + api_key: conf().TMDB_READ_API_KEY, + language: formattedLanguage, + }); + + // Then fetch full details for each show to get external_ids + const showPromises = listData.results + .slice(0, FETCH_QUANTITY) + .map((show: any) => + get(`/tv/${show.id}`, { + api_key: conf().TMDB_READ_API_KEY, + language: formattedLanguage, + append_to_response: "external_ids", + }), + ); + + const showDetails = await Promise.all(showPromises); + const allShows = showDetails.map((show) => ({ + ...show, + type: "show" as const, + })); + + // Shuffle + const shuffledShows = [...allShows].sort( + () => 0.5 - Math.random(), + ); + setMedia(shuffledShows.slice(0, SLIDE_QUANTITY)); + } + } } else if (effectiveCategory === "editorpicks") { // Shuffle editor picks Ids const allMovieIds = EDITOR_PICKS_MOVIES.map((item) => ({