diff --git a/overseerr-api.yml b/overseerr-api.yml index 2e2278fd7..5f00fb5d9 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1531,6 +1531,48 @@ paths: type: array items: $ref: '#/components/schemas/TvResult' + /discover/trending: + get: + summary: Trending TV and Movies + description: Returns a list of movie/tv shows in JSON format + tags: + - search + parameters: + - in: query + name: page + schema: + type: number + example: 1 + default: 1 + - in: query + name: language + schema: + type: string + example: en + responses: + '200': + description: Results + content: + application/json: + schema: + type: object + properties: + page: + type: number + example: 1 + totalPages: + type: number + example: 20 + totalResults: + type: number + example: 200 + results: + type: array + items: + anyOf: + - $ref: '#/components/schemas/MovieResult' + - $ref: '#/components/schemas/TvResult' + - $ref: '#/components/schemas/PersonResult' /request: get: summary: Get all requests diff --git a/server/api/themoviedb.ts b/server/api/themoviedb.ts index e4bad31f0..f83829c49 100644 --- a/server/api/themoviedb.ts +++ b/server/api/themoviedb.ts @@ -556,15 +556,19 @@ class TheMovieDb { public getAllTrending = async ({ page = 1, timeWindow = 'day', - }: { page?: number; timeWindow?: 'day' | 'week' } = {}): Promise< - TmdbSearchMultiResponse - > => { + language = 'en-US', + }: { + page?: number; + timeWindow?: 'day' | 'week'; + language?: string; + } = {}): Promise => { try { const response = await this.axios.get( `/trending/all/${timeWindow}`, { params: { page, + language, }, } ); diff --git a/server/routes/discover.ts b/server/routes/discover.ts index edcef169c..ab156ebf7 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -1,8 +1,24 @@ import { Router } from 'express'; -import TheMovieDb from '../api/themoviedb'; -import { mapMovieResult, mapTvResult } from '../models/Search'; +import TheMovieDb, { + TmdbMovieResult, + TmdbTvResult, + TmdbPersonResult, +} from '../api/themoviedb'; +import { mapMovieResult, mapTvResult, mapPersonResult } from '../models/Search'; import Media from '../entity/Media'; +const isMovie = ( + movie: TmdbMovieResult | TmdbTvResult | TmdbPersonResult +): movie is TmdbMovieResult => { + return (movie as TmdbMovieResult).title !== undefined; +}; + +const isPerson = ( + person: TmdbMovieResult | TmdbTvResult | TmdbPersonResult +): person is TmdbPersonResult => { + return (person as TmdbPersonResult).known_for !== undefined; +}; + const discoverRoutes = Router(); discoverRoutes.get('/movies', async (req, res) => { @@ -80,4 +96,36 @@ discoverRoutes.get('/tv', async (req, res) => { }); }); +discoverRoutes.get('/trending', async (req, res) => { + const tmdb = new TheMovieDb(); + + const data = await tmdb.getAllTrending({ + page: Number(req.query.page), + language: req.query.language as string, + }); + + const media = await Media.getRelatedMedia( + data.results.map((result) => result.id) + ); + + return res.status(200).json({ + page: data.page, + totalPages: data.total_pages, + totalResults: data.total_results, + results: data.results.map((result) => + isMovie(result) + ? mapMovieResult( + result, + media.find((req) => req.tmdbId === result.id) + ) + : isPerson(result) + ? mapPersonResult(result) + : mapTvResult( + result, + media.find((req) => req.tmdbId === result.id) + ) + ), + }); +}); + export default discoverRoutes; diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index b5a284735..41d63a418 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -1,7 +1,12 @@ import React, { useContext } from 'react'; import useSWR from 'swr'; -import type { MovieResult, TvResult } from '../../../server/models/Search'; +import type { + MovieResult, + TvResult, + PersonResult, +} from '../../../server/models/Search'; import TitleCard from '../TitleCard'; +import PersonCard from '../PersonCard'; import { MediaRequest } from '../../../server/entity/MediaRequest'; import RequestCard from '../TitleCard/RequestCard'; import Slider from '../Slider'; @@ -18,6 +23,7 @@ const messages = defineMessages({ recentlyAdded: 'Recently Added', nopending: 'No Pending Requests', upcoming: 'Upcoming Movies', + trending: 'Trending', }); interface MovieDiscoverResult { @@ -34,6 +40,13 @@ interface TvDiscoverResult { results: TvResult[]; } +interface MixedResult { + page: number; + totalResults: number; + totalPages: number; + results: (TvResult | MovieResult | PersonResult)[]; +} + const Discover: React.FC = () => { const intl = useIntl(); const { locale } = useContext(LanguageContext); @@ -48,6 +61,10 @@ const Discover: React.FC = () => { MovieDiscoverResult >(`/api/v1/discover/movies/upcoming?language=${locale}`); + const { data: trendingData, error: trendingError } = useSWR( + `/api/v1/discover/trending?language=${locale}` + ); + const { data: media, error: mediaError } = useSWR( '/api/v1/media?filter=available&take=20&sort=modified' ); @@ -84,7 +101,7 @@ const Discover: React.FC = () => { ( @@ -159,7 +176,7 @@ const Discover: React.FC = () => { ( @@ -176,6 +193,70 @@ const Discover: React.FC = () => { /> ))} /> +
+ +
+ { + switch (title.mediaType) { + case 'movie': + return ( + + ); + case 'tv': + return ( + + ); + case 'person': + return ( + + ); + } + })} + />