mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-03 21:37:42 -05:00
feat(rebase): rebase
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -14,5 +14,9 @@
|
|||||||
"name": "Local SQLite",
|
"name": "Local SQLite",
|
||||||
"database": "./config/db/db.sqlite3"
|
"database": "./config/db/db.sqlite3"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"src/i18n",
|
||||||
|
"src/i18n/locale"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
mediaServerType:
|
mediaServerType:
|
||||||
type: string
|
type: number
|
||||||
example: 'PLEX | JELLYFIN'
|
example: 'PLEX | JELLYFIN'
|
||||||
defaultPermissions:
|
defaultPermissions:
|
||||||
type: number
|
type: number
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export interface JellyfinLoginResponse {
|
|||||||
User: JellyfinUserResponse;
|
User: JellyfinUserResponse;
|
||||||
AccessToken: string;
|
AccessToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JellyfinLibrary {
|
export interface JellyfinLibrary {
|
||||||
type: 'show' | 'movie';
|
type: 'show' | 'movie';
|
||||||
key: string;
|
key: string;
|
||||||
@@ -45,6 +46,7 @@ export interface JellyfinMediaStream {
|
|||||||
Language?: string;
|
Language?: string;
|
||||||
DisplayTitle: string;
|
DisplayTitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JellyfinMediaSource {
|
export interface JellyfinMediaSource {
|
||||||
Protocol: string;
|
Protocol: string;
|
||||||
Id: string;
|
Id: string;
|
||||||
@@ -66,6 +68,7 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
|
|||||||
IsHD?: boolean;
|
IsHD?: boolean;
|
||||||
DateCreated?: string;
|
DateCreated?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class JellyfinAPI {
|
class JellyfinAPI {
|
||||||
private authToken?: string;
|
private authToken?: string;
|
||||||
private jellyfinHost: string;
|
private jellyfinHost: string;
|
||||||
@@ -78,12 +81,12 @@ class JellyfinAPI {
|
|||||||
let authHeaderVal = '';
|
let authHeaderVal = '';
|
||||||
if (this.authToken) {
|
if (this.authToken) {
|
||||||
authHeaderVal =
|
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 +
|
authToken +
|
||||||
'"';
|
'"';
|
||||||
} else {
|
} else {
|
||||||
authHeaderVal =
|
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({
|
this.axios = axios.create({
|
||||||
@@ -131,8 +134,7 @@ class JellyfinAPI {
|
|||||||
try {
|
try {
|
||||||
const account = await this.axios.get<any>('/Library/MediaFolders');
|
const account = await this.axios.get<any>('/Library/MediaFolders');
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
const response: JellyfinLibrary[] = [];
|
||||||
let response: JellyfinLibrary[] = [];
|
|
||||||
|
|
||||||
account.data.Items.forEach((Item: any) => {
|
account.data.Items.forEach((Item: any) => {
|
||||||
const library: JellyfinLibrary = {
|
const library: JellyfinLibrary = {
|
||||||
|
|||||||
5
server/constants/server.ts
Normal file
5
server/constants/server.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum MediaServerType {
|
||||||
|
PLEX = 1,
|
||||||
|
JELLYFIN, //also works for emby (identical APIs, etc)
|
||||||
|
NOT_CONFIGURED,
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import { getSettings } from '../lib/settings';
|
|||||||
import RadarrAPI from '../api/radarr';
|
import RadarrAPI from '../api/radarr';
|
||||||
import downloadTracker, { DownloadingItem } from '../lib/downloadtracker';
|
import downloadTracker, { DownloadingItem } from '../lib/downloadtracker';
|
||||||
import SonarrAPI from '../api/sonarr';
|
import SonarrAPI from '../api/sonarr';
|
||||||
|
import { MediaServerType } from '../constants/server';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
class Media {
|
class Media {
|
||||||
@@ -134,45 +135,40 @@ class Media {
|
|||||||
public ratingKey4k?: string;
|
public ratingKey4k?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
public jellyfinMediaID?: string;
|
public jellyfinMediaId?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
public jellyfinMediaID4k?: string;
|
public jellyfinMediaId4k?: string;
|
||||||
|
|
||||||
public serviceUrl?: string;
|
public serviceUrl?: string;
|
||||||
public serviceUrl4k?: string;
|
public serviceUrl4k?: string;
|
||||||
public downloadStatus?: DownloadingItem[] = [];
|
public downloadStatus?: DownloadingItem[] = [];
|
||||||
public downloadStatus4k?: DownloadingItem[] = [];
|
public downloadStatus4k?: DownloadingItem[] = [];
|
||||||
|
|
||||||
public plexUrl?: string;
|
public mediaUrl?: string;
|
||||||
public plexUrl4k?: string;
|
public mediaUrl4k?: string;
|
||||||
|
|
||||||
public jellyfinUrl?: string;
|
|
||||||
public jellyfinUrl4k?: string;
|
|
||||||
|
|
||||||
constructor(init?: Partial<Media>) {
|
constructor(init?: Partial<Media>) {
|
||||||
Object.assign(this, init);
|
Object.assign(this, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterLoad()
|
@AfterLoad()
|
||||||
public setPlexUrls(): void {
|
public setMediaUrls(): void {
|
||||||
const machineId = getSettings().plex.machineId;
|
const settings = getSettings();
|
||||||
if (this.ratingKey) {
|
if (settings.main.mediaServerType == MediaServerType.PLEX) {
|
||||||
this.plexUrl = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey}`;
|
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.plexUrl4k = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}`;
|
if (this.ratingKey4k) {
|
||||||
}
|
this.mediaUrl4k = `https://app.plex.tv/desktop#!/server/${settings.plex.machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}`;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
@AfterLoad()
|
if (this.jellyfinMediaId) {
|
||||||
public setJellyfinUrls(): void {
|
this.mediaUrl = `${settings.jellyfin.hostname}/web/#!/details?id=${this.jellyfinMediaId}&context=home&serverId=${settings.jellyfin.serverID}`;
|
||||||
const jellyfinSettings = getSettings().jellyfin;
|
}
|
||||||
if (this.jellyfinMediaID) {
|
if (this.jellyfinMediaId4k) {
|
||||||
this.jellyfinUrl = `${jellyfinSettings.hostname}/web/#!/details?id=${this.jellyfinMediaID}&context=home&serverId=${jellyfinSettings.serverID}`;
|
this.mediaUrl4k = `${settings.jellyfin.hostname}/web/#!/details?id=${this.jellyfinMediaId4k}&context=home&serverId=${settings.jellyfin.serverID}`;
|
||||||
}
|
}
|
||||||
if (this.jellyfinMediaID4k) {
|
|
||||||
this.jellyfinUrl4k = `${jellyfinSettings.hostname}/web/#!/details?id=${this.jellyfinMediaID4k}&context=home&serverId=${jellyfinSettings.serverID}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export class User {
|
|||||||
|
|
||||||
@AfterLoad()
|
@AfterLoad()
|
||||||
public setDisplayName(): void {
|
public setDisplayName(): void {
|
||||||
this.displayName = this.username || this.plexUsername;
|
this.displayName =
|
||||||
|
this.username || this.plexUsername || this.jellyfinUsername;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ app
|
|||||||
* OpenAPI validator. Otherwise, they are treated as objects instead of strings
|
* OpenAPI validator. Otherwise, they are treated as objects instead of strings
|
||||||
* and response validation will fail
|
* and response validation will fail
|
||||||
*/
|
*/
|
||||||
|
|
||||||
server.use((_req, res, next) => {
|
server.use((_req, res, next) => {
|
||||||
const original = res.json;
|
const original = res.json;
|
||||||
res.json = function jsonp(json) {
|
res.json = function jsonp(json) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface SettingsAboutResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicSettingsResponse {
|
export interface PublicSettingsResponse {
|
||||||
jfHost?: string;
|
jellyfinHost?: string;
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
applicationTitle: string;
|
applicationTitle: string;
|
||||||
hideAvailable: boolean;
|
hideAvailable: boolean;
|
||||||
@@ -15,7 +15,7 @@ export interface PublicSettingsResponse {
|
|||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
mediaServerType: string;
|
mediaServerType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CacheItem {
|
export interface CacheItem {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import Season from '../../entity/Season';
|
|||||||
import { uniqWith } from 'lodash';
|
import { uniqWith } from 'lodash';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import AsyncLock from '../../utils/asyncLock';
|
import AsyncLock from '../../utils/asyncLock';
|
||||||
|
import { MediaServerType } from '../../constants/server';
|
||||||
|
|
||||||
const BUNDLE_SIZE = 20;
|
const BUNDLE_SIZE = 20;
|
||||||
const UPDATE_RATE = 4 * 1000;
|
const UPDATE_RATE = 4 * 1000;
|
||||||
@@ -125,18 +126,18 @@ class JobJellyfinSync {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(hasOtherResolution || (has4k && !this.enable4kMovie)) &&
|
(hasOtherResolution || (has4k && !this.enable4kMovie)) &&
|
||||||
existing.jellyfinMediaID !== metadata.Id
|
existing.jellyfinMediaId !== metadata.Id
|
||||||
) {
|
) {
|
||||||
existing.jellyfinMediaID = metadata.Id;
|
existing.jellyfinMediaId = metadata.Id;
|
||||||
changedExisting = true;
|
changedExisting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
has4k &&
|
has4k &&
|
||||||
this.enable4kMovie &&
|
this.enable4kMovie &&
|
||||||
existing.jellyfinMediaID4k !== metadata.Id
|
existing.jellyfinMediaId4k !== metadata.Id
|
||||||
) {
|
) {
|
||||||
existing.jellyfinMediaID4k = metadata.Id;
|
existing.jellyfinMediaId4k = metadata.Id;
|
||||||
changedExisting = true;
|
changedExisting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +163,11 @@ class JobJellyfinSync {
|
|||||||
: MediaStatus.UNKNOWN;
|
: MediaStatus.UNKNOWN;
|
||||||
newMedia.mediaType = MediaType.MOVIE;
|
newMedia.mediaType = MediaType.MOVIE;
|
||||||
newMedia.mediaAddedAt = new Date(metadata.DateCreated ?? '');
|
newMedia.mediaAddedAt = new Date(metadata.DateCreated ?? '');
|
||||||
newMedia.jellyfinMediaID =
|
newMedia.jellyfinMediaId =
|
||||||
hasOtherResolution || (!this.enable4kMovie && has4k)
|
hasOtherResolution || (!this.enable4kMovie && has4k)
|
||||||
? metadata.Id
|
? metadata.Id
|
||||||
: undefined;
|
: undefined;
|
||||||
newMedia.jellyfinMediaID4k =
|
newMedia.jellyfinMediaId4k =
|
||||||
has4k && this.enable4kMovie ? metadata.Id : undefined;
|
has4k && this.enable4kMovie ? metadata.Id : undefined;
|
||||||
await mediaRepository.save(newMedia);
|
await mediaRepository.save(newMedia);
|
||||||
this.log(`Saved ${metadata.Name}`);
|
this.log(`Saved ${metadata.Name}`);
|
||||||
@@ -212,7 +213,7 @@ class JobJellyfinSync {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lets get the available seasons from Plex
|
// Lets get the available seasons from Jellyfin
|
||||||
const seasons = tvShow.seasons;
|
const seasons = tvShow.seasons;
|
||||||
const media = await this.getExisting(tvShow.id, MediaType.TV);
|
const media = await this.getExisting(tvShow.id, MediaType.TV);
|
||||||
|
|
||||||
@@ -229,7 +230,6 @@ class JobJellyfinSync {
|
|||||||
) ?? []
|
) ?? []
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
//girl bye idk what's happening here! LMFAO
|
|
||||||
for (const season of seasons) {
|
for (const season of seasons) {
|
||||||
const JellyfinSeasons = await this.jfClient.getSeasons(Id);
|
const JellyfinSeasons = await this.jfClient.getSeasons(Id);
|
||||||
const matchedJellyfinSeason = JellyfinSeasons.find(
|
const matchedJellyfinSeason = JellyfinSeasons.find(
|
||||||
@@ -242,7 +242,7 @@ class JobJellyfinSync {
|
|||||||
|
|
||||||
// Check if we found the matching season and it has all the available episodes
|
// Check if we found the matching season and it has all the available episodes
|
||||||
if (matchedJellyfinSeason) {
|
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(
|
const episodes = await this.jfClient.getEpisodes(
|
||||||
Id,
|
Id,
|
||||||
matchedJellyfinSeason.Id
|
matchedJellyfinSeason.Id
|
||||||
@@ -279,18 +279,18 @@ class JobJellyfinSync {
|
|||||||
if (
|
if (
|
||||||
media &&
|
media &&
|
||||||
(totalStandard > 0 || (total4k > 0 && !this.enable4kShow)) &&
|
(totalStandard > 0 || (total4k > 0 && !this.enable4kShow)) &&
|
||||||
media.jellyfinMediaID !== Id
|
media.jellyfinMediaId !== Id
|
||||||
) {
|
) {
|
||||||
media.jellyfinMediaID = Id;
|
media.jellyfinMediaId = Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
media &&
|
media &&
|
||||||
total4k > 0 &&
|
total4k > 0 &&
|
||||||
this.enable4kShow &&
|
this.enable4kShow &&
|
||||||
media.jellyfinMediaID4k !== Id
|
media.jellyfinMediaId4k !== Id
|
||||||
) {
|
) {
|
||||||
media.jellyfinMediaID4k = Id;
|
media.jellyfinMediaId4k = Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingSeason) {
|
if (existingSeason) {
|
||||||
@@ -438,8 +438,8 @@ class JobJellyfinSync {
|
|||||||
tmdbId: tvShow.id,
|
tmdbId: tvShow.id,
|
||||||
tvdbId: tvShow.external_ids.tvdb_id,
|
tvdbId: tvShow.external_ids.tvdb_id,
|
||||||
mediaAddedAt: new Date(metadata.DateCreated ?? ''),
|
mediaAddedAt: new Date(metadata.DateCreated ?? ''),
|
||||||
jellyfinMediaID: Id,
|
jellyfinMediaId: Id,
|
||||||
jellyfinMediaID4k: Id,
|
jellyfinMediaId4k: Id,
|
||||||
status: isAllStandardSeasons
|
status: isAllStandardSeasons
|
||||||
? MediaStatus.AVAILABLE
|
? MediaStatus.AVAILABLE
|
||||||
: newSeasons.some(
|
: newSeasons.some(
|
||||||
@@ -538,7 +538,7 @@ class JobJellyfinSync {
|
|||||||
public async run(): Promise<void> {
|
public async run(): Promise<void> {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
if (settings.main.mediaServerType != 'JELLYFIN') {
|
if (settings.main.mediaServerType != MediaServerType.JELLYFIN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { uniqWith } from 'lodash';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import animeList from '../../api/animelist';
|
import animeList from '../../api/animelist';
|
||||||
import AsyncLock from '../../utils/asyncLock';
|
import AsyncLock from '../../utils/asyncLock';
|
||||||
|
import { MediaServerType } from '../../constants/server';
|
||||||
|
|
||||||
const BUNDLE_SIZE = 20;
|
const BUNDLE_SIZE = 20;
|
||||||
const UPDATE_RATE = 4 * 1000;
|
const UPDATE_RATE = 4 * 1000;
|
||||||
@@ -803,7 +804,7 @@ class JobPlexSync {
|
|||||||
public async run(): Promise<void> {
|
public async run(): Promise<void> {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
if (settings.main.mediaServerType != 'PLEX') {
|
if (settings.main.mediaServerType != MediaServerType.PLEX) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { Permission } from './permissions';
|
import { Permission } from './permissions';
|
||||||
|
import { MediaServerType } from '../constants/server';
|
||||||
|
|
||||||
export interface Library {
|
export interface Library {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -81,7 +82,7 @@ export interface MainSettings {
|
|||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
trustProxy: boolean;
|
trustProxy: boolean;
|
||||||
mediaServerType: string;
|
mediaServerType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicSettings {
|
interface PublicSettings {
|
||||||
@@ -96,8 +97,8 @@ interface FullPublicSettings extends PublicSettings {
|
|||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
mediaServerType: string;
|
mediaServerType: number;
|
||||||
jfHost?: string;
|
jellyfinHost?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NotificationAgentConfig {
|
export interface NotificationAgentConfig {
|
||||||
@@ -208,7 +209,7 @@ class Settings {
|
|||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
trustProxy: false,
|
trustProxy: false,
|
||||||
mediaServerType: '',
|
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -372,8 +373,12 @@ class Settings {
|
|||||||
originalLanguage: this.data.main.originalLanguage,
|
originalLanguage: this.data.main.originalLanguage,
|
||||||
=======
|
=======
|
||||||
mediaServerType: this.main.mediaServerType,
|
mediaServerType: this.main.mediaServerType,
|
||||||
|
<<<<<<< HEAD
|
||||||
jfHost: this.jellyfin.hostname ?? '',
|
jfHost: this.jellyfin.hostname ?? '',
|
||||||
>>>>>>> feat(all): add initial Jellyfin/Emby support
|
>>>>>>> feat(all): add initial Jellyfin/Emby support
|
||||||
|
=======
|
||||||
|
jellyfinHost: this.jellyfin.hostname,
|
||||||
|
>>>>>>> feat(rebase): rebase
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Permission } from '../lib/permissions';
|
|||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { getSettings } from '../lib/settings';
|
import { getSettings } from '../lib/settings';
|
||||||
import { UserType } from '../constants/user';
|
import { UserType } from '../constants/user';
|
||||||
|
import { MediaServerType } from '../constants/server';
|
||||||
|
|
||||||
const authRoutes = Router();
|
const authRoutes = Router();
|
||||||
|
|
||||||
@@ -32,13 +33,18 @@ authRoutes.post('/plex', async (req, res, next) => {
|
|||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const body = req.body as {
|
const body = req.body as {
|
||||||
authToken?: string;
|
authToken?: string;
|
||||||
mediaServerType?: string;
|
|
||||||
jellyfinHostname?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!body.authToken) {
|
if (!body.authToken) {
|
||||||
return res.status(500).json({ error: 'You must provide an auth token' });
|
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 {
|
try {
|
||||||
// First we need to use this auth token to get the users email from plex.tv
|
// First we need to use this auth token to get the users email from plex.tv
|
||||||
const plextv = new PlexTvAPI(body.authToken);
|
const plextv = new PlexTvAPI(body.authToken);
|
||||||
@@ -80,6 +86,9 @@ authRoutes.post('/plex', async (req, res, next) => {
|
|||||||
userType: UserType.PLEX,
|
userType: UserType.PLEX,
|
||||||
});
|
});
|
||||||
await userRepository.save(user);
|
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
|
// 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) => {
|
authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||||
|
//lgtm [js/missing-rate-limiting]
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const body = req.body as {
|
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
|
//Make sure jellyfin login is enabled, but only if jellyfin is not already configured
|
||||||
if (
|
if (
|
||||||
settings.main.mediaServerType != 'JELLYFIN' &&
|
settings.main.mediaServerType != MediaServerType.JELLYFIN &&
|
||||||
settings.jellyfin.hostname != ''
|
settings.jellyfin.hostname != ''
|
||||||
) {
|
) {
|
||||||
return res.status(500).json({ error: 'Jellyfin login is disabled' });
|
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 != ''
|
||||||
? settings.jellyfin.hostname
|
? settings.jellyfin.hostname
|
||||||
: body.hostname;
|
: body.hostname;
|
||||||
// First we need to use this auth token to get the users email from plex tv
|
// First we need to attempt to log the user in to jellyfin
|
||||||
const plextv = new JellyfinAPI(hostname ?? '');
|
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
|
// Next let's see if the user already exists
|
||||||
let user = await userRepository.findOne({
|
let user = await userRepository.findOne({
|
||||||
@@ -181,12 +193,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
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) {
|
if (user.jellyfinAuthToken !== account.AccessToken) {
|
||||||
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.avatar = `${hostname}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
|
||||||
user.email = account.User.Name;
|
user.email = account.User.Name;
|
||||||
user.jellyfinUsername = 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)
|
//Update hostname in settings if it doesn't exist (initial configuration)
|
||||||
//Also set mediaservertype to JELLYFIN
|
//Also set mediaservertype to JELLYFIN
|
||||||
if (settings.jellyfin.hostname == '') {
|
if (settings.jellyfin.hostname == '') {
|
||||||
settings.main.mediaServerType = 'JELLYFIN';
|
settings.main.mediaServerType = MediaServerType.JELLYFIN;
|
||||||
settings.jellyfin.hostname = body.hostname ?? '';
|
settings.jellyfin.hostname = body.hostname ?? '';
|
||||||
settings.save();
|
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
|
// Set logged in session
|
||||||
if (req.session) {
|
if (req.session) {
|
||||||
req.session.userId = user.id;
|
req.session.userId = user?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(user?.filter() ?? {});
|
return res.status(200).json(user?.filter() ?? {});
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
|||||||
as="a"
|
as="a"
|
||||||
buttonType="ghost"
|
buttonType="ghost"
|
||||||
href={
|
href={
|
||||||
settings.currentSettings.jfHost +
|
settings.currentSettings.jellyfinHost +
|
||||||
'/web/#!/forgotpassword.html'
|
'/web/#!/forgotpassword.html'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import LocalLogin from './LocalLogin';
|
|||||||
import Accordion from '../Common/Accordion';
|
import Accordion from '../Common/Accordion';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
|
import { MediaServerType } from '../../../server/constants/server';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
signin: 'Sign In',
|
signin: 'Sign In',
|
||||||
@@ -136,13 +137,15 @@ const Login: React.FC = () => {
|
|||||||
onClick={() => handleClick(0)}
|
onClick={() => handleClick(0)}
|
||||||
disabled={!settings.currentSettings.localLogin}
|
disabled={!settings.currentSettings.localLogin}
|
||||||
>
|
>
|
||||||
{settings.currentSettings.mediaServerType == 'PLEX'
|
{settings.currentSettings.mediaServerType ==
|
||||||
|
MediaServerType.PLEX
|
||||||
? intl.formatMessage(messages.signinwithplex)
|
? intl.formatMessage(messages.signinwithplex)
|
||||||
: intl.formatMessage(messages.signinwithjellyfin)}
|
: intl.formatMessage(messages.signinwithjellyfin)}
|
||||||
</button>
|
</button>
|
||||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||||
<div className="px-10 py-8">
|
<div className="px-10 py-8">
|
||||||
{settings.currentSettings.mediaServerType == 'PLEX' ? (
|
{settings.currentSettings.mediaServerType ==
|
||||||
|
MediaServerType.PLEX ? (
|
||||||
<PlexLoginButton
|
<PlexLoginButton
|
||||||
isProcessing={isProcessing}
|
isProcessing={isProcessing}
|
||||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ import ConfirmButton from '../Common/ConfirmButton';
|
|||||||
import DownloadBlock from '../DownloadBlock';
|
import DownloadBlock from '../DownloadBlock';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
<<<<<<< HEAD
|
||||||
import PlayButton, { PlayButtonLink } from '../Common/PlayButton';
|
import PlayButton, { PlayButtonLink } from '../Common/PlayButton';
|
||||||
|
=======
|
||||||
|
import { MediaServerType } from '../../../server/constants/server';
|
||||||
|
>>>>>>> 2fe4add... feat(rebase): rebase
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
releasedate: 'Release Date',
|
releasedate: 'Release Date',
|
||||||
@@ -392,12 +396,8 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={
|
plexUrl={data.mediaInfo?.mediaUrl}
|
||||||
data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl
|
plexUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||||
}
|
|
||||||
plexUrl4k={
|
|
||||||
data.mediaInfo?.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -406,12 +406,12 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
status={data.mediaInfo?.status4k}
|
status={data.mediaInfo?.status4k}
|
||||||
is4k
|
is4k
|
||||||
inProgress={(data.mediaInfo?.downloadStatus4k ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus4k ?? []).length > 0}
|
||||||
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl}
|
plexUrl={data.mediaInfo?.mediaUrl}
|
||||||
plexUrl4k={
|
plexUrl4k={
|
||||||
data.mediaInfo?.plexUrl4k &&
|
data.mediaInfo?.mediaUrl4k &&
|
||||||
(hasPermission(Permission.REQUEST_4K) ||
|
(hasPermission(Permission.REQUEST_4K) ||
|
||||||
hasPermission(Permission.REQUEST_4K_MOVIE))
|
hasPermission(Permission.REQUEST_4K_MOVIE))
|
||||||
? data.mediaInfo.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k
|
? data.mediaInfo.mediaUrl4k
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -453,9 +453,114 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-10 flex flex-wrap justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap lg:mt-0">
|
<div className="relative z-10 flex flex-wrap justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap lg:mt-0">
|
||||||
|
<<<<<<< HEAD
|
||||||
<div className="mb-3 sm:mb-0">
|
<div className="mb-3 sm:mb-0">
|
||||||
<PlayButton links={mediaLinks} />
|
<PlayButton links={mediaLinks} />
|
||||||
</div>
|
</div>
|
||||||
|
=======
|
||||||
|
{trailerUrl ||
|
||||||
|
data.mediaInfo?.mediaUrl ||
|
||||||
|
data.mediaInfo?.mediaUrl4k ? (
|
||||||
|
<ButtonWithDropdown
|
||||||
|
buttonType="ghost"
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 mr-1"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
{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)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
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)) && (
|
||||||
|
<ButtonWithDropdown.Item
|
||||||
|
onClick={() => {
|
||||||
|
window.open(data.mediaInfo?.mediaUrl4k, '_blank');
|
||||||
|
}}
|
||||||
|
buttonType="ghost"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(
|
||||||
|
settings.currentSettings.mediaServerType ==
|
||||||
|
MediaServerType.PLEX
|
||||||
|
? messages.play4konplex
|
||||||
|
: messages.play4konjellyfin
|
||||||
|
)}
|
||||||
|
</ButtonWithDropdown.Item>
|
||||||
|
)}
|
||||||
|
{trailerUrl && (
|
||||||
|
<ButtonWithDropdown.Item
|
||||||
|
onClick={() => {
|
||||||
|
window.open(trailerUrl, '_blank');
|
||||||
|
}}
|
||||||
|
buttonType="ghost"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.watchtrailer)}
|
||||||
|
</ButtonWithDropdown.Item>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</ButtonWithDropdown>
|
||||||
|
) : null}
|
||||||
|
>>>>>>> 2fe4add... feat(rebase): rebase
|
||||||
<div className="mb-3 sm:mb-0">
|
<div className="mb-3 sm:mb-0">
|
||||||
<RequestButton
|
<RequestButton
|
||||||
mediaType="movie"
|
mediaType="movie"
|
||||||
@@ -696,7 +801,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
tvdbId={data.externalIds.tvdbId}
|
tvdbId={data.externalIds.tvdbId}
|
||||||
imdbId={data.externalIds.imdbId}
|
imdbId={data.externalIds.imdbId}
|
||||||
rtUrl={ratingData?.url}
|
rtUrl={ratingData?.url}
|
||||||
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.mediaUrl ?? data.mediaInfo?.mediaUrl4k}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import globalMessages from '../../i18n/globalMessages';
|
|||||||
import PermissionEdit from '../PermissionEdit';
|
import PermissionEdit from '../PermissionEdit';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import RegionSelector from '../RegionSelector';
|
import RegionSelector from '../RegionSelector';
|
||||||
|
import { MediaServerType } from '../../../server/constants/server';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
generalsettings: 'General Settings',
|
generalsettings: 'General Settings',
|
||||||
@@ -123,7 +124,8 @@ const SettingsMain: React.FC = () => {
|
|||||||
region: data?.region,
|
region: data?.region,
|
||||||
originalLanguage: data?.originalLanguage,
|
originalLanguage: data?.originalLanguage,
|
||||||
trustProxy: data?.trustProxy,
|
trustProxy: data?.trustProxy,
|
||||||
useJellyfin: data?.mediaServerType == 'JELLYFIN' ? true : false,
|
useJellyfin:
|
||||||
|
data?.mediaServerType == MediaServerType.JELLYFIN ? true : false,
|
||||||
}}
|
}}
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
validationSchema={MainSettingsSchema}
|
validationSchema={MainSettingsSchema}
|
||||||
@@ -139,7 +141,9 @@ const SettingsMain: React.FC = () => {
|
|||||||
region: values.region,
|
region: values.region,
|
||||||
originalLanguage: values.originalLanguage,
|
originalLanguage: values.originalLanguage,
|
||||||
trustProxy: values.trustProxy,
|
trustProxy: values.trustProxy,
|
||||||
mediaServerType: values.useJellyfin ? 'JELLYFIN' : 'PLEX',
|
mediaServerType: values.useJellyfin
|
||||||
|
? MediaServerType.JELLYFIN
|
||||||
|
: MediaServerType.PLEX,
|
||||||
});
|
});
|
||||||
|
|
||||||
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
const response = await axios.post('/api/v1/auth/login', {
|
const response = await axios.post('/api/v1/auth/plex', {
|
||||||
authToken: authToken,
|
authToken: authToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ import ConfirmButton from '../Common/ConfirmButton';
|
|||||||
import DownloadBlock from '../DownloadBlock';
|
import DownloadBlock from '../DownloadBlock';
|
||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
import useSettings from '../../hooks/useSettings';
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
<<<<<<< HEAD
|
||||||
import PlayButton, { PlayButtonLink } from '../Common/PlayButton';
|
import PlayButton, { PlayButtonLink } from '../Common/PlayButton';
|
||||||
|
=======
|
||||||
|
import { MediaServerType } from '../../../server/constants/server';
|
||||||
|
>>>>>>> 2fe4add... feat(rebase): rebase
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
firstAirDate: 'First Air Date',
|
firstAirDate: 'First Air Date',
|
||||||
@@ -413,12 +417,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={
|
plexUrl={data.mediaInfo?.mediaUrl}
|
||||||
data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl
|
plexUrl4k={data.mediaInfo?.mediaUrl4k}
|
||||||
}
|
|
||||||
plexUrl4k={
|
|
||||||
data.mediaInfo?.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -426,12 +426,12 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<StatusBadge
|
<StatusBadge
|
||||||
status={data.mediaInfo?.status}
|
status={data.mediaInfo?.status}
|
||||||
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||||
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.jellyfinUrl}
|
plexUrl={data.mediaInfo?.mediaUrl}
|
||||||
plexUrl4k={
|
plexUrl4k={
|
||||||
data.mediaInfo?.plexUrl4k &&
|
data.mediaInfo?.mediaUrl4k &&
|
||||||
(hasPermission(Permission.REQUEST_4K) ||
|
(hasPermission(Permission.REQUEST_4K) ||
|
||||||
hasPermission(Permission.REQUEST_4K_TV))
|
hasPermission(Permission.REQUEST_4K_TV))
|
||||||
? data.mediaInfo.plexUrl4k ?? data.mediaInfo?.jellyfinUrl4k
|
? data.mediaInfo.mediaUrl4k
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -709,7 +709,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
tvdbId={data.externalIds.tvdbId}
|
tvdbId={data.externalIds.tvdbId}
|
||||||
imdbId={data.externalIds.imdbId}
|
imdbId={data.externalIds.imdbId}
|
||||||
rtUrl={ratingData?.url}
|
rtUrl={ratingData?.url}
|
||||||
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.mediaUrl ?? data.mediaInfo?.mediaUrl4k}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import BulkEditModal from './BulkEditModal';
|
|||||||
import PageTitle from '../Common/PageTitle';
|
import PageTitle from '../Common/PageTitle';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
||||||
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
import { MediaServerType } from '../../../server/constants/server';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
@@ -78,6 +80,7 @@ type Sort = 'created' | 'updated' | 'requests' | 'displayname';
|
|||||||
const UserList: React.FC = () => {
|
const UserList: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const settings = useSettings();
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [currentSort, setCurrentSort] = useState<Sort>('created');
|
const [currentSort, setCurrentSort] = useState<Sort>('created');
|
||||||
@@ -408,14 +411,17 @@ const UserList: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(messages.createlocaluser)}
|
{intl.formatMessage(messages.createlocaluser)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{settings.currentSettings.mediaServerType ==
|
||||||
className="flex-grow outline lg:mr-2"
|
MediaServerType.PLEX && (
|
||||||
buttonType="primary"
|
<Button
|
||||||
disabled={isImporting}
|
className="flex-grow outline lg:mr-2"
|
||||||
onClick={() => importFromPlex()}
|
buttonType="primary"
|
||||||
>
|
disabled={isImporting}
|
||||||
{intl.formatMessage(messages.importfromplex)}
|
onClick={() => importFromPlex()}
|
||||||
</Button>
|
>
|
||||||
|
{intl.formatMessage(messages.importfromplex)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow mb-2 lg:mb-0 lg:flex-grow-0">
|
<div className="flex flex-grow mb-2 lg:mb-0 lg:flex-grow-0">
|
||||||
<span className="inline-flex items-center px-3 text-sm text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md">
|
<span className="inline-flex items-center px-3 text-sm text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md">
|
||||||
|
|||||||
@@ -599,7 +599,8 @@
|
|||||||
"components.Setup.finish": "Finish Setup",
|
"components.Setup.finish": "Finish Setup",
|
||||||
"components.Setup.finishing": "Finishing…",
|
"components.Setup.finishing": "Finishing…",
|
||||||
"components.Setup.setup": "Setup",
|
"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.signinWithJellyfin": "Use Jellyfin",
|
||||||
"components.Setup.syncingbackground": "Syncing will run in the background. You can continue the setup process in the meantime.",
|
"components.Setup.syncingbackground": "Syncing will run in the background. You can continue the setup process in the meantime.",
|
||||||
"components.Setup.tip": "Tip",
|
"components.Setup.tip": "Tip",
|
||||||
|
|||||||
Reference in New Issue
Block a user