diff --git a/overseerr-api.yml b/overseerr-api.yml index 00e1cf03a..8422a8916 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -3584,6 +3584,12 @@ paths: tags: - search parameters: + - in: query + name: sortBy + schema: + type: string + enum: [popularity.asc, popularity.desc, release_date.asc, release_date.desc, revenue.asc, revenue.desc, primary_release_date.asc, primary_release_date.desc, original_title.asc, original_title.desc, vote_average.asc, vote_average.desc, vote_count.asc, vote_count.desc] + default: popularity.desc - in: query name: page schema: @@ -3813,6 +3819,12 @@ paths: tags: - search parameters: + - in: query + name: sortBy + schema: + type: string + enum: [popularity.asc, popularity.desc, vote_average.asc, vote_average.desc, vote_count.asc, vote_count.desc, first_air_date.asc, first_air_date.desc] + default: popularity.desc - in: query name: page schema: @@ -4186,6 +4198,45 @@ paths: name: type: string example: Genre Name + /discover/people: + get: + summary: Discover people + description: Returns a list of people in a JSON object. + 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: + $ref: '#/components/schemas/PersonResult' /request: get: summary: Get all requests diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index ddc180592..f000fc17e 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -15,6 +15,7 @@ import { TmdbRegion, TmdbSearchMovieResponse, TmdbSearchMultiResponse, + TmdbSearchPeopleResponse, TmdbSearchTvResponse, TmdbSeasonWithEpisodes, TmdbTvDetails, @@ -801,6 +802,27 @@ class TheMovieDb extends ExternalAPI { throw new Error(`[TMDb] Failed to fetch TV genres: ${e.message}`); } } + + public async getDiscoverPeople({ + page = 1, + language = 'en', + }: { + page?: number; + language?: string; + }): Promise { + try { + const data = await this.get('/person/popular', { + params: { + page, + language, + }, + }); + + return data; + } catch (e) { + throw new Error(`[TMDb] Failed to fetch popular people: ${e.message}`); + } + } } export default TheMovieDb; diff --git a/server/api/themoviedb/interfaces.ts b/server/api/themoviedb/interfaces.ts index bd3c2d8be..332799e80 100644 --- a/server/api/themoviedb/interfaces.ts +++ b/server/api/themoviedb/interfaces.ts @@ -56,6 +56,10 @@ export interface TmdbSearchTvResponse extends TmdbPaginatedResponse { results: TmdbTvResult[]; } +export interface TmdbSearchPeopleResponse extends TmdbPaginatedResponse { + results: TmdbPersonResult[]; +} + export interface TmdbUpcomingMoviesResponse extends TmdbPaginatedResponse { dates: { maximum: string; diff --git a/server/models/Search.ts b/server/models/Search.ts index 0dab4e587..45f710b84 100644 --- a/server/models/Search.ts +++ b/server/models/Search.ts @@ -103,7 +103,7 @@ export const mapPersonResult = ( name: personResult.name, popularity: personResult.popularity, adult: personResult.adult, - mediaType: personResult.media_type, + mediaType: personResult.media_type || 'person', profilePath: personResult.profile_path, knownFor: personResult.known_for.map((result) => { if (result.media_type === 'movie') { diff --git a/server/routes/discover.ts b/server/routes/discover.ts index 16654e814..668d61e10 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -567,4 +567,22 @@ discoverRoutes.get<{ language: string }, GenreSliderItem[]>( } ); +discoverRoutes.get('/people', async (req, res) => { + const tmdb = new TheMovieDb(); + + const data = await tmdb.getDiscoverPeople({ + page: Number(req.query.page), + language: req.locale ?? (req.query.language as string), + }); + + return res.status(200).json({ + page: data.page, + totalPages: data.total_pages, + totalResults: data.total_results, + results: data.results + .map((result) => mapPersonResult(result)) + .filter((item) => !item.adult), + }); +}); + export default discoverRoutes; diff --git a/src/components/Discover/DiscoverPeople.tsx b/src/components/Discover/DiscoverPeople.tsx new file mode 100644 index 000000000..9466aae7f --- /dev/null +++ b/src/components/Discover/DiscoverPeople.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import type { PersonResult } from '../../../server/models/Search'; +import useDiscover from '../../hooks/useDiscover'; +import Error from '../../pages/_error'; +import Header from '../Common/Header'; +import ListView from '../Common/ListView'; +import PageTitle from '../Common/PageTitle'; + +const messages = defineMessages({ + popularpeople: 'Popular People', +}); + +const DiscoverPeople: React.FC = () => { + const intl = useIntl(); + const { + isLoadingInitialData, + isEmpty, + isLoadingMore, + isReachingEnd, + titles, + fetchMore, + error, + } = useDiscover('/api/v1/discover/people'); + + if (error) { + return ; + } + + return ( + <> + +
+
{intl.formatMessage(messages.popularpeople)}
+
+ 0) + } + isReachingEnd={isReachingEnd} + onScrollBottom={fetchMore} + /> + + ); +}; + +export default DiscoverPeople; diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index 3ebd6226c..39c0acf01 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -25,6 +25,7 @@ const messages = defineMessages({ noRequests: 'No requests.', upcoming: 'Upcoming Movies', trending: 'Trending', + popularpeople: 'Popular People', }); const Discover: React.FC = () => { @@ -116,6 +117,12 @@ const Discover: React.FC = () => { linkUrl="/discover/tv/upcoming" /> + ); }; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 74bec8f87..f9e2f6d8e 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -24,6 +24,7 @@ "components.Discover.discovertv": "Popular Series", "components.Discover.noRequests": "No requests.", "components.Discover.popularmovies": "Popular Movies", + "components.Discover.popularpeople": "Popular People", "components.Discover.populartv": "Popular Series", "components.Discover.recentlyAdded": "Recently Added", "components.Discover.recentrequests": "Recent Requests", diff --git a/src/pages/discover/people/index.tsx b/src/pages/discover/people/index.tsx new file mode 100644 index 000000000..29c77c763 --- /dev/null +++ b/src/pages/discover/people/index.tsx @@ -0,0 +1,9 @@ +import { NextPage } from 'next'; +import React from 'react'; +import DiscoverPeople from '../../../components/Discover/DiscoverPeople'; + +const DiscoverPeoplePage: NextPage = () => { + return ; +}; + +export default DiscoverPeoplePage;