feat(api): add excludeKeywords parameter to discovery queries (#1908)

Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me>
This commit is contained in:
0xsysr3ll
2025-09-16 21:32:39 +02:00
committed by GitHub
parent e9f2f4490f
commit cd479d0d17
6 changed files with 43 additions and 0 deletions

View File

@@ -5198,6 +5198,12 @@ paths:
schema:
type: string
example: 1,2
- in: query
name: excludeKeywords
schema:
type: string
example: 3,4
description: Comma-separated list of keyword IDs to exclude from results
- in: query
name: sortBy
schema:
@@ -5518,6 +5524,12 @@ paths:
schema:
type: string
example: 1,2
- in: query
name: excludeKeywords
schema:
type: string
example: 3,4
description: Comma-separated list of keyword IDs to exclude from results
- in: query
name: sortBy
schema:

View File

@@ -86,6 +86,7 @@ interface DiscoverMovieOptions {
genre?: string;
studio?: string;
keywords?: string;
excludeKeywords?: string;
sortBy?: SortOptions;
watchRegion?: string;
watchProviders?: string;
@@ -111,6 +112,7 @@ interface DiscoverTvOptions {
genre?: string;
network?: number;
keywords?: string;
excludeKeywords?: string;
sortBy?: SortOptions;
watchRegion?: string;
watchProviders?: string;
@@ -495,6 +497,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
genre,
studio,
keywords,
excludeKeywords,
withRuntimeGte,
withRuntimeLte,
voteAverageGte,
@@ -545,6 +548,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
with_genres: genre,
with_companies: studio,
with_keywords: keywords,
without_keywords: excludeKeywords,
'with_runtime.gte': withRuntimeGte,
'with_runtime.lte': withRuntimeLte,
'vote_average.gte': voteAverageGte,
@@ -577,6 +581,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
genre,
network,
keywords,
excludeKeywords,
withRuntimeGte,
withRuntimeLte,
voteAverageGte,
@@ -628,6 +633,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
with_genres: genre,
with_networks: network,
with_keywords: keywords,
without_keywords: excludeKeywords,
'with_runtime.gte': withRuntimeGte,
'with_runtime.lte': withRuntimeLte,
'vote_average.gte': voteAverageGte,

View File

@@ -61,6 +61,7 @@ const QueryFilterOptions = z.object({
studio: z.coerce.string().optional(),
genre: z.coerce.string().optional(),
keywords: z.coerce.string().optional(),
excludeKeywords: z.coerce.string().optional(),
language: z.coerce.string().optional(),
withRuntimeGte: z.coerce.string().optional(),
withRuntimeLte: z.coerce.string().optional(),
@@ -90,6 +91,7 @@ discoverRoutes.get('/movies', async (req, res, next) => {
try {
const query = ApiQuerySchema.parse(req.query);
const keywords = query.keywords;
const excludeKeywords = query.excludeKeywords;
const data = await tmdb.getDiscoverMovies({
page: Number(query.page),
@@ -105,6 +107,7 @@ discoverRoutes.get('/movies', async (req, res, next) => {
? new Date(query.primaryReleaseDateGte).toISOString().split('T')[0]
: undefined,
keywords,
excludeKeywords,
withRuntimeGte: query.withRuntimeGte,
withRuntimeLte: query.withRuntimeLte,
voteAverageGte: query.voteAverageGte,
@@ -381,6 +384,7 @@ discoverRoutes.get('/tv', async (req, res, next) => {
try {
const query = ApiQuerySchema.parse(req.query);
const keywords = query.keywords;
const excludeKeywords = query.excludeKeywords;
const data = await tmdb.getDiscoverTv({
page: Number(query.page),
sortBy: query.sortBy as SortOptions,
@@ -395,6 +399,7 @@ discoverRoutes.get('/tv', async (req, res, next) => {
: undefined,
originalLanguage: query.language,
keywords,
excludeKeywords,
withRuntimeGte: query.withRuntimeGte,
withRuntimeLte: query.withRuntimeLte,
voteAverageGte: query.voteAverageGte,

View File

@@ -33,6 +33,7 @@ const messages = defineMessages('components.Discover.FilterSlideover', {
studio: 'Studio',
genres: 'Genres',
keywords: 'Keywords',
excludeKeywords: 'Exclude Keywords',
originalLanguage: 'Original Language',
runtimeText: '{minValue}-{maxValue} minute runtime',
ratingText: 'Ratings between {minValue} and {maxValue}',
@@ -181,6 +182,19 @@ const FilterSlideover = ({
updateQueryParams('keywords', value?.map((v) => v.value).join(','));
}}
/>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.excludeKeywords)}
</span>
<KeywordSelector
defaultValue={currentFilters.excludeKeywords}
isMulti
onChange={(value) => {
updateQueryParams(
'excludeKeywords',
value?.map((v) => v.value).join(',')
);
}}
/>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.originalLanguage)}
</span>

View File

@@ -99,6 +99,7 @@ export const QueryFilterOptions = z.object({
studio: z.string().optional(),
genre: z.string().optional(),
keywords: z.string().optional(),
excludeKeywords: z.string().optional(),
language: z.string().optional(),
withRuntimeGte: z.string().optional(),
withRuntimeLte: z.string().optional(),
@@ -161,6 +162,10 @@ export const prepareFilterValues = (
filterValues.keywords = values.keywords;
}
if (values.excludeKeywords) {
filterValues.excludeKeywords = values.excludeKeywords;
}
if (values.language) {
filterValues.language = values.language;
}

View File

@@ -78,6 +78,7 @@
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# Active Filter} other {# Active Filters}}",
"components.Discover.FilterSlideover.certification": "Content Rating",
"components.Discover.FilterSlideover.clearfilters": "Clear Active Filters",
"components.Discover.FilterSlideover.excludeKeywords": "Exclude Keywords",
"components.Discover.FilterSlideover.filters": "Filters",
"components.Discover.FilterSlideover.firstAirDate": "First Air Date",
"components.Discover.FilterSlideover.from": "From",