This PR refactors and abstracts jellyfin hostname into, jellyfin ip, jellyfin port, jellyfin useSsl, and jellyfin urlBase. This makes it more consistent with how plex settings are stored as well. In addition, this improves validation as validation can be applied seperately to them instead of as one whole regex doing the work to validate the url. UI was updated to reflect this. BREAKING CHANGE: Jellyfin settings now does not include a hostname. Instead it abstracted it to ip, port, useSsl, and urlBase. However, migration of old settings to new settings should work automatically.
360 lines
10 KiB
TypeScript
360 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 { getHostname } from '@server/utils/getHostname';
|
|
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, type: 'varchar' })
|
|
public jellyfinMediaId?: string | null;
|
|
|
|
@Column({ nullable: true, type: 'varchar' })
|
|
public jellyfinMediaId4k?: string | null;
|
|
|
|
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 =
|
|
process.env.JELLYFIN_TYPE === 'emby' ? 'item' : 'details';
|
|
const { serverId, externalHostname } = getSettings().jellyfin;
|
|
// let jellyfinHost =
|
|
// externalHostname && externalHostname.length > 0
|
|
// ? externalHostname
|
|
// : hostname;
|
|
const jellyfinHost =
|
|
externalHostname && externalHostname.length > 0
|
|
? externalHostname
|
|
: getHostname();
|
|
|
|
// 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;
|