diff --git a/src/pages/discover/hooks/useDiscoverMedia.ts b/src/pages/discover/hooks/useDiscoverMedia.ts index 200500340..73e561a79 100644 --- a/src/pages/discover/hooks/useDiscoverMedia.ts +++ b/src/pages/discover/hooks/useDiscoverMedia.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { get, getMediaDetails } from "@/backend/metadata/tmdb"; +import { get } from "@/backend/metadata/tmdb"; import { PROVIDER_TO_TRAKT_MAP, getAppleMovieReleases, @@ -24,13 +24,6 @@ import { getTop10Movies, } from "@/backend/metadata/traktApi"; import { paginateResults } from "@/backend/metadata/traktFunctions"; -import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; -import type { - TMDBMovieData, - TMDBMovieSearchResult, - TMDBShowData, - TMDBShowSearchResult, -} from "@/backend/metadata/types/tmdb"; import type { TraktListResponse } from "@/backend/metadata/types/trakt"; import { EDITOR_PICKS_MOVIES, @@ -51,8 +44,6 @@ import { conf } from "@/setup/config"; import { useLanguageStore } from "@/stores/language"; import { getTmdbLanguageCode } from "@/utils/language"; -import { fetchFedSimilarItems } from "../lib/personalRecommendations"; - // Re-export types for backward compatibility export type { DiscoverContentType, @@ -331,110 +322,6 @@ export function useDiscoverMedia({ } }, [mediaType, formattedLanguage, isCarouselView]); - const fetchRecommendationsWithFedSimilar = useCallback( - async (mediaId: string) => { - const isTVShow = mediaType === "tv"; - const type = isTVShow ? TMDBContentTypes.TV : TMDBContentTypes.MOVIE; - - try { - // Try fed-similar API first - const fedSimilarIds = await fetchFedSimilarItems(mediaId, isTVShow); - - if (fedSimilarIds.length > 0) { - // Fetch full details for fed-similar items - const fedSimilarDetailPromises = fedSimilarIds - .slice(0, isCarouselView ? 20 : 100) - .map((tmdbId) => getMediaDetails(tmdbId, type)); - - const fedSimilarDetails = await Promise.allSettled( - fedSimilarDetailPromises, - ); - - const results: any[] = []; - - for (const result of fedSimilarDetails) { - if (result.status !== "fulfilled" || !result.value) continue; - const item = result.value as TMDBMovieData | TMDBShowData; - - let searchItem: TMDBMovieSearchResult | TMDBShowSearchResult; - if (isTVShow) { - const showItem = item as TMDBShowData; - searchItem = { - adult: showItem.adult ?? false, - backdrop_path: showItem.backdrop_path ?? "", - id: showItem.id, - name: showItem.name, - original_language: showItem.original_language ?? "", - original_name: showItem.original_name ?? "", - overview: showItem.overview ?? "", - poster_path: showItem.poster_path ?? "", - media_type: TMDBContentTypes.TV, - genre_ids: showItem.genres?.map((g) => g.id) ?? [], - popularity: showItem.popularity ?? 0, - first_air_date: showItem.first_air_date ?? "", - vote_average: showItem.vote_average, - vote_count: showItem.vote_count, - origin_country: showItem.origin_country ?? [], - }; - } else { - const movieItem = item as TMDBMovieData; - searchItem = { - adult: movieItem.adult ?? false, - backdrop_path: movieItem.backdrop_path ?? "", - id: movieItem.id, - title: movieItem.title, - original_language: movieItem.original_language ?? "", - original_title: movieItem.original_title ?? "", - overview: movieItem.overview ?? "", - poster_path: movieItem.poster_path ?? "", - media_type: TMDBContentTypes.MOVIE, - genre_ids: movieItem.genres?.map((g) => g.id) ?? [], - popularity: movieItem.popularity ?? 0, - release_date: movieItem.release_date ?? "", - video: movieItem.video ?? false, - vote_average: movieItem.vote_average, - vote_count: movieItem.vote_count, - }; - } - - results.push(searchItem); - } - - // If we have enough results from fed-similar, return them - const minResults = isCarouselView ? 5 : 10; - if (results.length >= minResults) { - console.info( - `Using fed-similar API results (${results.length} items)`, - ); - return { - results: results.map((item) => ({ - ...item, - type: mediaType === "movie" ? "movie" : "show", - })), - hasMore: false, - }; - } - } - - // Fall back to TMDB recommendations - console.info( - "Fed-similar API returned insufficient or no results, falling back to TMDB", - ); - const data = await fetchTMDBMedia( - `/${mediaType}/${mediaId}/recommendations`, - ); - return data; - } catch (err) { - console.error("Error fetching fed-similar recommendations:", err); - - // Try TMDB fallback on error - console.info("Attempting TMDB fallback..."); - return fetchTMDBMedia(`/${mediaType}/${mediaId}/recommendations`); - } - }, - [mediaType, isCarouselView, fetchTMDBMedia], - ); - const fetchMedia = useCallback(async () => { // Skip fetching recommendations if no ID is provided if (contentType === "recommendations" && !id) { @@ -573,7 +460,7 @@ export function useDiscoverMedia({ case "recommendations": if (!id) throw new Error("Media ID is required for recommendations"); - data = await fetchRecommendationsWithFedSimilar(id); + data = await fetchTMDBMedia(`/${mediaType}/${id}/recommendations`); setSectionTitle( t("discover.carousel.title.recommended", { title: mediaTitle }), ); @@ -598,7 +485,9 @@ export function useDiscoverMedia({ try { const data = await attemptFetch(contentType); setMedia((prevMedia) => { - const valid = data.results.filter((item: DiscoverMedia) => item.id != null); + const valid = data.results.filter( + (item: DiscoverMedia) => item.id != null, + ); return page === 1 ? valid : [...prevMedia, ...valid]; }); setHasMore(data.hasMore); @@ -613,7 +502,9 @@ export function useDiscoverMedia({ const fallbackData = await attemptFetch(fallbackType); setActualContentType(fallbackType); // Set actual content type to fallback setMedia((prevMedia) => { - const valid = fallbackData.results.filter((item: DiscoverMedia) => item.id != null); + const valid = fallbackData.results.filter( + (item: DiscoverMedia) => item.id != null, + ); return page === 1 ? valid : [...prevMedia, ...valid]; }); setHasMore(fallbackData.hasMore); @@ -637,7 +528,6 @@ export function useDiscoverMedia({ fetchTMDBMedia, fetchTraktMedia, fetchEditorPicks, - fetchRecommendationsWithFedSimilar, t, page, getTraktProviderFunction, diff --git a/src/pages/discover/hooks/useSimilarMedia.ts b/src/pages/discover/hooks/useSimilarMedia.ts index ac87a6c97..561b01408 100644 --- a/src/pages/discover/hooks/useSimilarMedia.ts +++ b/src/pages/discover/hooks/useSimilarMedia.ts @@ -1,16 +1,12 @@ import { useCallback, useEffect, useState } from "react"; -import { getMediaDetails, getRelatedMedia } from "@/backend/metadata/tmdb"; +import { getRelatedMedia } from "@/backend/metadata/tmdb"; import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; import type { - TMDBMovieData, TMDBMovieSearchResult, - TMDBShowData, TMDBShowSearchResult, } from "@/backend/metadata/types/tmdb"; -import { fetchFedSimilarItems } from "../lib/personalRecommendations"; - export function useSimilarMedia({ mediaId, mediaType, @@ -38,103 +34,16 @@ export function useSimilarMedia({ setError(null); try { - // Try fed-similar API first - const fedSimilarIds = await fetchFedSimilarItems(mediaId, isTVShow); - - if (fedSimilarIds.length > 0) { - // Fetch full details for fed-similar items - const fedSimilarDetailPromises = fedSimilarIds - .slice(0, limit) - .map((tmdbId) => getMediaDetails(tmdbId, type)); - - const fedSimilarDetails = await Promise.allSettled( - fedSimilarDetailPromises, - ); - - const results: (TMDBMovieSearchResult | TMDBShowSearchResult)[] = []; - - for (const result of fedSimilarDetails) { - if (result.status !== "fulfilled" || !result.value) continue; - const item = result.value as TMDBMovieData | TMDBShowData; - - let searchItem: TMDBMovieSearchResult | TMDBShowSearchResult; - if (isTVShow) { - const showItem = item as TMDBShowData; - searchItem = { - adult: showItem.adult ?? false, - backdrop_path: showItem.backdrop_path ?? "", - id: showItem.id, - name: showItem.name, - original_language: showItem.original_language ?? "", - original_name: showItem.original_name ?? "", - overview: showItem.overview ?? "", - poster_path: showItem.poster_path ?? "", - media_type: TMDBContentTypes.TV, - genre_ids: showItem.genres?.map((g) => g.id) ?? [], - popularity: showItem.popularity ?? 0, - first_air_date: showItem.first_air_date ?? "", - vote_average: showItem.vote_average, - vote_count: showItem.vote_count, - origin_country: showItem.origin_country ?? [], - }; - } else { - const movieItem = item as TMDBMovieData; - searchItem = { - adult: movieItem.adult ?? false, - backdrop_path: movieItem.backdrop_path ?? "", - id: movieItem.id, - title: movieItem.title, - original_language: movieItem.original_language ?? "", - original_title: movieItem.original_title ?? "", - overview: movieItem.overview ?? "", - poster_path: movieItem.poster_path ?? "", - media_type: TMDBContentTypes.MOVIE, - genre_ids: movieItem.genres?.map((g) => g.id) ?? [], - popularity: movieItem.popularity ?? 0, - release_date: movieItem.release_date ?? "", - video: movieItem.video ?? false, - vote_average: movieItem.vote_average, - vote_count: movieItem.vote_count, - }; - } - - results.push(searchItem); - } - - if (results.length >= limit / 2) { - // If we have enough results from fed-similar, use them - setMedia( - results.slice(0, limit) as - | TMDBMovieSearchResult[] - | TMDBShowSearchResult[], - ); - return; - } - } - - // Fall back to TMDB recommendations - console.info( - "Fed-similar API returned insufficient or no results, falling back to TMDB", - ); const tmdbResults = await getRelatedMedia(mediaId, type, limit); setMedia(tmdbResults); } catch (err) { console.error("Failed to load similar media:", err); - - // Try TMDB fallback on error - try { - console.info("Attempting TMDB fallback..."); - const tmdbResults = await getRelatedMedia(mediaId, type, limit); - setMedia(tmdbResults); - setError(null); - } catch (tmdbErr) { - setError((tmdbErr as Error).message); - setMedia([]); - } + setError((err as Error).message); + setMedia([]); } finally { setIsLoading(false); } - }, [mediaId, type, isTVShow, limit, enabled]); + }, [mediaId, type, limit, enabled]); useEffect(() => { fetch(); diff --git a/src/pages/discover/lib/personalRecommendations.ts b/src/pages/discover/lib/personalRecommendations.ts index 481886b72..ffacdd5b0 100644 --- a/src/pages/discover/lib/personalRecommendations.ts +++ b/src/pages/discover/lib/personalRecommendations.ts @@ -1,9 +1,7 @@ -import { getMediaDetails, getRelatedMedia } from "@/backend/metadata/tmdb"; +import { getRelatedMedia } from "@/backend/metadata/tmdb"; import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; import type { - TMDBMovieData, TMDBMovieSearchResult, - TMDBShowData, TMDBShowSearchResult, } from "@/backend/metadata/types/tmdb"; import type { DiscoverMedia } from "@/pages/discover/types/discover"; @@ -75,29 +73,9 @@ function bookmarkToDiscoverMedia(b: BookmarkSource): DiscoverMedia { }; } -/** - * Fetches similar items from the fed-similar API - */ -export async function fetchFedSimilarItems( - tmdbId: string, - isTVShow: boolean, -): Promise { - try { - const endpoint = isTVShow - ? `https://fed-similar.up.railway.app/tv/${tmdbId}` - : `https://fed-similar.up.railway.app/movie/${tmdbId}`; - const response = await fetch(endpoint); - if (!response.ok) return []; - const items = await response.json(); - return Array.isArray(items) ? items : []; - } catch (error) { - return []; - } -} - /** * Fetches personal recommendations by: - * 1. Getting related media from fed-similar API and TMDB for history, progress, and bookmark items + * 1. Getting related media from TMDB for history, progress, and bookmark items * 2. Merging and deduping, excluding items already in history/progress/bookmarks * 3. Adding up to MAX_BOOKMARK_REMINDERS bookmarked items as "reminders" */ @@ -145,104 +123,23 @@ export async function fetchPersonalRecommendations( } } - // Fetch from both fed-similar API and TMDB - const fedSimilarPromises = sourceIds.map((id) => - fetchFedSimilarItems(id, isTVShow), - ); - const tmdbPromises = sourceIds.map((id) => getRelatedMedia(id, type, RELATED_PER_ITEM_LIMIT), ); - const [fedSimilarResults, tmdbResults] = await Promise.allSettled([ - Promise.all(fedSimilarPromises), - Promise.all(tmdbPromises), - ]); + const tmdbResults = await Promise.allSettled(tmdbPromises); const merged: DiscoverMedia[] = []; const seenIds = new Set([]); - const seenFedSimilarIds = new Set(); - // Process fed-similar results first (higher priority) - if (fedSimilarResults.status === "fulfilled") { - for (const fedSimilarItems of fedSimilarResults.value) { - for (const tmdbId of fedSimilarItems) { - if (excludeIds.has(tmdbId) || seenFedSimilarIds.has(tmdbId)) { - continue; - } - seenFedSimilarIds.add(tmdbId); - } - } - - // Fetch full details for fed-similar items - const fedSimilarDetailPromises = Array.from(seenFedSimilarIds) - .slice(0, 20) - .map((tmdbId) => getMediaDetails(tmdbId, type)); + for (const result of tmdbResults) { + if (result.status !== "fulfilled") continue; - const fedSimilarDetails = await Promise.allSettled( - fedSimilarDetailPromises, - ); - - for (const result of fedSimilarDetails) { - if (result.status !== "fulfilled" || !result.value) continue; - const item = result.value as TMDBMovieData | TMDBShowData; - if (seenIds.has(item.id)) continue; + for (const item of result.value) { + const idStr = String(item.id); + if (excludeIds.has(idStr) || seenIds.has(item.id)) continue; seenIds.add(item.id); - - let searchItem: TMDBMovieSearchResult | TMDBShowSearchResult; - if (isTVShow) { - const showItem = item as TMDBShowData; - searchItem = { - adult: showItem.adult ?? false, - backdrop_path: showItem.backdrop_path ?? "", - id: showItem.id, - name: showItem.name, - original_language: showItem.original_language ?? "", - original_name: showItem.original_name ?? "", - overview: showItem.overview ?? "", - poster_path: showItem.poster_path ?? "", - media_type: TMDBContentTypes.TV, - genre_ids: showItem.genres?.map((g) => g.id) ?? [], - popularity: showItem.popularity ?? 0, - first_air_date: showItem.first_air_date ?? "", - vote_average: showItem.vote_average, - vote_count: showItem.vote_count, - origin_country: showItem.origin_country ?? [], - }; - } else { - const movieItem = item as TMDBMovieData; - searchItem = { - adult: movieItem.adult ?? false, - backdrop_path: movieItem.backdrop_path ?? "", - id: movieItem.id, - title: movieItem.title, - original_language: movieItem.original_language ?? "", - original_title: movieItem.original_title ?? "", - overview: movieItem.overview ?? "", - poster_path: movieItem.poster_path ?? "", - media_type: TMDBContentTypes.MOVIE, - genre_ids: movieItem.genres?.map((g) => g.id) ?? [], - popularity: movieItem.popularity ?? 0, - release_date: movieItem.release_date ?? "", - video: movieItem.video ?? false, - vote_average: movieItem.vote_average, - vote_count: movieItem.vote_count, - }; - } - - merged.push(toDiscoverMedia(searchItem, isTVShow)); - } - } - - // Process TMDB results (lower priority) - if (tmdbResults.status === "fulfilled") { - for (const result of tmdbResults.value) { - for (const item of result) { - const idStr = String(item.id); - if (excludeIds.has(idStr) || seenIds.has(item.id)) continue; - seenIds.add(item.id); - merged.push(toDiscoverMedia(item, isTVShow)); - } + merged.push(toDiscoverMedia(item, isTVShow)); } }