feat(usersettings): add separate setting for streaming region (#993)

* feat: add separate setting for streaming region

Currently, the "Currently Streaming On" information is based on the Discover Region setting. This PR
adds a new setting to specify which region should be used to display the streaming region.

re #890

* fix: add missing newline

* fix: rename migration function
This commit is contained in:
Gauthier
2024-12-08 17:19:11 +01:00
committed by GitHub
parent 84fd884052
commit 89831f7090
23 changed files with 243 additions and 78 deletions

View File

@@ -16,7 +16,8 @@
"hideAvailable": false,
"localLogin": true,
"newPlexLogin": true,
"region": "",
"discoverRegion": "",
"streamingRegion": "",
"originalLanguage": "",
"trustProxy": false,
"mediaServerType": 1,

View File

@@ -58,9 +58,9 @@ You should enable this if you are having issues with loading images directly fro
Set the default display language for Jellyseerr. Users can override this setting in their user settings.
## Discover Region & Discover Language
## Discover Region, Discover Language & Streaming Region
These settings filter content shown on the "Discover" home page based on regional availability and original language, respectively. Users can override these global settings by configuring these same options in their user settings.
These settings filter content shown on the "Discover" home page based on regional availability and original language, respectively. The Streaming Region filters the available streaming providers on the media page. Users can override these global settings by configuring these same options in their user settings.
## Hide Available Media

View File

@@ -35,7 +35,7 @@ Users can override the [global display language](/using-jellyseerr/settings/gene
### Discover Region & Discover Language
Users can override the [global filter settings](/using-jellyseerr/settings/general#discover-region--discover-language) to suit their own preferences.
Users can override the [global filter settings](/using-jellyseerr/settings/general#discover-region-discover-language--streaming-region) to suit their own preferences.
### Movie Request Limit & Series Request Limit

View File

@@ -143,10 +143,12 @@ components:
properties:
locale:
type: string
region:
discoverRegion:
type: string
originalLanguage:
type: string
streamingRegion:
type: string
MainSettings:
type: object
properties:

View File

@@ -99,12 +99,12 @@ interface DiscoverTvOptions {
}
class TheMovieDb extends ExternalAPI {
private region?: string;
private discoverRegion?: string;
private originalLanguage?: string;
constructor({
region,
discoverRegion,
originalLanguage,
}: { region?: string; originalLanguage?: string } = {}) {
}: { discoverRegion?: string; originalLanguage?: string } = {}) {
super(
'https://api.themoviedb.org/3',
{
@@ -118,7 +118,7 @@ class TheMovieDb extends ExternalAPI {
},
}
);
this.region = region;
this.discoverRegion = discoverRegion;
this.originalLanguage = originalLanguage;
}
@@ -469,7 +469,7 @@ class TheMovieDb extends ExternalAPI {
page: page.toString(),
include_adult: includeAdult ? 'true' : 'false',
language,
region: this.region || '',
region: this.discoverRegion || '',
with_original_language:
originalLanguage && originalLanguage !== 'all'
? originalLanguage
@@ -541,7 +541,7 @@ class TheMovieDb extends ExternalAPI {
sort_by: sortBy,
page: page.toString(),
language,
region: this.region || '',
region: this.discoverRegion || '',
// Set our release date values, but check if one is set and not the other,
// so we can force a past date or a future date. TMDB Requires both values if one is set!
'first_air_date.gte':
@@ -594,7 +594,7 @@ class TheMovieDb extends ExternalAPI {
{
page: page.toString(),
language,
region: this.region || '',
region: this.discoverRegion || '',
originalLanguage: this.originalLanguage || '',
}
);
@@ -620,7 +620,7 @@ class TheMovieDb extends ExternalAPI {
{
page: page.toString(),
language,
region: this.region || '',
region: this.discoverRegion || '',
}
);

View File

@@ -31,7 +31,10 @@ export class UserSettings {
public locale?: string;
@Column({ nullable: true })
public region?: string;
public discoverRegion?: string;
@Column({ nullable: true })
public streamingRegion?: string;
@Column({ nullable: true })
public originalLanguage?: string;

View File

@@ -32,7 +32,8 @@ export interface PublicSettingsResponse {
localLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;
discoverRegion: string;
streamingRegion: string;
originalLanguage: string;
mediaServerType: number;
partialRequestsEnabled: boolean;

View File

@@ -5,7 +5,8 @@ export interface UserSettingsGeneralResponse {
email?: string;
discordId?: string;
locale?: string;
region?: string;
discoverRegion?: string;
streamingRegion?: string;
originalLanguage?: string;
movieQuotaLimit?: number;
movieQuotaDays?: number;

View File

@@ -124,7 +124,8 @@ export interface MainSettings {
hideAvailable: boolean;
localLogin: boolean;
newPlexLogin: boolean;
region: string;
discoverRegion: string;
streamingRegion: string;
originalLanguage: string;
trustProxy: boolean;
mediaServerType: number;
@@ -144,7 +145,8 @@ interface FullPublicSettings extends PublicSettings {
localLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;
discoverRegion: string;
streamingRegion: string;
originalLanguage: string;
mediaServerType: number;
jellyfinExternalHost?: string;
@@ -333,7 +335,8 @@ class Settings {
hideAvailable: false,
localLogin: true,
newPlexLogin: true,
region: '',
discoverRegion: '',
streamingRegion: '',
originalLanguage: '',
trustProxy: false,
mediaServerType: MediaServerType.NOT_CONFIGURED,
@@ -576,7 +579,8 @@ class Settings {
series4kEnabled: this.data.sonarr.some(
(sonarr) => sonarr.is4k && sonarr.isDefault
),
region: this.data.main.region,
discoverRegion: this.data.main.discoverRegion,
streamingRegion: this.data.main.streamingRegion,
originalLanguage: this.data.main.originalLanguage,
mediaServerType: this.main.mediaServerType,
partialRequestsEnabled: this.data.main.partialRequestsEnabled,

View File

@@ -0,0 +1,17 @@
import type { AllSettings } from '@server/lib/settings';
const migrateRegionSetting = (settings: any): AllSettings => {
const oldRegion = settings.main.region;
if (oldRegion) {
settings.main.discoverRegion = oldRegion;
settings.main.streamingRegion = oldRegion;
} else {
settings.main.discoverRegion = '';
settings.main.streamingRegion = 'US';
}
delete settings.main.region;
return settings;
};
export default migrateRegionSetting;

View File

@@ -0,0 +1,53 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserSettingsStreamingRegion1727907530757
implements MigrationInterface
{
name = 'AddUserSettingsStreamingRegion1727907530757';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "pushoverSound" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound") SELECT "id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound" FROM "user_settings"`
);
await queryRunner.query(`DROP TABLE "user_settings"`);
await queryRunner.query(
`ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"`
);
await queryRunner.query(
`CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "pushoverSound" varchar, "discoverRegion" varchar, "streamingRegion" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound") SELECT "id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound" FROM "user_settings"`
);
await queryRunner.query(`DROP TABLE "user_settings"`);
await queryRunner.query(
`ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"`
);
await queryRunner.query(
`CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "pushoverSound" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "user_settings"("id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound") SELECT "id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound" FROM "temporary_user_settings"`
);
await queryRunner.query(`DROP TABLE "temporary_user_settings"`);
await queryRunner.query(
`ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"`
);
await queryRunner.query(
`CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "pushoverSound" varchar, "region" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "user_settings"("id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound") SELECT "id", "notificationTypes", "discordId", "userId", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "watchlistSyncMovies", "watchlistSyncTv", "pushoverSound" FROM "temporary_user_settings"`
);
await queryRunner.query(`DROP TABLE "temporary_user_settings"`);
}
}

View File

@@ -29,12 +29,12 @@ import { z } from 'zod';
export const createTmdbWithRegionLanguage = (user?: User): TheMovieDb => {
const settings = getSettings();
const region =
user?.settings?.region === 'all'
const discoverRegion =
user?.settings?.streamingRegion === 'all'
? ''
: user?.settings?.region
? user?.settings?.region
: settings.main.region;
: user?.settings?.streamingRegion
? user?.settings?.streamingRegion
: settings.main.discoverRegion;
const originalLanguage =
user?.settings?.originalLanguage === 'all'
@@ -44,7 +44,7 @@ export const createTmdbWithRegionLanguage = (user?: User): TheMovieDb => {
: settings.main.originalLanguage;
return new TheMovieDb({
region,
discoverRegion,
originalLanguage,
});
};

View File

@@ -57,7 +57,8 @@ userSettingsRoutes.get<{ id: string }, UserSettingsGeneralResponse>(
email: user.email,
discordId: user.settings?.discordId,
locale: user.settings?.locale,
region: user.settings?.region,
discoverRegion: user.settings?.discoverRegion,
streamingRegion: user.settings?.streamingRegion,
originalLanguage: user.settings?.originalLanguage,
movieQuotaLimit: user.movieQuotaLimit,
movieQuotaDays: user.movieQuotaDays,
@@ -147,7 +148,8 @@ userSettingsRoutes.post<
user: req.user,
discordId: req.body.discordId,
locale: req.body.locale,
region: req.body.region,
discoverRegion: req.body.discoverRegion,
streamingRegion: req.body.streamingRegion,
originalLanguage: req.body.originalLanguage,
watchlistSyncMovies: req.body.watchlistSyncMovies,
watchlistSyncTv: req.body.watchlistSyncTv,
@@ -155,7 +157,8 @@ userSettingsRoutes.post<
} else {
user.settings.discordId = req.body.discordId;
user.settings.locale = req.body.locale;
user.settings.region = req.body.region;
user.settings.discoverRegion = req.body.discoverRegion;
user.settings.streamingRegion = req.body.streamingRegion;
user.settings.originalLanguage = req.body.originalLanguage;
user.settings.watchlistSyncMovies = req.body.watchlistSyncMovies;
user.settings.watchlistSyncTv = req.body.watchlistSyncTv;
@@ -167,7 +170,8 @@ userSettingsRoutes.post<
username: savedUser.username,
discordId: savedUser.settings?.discordId,
locale: savedUser.settings?.locale,
region: savedUser.settings?.region,
discoverRegion: savedUser.settings?.discoverRegion,
streamingRegion: savedUser.settings?.streamingRegion,
originalLanguage: savedUser.settings?.originalLanguage,
watchlistSyncMovies: savedUser.settings?.watchlistSyncMovies,
watchlistSyncTv: savedUser.settings?.watchlistSyncTv,

View File

@@ -222,14 +222,14 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
});
}
const region = user?.settings?.region
? user.settings.region
: settings.currentSettings.region
? settings.currentSettings.region
const discoverRegion = user?.settings?.discoverRegion
? user.settings.discoverRegion
: settings.currentSettings.discoverRegion
? settings.currentSettings.discoverRegion
: 'US';
const releases = data.releases.results.find(
(r) => r.iso_3166_1 === region
(r) => r.iso_3166_1 === discoverRegion
)?.release_dates;
// Release date types:
@@ -282,9 +282,15 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
);
}
const streamingRegion = user?.settings?.streamingRegion
? user.settings.streamingRegion
: settings.currentSettings.streamingRegion
? settings.currentSettings.streamingRegion
: 'US';
const streamingProviders =
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
?.flatrate ?? [];
data?.watchProviders?.find(
(provider) => provider.iso_3166_1 === streamingRegion
)?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {

View File

@@ -21,6 +21,7 @@ interface RegionSelectorProps {
isUserSetting?: boolean;
disableAll?: boolean;
watchProviders?: boolean;
regionType?: 'discover' | 'streaming';
onChange?: (fieldName: string, region: string) => void;
}
@@ -30,6 +31,7 @@ const RegionSelector = ({
isUserSetting = false,
disableAll = false,
watchProviders = false,
regionType = 'discover',
onChange,
}: RegionSelectorProps) => {
const { currentSettings } = useSettings();
@@ -63,6 +65,11 @@ const RegionSelector = ({
sortedRegions?.find((region) => region.iso_3166_1 === regionCode)?.name ??
regionCode;
const regionValue =
regionType === 'discover'
? currentSettings.discoverRegion
: currentSettings.streamingRegion;
useEffect(() => {
if (regions && value) {
if (value === 'all') {
@@ -97,14 +104,12 @@ const RegionSelector = ({
countries.includes(selectedRegion?.iso_3166_1)) ||
(isUserSetting &&
!selectedRegion &&
currentSettings.region &&
countries.includes(currentSettings.region))) && (
regionValue &&
countries.includes(regionValue))) && (
<span className="mr-2 h-4 overflow-hidden text-base leading-4">
<span
className={`flag:${
selectedRegion
? selectedRegion.iso_3166_1
: currentSettings.region
selectedRegion ? selectedRegion.iso_3166_1 : regionValue
}`}
/>
</span>
@@ -114,8 +119,8 @@ const RegionSelector = ({
? regionName(selectedRegion.iso_3166_1)
: isUserSetting && selectedRegion?.iso_3166_1 !== 'all'
? intl.formatMessage(messages.regionServerDefault, {
region: currentSettings.region
? regionName(currentSettings.region)
region: regionValue
? regionName(regionValue)
: intl.formatMessage(messages.regionDefault),
})
: intl.formatMessage(messages.regionDefault)}
@@ -148,8 +153,8 @@ const RegionSelector = ({
<span className="mr-2 text-base">
<span
className={
countries.includes(currentSettings.region)
? `flag:${currentSettings.region}`
countries.includes(regionValue)
? `flag:${regionValue}`
: 'pr-6'
}
/>
@@ -160,8 +165,8 @@ const RegionSelector = ({
} block truncate`}
>
{intl.formatMessage(messages.regionServerDefault, {
region: currentSettings.region
? regionName(currentSettings.region)
region: regionValue
? regionName(regionValue)
: intl.formatMessage(messages.regionDefault),
})}
</span>

View File

@@ -374,7 +374,11 @@ export const WatchProviderSelector = ({
const { currentSettings } = useSettings();
const [showMore, setShowMore] = useState(false);
const [watchRegion, setWatchRegion] = useState(
region ? region : currentSettings.region ? currentSettings.region : 'US'
region
? region
: currentSettings.discoverRegion
? currentSettings.discoverRegion
: 'US'
);
const [activeProvider, setActiveProvider] = useState<number[]>(
activeProviders ?? []

View File

@@ -31,10 +31,12 @@ const messages = defineMessages('components.Settings.SettingsMain', {
apikey: 'API Key',
applicationTitle: 'Application Title',
applicationurl: 'Application URL',
region: 'Discover Region',
regionTip: 'Filter content by regional availability',
discoverRegion: 'Discover Region',
discoverRegionTip: 'Filter content by regional availability',
originallanguage: 'Discover Language',
originallanguageTip: 'Filter content by original language',
streamingRegion: 'Streaming Region',
streamingRegionTip: 'Show streaming sites by regional availability',
toastApiKeySuccess: 'New API key generated successfully!',
toastApiKeyFailure: 'Something went wrong while generating a new API key.',
toastSettingsSuccess: 'Settings saved successfully!',
@@ -152,8 +154,9 @@ const SettingsMain = () => {
csrfProtection: data?.csrfProtection,
hideAvailable: data?.hideAvailable,
locale: data?.locale ?? 'en',
region: data?.region,
discoverRegion: data?.discoverRegion,
originalLanguage: data?.originalLanguage,
streamingRegion: data?.streamingRegion,
partialRequestsEnabled: data?.partialRequestsEnabled,
trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages,
@@ -181,7 +184,8 @@ const SettingsMain = () => {
csrfProtection: values.csrfProtection,
hideAvailable: values.hideAvailable,
locale: values.locale,
region: values.region,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
trustProxy: values.trustProxy,
@@ -402,17 +406,17 @@ const SettingsMain = () => {
</div>
</div>
<div className="form-row">
<label htmlFor="region" className="text-label">
<span>{intl.formatMessage(messages.region)}</span>
<label htmlFor="discoverRegion" className="text-label">
<span>{intl.formatMessage(messages.discoverRegion)}</span>
<span className="label-tip">
{intl.formatMessage(messages.regionTip)}
{intl.formatMessage(messages.discoverRegionTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<RegionSelector
value={values.region ?? ''}
name="region"
value={values.discoverRegion ?? ''}
name="discoverRegion"
onChange={setFieldValue}
/>
</div>
@@ -434,6 +438,25 @@ const SettingsMain = () => {
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="streamingRegion" className="text-label">
<span>{intl.formatMessage(messages.streamingRegion)}</span>
<span className="label-tip">
{intl.formatMessage(messages.streamingRegionTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<RegionSelector
value={values.streamingRegion || 'US'}
name="streamingRegion"
onChange={setFieldValue}
regionType="streaming"
disableAll
/>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="hideAvailable" className="checkbox-label">
<span className="mr-2">

View File

@@ -222,15 +222,15 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
});
}
const region = user?.settings?.region
? user.settings.region
: settings.currentSettings.region
? settings.currentSettings.region
const discoverRegion = user?.settings?.discoverRegion
? user.settings.discoverRegion
: settings.currentSettings.discoverRegion
? settings.currentSettings.discoverRegion
: 'US';
const seriesAttributes: React.ReactNode[] = [];
const contentRating = data.contentRatings.results.find(
(r) => r.iso_3166_1 === region
(r) => r.iso_3166_1 === discoverRegion
)?.rating;
if (contentRating) {
seriesAttributes.push(
@@ -312,9 +312,15 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
(showHasSpecials ? seasonCount + 1 : seasonCount) <=
getAllRequestedSeasons(true).length;
const streamingRegion = user?.settings?.streamingRegion
? user.settings.streamingRegion
: settings.currentSettings.streamingRegion
? settings.currentSettings.streamingRegion
: 'US';
const streamingProviders =
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
?.flatrate ?? [];
data?.watchProviders?.find(
(provider) => provider.iso_3166_1 === streamingRegion
)?.flatrate ?? [];
function getAvalaibleMediaServerName() {
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {

View File

@@ -48,8 +48,12 @@ const messages = defineMessages(
'Another user already has this username. You must set an email',
region: 'Discover Region',
regionTip: 'Filter content by regional availability',
discoverRegion: 'Discover Region',
discoverRegionTip: 'Filter content by regional availability',
originallanguage: 'Discover Language',
originallanguageTip: 'Filter content by original language',
streamingRegion: 'Streaming Region',
streamingRegionTip: 'Show streaming sites by regional availability',
movierequestlimit: 'Movie Request Limit',
seriesrequestlimit: 'Series Request Limit',
enableOverride: 'Override Global Limit',
@@ -144,7 +148,8 @@ const UserGeneralSettings = () => {
email: data?.email?.includes('@') ? data.email : '',
discordId: data?.discordId ?? '',
locale: data?.locale,
region: data?.region,
discoverRegion: data?.discoverRegion,
streamingRegion: data?.streamingRegion,
originalLanguage: data?.originalLanguage,
movieQuotaLimit: data?.movieQuotaLimit,
movieQuotaDays: data?.movieQuotaDays,
@@ -168,7 +173,8 @@ const UserGeneralSettings = () => {
values.email || user?.jellyfinUsername || user?.plexUsername,
discordId: values.discordId,
locale: values.locale,
region: values.region,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
movieQuotaLimit: movieQuotaEnabled
? values.movieQuotaLimit
@@ -400,17 +406,17 @@ const UserGeneralSettings = () => {
</div>
</div>
<div className="form-row">
<label htmlFor="displayName" className="text-label">
<span>{intl.formatMessage(messages.region)}</span>
<label htmlFor="discoverRegion" className="text-label">
<span>{intl.formatMessage(messages.discoverRegion)}</span>
<span className="label-tip">
{intl.formatMessage(messages.regionTip)}
{intl.formatMessage(messages.discoverRegionTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<RegionSelector
name="region"
value={values.region ?? ''}
name="discoverRegion"
value={values.discoverRegion ?? ''}
isUserSetting
onChange={setFieldValue}
/>
@@ -435,6 +441,26 @@ const UserGeneralSettings = () => {
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="streamingRegionTip" className="text-label">
<span>{intl.formatMessage(messages.streamingRegion)}</span>
<span className="label-tip">
{intl.formatMessage(messages.streamingRegionTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<RegionSelector
name="streamingRegion"
value={values.streamingRegion || ''}
isUserSetting
onChange={setFieldValue}
regionType="streaming"
disableAll
/>
</div>
</div>
</div>
{currentHasPermission(Permission.MANAGE_USERS) &&
!hasPermission(Permission.MANAGE_USERS) && (
<>

View File

@@ -16,7 +16,8 @@ const defaultSettings = {
localLogin: true,
movie4kEnabled: false,
series4kEnabled: false,
region: '',
discoverRegion: '',
streamingRegion: '',
originalLanguage: '',
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,

View File

@@ -29,7 +29,8 @@ type NotificationAgentTypes = Record<NotificationAgentKey, number>;
export interface UserSettings {
discordId?: string;
region?: string;
discoverRegion?: string;
streamingRegion?: string;
originalLanguage?: string;
locale?: string;
notificationTypes: Partial<NotificationAgentTypes>;

View File

@@ -880,6 +880,8 @@
"components.Settings.SettingsMain.csrfProtection": "Enable CSRF Protection",
"components.Settings.SettingsMain.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.discoverRegion": "Discover Region",
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
"components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
@@ -897,8 +899,8 @@
"components.Settings.SettingsMain.proxyPort": "Proxy Port",
"components.Settings.SettingsMain.proxySsl": "Use SSL For Proxy",
"components.Settings.SettingsMain.proxyUser": "Proxy Username",
"components.Settings.SettingsMain.region": "Discover Region",
"components.Settings.SettingsMain.regionTip": "Filter content by regional availability",
"components.Settings.SettingsMain.streamingRegion": "Streaming Region",
"components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability",
"components.Settings.SettingsMain.toastApiKeyFailure": "Something went wrong while generating a new API key.",
"components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!",
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
@@ -1097,7 +1099,7 @@
"components.Setup.finishing": "Finishing…",
"components.Setup.servertype": "Choose Server Type",
"components.Setup.setup": "Setup",
"components.Setup.signin": "Sign In",
"components.Setup.signin": "Sign in to your account",
"components.Setup.signinMessage": "Get started by signing in",
"components.Setup.signinWithEmby": "Enter your Emby details",
"components.Setup.signinWithJellyfin": "Enter your Jellyfin details",
@@ -1223,6 +1225,8 @@
"components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Display Language",
"components.UserProfile.UserSettings.UserGeneralSettings.discordId": "Discord User ID",
"components.UserProfile.UserSettings.UserGeneralSettings.discordIdTip": "The <FindDiscordIdLink>multi-digit ID number</FindDiscordIdLink> associated with your Discord user account",
"components.UserProfile.UserSettings.UserGeneralSettings.discoverRegion": "Discover Region",
"components.UserProfile.UserSettings.UserGeneralSettings.discoverRegionTip": "Filter content by regional availability",
"components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Display Name",
"components.UserProfile.UserSettings.UserGeneralSettings.email": "Email",
"components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Override Global Limit",
@@ -1246,6 +1250,8 @@
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Save Changes",
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Saving…",
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Series Request Limit",
"components.UserProfile.UserSettings.UserGeneralSettings.streamingRegion": "Streaming Region",
"components.UserProfile.UserSettings.UserGeneralSettings.streamingRegionTip": "Show streaming sites by regional availability",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Something went wrong while saving settings.",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "This email is already taken!",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmailEmpty": "Another user already has this username. You must set an email",

View File

@@ -192,7 +192,8 @@ CoreApp.getInitialProps = async (initialProps) => {
movie4kEnabled: false,
series4kEnabled: false,
localLogin: true,
region: '',
discoverRegion: '',
streamingRegion: '',
originalLanguage: '',
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,