Introduce the ability to select the media server type on the setup page. Users can now choose their preferred media server (e.g., Plex through the Plex sign-in or Emby/Jellyfin sign-in to select either Emby or Jellyfin). The selected media server type is then reflected in the application settings. This enhancement provides users with increased flexibility and customization options during the initial setup process, eliminating the need to rely on environment variables (which cannot be set if using platforms like snaps). Existing Emby users, who use the environment variable, should log out and log back in after updating to set their mediaServerType to Emby. BREAKING CHANGE: This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and instead rely on the mediaServerType that is set in the `settings.json`. Existing environment variable users can log out and log back in to set the mediaServerType to `3` (Emby).
357 lines
10 KiB
TypeScript
357 lines
10 KiB
TypeScript
import RadarrAPI from '@server/api/servarr/radarr';
|
|
import SonarrAPI from '@server/api/servarr/sonarr';
|
|
import { MediaStatus, MediaType } from '@server/constants/media';
|
|
import { MediaServerType } from '@server/constants/server';
|
|
import { getRepository } from '@server/datasource';
|
|
import type { User } from '@server/entity/User';
|
|
import { Watchlist } from '@server/entity/Watchlist';
|
|
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
|
import downloadTracker from '@server/lib/downloadtracker';
|
|
import { getSettings } from '@server/lib/settings';
|
|
import logger from '@server/logger';
|
|
import {
|
|
AfterLoad,
|
|
Column,
|
|
CreateDateColumn,
|
|
Entity,
|
|
Index,
|
|
OneToMany,
|
|
PrimaryGeneratedColumn,
|
|
UpdateDateColumn,
|
|
} from 'typeorm';
|
|
import Issue from './Issue';
|
|
import { MediaRequest } from './MediaRequest';
|
|
import Season from './Season';
|
|
|
|
@Entity()
|
|
class Media {
|
|
public static async getRelatedMedia(
|
|
user: User | undefined,
|
|
tmdbIds: number | number[]
|
|
): Promise<Media[]> {
|
|
const mediaRepository = getRepository(Media);
|
|
|
|
try {
|
|
let finalIds: number[];
|
|
if (!Array.isArray(tmdbIds)) {
|
|
finalIds = [tmdbIds];
|
|
} else {
|
|
finalIds = tmdbIds;
|
|
}
|
|
|
|
const media = await mediaRepository
|
|
.createQueryBuilder('media')
|
|
.leftJoinAndSelect(
|
|
'media.watchlists',
|
|
'watchlist',
|
|
'media.id= watchlist.media and watchlist.requestedBy = :userId',
|
|
{ userId: user?.id }
|
|
) //,
|
|
.where(' media.tmdbId in (:...finalIds)', { finalIds })
|
|
.getMany();
|
|
|
|
return media;
|
|
} catch (e) {
|
|
logger.error(e.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public static async getMedia(
|
|
id: number,
|
|
mediaType: MediaType
|
|
): Promise<Media | undefined> {
|
|
const mediaRepository = getRepository(Media);
|
|
|
|
try {
|
|
const media = await mediaRepository.findOne({
|
|
where: { tmdbId: id, mediaType },
|
|
relations: { requests: true, issues: true },
|
|
});
|
|
|
|
return media ?? undefined;
|
|
} catch (e) {
|
|
logger.error(e.message);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
@PrimaryGeneratedColumn()
|
|
public id: number;
|
|
|
|
@Column({ type: 'varchar' })
|
|
public mediaType: MediaType;
|
|
|
|
@Column()
|
|
@Index()
|
|
public tmdbId: number;
|
|
|
|
@Column({ unique: true, nullable: true })
|
|
@Index()
|
|
public tvdbId?: number;
|
|
|
|
@Column({ nullable: true })
|
|
@Index()
|
|
public imdbId?: string;
|
|
|
|
@Column({ type: 'int', default: MediaStatus.UNKNOWN })
|
|
public status: MediaStatus;
|
|
|
|
@Column({ type: 'int', default: MediaStatus.UNKNOWN })
|
|
public status4k: MediaStatus;
|
|
|
|
@OneToMany(() => MediaRequest, (request) => request.media, { cascade: true })
|
|
public requests: MediaRequest[];
|
|
|
|
@OneToMany(() => Watchlist, (watchlist) => watchlist.media)
|
|
public watchlists: null | Watchlist[];
|
|
|
|
@OneToMany(() => Season, (season) => season.media, {
|
|
cascade: true,
|
|
eager: true,
|
|
})
|
|
public seasons: Season[];
|
|
|
|
@OneToMany(() => Issue, (issue) => issue.media, { cascade: true })
|
|
public issues: Issue[];
|
|
|
|
@CreateDateColumn()
|
|
public createdAt: Date;
|
|
|
|
@UpdateDateColumn()
|
|
public updatedAt: Date;
|
|
|
|
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
|
|
public lastSeasonChange: Date;
|
|
|
|
@Column({ type: 'datetime', nullable: true })
|
|
public mediaAddedAt: Date;
|
|
|
|
@Column({ nullable: true, type: 'int' })
|
|
public serviceId?: number | null;
|
|
|
|
@Column({ nullable: true, type: 'int' })
|
|
public serviceId4k?: number | null;
|
|
|
|
@Column({ nullable: true, type: 'int' })
|
|
public externalServiceId?: number | null;
|
|
|
|
@Column({ nullable: true, type: 'int' })
|
|
public externalServiceId4k?: number | null;
|
|
|
|
@Column({ nullable: true, type: 'varchar' })
|
|
public externalServiceSlug?: string | null;
|
|
|
|
@Column({ nullable: true, type: 'varchar' })
|
|
public externalServiceSlug4k?: string | null;
|
|
|
|
@Column({ nullable: true, type: 'varchar' })
|
|
public ratingKey?: string | null;
|
|
|
|
@Column({ nullable: true, type: 'varchar' })
|
|
public ratingKey4k?: string | null;
|
|
|
|
@Column({ nullable: true })
|
|
public jellyfinMediaId?: string;
|
|
|
|
@Column({ nullable: true })
|
|
public jellyfinMediaId4k?: string;
|
|
|
|
public serviceUrl?: string;
|
|
public serviceUrl4k?: string;
|
|
public downloadStatus?: DownloadingItem[] = [];
|
|
public downloadStatus4k?: DownloadingItem[] = [];
|
|
|
|
public mediaUrl?: string;
|
|
public mediaUrl4k?: string;
|
|
|
|
public iOSPlexUrl?: string;
|
|
public iOSPlexUrl4k?: string;
|
|
|
|
public tautulliUrl?: string;
|
|
public tautulliUrl4k?: string;
|
|
|
|
constructor(init?: Partial<Media>) {
|
|
Object.assign(this, init);
|
|
}
|
|
|
|
@AfterLoad()
|
|
public setPlexUrls(): void {
|
|
const { machineId, webAppUrl } = getSettings().plex;
|
|
const { externalUrl: tautulliUrl } = getSettings().tautulli;
|
|
|
|
if (getSettings().main.mediaServerType == MediaServerType.PLEX) {
|
|
if (this.ratingKey) {
|
|
this.mediaUrl = `${
|
|
webAppUrl ? webAppUrl : 'https://app.plex.tv/desktop'
|
|
}#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${
|
|
this.ratingKey
|
|
}`;
|
|
|
|
this.iOSPlexUrl = `plex://preplay/?metadataKey=%2Flibrary%2Fmetadata%2F${this.ratingKey}&server=${machineId}`;
|
|
|
|
if (tautulliUrl) {
|
|
this.tautulliUrl = `${tautulliUrl}/info?rating_key=${this.ratingKey}`;
|
|
}
|
|
|
|
if (this.ratingKey4k) {
|
|
this.mediaUrl4k = `${
|
|
webAppUrl ? webAppUrl : 'https://app.plex.tv/desktop'
|
|
}#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${
|
|
this.ratingKey4k
|
|
}`;
|
|
|
|
this.iOSPlexUrl4k = `plex://preplay/?metadataKey=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}&server=${machineId}`;
|
|
|
|
if (tautulliUrl) {
|
|
this.tautulliUrl4k = `${tautulliUrl}/info?rating_key=${this.ratingKey4k}`;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const pageName =
|
|
getSettings().main.mediaServerType == MediaServerType.EMBY
|
|
? 'item'
|
|
: 'details';
|
|
const { serverId, hostname, externalHostname } = getSettings().jellyfin;
|
|
let jellyfinHost =
|
|
externalHostname && externalHostname.length > 0
|
|
? externalHostname
|
|
: hostname;
|
|
|
|
jellyfinHost = jellyfinHost.endsWith('/')
|
|
? jellyfinHost.slice(0, -1)
|
|
: jellyfinHost;
|
|
|
|
if (this.jellyfinMediaId) {
|
|
this.mediaUrl = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
|
|
}
|
|
if (this.jellyfinMediaId4k) {
|
|
this.mediaUrl4k = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
@AfterLoad()
|
|
public setServiceUrl(): void {
|
|
if (this.mediaType === MediaType.MOVIE) {
|
|
if (this.serviceId !== null && this.externalServiceSlug !== null) {
|
|
const settings = getSettings();
|
|
const server = settings.radarr.find(
|
|
(radarr) => radarr.id === this.serviceId
|
|
);
|
|
|
|
if (server) {
|
|
this.serviceUrl = server.externalUrl
|
|
? `${server.externalUrl}/movie/${this.externalServiceSlug}`
|
|
: RadarrAPI.buildUrl(server, `/movie/${this.externalServiceSlug}`);
|
|
}
|
|
}
|
|
|
|
if (this.serviceId4k !== null && this.externalServiceSlug4k !== null) {
|
|
const settings = getSettings();
|
|
const server = settings.radarr.find(
|
|
(radarr) => radarr.id === this.serviceId4k
|
|
);
|
|
|
|
if (server) {
|
|
this.serviceUrl4k = server.externalUrl
|
|
? `${server.externalUrl}/movie/${this.externalServiceSlug4k}`
|
|
: RadarrAPI.buildUrl(
|
|
server,
|
|
`/movie/${this.externalServiceSlug4k}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.mediaType === MediaType.TV) {
|
|
if (this.serviceId !== null && this.externalServiceSlug !== null) {
|
|
const settings = getSettings();
|
|
const server = settings.sonarr.find(
|
|
(sonarr) => sonarr.id === this.serviceId
|
|
);
|
|
|
|
if (server) {
|
|
this.serviceUrl = server.externalUrl
|
|
? `${server.externalUrl}/series/${this.externalServiceSlug}`
|
|
: SonarrAPI.buildUrl(server, `/series/${this.externalServiceSlug}`);
|
|
}
|
|
}
|
|
|
|
if (this.serviceId4k !== null && this.externalServiceSlug4k !== null) {
|
|
const settings = getSettings();
|
|
const server = settings.sonarr.find(
|
|
(sonarr) => sonarr.id === this.serviceId4k
|
|
);
|
|
|
|
if (server) {
|
|
this.serviceUrl4k = server.externalUrl
|
|
? `${server.externalUrl}/series/${this.externalServiceSlug4k}`
|
|
: SonarrAPI.buildUrl(
|
|
server,
|
|
`/series/${this.externalServiceSlug4k}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@AfterLoad()
|
|
public getDownloadingItem(): void {
|
|
if (this.mediaType === MediaType.MOVIE) {
|
|
if (
|
|
this.externalServiceId !== undefined &&
|
|
this.externalServiceId !== null &&
|
|
this.serviceId !== undefined &&
|
|
this.serviceId !== null
|
|
) {
|
|
this.downloadStatus = downloadTracker.getMovieProgress(
|
|
this.serviceId,
|
|
this.externalServiceId
|
|
);
|
|
}
|
|
|
|
if (
|
|
this.externalServiceId4k !== undefined &&
|
|
this.externalServiceId4k !== null &&
|
|
this.serviceId4k !== undefined &&
|
|
this.serviceId4k !== null
|
|
) {
|
|
this.downloadStatus4k = downloadTracker.getMovieProgress(
|
|
this.serviceId4k,
|
|
this.externalServiceId4k
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this.mediaType === MediaType.TV) {
|
|
if (
|
|
this.externalServiceId !== undefined &&
|
|
this.externalServiceId !== null &&
|
|
this.serviceId !== undefined &&
|
|
this.serviceId !== null
|
|
) {
|
|
this.downloadStatus = downloadTracker.getSeriesProgress(
|
|
this.serviceId,
|
|
this.externalServiceId
|
|
);
|
|
}
|
|
|
|
if (
|
|
this.externalServiceId4k !== undefined &&
|
|
this.externalServiceId4k !== null &&
|
|
this.serviceId4k !== undefined &&
|
|
this.serviceId4k !== null
|
|
) {
|
|
this.downloadStatus4k = downloadTracker.getSeriesProgress(
|
|
this.serviceId4k,
|
|
this.externalServiceId4k
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Media;
|