From 32d9cd040c9e784aae82a2c22fea7d368b9b27c4 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Sun, 10 Aug 2025 12:44:48 -0600 Subject: [PATCH] update all movie lists --- src/backend/metadata/letterboxd.ts | 1 + src/backend/metadata/traktApi.ts | 118 +++++++++++++++++++++++++++ src/pages/discover/AllMovieLists.tsx | 55 +++++++++---- 3 files changed, 159 insertions(+), 15 deletions(-) diff --git a/src/backend/metadata/letterboxd.ts b/src/backend/metadata/letterboxd.ts index 4c216a52..6278fde3 100644 --- a/src/backend/metadata/letterboxd.ts +++ b/src/backend/metadata/letterboxd.ts @@ -1,3 +1,4 @@ +// This endpoint is not used anymore, but we keep it here for reference or if we feel like fixing the backend import { conf } from "@/utils/setup/config"; export interface TmdbMovie { diff --git a/src/backend/metadata/traktApi.ts b/src/backend/metadata/traktApi.ts index 9a170c07..a54f7aee 100644 --- a/src/backend/metadata/traktApi.ts +++ b/src/backend/metadata/traktApi.ts @@ -1,4 +1,6 @@ +import { getMediaDetails } from "./tmdb"; import { MWMediaType } from "./types/mw"; +import { TMDBContentTypes, TMDBMovieData } from "./types/tmdb"; export interface TraktLatestResponse { tmdb_ids: number[]; @@ -41,6 +43,13 @@ export interface TraktNetworkResponse { count: number; } +export interface CuratedMovieList { + listName: string; + listSlug: string; + tmdbIds: number[]; + count: number; +} + // Pagination utility export function paginateResults( results: TraktLatestResponse, @@ -116,6 +125,115 @@ export const getDiscoverContent = () => export const getNetworkContent = (tmdbId: string) => fetchFromTrakt(`/network/${tmdbId}`); +// Curated movie lists (replacing Letterboxd functionality) +export const getNarrativeMovies = () => fetchFromTrakt("/narrative"); +export const getTopMovies = () => fetchFromTrakt("/top"); +export const getLifetimeMovies = () => fetchFromTrakt("/lifetime"); +export const getNeverHeardMovies = () => fetchFromTrakt("/never"); +export const getLGBTQContent = () => fetchFromTrakt("/LGBTQ"); +export const getMindfuckMovies = () => fetchFromTrakt("/mindfuck"); +export const getTrueStoryMovies = () => fetchFromTrakt("/truestory"); +export const getGreatestTVShows = () => fetchFromTrakt("/greatesttv"); + +// Get all curated movie lists +export const getCuratedMovieLists = async (): Promise => { + const listConfigs = [ + { + name: "Letterboxd Top 250 Narrative Feature Films", + slug: "narrative", + endpoint: "/narrative", + }, + { + name: "1001 Greatest Movies of All Time", + slug: "top", + endpoint: "/top", + }, + { + name: "1001 Movies You Must See Before You Die", + slug: "lifetime", + endpoint: "/lifetime", + }, + { + name: "Great Movies You May Have Never Heard Of", + slug: "never", + endpoint: "/never", + }, + { + name: "LGBT Movies/Shows", + slug: "LGBTQ", + endpoint: "/LGBTQ", + }, + { + name: "Best Mindfuck Movies", + slug: "mindfuck", + endpoint: "/mindfuck", + }, + { + name: "Based on a True Story Movies", + slug: "truestory", + endpoint: "/truestory", + }, + { + name: "Rolling Stone's 100 Greatest TV Shows", + slug: "greatesttv", + endpoint: "/greatesttv", + }, + ]; + + const lists: CuratedMovieList[] = []; + + for (const config of listConfigs) { + try { + const response = await fetchFromTrakt(config.endpoint); + lists.push({ + listName: config.name, + listSlug: config.slug, + tmdbIds: response.tmdb_ids.slice(0, 30), // Limit to first 30 items + count: Math.min(response.count, 30), // Update count to reflect the limit + }); + } catch (error) { + console.error(`Failed to fetch ${config.name}:`, error); + } + } + + return lists; +}; + +// Fetch movie details for multiple TMDB IDs +export const getMovieDetailsForIds = async ( + tmdbIds: number[], + limit: number = 50, +): Promise => { + const limitedIds = tmdbIds.slice(0, limit); + const movieDetails: TMDBMovieData[] = []; + + // Process in smaller batches to avoid overwhelming the API + const batchSize = 10; + for (let i = 0; i < limitedIds.length; i += batchSize) { + const batch = limitedIds.slice(i, i + batchSize); + const batchPromises = batch.map(async (id) => { + try { + const details = await getMediaDetails( + id.toString(), + TMDBContentTypes.MOVIE, + ); + return details as TMDBMovieData; + } catch (error) { + console.error(`Failed to fetch movie details for ID ${id}:`, error); + return null; + } + }); + + const batchResults = await Promise.all(batchPromises); + const validResults = batchResults.filter( + (result): result is TMDBMovieData => result !== null, + ); + movieDetails.push(...validResults); + } + + return movieDetails; +}; + // Type conversion utilities export function convertToMediaType(type: TraktContentType): MWMediaType { return type === "movie" ? MWMediaType.MOVIE : MWMediaType.SERIES; diff --git a/src/pages/discover/AllMovieLists.tsx b/src/pages/discover/AllMovieLists.tsx index 99e4553d..90ebd394 100644 --- a/src/pages/discover/AllMovieLists.tsx +++ b/src/pages/discover/AllMovieLists.tsx @@ -2,7 +2,12 @@ import { t } from "i18next"; import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { TmdbMovie, getLetterboxdLists } from "@/backend/metadata/letterboxd"; +import { + CuratedMovieList, + getCuratedMovieLists, + getMovieDetailsForIds, +} from "@/backend/metadata/traktApi"; +import { TMDBMovieData } from "@/backend/metadata/types/tmdb"; import { Icon, Icons } from "@/components/Icon"; import { WideContainer } from "@/components/layout/WideContainer"; import { MediaCard } from "@/components/media/MediaCard"; @@ -19,29 +24,49 @@ import { MediaCarousel } from "./components/MediaCarousel"; export function DiscoverMore() { const [detailsData, setDetailsData] = useState(); - const [letterboxdLists, setLetterboxdLists] = useState([]); + const [curatedLists, setCuratedLists] = useState([]); + const [movieDetails, setMovieDetails] = useState<{ + [listSlug: string]: TMDBMovieData[]; + }>({}); const detailsModal = useModal("discover-details"); const carouselRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); const navigate = useNavigate(); const { lastView } = useDiscoverStore(); const { isMobile } = useIsMobile(); - // Track overflow states for Letterboxd lists + // Track overflow states for curated lists const [overflowStates, setOverflowStates] = useState<{ [key: string]: boolean; }>({}); useEffect(() => { - const fetchLetterboxdLists = async () => { + const fetchCuratedLists = async () => { try { - const response = await getLetterboxdLists(); - setLetterboxdLists(response.lists); + const lists = await getCuratedMovieLists(); + setCuratedLists(lists); + + // Fetch movie details for each list + const details: { [listSlug: string]: TMDBMovieData[] } = {}; + for (const list of lists) { + try { + const movies = await getMovieDetailsForIds(list.tmdbIds, 50); + if (movies.length > 0) { + details[list.listSlug] = movies; + } + } catch (error) { + console.error( + `Failed to fetch movies for list ${list.listSlug}:`, + error, + ); + } + } + setMovieDetails(details); } catch (error) { - console.error("Failed to fetch Letterboxd lists:", error); + console.error("Failed to fetch curated lists:", error); } }; - fetchLetterboxdLists(); + fetchCuratedLists(); }, []); const handleShowDetails = async (media: MediaItem) => { @@ -143,9 +168,9 @@ export function DiscoverMore() { /> - {/* Letterboxd Lists */} - {letterboxdLists.map((list) => ( -
+ {/* Curated Movie Lists */} + {curatedLists.map((list) => ( +
@@ -158,11 +183,11 @@ export function DiscoverMore() {
setCarouselRef(el, list.listUrl)} + ref={(el) => setCarouselRef(el, list.listSlug)} onWheel={handleWheel} >
- {list.tmdbMovies.map((movie: TmdbMovie) => ( + {movieDetails[list.listSlug]?.map((movie: TMDBMovieData) => (
{!isMobile && ( )}