Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4263,6 +4263,11 @@ paths:
schema:
type: string
example: 18
- in: query
name: filterGenre
schema:
type: string
example: 28
- in: query
name: studio
schema:
Expand Down Expand Up @@ -4552,6 +4557,11 @@ paths:
schema:
type: string
example: 18
- in: query
name: filterGenre
schema:
type: string
example: 28
- in: query
name: network
schema:
Expand Down
6 changes: 6 additions & 0 deletions server/api/themoviedb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ interface DiscoverMovieOptions {
voteCountLte?: string;
originalLanguage?: string;
genre?: string;
filterGenre?: string;
studio?: string;
keywords?: string;
sortBy?: SortOptions;
Expand All @@ -90,6 +91,7 @@ interface DiscoverTvOptions {
includeEmptyReleaseDate?: boolean;
originalLanguage?: string;
genre?: string;
filterGenre?: string;
network?: number;
keywords?: string;
sortBy?: SortOptions;
Expand Down Expand Up @@ -460,6 +462,7 @@ class TheMovieDb extends ExternalAPI {
primaryReleaseDateLte,
originalLanguage,
genre,
filterGenre,
studio,
keywords,
withRuntimeGte,
Expand Down Expand Up @@ -506,6 +509,7 @@ class TheMovieDb extends ExternalAPI {
? defaultFutureDate
: primaryReleaseDateLte,
with_genres: genre,
without_genres: filterGenre,
with_companies: studio,
with_keywords: keywords,
'with_runtime.gte': withRuntimeGte,
Expand Down Expand Up @@ -534,6 +538,7 @@ class TheMovieDb extends ExternalAPI {
includeEmptyReleaseDate = false,
originalLanguage,
genre,
filterGenre,
network,
keywords,
withRuntimeGte,
Expand Down Expand Up @@ -580,6 +585,7 @@ class TheMovieDb extends ExternalAPI {
: this.originalLanguage,
include_null_first_air_dates: includeEmptyReleaseDate,
with_genres: genre,
without_genres: filterGenre,
with_networks: network,
with_keywords: keywords,
'with_runtime.gte': withRuntimeGte,
Expand Down
6 changes: 6 additions & 0 deletions server/entity/UserSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export class UserSettings {
@Column({ nullable: true })
public originalLanguage?: string;

@Column({ nullable: true })
public filterTvGenresDefault?: string;

@Column({ nullable: true })
public filterMovieGenresDefault?: string;

@Column({ nullable: true })
public pgpKey?: string;

Expand Down
2 changes: 2 additions & 0 deletions server/interfaces/api/settingsInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export interface PublicSettingsResponse {
series4kEnabled: boolean;
region: string;
originalLanguage: string;
filterMovieGenresDefault: string;
filterTvGenresDefault: string;
partialRequestsEnabled: boolean;
cacheImages: boolean;
vapidPublic: string;
Expand Down
2 changes: 2 additions & 0 deletions server/interfaces/api/userSettingsInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface UserSettingsGeneralResponse {
locale?: string;
region?: string;
originalLanguage?: string;
filterMovieGenresDefault?: string;
filterTvGenresDefault?: string;
movieQuotaLimit?: number;
movieQuotaDays?: number;
tvQuotaLimit?: number;
Expand Down
4 changes: 4 additions & 0 deletions server/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export interface MainSettings {
newPlexLogin: boolean;
region: string;
originalLanguage: string;
filterTvGenresDefault: string;
filterMovieGenresDefault: string;
trustProxy: boolean;
partialRequestsEnabled: boolean;
locale: string;
Expand Down Expand Up @@ -298,6 +300,8 @@ class Settings {
newPlexLogin: true,
region: '',
originalLanguage: '',
filterTvGenresDefault: '',
filterMovieGenresDefault: '',
trustProxy: false,
partialRequestsEnabled: true,
locale: 'en',
Expand Down
54 changes: 54 additions & 0 deletions server/routes/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const QueryFilterOptions = z.object({
firstAirDateLte: z.coerce.string().optional(),
studio: z.coerce.string().optional(),
genre: z.coerce.string().optional(),
filterGenre: z.coerce.string().optional(),
keywords: z.coerce.string().optional(),
language: z.coerce.string().optional(),
withRuntimeGte: z.coerce.string().optional(),
Expand All @@ -80,12 +81,38 @@ discoverRoutes.get('/movies', async (req, res, next) => {
try {
const query = QueryFilterOptions.parse(req.query);
const keywords = query.keywords;

// Handle user default excluded genres
let filterGenre = query.filterGenre;
if (filterGenre === 'none') {
filterGenre = undefined;
} else if (
filterGenre === undefined &&
req.user?.settings?.filterMovieGenresDefault
) {
filterGenre = req.user.settings.filterMovieGenresDefault;
}

// Resolve conflicts: when explicit genres are present, remove them from exclusions
if (query.genre && filterGenre) {
const explicitGenres = query.genre.split(',');
const excludedGenres = filterGenre.split(',');
const resolvedExclusions = excludedGenres.filter(
(id) => !explicitGenres.includes(id)
);
filterGenre =
resolvedExclusions.length > 0
? resolvedExclusions.join(',')
: undefined;
}

const data = await tmdb.getDiscoverMovies({
page: Number(query.page),
sortBy: query.sortBy as SortOptions,
language: req.locale ?? query.language,
originalLanguage: query.language,
genre: query.genre,
filterGenre,
studio: query.studio,
primaryReleaseDateLte: query.primaryReleaseDateLte
? new Date(query.primaryReleaseDateLte).toISOString().split('T')[0]
Expand Down Expand Up @@ -357,11 +384,38 @@ discoverRoutes.get('/tv', async (req, res, next) => {
try {
const query = QueryFilterOptions.parse(req.query);
const keywords = query.keywords;

// Handle user default excluded genres
let filterGenre = query.filterGenre;
if (filterGenre === 'none') {
filterGenre = undefined;
} else if (
filterGenre === undefined &&
req.user?.settings?.filterTvGenresDefault
) {
filterGenre = req.user.settings.filterTvGenresDefault;
}

// Always resolve conflicts between explicit genres and exclusions
if (query.genre && filterGenre) {
const explicitGenres = query.genre.split(',');
const excludedGenres = filterGenre.split(',');
const resolvedExclusions = excludedGenres.filter(
(id) => !explicitGenres.includes(id)
);

filterGenre =
resolvedExclusions.length > 0
? resolvedExclusions.join(',')
: undefined;
}

const data = await tmdb.getDiscoverTv({
page: Number(query.page),
sortBy: query.sortBy as SortOptions,
language: req.locale ?? query.language,
genre: query.genre,
filterGenre,
network: query.network ? Number(query.network) : undefined,
firstAirDateLte: query.firstAirDateLte
? new Date(query.firstAirDateLte).toISOString().split('T')[0]
Expand Down
9 changes: 9 additions & 0 deletions server/routes/user/usersettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
locale: user.settings?.locale,
region: user.settings?.region,
originalLanguage: user.settings?.originalLanguage,
filterTvGenresDefault: user.settings?.filterTvGenresDefault,
filterMovieGenresDefault: user.settings?.filterMovieGenresDefault,
movieQuotaLimit: user.movieQuotaLimit,
movieQuotaDays: user.movieQuotaDays,
tvQuotaLimit: user.tvQuotaLimit,
Expand Down Expand Up @@ -116,6 +118,8 @@ userSettingsRoutes.post<
locale: req.body.locale,
region: req.body.region,
originalLanguage: req.body.originalLanguage,
filterMovieGenresDefault: req.body.filterMovieGenresDefault,
filterTvGenresDefault: req.body.filterTvGenresDefault,
watchlistSyncMovies: req.body.watchlistSyncMovies,
watchlistSyncTv: req.body.watchlistSyncTv,
});
Expand All @@ -124,6 +128,9 @@ userSettingsRoutes.post<
user.settings.locale = req.body.locale;
user.settings.region = req.body.region;
user.settings.originalLanguage = req.body.originalLanguage;
user.settings.filterMovieGenresDefault =
req.body.filterMovieGenresDefault;
user.settings.filterTvGenresDefault = req.body.filterTvGenresDefault;
user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies;
user.settings.watchlistSyncTv = req.body.watchlistSyncTv;
}
Expand All @@ -136,6 +143,8 @@ userSettingsRoutes.post<
locale: user.settings.locale,
region: user.settings.region,
originalLanguage: user.settings.originalLanguage,
filterMovieGenresDefault: user.settings.filterMovieGenresDefault,
filterTvGenresDefault: user.settings.filterTvGenresDefault,
watchlistSyncMovies: user.settings.watchlistSyncMovies,
watchlistSyncTv: user.settings.watchlistSyncTv,
});
Expand Down
8 changes: 6 additions & 2 deletions src/components/Discover/DiscoverMovies/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import FilterSlideover from '@app/components/Discover/FilterSlideover';
import useDiscover from '@app/hooks/useDiscover';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import { useUser } from '@app/hooks/useUser';
import Error from '@app/pages/_error';
import { BarsArrowDownIcon, FunnelIcon } from '@heroicons/react/24/solid';
import type { SortOptions as TMDBSortOptions } from '@server/api/themoviedb';
Expand Down Expand Up @@ -47,9 +48,9 @@ const DiscoverMovies = () => {
const intl = useIntl();
const router = useRouter();
const updateQueryParams = useUpdateQueryParams({});
const { user } = useUser();

const preparedFilters = prepareFilterValues(router.query);

const {
isLoadingInitialData,
isEmpty,
Expand Down Expand Up @@ -124,7 +125,10 @@ const DiscoverMovies = () => {
<FunnelIcon />
<span>
{intl.formatMessage(messages.activefilters, {
count: countActiveFilters(preparedFilters),
count: countActiveFilters(
preparedFilters,
!!user?.settings?.filterMovieGenresDefault
),
})}
</span>
</Button>
Expand Down
7 changes: 6 additions & 1 deletion src/components/Discover/DiscoverTv/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import FilterSlideover from '@app/components/Discover/FilterSlideover';
import useDiscover from '@app/hooks/useDiscover';
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import { useUser } from '@app/hooks/useUser';
import Error from '@app/pages/_error';
import { BarsArrowDownIcon, FunnelIcon } from '@heroicons/react/24/solid';
import type { SortOptions as TMDBSortOptions } from '@server/api/themoviedb';
Expand Down Expand Up @@ -46,6 +47,7 @@ const SortOptions: Record<string, TMDBSortOptions> = {
const DiscoverTv = () => {
const intl = useIntl();
const router = useRouter();
const { user } = useUser();
const [showFilters, setShowFilters] = useState(false);
const preparedFilters = prepareFilterValues(router.query);
const updateQueryParams = useUpdateQueryParams({});
Expand Down Expand Up @@ -122,7 +124,10 @@ const DiscoverTv = () => {
<FunnelIcon />
<span>
{intl.formatMessage(messages.activefilters, {
count: countActiveFilters(preparedFilters),
count: countActiveFilters(
preparedFilters,
!!user?.settings?.filterTvGenresDefault
),
})}
</span>
</Button>
Expand Down
Loading
Loading