|
|
|
import { truncate } from 'lodash';
|
|
|
|
import type { EntitySubscriberInterface, UpdateEvent } from 'typeorm';
|
|
|
|
import { EventSubscriber, In, Not } from 'typeorm';
|
|
|
|
import TheMovieDb from '../api/themoviedb';
|
|
|
|
import { MediaRequestStatus, MediaStatus, MediaType } from '../constants/media';
|
|
|
|
import { getRepository } from '../datasource';
|
|
|
|
import Media from '../entity/Media';
|
|
|
|
import { MediaRequest } from '../entity/MediaRequest';
|
|
|
|
import Season from '../entity/Season';
|
|
|
|
import notificationManager, { Notification } from '../lib/notifications';
|
|
|
|
import logger from '../logger';
|
|
|
|
|
|
|
|
@EventSubscriber()
|
|
|
|
export class MediaSubscriber implements EntitySubscriberInterface<Media> {
|
|
|
|
private async notifyAvailableMovie(
|
|
|
|
entity: Media,
|
|
|
|
dbEntity: Media,
|
|
|
|
is4k: boolean
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
entity[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE &&
|
|
|
|
dbEntity[is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE
|
|
|
|
) {
|
|
|
|
if (entity.mediaType === MediaType.MOVIE) {
|
|
|
|
const requestRepository = getRepository(MediaRequest);
|
|
|
|
const relatedRequests = await requestRepository.find({
|
|
|
|
where: {
|
|
|
|
media: {
|
|
|
|
id: entity.id,
|
|
|
|
},
|
|
|
|
is4k,
|
|
|
|
status: Not(MediaRequestStatus.DECLINED),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (relatedRequests.length > 0) {
|
|
|
|
const tmdb = new TheMovieDb();
|
|
|
|
|
|
|
|
try {
|
|
|
|
const movie = await tmdb.getMovie({ movieId: entity.tmdbId });
|
|
|
|
|
|
|
|
relatedRequests.forEach((request) => {
|
|
|
|
notificationManager.sendNotification(
|
|
|
|
Notification.MEDIA_AVAILABLE,
|
|
|
|
{
|
|
|
|
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
|
|
|
|
notifyAdmin: false,
|
|
|
|
notifyUser: request.requestedBy,
|
|
|
|
subject: `${movie.title}${
|
|
|
|
movie.release_date
|
|
|
|
? ` (${movie.release_date.slice(0, 4)})`
|
|
|
|
: ''
|
|
|
|
}`,
|
|
|
|
message: truncate(movie.overview, {
|
|
|
|
length: 500,
|
|
|
|
separator: /\s/,
|
|
|
|
omission: '…',
|
|
|
|
}),
|
|
|
|
media: entity,
|
|
|
|
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
|
|
|
request,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
logger.error('Something went wrong sending media notification(s)', {
|
|
|
|
label: 'Notifications',
|
|
|
|
errorMessage: e.message,
|
|
|
|
mediaId: entity.id,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async notifyAvailableSeries(
|
|
|
|
entity: Media,
|
|
|
|
dbEntity: Media,
|
|
|
|
is4k: boolean
|
|
|
|
) {
|
|
|
|
const seasonRepository = getRepository(Season);
|
|
|
|
const newAvailableSeasons = entity.seasons
|
|
|
|
.filter(
|
|
|
|
(season) =>
|
|
|
|
season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
|
|
|
|
)
|
|
|
|
.map((season) => season.seasonNumber);
|
|
|
|
const oldSeasonIds = dbEntity.seasons.map((season) => season.id);
|
|
|
|
const oldSeasons = await seasonRepository.findBy({ id: In(oldSeasonIds) });
|
|
|
|
const oldAvailableSeasons = oldSeasons
|
|
|
|
.filter(
|
|
|
|
(season) =>
|
|
|
|
season[is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE
|
|
|
|
)
|
|
|
|
.map((season) => season.seasonNumber);
|
|
|
|
|
|
|
|
const changedSeasons = newAvailableSeasons.filter(
|
|
|
|
(seasonNumber) => !oldAvailableSeasons.includes(seasonNumber)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (changedSeasons.length > 0) {
|
|
|
|
const tmdb = new TheMovieDb();
|
|
|
|
const requestRepository = getRepository(MediaRequest);
|
|
|
|
const processedSeasons: number[] = [];
|
|
|
|
|
|
|
|
for (const changedSeasonNumber of changedSeasons) {
|
|
|
|
const requests = await requestRepository.find({
|
|
|
|
where: {
|
|
|
|
media: {
|
|
|
|
id: entity.id,
|
|
|
|
},
|
|
|
|
is4k,
|
|
|
|
status: Not(MediaRequestStatus.DECLINED),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const request = requests.find(
|
|
|
|
(request) =>
|
|
|
|
// Check if the season is complete AND it contains the current season that was just marked available
|
|
|
|
request.seasons.every((season) =>
|
|
|
|
newAvailableSeasons.includes(season.seasonNumber)
|
|
|
|
) &&
|
|
|
|
request.seasons.some(
|
|
|
|
(season) => season.seasonNumber === changedSeasonNumber
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (request && !processedSeasons.includes(changedSeasonNumber)) {
|
|
|
|
processedSeasons.push(
|
|
|
|
...request.seasons.map((season) => season.seasonNumber)
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const tv = await tmdb.getTvShow({ tvId: entity.tmdbId });
|
|
|
|
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
|
|
|
|
event: `${is4k ? '4K ' : ''}Series Request Now Available`,
|
|
|
|
subject: `${tv.name}${
|
|
|
|
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
|
|
|
}`,
|
|
|
|
message: truncate(tv.overview, {
|
|
|
|
length: 500,
|
|
|
|
separator: /\s/,
|
|
|
|
omission: '…',
|
|
|
|
}),
|
|
|
|
notifyAdmin: false,
|
|
|
|
notifyUser: request.requestedBy,
|
|
|
|
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
|
|
|
media: entity,
|
|
|
|
extra: [
|
|
|
|
{
|
|
|
|
name: 'Requested Seasons',
|
|
|
|
value: request.seasons
|
|
|
|
.map((season) => season.seasonNumber)
|
|
|
|
.join(', '),
|
|
|
|
},
|
|
|
|
],
|
|
|
|
request,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
logger.error('Something went wrong sending media notification(s)', {
|
|
|
|
label: 'Notifications',
|
|
|
|
errorMessage: e.message,
|
|
|
|
mediaId: entity.id,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async updateChildRequestStatus(event: Media, is4k: boolean) {
|
|
|
|
const requestRepository = getRepository(MediaRequest);
|
|
|
|
|
|
|
|
const requests = await requestRepository.find({
|
|
|
|
where: { media: { id: event.id } },
|
|
|
|
});
|
|
|
|
|
|
|
|
for (const request of requests) {
|
|
|
|
if (
|
|
|
|
request.is4k === is4k &&
|
|
|
|
request.status === MediaRequestStatus.PENDING
|
|
|
|
) {
|
|
|
|
request.status = MediaRequestStatus.APPROVED;
|
|
|
|
await requestRepository.save(request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public beforeUpdate(event: UpdateEvent<Media>): void {
|
|
|
|
if (!event.entity) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.mediaType === MediaType.MOVIE &&
|
|
|
|
event.entity.status === MediaStatus.AVAILABLE
|
|
|
|
) {
|
|
|
|
this.notifyAvailableMovie(
|
|
|
|
event.entity as Media,
|
|
|
|
event.databaseEntity,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.mediaType === MediaType.MOVIE &&
|
|
|
|
event.entity.status4k === MediaStatus.AVAILABLE
|
|
|
|
) {
|
|
|
|
this.notifyAvailableMovie(
|
|
|
|
event.entity as Media,
|
|
|
|
event.databaseEntity,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.mediaType === MediaType.TV &&
|
|
|
|
(event.entity.status === MediaStatus.AVAILABLE ||
|
|
|
|
event.entity.status === MediaStatus.PARTIALLY_AVAILABLE)
|
|
|
|
) {
|
|
|
|
this.notifyAvailableSeries(
|
|
|
|
event.entity as Media,
|
|
|
|
event.databaseEntity,
|
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.mediaType === MediaType.TV &&
|
|
|
|
(event.entity.status4k === MediaStatus.AVAILABLE ||
|
|
|
|
event.entity.status4k === MediaStatus.PARTIALLY_AVAILABLE)
|
|
|
|
) {
|
|
|
|
this.notifyAvailableSeries(
|
|
|
|
event.entity as Media,
|
|
|
|
event.databaseEntity,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.status === MediaStatus.AVAILABLE &&
|
|
|
|
event.databaseEntity.status === MediaStatus.PENDING
|
|
|
|
) {
|
|
|
|
this.updateChildRequestStatus(event.entity as Media, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
event.entity.status4k === MediaStatus.AVAILABLE &&
|
|
|
|
event.databaseEntity.status4k === MediaStatus.PENDING
|
|
|
|
) {
|
|
|
|
this.updateChildRequestStatus(event.entity as Media, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public listenTo(): typeof Media {
|
|
|
|
return Media;
|
|
|
|
}
|
|
|
|
}
|