feat: add language-filtered Discover pages (#1111)

pull/1069/head^2
TheCatLady 4 years ago committed by GitHub
parent 109aca8229
commit 75011610e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3332,6 +3332,53 @@ paths:
type: array
items:
$ref: '#/components/schemas/MovieResult'
/discover/movies/language/{language}:
get:
summary: Discover movies by original language
description: Returns a list of movies based on the provided ISO 639-1 language code in a JSON object.
tags:
- search
parameters:
- in: path
name: language
required: true
schema:
type: string
example: en
- 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
language:
$ref: '#/components/schemas/SpokenLanguage'
results:
type: array
items:
$ref: '#/components/schemas/MovieResult'
/discover/movies/studio/{studioId}:
get:
summary: Discover movies by studio
@ -3467,6 +3514,53 @@ paths:
type: array
items:
$ref: '#/components/schemas/TvResult'
/discover/tv/language/{language}:
get:
summary: Discover TV shows by original language
description: Returns a list of TV shows based on the provided ISO 639-1 language code in a JSON object.
tags:
- search
parameters:
- in: path
name: language
required: true
schema:
type: string
example: en
- 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
language:
$ref: '#/components/schemas/SpokenLanguage'
results:
type: array
items:
$ref: '#/components/schemas/TvResult'
/discover/tv/genre/{genreId}:
get:
summary: Discover TV shows by genre

@ -34,6 +34,7 @@ interface DiscoverMovieOptions {
language?: string;
primaryReleaseDateGte?: string;
primaryReleaseDateLte?: string;
originalLanguage?: string;
genre?: number;
studio?: number;
sortBy?:
@ -59,6 +60,7 @@ interface DiscoverTvOptions {
firstAirDateGte?: string;
firstAirDateLte?: string;
includeEmptyReleaseDate?: boolean;
originalLanguage?: string;
genre?: number;
network?: number;
sortBy?:
@ -368,6 +370,7 @@ class TheMovieDb extends ExternalAPI {
language = 'en',
primaryReleaseDateGte,
primaryReleaseDateLte,
originalLanguage,
genre,
studio,
}: DiscoverMovieOptions = {}): Promise<TmdbSearchMovieResponse> => {
@ -379,7 +382,7 @@ class TheMovieDb extends ExternalAPI {
include_adult: includeAdult,
language,
region: this.region,
with_original_language: this.originalLanguage,
with_original_language: originalLanguage ?? this.originalLanguage,
'primary_release_date.gte': primaryReleaseDateGte,
'primary_release_date.lte': primaryReleaseDateLte,
with_genres: genre,
@ -400,6 +403,7 @@ class TheMovieDb extends ExternalAPI {
firstAirDateGte,
firstAirDateLte,
includeEmptyReleaseDate = false,
originalLanguage,
genre,
network,
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
@ -412,7 +416,7 @@ class TheMovieDb extends ExternalAPI {
region: this.region,
'first_air_date.gte': firstAirDateGte,
'first_air_date.lte': firstAirDateLte,
with_original_language: this.originalLanguage,
with_original_language: originalLanguage ?? this.originalLanguage,
include_null_first_air_dates: includeEmptyReleaseDate,
with_genres: genre,
with_networks: network,

@ -63,23 +63,25 @@ discoverRoutes.get('/movies', async (req, res) => {
});
});
discoverRoutes.get<{ genreId: string }>(
'/movies/genre/:genreId',
async (req, res) => {
discoverRoutes.get<{ language: string }>(
'/movies/language/:language',
async (req, res, next) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
const genres = await tmdb.getMovieGenres({
language: req.query.language as string,
});
const languages = await tmdb.getLanguages();
const genre = genres.find(
(genre) => genre.id === Number(req.params.genreId)
const language = languages.find(
(lang) => lang.iso_639_1 === req.params.language
);
if (!language) {
return next({ status: 404, message: 'Unable to retrieve language' });
}
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: req.query.language as string,
genre: Number(req.params.genreId),
originalLanguage: req.params.language,
});
const media = await Media.getRelatedMedia(
@ -90,7 +92,7 @@ discoverRoutes.get<{ genreId: string }>(
page: data.page,
totalPages: data.total_pages,
totalResults: data.total_results,
genre,
language,
results: data.results.map((result) =>
mapMovieResult(
result,
@ -104,17 +106,27 @@ discoverRoutes.get<{ genreId: string }>(
}
);
discoverRoutes.get<{ studioId: string }>(
'/movies/studio/:studioId',
async (req, res) => {
const tmdb = new TheMovieDb();
discoverRoutes.get<{ genreId: string }>(
'/movies/genre/:genreId',
async (req, res, next) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
const studio = await tmdb.getStudio(Number(req.params.studioId));
const genres = await tmdb.getMovieGenres({
language: req.query.language as string,
});
const genre = genres.find(
(genre) => genre.id === Number(req.params.genreId)
);
if (!genre) {
return next({ status: 404, message: 'Unable to retrieve genre' });
}
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: req.query.language as string,
studio: Number(req.params.studioId),
genre: Number(req.params.genreId),
});
const media = await Media.getRelatedMedia(
@ -125,7 +137,7 @@ discoverRoutes.get<{ studioId: string }>(
page: data.page,
totalPages: data.total_pages,
totalResults: data.total_results,
studio: mapProductionCompany(studio),
genre,
results: data.results.map((result) =>
mapMovieResult(
result,
@ -139,6 +151,45 @@ discoverRoutes.get<{ studioId: string }>(
}
);
discoverRoutes.get<{ studioId: string }>(
'/movies/studio/:studioId',
async (req, res, next) => {
const tmdb = new TheMovieDb();
try {
const studio = await tmdb.getStudio(Number(req.params.studioId));
const data = await tmdb.getDiscoverMovies({
page: Number(req.query.page),
language: req.query.language as string,
studio: Number(req.params.studioId),
});
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,
studio: mapProductionCompany(studio),
results: data.results.map((result) =>
mapMovieResult(
result,
media.find(
(med) =>
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
)
)
),
});
} catch (e) {
return next({ status: 404, message: 'Unable to retrieve studio' });
}
}
);
discoverRoutes.get('/movies/upcoming', async (req, res) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
@ -202,23 +253,25 @@ discoverRoutes.get('/tv', async (req, res) => {
});
});
discoverRoutes.get<{ genreId: string }>(
'/tv/genre/:genreId',
async (req, res) => {
discoverRoutes.get<{ language: string }>(
'/tv/language/:language',
async (req, res, next) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
const genres = await tmdb.getTvGenres({
language: req.query.language as string,
});
const languages = await tmdb.getLanguages();
const genre = genres.find(
(genre) => genre.id === Number(req.params.genreId)
const language = languages.find(
(lang) => lang.iso_639_1 === req.params.language
);
if (!language) {
return next({ status: 404, message: 'Unable to retrieve language' });
}
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: req.query.language as string,
genre: Number(req.params.genreId),
originalLanguage: req.params.language,
});
const media = await Media.getRelatedMedia(
@ -229,7 +282,7 @@ discoverRoutes.get<{ genreId: string }>(
page: data.page,
totalPages: data.total_pages,
totalResults: data.total_results,
genre,
language,
results: data.results.map((result) =>
mapTvResult(
result,
@ -242,17 +295,27 @@ discoverRoutes.get<{ genreId: string }>(
}
);
discoverRoutes.get<{ networkId: string }>(
'/tv/network/:networkId',
async (req, res) => {
const tmdb = new TheMovieDb();
discoverRoutes.get<{ genreId: string }>(
'/tv/genre/:genreId',
async (req, res, next) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
const network = await tmdb.getNetwork(Number(req.params.networkId));
const genres = await tmdb.getTvGenres({
language: req.query.language as string,
});
const genre = genres.find(
(genre) => genre.id === Number(req.params.genreId)
);
if (!genre) {
return next({ status: 404, message: 'Unable to retrieve genre' });
}
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: req.query.language as string,
network: Number(req.params.networkId),
genre: Number(req.params.genreId),
});
const media = await Media.getRelatedMedia(
@ -263,7 +326,7 @@ discoverRoutes.get<{ networkId: string }>(
page: data.page,
totalPages: data.total_pages,
totalResults: data.total_results,
network: mapNetwork(network),
genre,
results: data.results.map((result) =>
mapTvResult(
result,
@ -276,6 +339,45 @@ discoverRoutes.get<{ networkId: string }>(
}
);
discoverRoutes.get<{ networkId: string }>(
'/tv/network/:networkId',
async (req, res, next) => {
const tmdb = new TheMovieDb();
try {
const network = await tmdb.getNetwork(Number(req.params.networkId));
const data = await tmdb.getDiscoverTv({
page: Number(req.query.page),
language: req.query.language as string,
network: Number(req.params.networkId),
});
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,
network: mapNetwork(network),
results: data.results.map((result) =>
mapTvResult(
result,
media.find(
(med) =>
med.tmdbId === result.id && med.mediaType === MediaType.TV
)
)
),
});
} catch (e) {
return next({ status: 404, message: 'Unable to retrieve network' });
}
}
);
discoverRoutes.get('/tv/upcoming', async (req, res) => {
const tmdb = createTmdbWithRegionLanaguage(req.user);
@ -331,15 +433,18 @@ discoverRoutes.get('/trending', async (req, res) => {
? mapMovieResult(
result,
media.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
(med) =>
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
)
)
: isPerson(result)
? mapPersonResult(result)
: mapTvResult(
result,
media.find((req) => req.tmdbId === result.id && MediaType.TV)
media.find(
(med) =>
med.tmdbId === result.id && med.mediaType === MediaType.TV
)
)
),
});
@ -368,8 +473,8 @@ discoverRoutes.get<{ keywordId: string }>(
mapMovieResult(
result,
media.find(
(req) =>
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
(med) =>
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
)
)
),

@ -0,0 +1,71 @@
import React from 'react';
import type { MovieResult } from '../../../../server/models/Search';
import ListView from '../../Common/ListView';
import { defineMessages, useIntl } from 'react-intl';
import Header from '../../Common/Header';
import PageTitle from '../../Common/PageTitle';
import { useRouter } from 'next/router';
import globalMessages from '../../../i18n/globalMessages';
import useDiscover from '../../../hooks/useDiscover';
import Error from '../../../pages/_error';
const messages = defineMessages({
languageMovies: '{language} Movies',
});
const DiscoverMovieLanguage: React.FC = () => {
const router = useRouter();
const intl = useIntl();
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<
MovieResult,
{
originalLanguage: {
iso_639_1: string;
english_name: string;
name: string;
};
}
>(`/api/v1/discover/movies/language/${router.query.language}`);
if (error) {
return <Error statusCode={500} />;
}
const title = isLoadingInitialData
? intl.formatMessage(globalMessages.loading)
: intl.formatMessage(messages.languageMovies, {
language: intl.formatDisplayName(router.query.language as string, {
type: 'language',
fallback: 'none',
}),
});
return (
<>
<PageTitle title={title} />
<div className="mt-1 mb-5">
<Header>{title}</Header>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
isReachingEnd={isReachingEnd}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverMovieLanguage;

@ -0,0 +1,71 @@
import React from 'react';
import type { TvResult } from '../../../../server/models/Search';
import ListView from '../../Common/ListView';
import { defineMessages, useIntl } from 'react-intl';
import Header from '../../Common/Header';
import PageTitle from '../../Common/PageTitle';
import { useRouter } from 'next/router';
import globalMessages from '../../../i18n/globalMessages';
import useDiscover from '../../../hooks/useDiscover';
import Error from '../../../pages/_error';
const messages = defineMessages({
languageSeries: '{language} Series',
});
const DiscoverTvLanguage: React.FC = () => {
const router = useRouter();
const intl = useIntl();
const {
isLoadingInitialData,
isEmpty,
isLoadingMore,
isReachingEnd,
titles,
fetchMore,
error,
} = useDiscover<
TvResult,
{
originalLanguage: {
iso_639_1: string;
english_name: string;
name: string;
};
}
>(`/api/v1/discover/tv/language/${router.query.language}`);
if (error) {
return <Error statusCode={500} />;
}
const title = isLoadingInitialData
? intl.formatMessage(globalMessages.loading)
: intl.formatMessage(messages.languageSeries, {
language: intl.formatDisplayName(router.query.language as string, {
type: 'language',
fallback: 'none',
}),
});
return (
<>
<PageTitle title={title} />
<div className="mt-1 mb-5">
<Header>{title}</Header>
</div>
<ListView
items={titles}
isEmpty={isEmpty}
isLoading={
isLoadingInitialData || (isLoadingMore && (titles?.length ?? 0) > 0)
}
isReachingEnd={isReachingEnd}
onScrollBottom={fetchMore}
/>
</>
);
};
export default DiscoverTvLanguage;

@ -651,19 +651,23 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
</span>
</div>
)}
{data.spokenLanguages.some(
(lng) => lng.iso_639_1 === data.originalLanguage
) && (
{data.originalLanguage && (
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0">
<span className="text-sm">
{intl.formatMessage(messages.originallanguage)}
</span>
<span className="flex-1 text-sm text-right text-gray-400">
{
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
)?.name
}
<Link href={`/discover/tv/language/${data.originalLanguage}`}>
<a className="hover:underline">
{intl.formatDisplayName(data.originalLanguage, {
type: 'language',
fallback: 'none',
}) ??
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
)?.name}
</a>
</Link>
</span>
</div>
)}

@ -675,19 +675,23 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
{data.status}
</span>
</div>
{data.spokenLanguages.some(
(lng) => lng.iso_639_1 === data.originalLanguage
) && (
{data.originalLanguage && (
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0">
<span className="text-sm">
{intl.formatMessage(messages.originallanguage)}
</span>
<span className="flex-1 text-sm text-right text-gray-400">
{
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
)?.name
}
<Link href={`/discover/tv/language/${data.originalLanguage}`}>
<a className="hover:underline">
{intl.formatDisplayName(data.originalLanguage, {
type: 'language',
fallback: 'none',
}) ??
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
)?.name}
</a>
</Link>
</span>
</div>
)}

@ -15,9 +15,11 @@
"components.CollectionDetails.requestswillbecreated4k": "The following titles will have 4K requests created for them:",
"components.Common.ListView.noresults": "No results.",
"components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Movies",
"components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Movies",
"components.Discover.DiscoverNetwork.networkSeries": "{network} Series",
"components.Discover.DiscoverStudio.studioMovies": "{studio} Movies",
"components.Discover.DiscoverTvGenre.genreSeries": "{genre} Series",
"components.Discover.DiscoverTvLanguage.languageSeries": "{language} Series",
"components.Discover.NetworkSlider.networks": "Networks",
"components.Discover.StudioSlider.studios": "Studios",
"components.Discover.discover": "Discover",

@ -0,0 +1,9 @@
import React from 'react';
import { NextPage } from 'next';
import DiscoverMovieLanguage from '../../../../../components/Discover/DiscoverMovieLanguage';
const DiscoverMovieLanguagePage: NextPage = () => {
return <DiscoverMovieLanguage />;
};
export default DiscoverMovieLanguagePage;

@ -0,0 +1,9 @@
import React from 'react';
import { NextPage } from 'next';
import DiscoverTvLanguage from '../../../../../components/Discover/DiscoverTvLanguage';
const DiscoverTvLanguagePage: NextPage = () => {
return <DiscoverTvLanguage />;
};
export default DiscoverTvLanguagePage;
Loading…
Cancel
Save