Files
channels-seerr/server/entity/Media.ts
fallenbagel 395a91c2f0 feat: add Media Server Selection to Setup Page
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).
2023-11-18 02:20:05 +05:00

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;