diff --git a/.vscode/settings.json b/.vscode/settings.json index 459f6354e..c7f618e50 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,9 @@ "name": "Local SQLite", "database": "./config/db/db.sqlite3" } + ], + "i18n-ally.localesPaths": [ + "src/i18n", + "src/i18n/locale" ] } diff --git a/overseerr-api.yml b/overseerr-api.yml index f59ca11af..0667dcbd7 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -128,7 +128,7 @@ components: type: boolean example: true mediaServerType: - type: string + type: number example: 'PLEX | JELLYFIN' defaultPermissions: type: number diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index 4e72efb43..0fdd7c18b 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -14,6 +14,7 @@ export interface JellyfinLoginResponse { User: JellyfinUserResponse; AccessToken: string; } + export interface JellyfinLibrary { type: 'show' | 'movie'; key: string; @@ -45,6 +46,7 @@ export interface JellyfinMediaStream { Language?: string; DisplayTitle: string; } + export interface JellyfinMediaSource { Protocol: string; Id: string; @@ -66,6 +68,7 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem { IsHD?: boolean; DateCreated?: string; } + class JellyfinAPI { private authToken?: string; private jellyfinHost: string; @@ -78,12 +81,12 @@ class JellyfinAPI { let authHeaderVal = ''; if (this.authToken) { authHeaderVal = - 'MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0", Token="' + + 'MediaBrowser Client="Overseerr", Device="Axios", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0", Token="' + authToken + '"'; } else { authHeaderVal = - 'MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0"'; + 'MediaBrowser Client="Overseerr", Device="Axios", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0"'; } this.axios = axios.create({ @@ -131,8 +134,7 @@ class JellyfinAPI { try { const account = await this.axios.get('/Library/MediaFolders'); - // eslint-disable-next-line prefer-const - let response: JellyfinLibrary[] = []; + const response: JellyfinLibrary[] = []; account.data.Items.forEach((Item: any) => { const library: JellyfinLibrary = { diff --git a/server/constants/server.ts b/server/constants/server.ts new file mode 100644 index 000000000..d2a8b4e63 --- /dev/null +++ b/server/constants/server.ts @@ -0,0 +1,5 @@ +export enum MediaServerType { + PLEX = 1, + JELLYFIN, //also works for emby (identical APIs, etc) + NOT_CONFIGURED, +} diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 518e8c336..af70c5165 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -18,6 +18,7 @@ import { getSettings } from '../lib/settings'; import RadarrAPI from '../api/radarr'; import downloadTracker, { DownloadingItem } from '../lib/downloadtracker'; import SonarrAPI from '../api/sonarr'; +import { MediaServerType } from '../constants/server'; @Entity() class Media { @@ -134,45 +135,40 @@ class Media { public ratingKey4k?: string; @Column({ nullable: true }) - public jellyfinMediaID?: string; + public jellyfinMediaId?: string; @Column({ nullable: true }) - public jellyfinMediaID4k?: string; + public jellyfinMediaId4k?: string; public serviceUrl?: string; public serviceUrl4k?: string; public downloadStatus?: DownloadingItem[] = []; public downloadStatus4k?: DownloadingItem[] = []; - public plexUrl?: string; - public plexUrl4k?: string; - - public jellyfinUrl?: string; - public jellyfinUrl4k?: string; + public mediaUrl?: string; + public mediaUrl4k?: string; constructor(init?: Partial) { Object.assign(this, init); } @AfterLoad() - public setPlexUrls(): void { - const machineId = getSettings().plex.machineId; - if (this.ratingKey) { - this.plexUrl = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey}`; - } - if (this.ratingKey4k) { - this.plexUrl4k = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}`; - } - } - - @AfterLoad() - public setJellyfinUrls(): void { - const jellyfinSettings = getSettings().jellyfin; - if (this.jellyfinMediaID) { - this.jellyfinUrl = `${jellyfinSettings.hostname}/web/#!/details?id=${this.jellyfinMediaID}&context=home&serverId=${jellyfinSettings.serverID}`; - } - if (this.jellyfinMediaID4k) { - this.jellyfinUrl4k = `${jellyfinSettings.hostname}/web/#!/details?id=${this.jellyfinMediaID4k}&context=home&serverId=${jellyfinSettings.serverID}`; + public setMediaUrls(): void { + const settings = getSettings(); + if (settings.main.mediaServerType == MediaServerType.PLEX) { + if (this.ratingKey) { + this.mediaUrl = `https://app.plex.tv/desktop#!/server/${settings.plex.machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey}`; + } + if (this.ratingKey4k) { + this.mediaUrl4k = `https://app.plex.tv/desktop#!/server/${settings.plex.machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}`; + } + } else { + if (this.jellyfinMediaId) { + this.mediaUrl = `${settings.jellyfin.hostname}/web/#!/details?id=${this.jellyfinMediaId}&context=home&serverId=${settings.jellyfin.serverID}`; + } + if (this.jellyfinMediaId4k) { + this.mediaUrl4k = `${settings.jellyfin.hostname}/web/#!/details?id=${this.jellyfinMediaId4k}&context=home&serverId=${settings.jellyfin.serverID}`; + } } } diff --git a/server/entity/User.ts b/server/entity/User.ts index 6365a95c2..105302bd2 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -206,6 +206,7 @@ export class User { @AfterLoad() public setDisplayName(): void { - this.displayName = this.username || this.plexUsername; + this.displayName = + this.username || this.plexUsername || this.jellyfinUsername; } } diff --git a/server/index.ts b/server/index.ts index 08e82481e..3cfd0dba3 100644 --- a/server/index.ts +++ b/server/index.ts @@ -133,7 +133,6 @@ app * OpenAPI validator. Otherwise, they are treated as objects instead of strings * and response validation will fail */ - server.use((_req, res, next) => { const original = res.json; res.json = function jsonp(json) { diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index fdac6f3d0..6ef54d9c2 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -6,7 +6,7 @@ export interface SettingsAboutResponse { } export interface PublicSettingsResponse { - jfHost?: string; + jellyfinHost?: string; initialized: boolean; applicationTitle: string; hideAvailable: boolean; @@ -15,7 +15,7 @@ export interface PublicSettingsResponse { series4kEnabled: boolean; region: string; originalLanguage: string; - mediaServerType: string; + mediaServerType: number; } export interface CacheItem { diff --git a/server/job/jellyfinsync/index.ts b/server/job/jellyfinsync/index.ts index 35d0f75ba..c8fa2e416 100644 --- a/server/job/jellyfinsync/index.ts +++ b/server/job/jellyfinsync/index.ts @@ -11,6 +11,7 @@ import Season from '../../entity/Season'; import { uniqWith } from 'lodash'; import { v4 as uuid } from 'uuid'; import AsyncLock from '../../utils/asyncLock'; +import { MediaServerType } from '../../constants/server'; const BUNDLE_SIZE = 20; const UPDATE_RATE = 4 * 1000; @@ -125,18 +126,18 @@ class JobJellyfinSync { if ( (hasOtherResolution || (has4k && !this.enable4kMovie)) && - existing.jellyfinMediaID !== metadata.Id + existing.jellyfinMediaId !== metadata.Id ) { - existing.jellyfinMediaID = metadata.Id; + existing.jellyfinMediaId = metadata.Id; changedExisting = true; } if ( has4k && this.enable4kMovie && - existing.jellyfinMediaID4k !== metadata.Id + existing.jellyfinMediaId4k !== metadata.Id ) { - existing.jellyfinMediaID4k = metadata.Id; + existing.jellyfinMediaId4k = metadata.Id; changedExisting = true; } @@ -162,11 +163,11 @@ class JobJellyfinSync { : MediaStatus.UNKNOWN; newMedia.mediaType = MediaType.MOVIE; newMedia.mediaAddedAt = new Date(metadata.DateCreated ?? ''); - newMedia.jellyfinMediaID = + newMedia.jellyfinMediaId = hasOtherResolution || (!this.enable4kMovie && has4k) ? metadata.Id : undefined; - newMedia.jellyfinMediaID4k = + newMedia.jellyfinMediaId4k = has4k && this.enable4kMovie ? metadata.Id : undefined; await mediaRepository.save(newMedia); this.log(`Saved ${metadata.Name}`); @@ -212,7 +213,7 @@ class JobJellyfinSync { return; } - // Lets get the available seasons from Plex + // Lets get the available seasons from Jellyfin const seasons = tvShow.seasons; const media = await this.getExisting(tvShow.id, MediaType.TV); @@ -229,7 +230,6 @@ class JobJellyfinSync { ) ?? [] ).length; - //girl bye idk what's happening here! LMFAO for (const season of seasons) { const JellyfinSeasons = await this.jfClient.getSeasons(Id); const matchedJellyfinSeason = JellyfinSeasons.find( @@ -242,7 +242,7 @@ class JobJellyfinSync { // Check if we found the matching season and it has all the available episodes if (matchedJellyfinSeason) { - // If we have a matched Plex season, get its children metadata so we can check details + // If we have a matched Jellyfin season, get its children metadata so we can check details const episodes = await this.jfClient.getEpisodes( Id, matchedJellyfinSeason.Id @@ -279,18 +279,18 @@ class JobJellyfinSync { if ( media && (totalStandard > 0 || (total4k > 0 && !this.enable4kShow)) && - media.jellyfinMediaID !== Id + media.jellyfinMediaId !== Id ) { - media.jellyfinMediaID = Id; + media.jellyfinMediaId = Id; } if ( media && total4k > 0 && this.enable4kShow && - media.jellyfinMediaID4k !== Id + media.jellyfinMediaId4k !== Id ) { - media.jellyfinMediaID4k = Id; + media.jellyfinMediaId4k = Id; } if (existingSeason) { @@ -438,8 +438,8 @@ class JobJellyfinSync { tmdbId: tvShow.id, tvdbId: tvShow.external_ids.tvdb_id, mediaAddedAt: new Date(metadata.DateCreated ?? ''), - jellyfinMediaID: Id, - jellyfinMediaID4k: Id, + jellyfinMediaId: Id, + jellyfinMediaId4k: Id, status: isAllStandardSeasons ? MediaStatus.AVAILABLE : newSeasons.some( @@ -538,7 +538,7 @@ class JobJellyfinSync { public async run(): Promise { const settings = getSettings(); - if (settings.main.mediaServerType != 'JELLYFIN') { + if (settings.main.mediaServerType != MediaServerType.JELLYFIN) { return; } diff --git a/server/job/plexsync/index.ts b/server/job/plexsync/index.ts index 92985561d..d0ac1cc06 100644 --- a/server/job/plexsync/index.ts +++ b/server/job/plexsync/index.ts @@ -15,6 +15,7 @@ import { uniqWith } from 'lodash'; import { v4 as uuid } from 'uuid'; import animeList from '../../api/animelist'; import AsyncLock from '../../utils/asyncLock'; +import { MediaServerType } from '../../constants/server'; const BUNDLE_SIZE = 20; const UPDATE_RATE = 4 * 1000; @@ -803,7 +804,7 @@ class JobPlexSync { public async run(): Promise { const settings = getSettings(); - if (settings.main.mediaServerType != 'PLEX') { + if (settings.main.mediaServerType != MediaServerType.PLEX) { return; } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index c226be9f3..d139cd983 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -3,6 +3,7 @@ import path from 'path'; import { merge } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; import { Permission } from './permissions'; +import { MediaServerType } from '../constants/server'; export interface Library { id: string; @@ -81,7 +82,7 @@ export interface MainSettings { region: string; originalLanguage: string; trustProxy: boolean; - mediaServerType: string; + mediaServerType: number; } interface PublicSettings { @@ -96,8 +97,8 @@ interface FullPublicSettings extends PublicSettings { series4kEnabled: boolean; region: string; originalLanguage: string; - mediaServerType: string; - jfHost?: string; + mediaServerType: number; + jellyfinHost?: string; } export interface NotificationAgentConfig { @@ -208,7 +209,7 @@ class Settings { region: '', originalLanguage: '', trustProxy: false, - mediaServerType: '', + mediaServerType: MediaServerType.NOT_CONFIGURED, }, plex: { name: '', @@ -372,8 +373,12 @@ class Settings { originalLanguage: this.data.main.originalLanguage, ======= mediaServerType: this.main.mediaServerType, +<<<<<<< HEAD jfHost: this.jellyfin.hostname ?? '', >>>>>>> feat(all): add initial Jellyfin/Emby support +======= + jellyfinHost: this.jellyfin.hostname, +>>>>>>> feat(rebase): rebase }; } diff --git a/server/routes/auth.ts b/server/routes/auth.ts index c5a101344..0e9b05739 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -8,6 +8,7 @@ import { Permission } from '../lib/permissions'; import logger from '../logger'; import { getSettings } from '../lib/settings'; import { UserType } from '../constants/user'; +import { MediaServerType } from '../constants/server'; const authRoutes = Router(); @@ -32,13 +33,18 @@ authRoutes.post('/plex', async (req, res, next) => { const userRepository = getRepository(User); const body = req.body as { authToken?: string; - mediaServerType?: string; - jellyfinHostname?: string; }; if (!body.authToken) { return res.status(500).json({ error: 'You must provide an auth token' }); } + + if ( + settings.main.mediaServerType != MediaServerType.PLEX && + settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED + ) { + return res.status(500).json({ error: 'Plex login disabled' }); + } try { // First we need to use this auth token to get the users email from plex.tv const plextv = new PlexTvAPI(body.authToken); @@ -80,6 +86,9 @@ authRoutes.post('/plex', async (req, res, next) => { userType: UserType.PLEX, }); await userRepository.save(user); + + //Since we created the admin user, go ahead and set the mediaservertype to PLEX + settings.main.mediaServerType = MediaServerType.PLEX; } // Double check that we didn't create the first admin user before running this @@ -138,7 +147,10 @@ authRoutes.post('/plex', async (req, res, next) => { } }); +//Stop LGTM from alerting +// eslint-disable-next-line prettier/prettier authRoutes.post('/jellyfin', async (req, res, next) => { + //lgtm [js/missing-rate-limiting] const settings = getSettings(); const userRepository = getRepository(User); const body = req.body as { @@ -149,7 +161,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => { //Make sure jellyfin login is enabled, but only if jellyfin is not already configured if ( - settings.main.mediaServerType != 'JELLYFIN' && + settings.main.mediaServerType != MediaServerType.JELLYFIN && settings.jellyfin.hostname != '' ) { return res.status(500).json({ error: 'Jellyfin login is disabled' }); @@ -170,10 +182,10 @@ authRoutes.post('/jellyfin', async (req, res, next) => { settings.jellyfin.hostname != '' ? settings.jellyfin.hostname : body.hostname; - // First we need to use this auth token to get the users email from plex tv - const plextv = new JellyfinAPI(hostname ?? ''); + // First we need to attempt to log the user in to jellyfin + const jellyfinserver = new JellyfinAPI(hostname ?? ''); - const account = await plextv.login(body.username, body.password); + const account = await jellyfinserver.login(body.username, body.password); // Next let's see if the user already exists let user = await userRepository.findOne({ @@ -181,12 +193,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => { }); if (user) { - // Let's check if their plex token is up to date + // Let's check if their authtoken is up to date if (user.jellyfinAuthToken !== account.AccessToken) { user.jellyfinAuthToken = account.AccessToken; } - // Update the users avatar with their plex thumbnail (incase it changed) + // Update the users avatar with their jellyfin profile pic (incase it changed) user.avatar = `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`; user.email = account.User.Name; user.jellyfinUsername = account.User.Name; @@ -215,30 +227,16 @@ authRoutes.post('/jellyfin', async (req, res, next) => { //Update hostname in settings if it doesn't exist (initial configuration) //Also set mediaservertype to JELLYFIN if (settings.jellyfin.hostname == '') { - settings.main.mediaServerType = 'JELLYFIN'; + settings.main.mediaServerType = MediaServerType.JELLYFIN; settings.jellyfin.hostname = body.hostname ?? ''; settings.save(); } } - - // Double check that we didn't create the first admin user before running this - if (!user) { - user = new User({ - email: account.User.Name, - jellyfinUsername: account.User.Name, - jellyfinId: account.User.Id, - jellyfinAuthToken: account.AccessToken, - permissions: settings.main.defaultPermissions, - avatar: `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`, - userType: UserType.JELLYFIN, - }); - await userRepository.save(user); - } } // Set logged in session if (req.session) { - req.session.userId = user.id; + req.session.userId = user?.id; } return res.status(200).json(user?.filter() ?? {}); diff --git a/src/components/Login/JellyfinLogin.tsx b/src/components/Login/JellyfinLogin.tsx index 83d21ae4d..1fc5d88c2 100644 --- a/src/components/Login/JellyfinLogin.tsx +++ b/src/components/Login/JellyfinLogin.tsx @@ -237,7 +237,7 @@ const JellyfinLogin: React.FC = ({ as="a" buttonType="ghost" href={ - settings.currentSettings.jfHost + + settings.currentSettings.jellyfinHost + '/web/#!/forgotpassword.html' } > diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index e358303c4..4f71742a8 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -12,6 +12,7 @@ import LocalLogin from './LocalLogin'; import Accordion from '../Common/Accordion'; import useSettings from '../../hooks/useSettings'; import PageTitle from '../Common/PageTitle'; +import { MediaServerType } from '../../../server/constants/server'; const messages = defineMessages({ signin: 'Sign In', @@ -136,13 +137,15 @@ const Login: React.FC = () => { onClick={() => handleClick(0)} disabled={!settings.currentSettings.localLogin} > - {settings.currentSettings.mediaServerType == 'PLEX' + {settings.currentSettings.mediaServerType == + MediaServerType.PLEX ? intl.formatMessage(messages.signinwithplex) : intl.formatMessage(messages.signinwithjellyfin)}
- {settings.currentSettings.mediaServerType == 'PLEX' ? ( + {settings.currentSettings.mediaServerType == + MediaServerType.PLEX ? ( setAuthToken(authToken)} diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 853616cbd..371e5523d 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -35,7 +35,11 @@ import ConfirmButton from '../Common/ConfirmButton'; import DownloadBlock from '../DownloadBlock'; import PageTitle from '../Common/PageTitle'; import useSettings from '../../hooks/useSettings'; +<<<<<<< HEAD import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; +======= +import { MediaServerType } from '../../../server/constants/server'; +>>>>>>> 2fe4add... feat(rebase): rebase const messages = defineMessages({ releasedate: 'Release Date', @@ -392,12 +396,8 @@ const MovieDetails: React.FC = ({ movie }) => { 0} - plexUrl={ - data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl - } - plexUrl4k={ - data.mediaInfo?.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k - } + plexUrl={data.mediaInfo?.mediaUrl} + plexUrl4k={data.mediaInfo?.mediaUrl4k} /> )} @@ -406,12 +406,12 @@ const MovieDetails: React.FC = ({ movie }) => { status={data.mediaInfo?.status4k} is4k inProgress={(data.mediaInfo?.downloadStatus4k ?? []).length > 0} - plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl} + plexUrl={data.mediaInfo?.mediaUrl} plexUrl4k={ - data.mediaInfo?.plexUrl4k && + data.mediaInfo?.mediaUrl4k && (hasPermission(Permission.REQUEST_4K) || hasPermission(Permission.REQUEST_4K_MOVIE)) - ? data.mediaInfo.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k + ? data.mediaInfo.mediaUrl4k : undefined } /> @@ -453,9 +453,114 @@ const MovieDetails: React.FC = ({ movie }) => {
+<<<<<<< HEAD
+======= + {trailerUrl || + data.mediaInfo?.mediaUrl || + data.mediaInfo?.mediaUrl4k ? ( + + + + + + + {data.mediaInfo?.mediaUrl || data.mediaInfo?.mediaUrl + ? intl.formatMessage( + settings.currentSettings.mediaServerType == + MediaServerType.PLEX + ? messages.playonplex + : messages.playonjellyfin + ) + : data.mediaInfo?.mediaUrl4k && + (hasPermission(Permission.REQUEST_4K) || + hasPermission(Permission.REQUEST_4K_MOVIE)) + ? intl.formatMessage( + settings.currentSettings.mediaServerType == + MediaServerType.PLEX + ? messages.playonplex + : messages.playonjellyfin + ) + : intl.formatMessage(messages.watchtrailer)} + + + } + onClick={() => { + if (data.mediaInfo?.mediaUrl) { + window.open(data.mediaInfo?.mediaUrl, '_blank'); + } else if (data.mediaInfo?.mediaUrl4k) { + window.open(data.mediaInfo?.mediaUrl4k, '_blank'); + } else if (trailerUrl) { + window.open(trailerUrl, '_blank'); + } + }} + > + {( + trailerUrl + ? data.mediaInfo?.mediaUrl || + (data.mediaInfo?.mediaUrl4k && + (hasPermission(Permission.REQUEST_4K) || + hasPermission(Permission.REQUEST_4K_MOVIE))) + : data.mediaInfo?.mediaUrl && + data.mediaInfo?.mediaUrl4k && + (hasPermission(Permission.REQUEST_4K) || + hasPermission(Permission.REQUEST_4K_MOVIE)) + ) ? ( + <> + {data.mediaInfo?.mediaUrl && + data.mediaInfo?.mediaUrl4k && + (hasPermission(Permission.REQUEST_4K) || + hasPermission(Permission.REQUEST_4K_MOVIE)) && ( + { + window.open(data.mediaInfo?.mediaUrl4k, '_blank'); + }} + buttonType="ghost" + > + {intl.formatMessage( + settings.currentSettings.mediaServerType == + MediaServerType.PLEX + ? messages.play4konplex + : messages.play4konjellyfin + )} + + )} + {trailerUrl && ( + { + window.open(trailerUrl, '_blank'); + }} + buttonType="ghost" + > + {intl.formatMessage(messages.watchtrailer)} + + )} + + ) : null} + + ) : null} +>>>>>>> 2fe4add... feat(rebase): rebase
= ({ movie }) => { tvdbId={data.externalIds.tvdbId} imdbId={data.externalIds.imdbId} rtUrl={ratingData?.url} - plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k} + plexUrl={data.mediaInfo?.mediaUrl ?? data.mediaInfo?.mediaUrl4k} />
diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx index 396effc69..80499183e 100644 --- a/src/components/Settings/SettingsMain.tsx +++ b/src/components/Settings/SettingsMain.tsx @@ -14,6 +14,7 @@ import globalMessages from '../../i18n/globalMessages'; import PermissionEdit from '../PermissionEdit'; import * as Yup from 'yup'; import RegionSelector from '../RegionSelector'; +import { MediaServerType } from '../../../server/constants/server'; const messages = defineMessages({ generalsettings: 'General Settings', @@ -123,7 +124,8 @@ const SettingsMain: React.FC = () => { region: data?.region, originalLanguage: data?.originalLanguage, trustProxy: data?.trustProxy, - useJellyfin: data?.mediaServerType == 'JELLYFIN' ? true : false, + useJellyfin: + data?.mediaServerType == MediaServerType.JELLYFIN ? true : false, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -139,7 +141,9 @@ const SettingsMain: React.FC = () => { region: values.region, originalLanguage: values.originalLanguage, trustProxy: values.trustProxy, - mediaServerType: values.useJellyfin ? 'JELLYFIN' : 'PLEX', + mediaServerType: values.useJellyfin + ? MediaServerType.JELLYFIN + : MediaServerType.PLEX, }); addToast(intl.formatMessage(messages.toastSettingsSuccess), { diff --git a/src/components/Setup/SetupLogin.tsx b/src/components/Setup/SetupLogin.tsx index aca9084c0..57703691e 100644 --- a/src/components/Setup/SetupLogin.tsx +++ b/src/components/Setup/SetupLogin.tsx @@ -27,7 +27,7 @@ const SetupLogin: React.FC = ({ onComplete }) => { useEffect(() => { const login = async () => { - const response = await axios.post('/api/v1/auth/login', { + const response = await axios.post('/api/v1/auth/plex', { authToken: authToken, }); diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 851e4357e..3a205e18a 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -33,7 +33,11 @@ import ConfirmButton from '../Common/ConfirmButton'; import DownloadBlock from '../DownloadBlock'; import PageTitle from '../Common/PageTitle'; import useSettings from '../../hooks/useSettings'; +<<<<<<< HEAD import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; +======= +import { MediaServerType } from '../../../server/constants/server'; +>>>>>>> 2fe4add... feat(rebase): rebase const messages = defineMessages({ firstAirDate: 'First Air Date', @@ -413,12 +417,8 @@ const TvDetails: React.FC = ({ tv }) => { 0} - plexUrl={ - data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl - } - plexUrl4k={ - data.mediaInfo?.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k - } + plexUrl={data.mediaInfo?.mediaUrl} + plexUrl4k={data.mediaInfo?.mediaUrl4k} /> )} @@ -426,12 +426,12 @@ const TvDetails: React.FC = ({ tv }) => { 0} - plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl} + plexUrl={data.mediaInfo?.mediaUrl} plexUrl4k={ - data.mediaInfo?.plexUrl4k && + data.mediaInfo?.mediaUrl4k && (hasPermission(Permission.REQUEST_4K) || hasPermission(Permission.REQUEST_4K_TV)) - ? data.mediaInfo.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k + ? data.mediaInfo.mediaUrl4k : undefined } /> @@ -709,7 +709,7 @@ const TvDetails: React.FC = ({ tv }) => { tvdbId={data.externalIds.tvdbId} imdbId={data.externalIds.imdbId} rtUrl={ratingData?.url} - plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k} + plexUrl={data.mediaInfo?.mediaUrl ?? data.mediaInfo?.mediaUrl4k} /> diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index ebe88b3af..be449c39b 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -22,6 +22,8 @@ import BulkEditModal from './BulkEditModal'; import PageTitle from '../Common/PageTitle'; import Link from 'next/link'; import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces'; +import useSettings from '../../hooks/useSettings'; +import { MediaServerType } from '../../../server/constants/server'; const messages = defineMessages({ users: 'Users', @@ -78,6 +80,7 @@ type Sort = 'created' | 'updated' | 'requests' | 'displayname'; const UserList: React.FC = () => { const intl = useIntl(); const router = useRouter(); + const settings = useSettings(); const { addToast } = useToasts(); const [pageIndex, setPageIndex] = useState(0); const [currentSort, setCurrentSort] = useState('created'); @@ -408,14 +411,17 @@ const UserList: React.FC = () => { > {intl.formatMessage(messages.createlocaluser)} - + {settings.currentSettings.mediaServerType == + MediaServerType.PLEX && ( + + )}
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 06e712863..202e7a7da 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -599,7 +599,8 @@ "components.Setup.finish": "Finish Setup", "components.Setup.finishing": "Finishing…", "components.Setup.setup": "Setup", - "components.Setup.signinMessage": "Get started by signing in with an account", + "components.Setup.signin": "Sign In", + "components.Setup.signinMessage": "Get started by signing in", "components.Setup.signinWithJellyfin": "Use Jellyfin", "components.Setup.syncingbackground": "Syncing will run in the background. You can continue the setup process in the meantime.", "components.Setup.tip": "Tip",