diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index e9c832a85..9393370dc 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -46,6 +46,7 @@ export interface PublicSettingsResponse { locale: string; emailEnabled: boolean; newPlexLogin: boolean; + youtubeUrl: string; } export interface CacheItem { diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 9fa011af4..490b926a7 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -135,6 +135,7 @@ export interface MainSettings { partialRequestsEnabled: boolean; enableSpecialEpisodes: boolean; locale: string; + youtubeUrl: string; } export interface NetworkSettings { @@ -172,6 +173,7 @@ interface FullPublicSettings extends PublicSettings { emailEnabled: boolean; userEmailRequired: boolean; newPlexLogin: boolean; + youtubeUrl: string; } export interface NotificationAgentConfig { @@ -375,6 +377,7 @@ class Settings { partialRequestsEnabled: true, enableSpecialEpisodes: false, locale: 'en', + youtubeUrl: '', }, plex: { name: '', @@ -646,6 +649,7 @@ class Settings { userEmailRequired: this.data.notifications.agents.email.options.userEmailRequired, newPlexLogin: this.data.main.newPlexLogin, + youtubeUrl: this.data.main.youtubeUrl, }; } diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 29b3ab564..80ede2d0e 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -210,10 +210,16 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { svg: , }); } - const trailerUrl = data.relatedVideos + + const trailerVideo = data.relatedVideos ?.filter((r) => r.type === 'Trailer') .sort((a, b) => a.size - b.size) - .pop()?.url; + .pop(); + const trailerUrl = + trailerVideo?.site === 'YouTube' && + settings.currentSettings.youtubeUrl != '' + ? `${settings.currentSettings.youtubeUrl}${trailerVideo?.key}` + : trailerVideo?.url; if (trailerUrl) { mediaLinks.push({ diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index 41673813f..d1e809a28 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -64,6 +64,9 @@ const messages = defineMessages('components.Settings.SettingsMain', { partialRequestsEnabled: 'Allow Partial Series Requests', enableSpecialEpisodes: 'Allow Special Episodes Requests', locale: 'Display Language', + youtubeUrl: 'YouTube URL', + validationUrl: 'You must provide a valid URL', + validationUrlTrailingSlash: 'URL must not end in a trailing slash', }); const SettingsMain = () => { @@ -105,6 +108,13 @@ const SettingsMain = () => { 'Number must be less than or equal to 250.', (value) => (value ?? 0) <= 250 ), + youtubeUrl: Yup.string() + .url(intl.formatMessage(messages.validationUrl)) + .test( + 'no-trailing-slash', + intl.formatMessage(messages.validationUrlTrailingSlash), + (value) => !value || !value.endsWith('/') + ), }); const regenerate = async () => { @@ -160,6 +170,7 @@ const SettingsMain = () => { partialRequestsEnabled: data?.partialRequestsEnabled, enableSpecialEpisodes: data?.enableSpecialEpisodes, cacheImages: data?.cacheImages, + youtubeUrl: data?.youtubeUrl, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -179,6 +190,7 @@ const SettingsMain = () => { partialRequestsEnabled: values.partialRequestsEnabled, enableSpecialEpisodes: values.enableSpecialEpisodes, cacheImages: values.cacheImages, + youtubeUrl: values.youtubeUrl, }); mutate('/api/v1/settings/public'); mutate('/api/v1/status'); @@ -519,6 +531,26 @@ const SettingsMain = () => { /> +
+ +
+
+ +
+ {errors.youtubeUrl && + touched.youtubeUrl && + typeof errors.youtubeUrl === 'string' && ( +
{errors.youtubeUrl}
+ )} +
+
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 5165d51ba..1b008e40b 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -208,10 +208,15 @@ const TvDetails = ({ tv }: TvDetailsProps) => { }); } - const trailerUrl = data.relatedVideos + const trailerVideo = data.relatedVideos ?.filter((r) => r.type === 'Trailer') .sort((a, b) => a.size - b.size) - .pop()?.url; + .pop(); + const trailerUrl = + trailerVideo?.site === 'YouTube' && + settings.currentSettings.youtubeUrl != '' + ? `${settings.currentSettings.youtubeUrl}${trailerVideo?.key}` + : trailerVideo?.url; if (trailerUrl) { mediaLinks.push({ diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index 372961201..aa838b406 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -30,6 +30,7 @@ const defaultSettings = { locale: 'en', emailEnabled: false, newPlexLogin: true, + youtubeUrl: '', }; export const SettingsContext = React.createContext({ diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index ccf52ca04..a08ec69e7 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -104,6 +104,7 @@ "components.Discover.StudioSlider.studios": "Studios", "components.Discover.TvGenreList.seriesgenres": "Series Genres", "components.Discover.TvGenreSlider.tvgenres": "Series Genres", + "components.DiscoverTvUpcoming.upcomingtv": "Upcoming Series", "components.Discover.createnewslider": "Create New Slider", "components.Discover.customizediscover": "Customize Discover", "components.Discover.discover": "Discover", @@ -137,7 +138,6 @@ "components.Discover.upcomingtv": "Upcoming Series", "components.Discover.updatefailed": "Something went wrong updating the discover customization settings.", "components.Discover.updatesuccess": "Updated discover customization settings.", - "components.DiscoverTvUpcoming.upcomingtv": "Upcoming Series", "components.DownloadBlock.estimatedtime": "Estimated {time}", "components.DownloadBlock.formattedTitle": "{title}: Season {seasonNumber} Episode {episodeNumber}", "components.IssueDetails.IssueComment.areyousuredelete": "Are you sure you want to delete this comment?", @@ -975,6 +975,9 @@ "components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title", "components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL", "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.SettingsMain.validationUrl": "You must provide a valid URL", + "components.Settings.SettingsMain.validationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.SettingsMain.youtubeUrl": "YouTube URL", "components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection", "components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!", "components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 260dcfe74..88513e3eb 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -248,6 +248,7 @@ CoreApp.getInitialProps = async (initialProps) => { locale: 'en', emailEnabled: false, newPlexLogin: true, + youtubeUrl: '', }; if (ctx.res) {