0)
diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx
index 7de8f83785..1e90e5a69c 100644
--- a/src/components/Settings/SettingsMain/index.tsx
+++ b/src/components/Settings/SettingsMain/index.tsx
@@ -6,11 +6,13 @@ import SensitiveInput from '@app/components/Common/SensitiveInput';
import LanguageSelector from '@app/components/LanguageSelector';
import RegionSelector from '@app/components/RegionSelector';
import CopyButton from '@app/components/Settings/CopyButton';
+import LibraryItem from '@app/components/Settings/LibraryItem';
import SettingsBadge from '@app/components/Settings/SettingsBadge';
import { availableLanguages } from '@app/context/LanguageContext';
import useLocale from '@app/hooks/useLocale';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
+import type { FilterItem } from '@app/types/filters';
import defineMessages from '@app/utils/defineMessages';
import { isValidURL } from '@app/utils/urlValidationHelper';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
@@ -72,6 +74,22 @@ const messages = defineMessages('components.Settings.SettingsMain', {
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
});
+const filters: FilterItem[] = [
+ { key: 'filterSearch', name: 'Search' },
+ { key: 'filterTrending', name: 'Trending' },
+ { key: 'filterSimilarSeries', name: 'Similar Series' },
+ { key: 'filterSimilarMovies', name: 'Similar Movies' },
+ { key: 'filterTvRecommendations', name: 'TV Recommendations' },
+ { key: 'filterMovieRecommendations', name: 'Movie Recommendations' },
+ { key: 'filterTvUpcoming', name: 'Upcoming Series' },
+ { key: 'filterUpcomingMovies', name: 'Upcoming Movies' },
+ { key: 'filterDiscoverMovies', name: 'Discover Movies' },
+ { key: 'filterTvDiscover', name: 'Discover Series' },
+ { key: 'filterPopularMovies', name: 'Popular Movies' },
+ { key: 'filterTvPopular', name: 'Popular Series' },
+ { key: 'filterCustomSliders', name: 'Custom Sliders' },
+];
+
const SettingsMain = () => {
const { addToast } = useToasts();
const { user: currentUser, hasPermission: userHasPermission } = useUser();
@@ -175,6 +193,20 @@ const SettingsMain = () => {
enableSpecialEpisodes: data?.enableSpecialEpisodes,
cacheImages: data?.cacheImages,
youtubeUrl: data?.youtubeUrl,
+ filterSearch: data?.filters?.filterSearch,
+ filterTrending: data?.filters?.filterTrending,
+ filterSimilarSeries: data?.filters?.filterSimilarSeries,
+ filterTvRecommendations: data?.filters?.filterTvRecommendations,
+ filterSimilarMovies: data?.filters?.filterSimilarMovies,
+ filterMovieRecommendations:
+ data?.filters?.filterMovieRecommendations,
+ filterTvUpcoming: data?.filters?.filterTvUpcoming,
+ filterUpcomingMovies: data?.filters?.filterUpcomingMovies,
+ filterDiscoverMovies: data?.filters?.filterDiscoverMovies,
+ filterTvDiscover: data?.filters?.filterTvDiscover,
+ filterPopularMovies: data?.filters?.filterPopularMovies,
+ filterTvPopular: data?.filters?.filterTvPopular,
+ filterCustomSliders: data?.filters?.filterCustomSliders,
}}
enableReinitialize
validationSchema={MainSettingsSchema}
@@ -195,6 +227,21 @@ const SettingsMain = () => {
enableSpecialEpisodes: values.enableSpecialEpisodes,
cacheImages: values.cacheImages,
youtubeUrl: values.youtubeUrl,
+ filters: {
+ filterSearch: values.filterSearch,
+ filterTrending: values.filterTrending,
+ filterSimilarSeries: values.filterSimilarSeries,
+ filterTvRecommendations: values.filterTvRecommendations,
+ filterSimilarMovies: values.filterSimilarMovies,
+ filterMovieRecommendations: values.filterMovieRecommendations,
+ filterTvUpcoming: values.filterTvUpcoming,
+ filterUpcomingMovies: values.filterUpcomingMovies,
+ filterDiscoverMovies: values.filterDiscoverMovies,
+ filterTvDiscover: values.filterTvDiscover,
+ filterPopularMovies: values.filterPopularMovies,
+ filterTvPopular: values.filterTvPopular,
+ filterCustomSliders: values.filterCustomSliders,
+ },
});
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
@@ -383,6 +430,31 @@ const SettingsMain = () => {
+
+
+ Filter by Original Language
+
+ Filter content by their original language. This will take
+ the values from the Discover Language setting.
+
+
+
+
+ {filters.map(({ key, name }) => (
+ setFieldValue(key, !values[key])}
+ />
+ ))}
+
+
+
0)
+ isLoadingInitialData ||
+ (isLoadingMore && (filteredTitles?.length ?? 0) > 0)
}
onScrollBottom={fetchMore}
/>
diff --git a/src/components/TvDetails/TvSimilar.tsx b/src/components/TvDetails/TvSimilar.tsx
index c6afd9b3c9..db68687ca8 100644
--- a/src/components/TvDetails/TvSimilar.tsx
+++ b/src/components/TvDetails/TvSimilar.tsx
@@ -2,7 +2,9 @@ import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
+import useFilterByLanguages from '@app/hooks/useFilterByLanguages';
import Error from '@app/pages/_error';
+import { FilterByLanguage } from '@app/types/filters';
import defineMessages from '@app/utils/defineMessages';
import type { TvResult } from '@server/models/Search';
import type { TvDetails } from '@server/models/Tv';
@@ -29,6 +31,13 @@ const TvSimilar = () => {
error,
} = useDiscover(`/api/v1/tv/${router.query.tvId}/similar`);
+ const filteredTitles = useFilterByLanguages({
+ titles,
+ movie: true,
+ tv: false,
+ key: FilterByLanguage.SIMILAR_SERIES,
+ });
+
if (error) {
return ;
}
@@ -48,11 +57,12 @@ const TvSimilar = () => {
0)
+ isLoadingInitialData ||
+ (isLoadingMore && (filteredTitles?.length ?? 0) > 0)
}
onScrollBottom={fetchMore}
/>
diff --git a/src/hooks/useFilterByLanguages.ts b/src/hooks/useFilterByLanguages.ts
new file mode 100644
index 0000000000..058aaadc60
--- /dev/null
+++ b/src/hooks/useFilterByLanguages.ts
@@ -0,0 +1,57 @@
+import type { FilterByLanguage } from '@app/types/filters';
+import type { FullPublicSettings } from '@server/lib/settings';
+import type {
+ MovieResult,
+ PersonResult,
+ TvResult,
+} from '@server/models/Search';
+import { useEffect, useState } from 'react';
+import useSWR from 'swr';
+
+interface Props {
+ titles: (MovieResult | TvResult | PersonResult)[];
+ movie: boolean;
+ tv: boolean;
+ key?: FilterByLanguage;
+}
+
+const useFilterByLanguages = (props: Readonly) => {
+ const { titles, movie, tv, key } = props;
+
+ const { data: publicSettings } = useSWR(
+ '/api/v1/settings/public'
+ );
+ const [originalLanguages, setOriginalLanguages] = useState([]);
+ const [applyFilter, setApplyFilter] = useState(false);
+
+ useEffect(
+ function syncFilterByLanguage() {
+ if (!publicSettings || !key) return;
+
+ setApplyFilter(publicSettings.filters[key]);
+
+ if (publicSettings.originalLanguage) {
+ const languages = publicSettings.originalLanguage
+ .split('|')
+ .map((lang) => lang.trim());
+ setOriginalLanguages(languages);
+ }
+ },
+ [publicSettings, key]
+ );
+
+ return titles.filter((title) => {
+ if (!applyFilter) return true;
+
+ if (
+ (movie && title.mediaType === 'movie') ||
+ (tv && title.mediaType === 'tv')
+ ) {
+ return originalLanguages.includes(title.originalLanguage);
+ }
+
+ return true;
+ });
+};
+
+export default useFilterByLanguages;
diff --git a/src/types/filters.ts b/src/types/filters.ts
new file mode 100644
index 0000000000..e1df05762c
--- /dev/null
+++ b/src/types/filters.ts
@@ -0,0 +1,35 @@
+export enum FilterByLanguage {
+ SEARCH = 'filterSearch',
+ TRENDING = 'filterTrending',
+ SIMILAR_SERIES = 'filterSimilarSeries',
+ TV_RECOMMENDATIONS = 'filterTvRecommendations',
+ SIMILAR_MOVIES = 'filterSimilarMovies',
+ MOVIE_RECOMMENDATIONS = 'filterMovieRecommendations',
+ TV_UPCOMING = 'filterTvUpcoming',
+ UPCOMING_MOVIES = 'filterUpcomingMovies',
+ DISCOVER_MOVIES = 'filterDiscoverMovies',
+ TV_DISCOVER = 'filterTvDiscover',
+ POPULAR_MOVIES = 'filterPopularMovies',
+ TV_POPULAR = 'filterTvPopular',
+ CUSTOM_SLIDERS = 'filterCustomSliders',
+}
+
+type FilterKey =
+ | 'filterSearch'
+ | 'filterTrending'
+ | 'filterSimilarSeries'
+ | 'filterSimilarMovies'
+ | 'filterTvRecommendations'
+ | 'filterMovieRecommendations'
+ | 'filterTvUpcoming'
+ | 'filterUpcomingMovies'
+ | 'filterDiscoverMovies'
+ | 'filterTvDiscover'
+ | 'filterPopularMovies'
+ | 'filterTvPopular'
+ | 'filterCustomSliders';
+
+export interface FilterItem {
+ key: FilterKey;
+ name: string;
+}