From f1d97d48927b3d366f3ee874ab54dd0f00e1dd04 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:28:57 -0600 Subject: [PATCH] add ratings to featured carousel --- .../discover/components/FeaturedCarousel.tsx | 168 ++++++++++++++++-- 1 file changed, 152 insertions(+), 16 deletions(-) diff --git a/src/pages/discover/components/FeaturedCarousel.tsx b/src/pages/discover/components/FeaturedCarousel.tsx index 63a8b31a..defd32c3 100644 --- a/src/pages/discover/components/FeaturedCarousel.tsx +++ b/src/pages/discover/components/FeaturedCarousel.tsx @@ -4,6 +4,7 @@ import { ReactNode, useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useWindowSize } from "react-use"; +import { isExtensionActive } from "@/backend/extension/messaging"; import { get, getMediaLogo } from "@/backend/metadata/tmdb"; import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; import { Button } from "@/components/buttons/Button"; @@ -13,6 +14,7 @@ import { conf } from "@/setup/config"; import { useDiscoverStore } from "@/stores/discover"; import { useLanguageStore } from "@/stores/language"; import { usePreferencesStore } from "@/stores/preferences"; +import { scrapeIMDb } from "@/utils/imdbScraper"; import { getTmdbLanguageCode } from "@/utils/language"; import { EDITOR_PICKS_MOVIES, EDITOR_PICKS_TV_SHOWS } from "../discoverContent"; @@ -25,6 +27,14 @@ export interface FeaturedMedia extends Partial { title?: string; name?: string; type: "movie" | "show"; + vote_average?: number; + vote_count?: number; + number_of_seasons?: number; + imdb_rating?: number; + imdb_votes?: number; + external_ids?: { + imdb_id?: string; + }; } interface FeaturedCarouselProps { @@ -35,6 +45,11 @@ interface FeaturedCarouselProps { forcedCategory?: "movies" | "tvshows" | "editorpicks"; } +interface IMDbRatingData { + rating: number; + votes: number; +} + function FeaturedCarouselSkeleton({ shorter }: { shorter?: boolean }) { return (
(); const [touchStart, setTouchStart] = useState(null); const [touchEnd, setTouchEnd] = useState(null); + const [imdbRatings, setImdbRatings] = useState< + Record + >({}); + const hasExtension = useRef(false); const logoFetchController = useRef(null); const autoPlayInterval = useRef(null); const navigate = useNavigate(); @@ -119,12 +138,52 @@ export function FeaturedCarousel({ const formattedLanguage = getTmdbLanguageCode(userLanguage); const { width: windowWidth, height: windowHeight } = useWindowSize(); + const currentMedia = media[currentIndex]; + const SLIDE_QUANTITY = 10; const FETCH_QUANTITY = 20; const SLIDE_QUANTITY_EDITOR_PICKS_MOVIES = 6; const SLIDE_QUANTITY_EDITOR_PICKS_TV_SHOWS = 4; const SLIDE_DURATION = 8000; + // Check for extension on mount + useEffect(() => { + isExtensionActive().then((active) => { + hasExtension.current = active; + }); + }, []); + + // Fetch IMDb ratings when media changes + useEffect(() => { + const fetchImdbRatings = async () => { + if (!hasExtension.current || !currentMedia?.external_ids?.imdb_id) return; + + try { + const imdbData = await scrapeIMDb(currentMedia.external_ids.imdb_id); + // Only update if we have both rating and votes as non-null numbers + if ( + typeof imdbData.imdb_rating === "number" && + typeof imdbData.votes === "number" + ) { + const ratingData: IMDbRatingData = { + rating: imdbData.imdb_rating, + votes: imdbData.votes, + }; + setImdbRatings((prev) => ({ + ...prev, + [currentMedia.external_ids!.imdb_id!]: ratingData, + })); + } + } catch (error) { + console.error("Error fetching IMDb ratings:", error); + } + }; + + if (currentMedia) { + fetchImdbRatings(); + } + }, [currentMedia]); + useEffect(() => { const fetchFeaturedMedia = async () => { setIsLoading(true); @@ -134,32 +193,56 @@ export function FeaturedCarousel({ } try { if (effectiveCategory === "movies") { - const data = await get("/movie/popular", { + // First get the list of popular movies + const listData = await get("/movie/popular", { api_key: conf().TMDB_READ_API_KEY, language: formattedLanguage, }); - // Fetch movie items and randomly select - const allMovies = data.results + + // Then fetch full details for each movie to get external_ids + const moviePromises = listData.results .slice(0, FETCH_QUANTITY) - .map((movie: any) => ({ - ...movie, - type: "movie" as const, - })); + .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") { - const data = await get("/tv/popular", { + // First get the list of popular shows + const listData = await get("/tv/popular", { api_key: conf().TMDB_READ_API_KEY, language: formattedLanguage, }); - // Fetch show items - const allShows = data.results + + // Then fetch full details for each show to get external_ids + const showPromises = listData.results .slice(0, FETCH_QUANTITY) - .map((show: any) => ({ - ...show, - type: "show" as const, - })); + .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)); @@ -192,6 +275,7 @@ export function FeaturedCarousel({ get(`/movie/${id}`, { api_key: conf().TMDB_READ_API_KEY, language: formattedLanguage, + append_to_response: "external_ids", }), ); @@ -199,6 +283,7 @@ export function FeaturedCarousel({ get(`/tv/${id}`, { api_key: conf().TMDB_READ_API_KEY, language: formattedLanguage, + append_to_response: "external_ids", }), ); @@ -358,7 +443,6 @@ export function FeaturedCarousel({ return ; } - const currentMedia = media[currentIndex]; const mediaTitle = currentMedia.title || currentMedia.name; let searchClasses = ""; @@ -470,7 +554,7 @@ export function FeaturedCarousel({ searchClasses, )} > -
+
{logoUrl && enableImageLogos ? ( )} + {/* TMDB Rating and Year/Seasons */} +
+ {currentMedia?.vote_average && ( +
+ + {currentMedia.vote_average.toFixed(1)} + {currentMedia.vote_count && ( + + ({currentMedia.vote_count.toLocaleString()}) + + )} +
+ )} + {currentMedia?.external_ids?.imdb_id && + imdbRatings[currentMedia.external_ids.imdb_id] && ( + <> + +
+ + + {imdbRatings[ + currentMedia.external_ids.imdb_id + ].rating.toFixed(1)} + + + ( + {imdbRatings[ + currentMedia.external_ids.imdb_id + ].votes.toLocaleString()} + ) + +
+ + )} + {currentMedia?.release_date && ( + <> + + + {new Date(currentMedia.release_date).getFullYear()} + + + )} + {currentMedia?.type === "show" && + currentMedia?.number_of_seasons && ( + <> + + + {currentMedia.number_of_seasons} {t("details.seasons")} + + + )} +

{currentMedia.overview}