Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 8 additions & 118 deletions src/pages/discover/hooks/useDiscoverMedia.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 }),
);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -637,7 +528,6 @@ export function useDiscoverMedia({
fetchTMDBMedia,
fetchTraktMedia,
fetchEditorPicks,
fetchRecommendationsWithFedSimilar,
t,
page,
getTraktProviderFunction,
Expand Down
99 changes: 4 additions & 95 deletions src/pages/discover/hooks/useSimilarMedia.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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();
Expand Down
Loading
Loading