From cd479d0d1787bd8c289e111844ba3f85cb4ec23a Mon Sep 17 00:00:00 2001 From: 0xsysr3ll <31414959+0xSysR3ll@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:32:39 +0200 Subject: [PATCH] feat(api): add excludeKeywords parameter to discovery queries (#1908) Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> --- jellyseerr-api.yml | 12 ++++++++++++ server/api/themoviedb/index.ts | 6 ++++++ server/routes/discover.ts | 5 +++++ src/components/Discover/FilterSlideover/index.tsx | 14 ++++++++++++++ src/components/Discover/constants.ts | 5 +++++ src/i18n/locale/en.json | 1 + 6 files changed, 43 insertions(+) diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml index 2535059a..69484e24 100644 --- a/jellyseerr-api.yml +++ b/jellyseerr-api.yml @@ -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: diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index 9fc19c72..a01f356c 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -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, diff --git a/server/routes/discover.ts b/server/routes/discover.ts index 4fdd1167..c6dab52a 100644 --- a/server/routes/discover.ts +++ b/server/routes/discover.ts @@ -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, diff --git a/src/components/Discover/FilterSlideover/index.tsx b/src/components/Discover/FilterSlideover/index.tsx index 1f06cf0a..abdd0105 100644 --- a/src/components/Discover/FilterSlideover/index.tsx +++ b/src/components/Discover/FilterSlideover/index.tsx @@ -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(',')); }} /> + + {intl.formatMessage(messages.excludeKeywords)} + + { + updateQueryParams( + 'excludeKeywords', + value?.map((v) => v.value).join(',') + ); + }} + /> {intl.formatMessage(messages.originalLanguage)} diff --git a/src/components/Discover/constants.ts b/src/components/Discover/constants.ts index 425ee7de..4ce5e34f 100644 --- a/src/components/Discover/constants.ts +++ b/src/components/Discover/constants.ts @@ -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; } diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 7153d5a3..69573dc0 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -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",