import RadarrAPI from '@server/api/servarr/radarr'; import SonarrAPI from '@server/api/servarr/sonarr'; import { MediaRequestStatus, MediaStatus, MediaType, } from '@server/constants/media'; import { getRepository } from '@server/datasource'; 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, AfterUpdate, Column, CreateDateColumn, Entity, In, 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( tmdbIds: number | number[] ): Promise { const mediaRepository = getRepository(Media); try { let finalIds: number[]; if (!Array.isArray(tmdbIds)) { finalIds = [tmdbIds]; } else { finalIds = tmdbIds; } const media = await mediaRepository.find({ where: { tmdbId: In(finalIds) }, }); return media; } catch (e) { logger.error(e.message); return []; } } public static async getMedia( id: number, mediaType: MediaType ): Promise { 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(() => 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; public serviceUrl?: string; public serviceUrl4k?: string; public downloadStatus?: DownloadingItem[] = []; public downloadStatus4k?: DownloadingItem[] = []; public plexUrl?: string; public plexUrl4k?: string; public iOSPlexUrl?: string; public iOSPlexUrl4k?: string; public tautulliUrl?: string; public tautulliUrl4k?: string; constructor(init?: Partial) { Object.assign(this, init); } @AfterLoad() public setPlexUrls(): void { const { machineId, webAppUrl } = getSettings().plex; const { externalUrl: tautulliUrl } = getSettings().tautulli; if (this.ratingKey) { this.plexUrl = `${ 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.plexUrl4k = `${ 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}`; } } } @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 ); } } } @AfterUpdate() public async updateRelatedMediaRequest(): Promise { const requestRepository = getRepository(MediaRequest); const relatedRequests = await requestRepository.find({ relations: { media: true, }, where: { media: { id: this.id }, status: MediaRequestStatus.APPROVED, }, }); // Check the media entity status and if // available or deleted, set the related request // to completed if (relatedRequests.length > 0) { relatedRequests.forEach((request) => { if ( this[request.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE || this[request.is4k ? 'status4k' : 'status'] === MediaStatus.DELETED ) { request.status = MediaRequestStatus.COMPLETED; } }); requestRepository.save(relatedRequests); } } } export default Media;