mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
fix: address unhandled promise rejections & bump node to v16.13 (#2398)
* fix: unhandled promise rejections * build(deps): bump node from 14.18 to 16.13 * fix: unhandled promise rejection in new Plex users endpoint * fix: build error Co-authored-by: Ryan Cohen <ryan@sct.dev>
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Lint & Test Build
|
name: Lint & Test Build
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
container: node:14.18-alpine
|
container: node:16.13-alpine
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2.4.0
|
uses: actions/checkout@v2.4.0
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
name: Lint & Test Build
|
name: Lint & Test Build
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
container: node:14.18-alpine
|
container: node:16.13-alpine
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2.4.0
|
uses: actions/checkout@v2.4.0
|
||||||
|
2
.github/workflows/snap.yaml
vendored
2
.github/workflows/snap.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
name: Lint & Test Build
|
name: Lint & Test Build
|
||||||
needs: jobs
|
needs: jobs
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
container: node:14.18-alpine
|
container: node:16.13-alpine
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2.4.0
|
uses: actions/checkout@v2.4.0
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM node:14.18-alpine AS BUILD_IMAGE
|
FROM node:16.13-alpine AS BUILD_IMAGE
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ RUN touch config/DOCKER
|
|||||||
RUN echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json
|
RUN echo "{\"commitTag\": \"${COMMIT_TAG}\"}" > committag.json
|
||||||
|
|
||||||
|
|
||||||
FROM node:14.18-alpine
|
FROM node:16.13-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM node:14.18-alpine
|
FROM node:16.13-alpine
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import ServarrBase from './base';
|
import ServarrBase from './base';
|
||||||
|
|
||||||
interface RadarrMovieOptions {
|
export interface RadarrMovieOptions {
|
||||||
title: string;
|
title: string;
|
||||||
qualityProfileId: number;
|
qualityProfileId: number;
|
||||||
minimumAvailability: string;
|
minimumAvailability: string;
|
||||||
|
@@ -63,7 +63,7 @@ export interface SonarrSeries {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddSeriesOptions {
|
export interface AddSeriesOptions {
|
||||||
tvdbid: number;
|
tvdbid: number;
|
||||||
title: string;
|
title: string;
|
||||||
profileId: number;
|
profileId: number;
|
||||||
|
@@ -13,8 +13,11 @@ import {
|
|||||||
RelationCount,
|
RelationCount,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import RadarrAPI from '../api/servarr/radarr';
|
import RadarrAPI, { RadarrMovieOptions } from '../api/servarr/radarr';
|
||||||
import SonarrAPI, { SonarrSeries } from '../api/servarr/sonarr';
|
import SonarrAPI, {
|
||||||
|
AddSeriesOptions,
|
||||||
|
SonarrSeries,
|
||||||
|
} from '../api/servarr/sonarr';
|
||||||
import TheMovieDb from '../api/themoviedb';
|
import TheMovieDb from '../api/themoviedb';
|
||||||
import { ANIME_KEYWORD_ID } from '../api/themoviedb/constants';
|
import { ANIME_KEYWORD_ID } from '../api/themoviedb/constants';
|
||||||
import { MediaRequestStatus, MediaStatus, MediaType } from '../constants/media';
|
import { MediaRequestStatus, MediaStatus, MediaType } from '../constants/media';
|
||||||
@@ -135,55 +138,15 @@ export class MediaRequest {
|
|||||||
where: { id: this.media.id },
|
where: { id: this.media.id },
|
||||||
});
|
});
|
||||||
if (!media) {
|
if (!media) {
|
||||||
logger.error('No parent media!', { label: 'Media Request' });
|
logger.error('Media data not found', {
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tmdb = new TheMovieDb();
|
|
||||||
if (this.type === MediaType.MOVIE) {
|
|
||||||
const movie = await tmdb.getMovie({ movieId: media.tmdbId });
|
|
||||||
notificationManager.sendNotification(Notification.MEDIA_PENDING, {
|
|
||||||
event: `New ${this.is4k ? '4K ' : ''}Movie Request`,
|
|
||||||
subject: `${movie.title}${
|
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
|
||||||
}`,
|
|
||||||
message: truncate(movie.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
|
||||||
media,
|
|
||||||
request: this,
|
|
||||||
notifyAdmin: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.type === MediaType.TV) {
|
this.sendNotification(media, Notification.MEDIA_PENDING);
|
||||||
const tv = await tmdb.getTvShow({ tvId: media.tmdbId });
|
|
||||||
notificationManager.sendNotification(Notification.MEDIA_PENDING, {
|
|
||||||
event: `New ${this.is4k ? '4K ' : ''}Series Request`,
|
|
||||||
subject: `${tv.name}${
|
|
||||||
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
|
||||||
}`,
|
|
||||||
message: truncate(tv.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
|
||||||
media,
|
|
||||||
extra: [
|
|
||||||
{
|
|
||||||
name: 'Requested Seasons',
|
|
||||||
value: this.seasons
|
|
||||||
.map((season) => season.seasonNumber)
|
|
||||||
.join(', '),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
request: this,
|
|
||||||
notifyAdmin: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,90 +167,30 @@ export class MediaRequest {
|
|||||||
where: { id: this.media.id },
|
where: { id: this.media.id },
|
||||||
});
|
});
|
||||||
if (!media) {
|
if (!media) {
|
||||||
logger.error('No parent media!', { label: 'Media Request' });
|
logger.error('Media data not found', {
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE) {
|
if (media[this.is4k ? 'status4k' : 'status'] === MediaStatus.AVAILABLE) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Media became available before request was approved. Approval notification will be skipped.',
|
'Media became available before request was approved. Skipping approval notification',
|
||||||
{ label: 'Media Request' }
|
{ label: 'Media Request', requestId: this.id, mediaId: this.media.id }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmdb = new TheMovieDb();
|
this.sendNotification(
|
||||||
if (this.media.mediaType === MediaType.MOVIE) {
|
media,
|
||||||
const movie = await tmdb.getMovie({ movieId: this.media.tmdbId });
|
this.status === MediaRequestStatus.APPROVED
|
||||||
notificationManager.sendNotification(
|
? autoApproved
|
||||||
this.status === MediaRequestStatus.APPROVED
|
? Notification.MEDIA_AUTO_APPROVED
|
||||||
? autoApproved
|
: Notification.MEDIA_APPROVED
|
||||||
? Notification.MEDIA_AUTO_APPROVED
|
: Notification.MEDIA_DECLINED
|
||||||
: Notification.MEDIA_APPROVED
|
);
|
||||||
: Notification.MEDIA_DECLINED,
|
|
||||||
{
|
|
||||||
event: `${this.is4k ? '4K ' : ''}Movie Request ${
|
|
||||||
this.status === MediaRequestStatus.APPROVED
|
|
||||||
? autoApproved
|
|
||||||
? 'Automatically Approved'
|
|
||||||
: 'Approved'
|
|
||||||
: 'Declined'
|
|
||||||
}`,
|
|
||||||
subject: `${movie.title}${
|
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
|
||||||
}`,
|
|
||||||
message: truncate(movie.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
|
||||||
notifyAdmin: autoApproved,
|
|
||||||
notifyUser: autoApproved ? undefined : this.requestedBy,
|
|
||||||
media,
|
|
||||||
request: this,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (this.media.mediaType === MediaType.TV) {
|
|
||||||
const tv = await tmdb.getTvShow({ tvId: this.media.tmdbId });
|
|
||||||
notificationManager.sendNotification(
|
|
||||||
this.status === MediaRequestStatus.APPROVED
|
|
||||||
? autoApproved
|
|
||||||
? Notification.MEDIA_AUTO_APPROVED
|
|
||||||
: Notification.MEDIA_APPROVED
|
|
||||||
: Notification.MEDIA_DECLINED,
|
|
||||||
{
|
|
||||||
event: `${this.is4k ? '4K ' : ''}Series Request ${
|
|
||||||
this.status === MediaRequestStatus.APPROVED
|
|
||||||
? autoApproved
|
|
||||||
? 'Automatically Approved'
|
|
||||||
: 'Approved'
|
|
||||||
: 'Declined'
|
|
||||||
}`,
|
|
||||||
subject: `${tv.name}${
|
|
||||||
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
|
||||||
}`,
|
|
||||||
message: truncate(tv.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
|
||||||
notifyAdmin: autoApproved,
|
|
||||||
notifyUser: autoApproved ? undefined : this.requestedBy,
|
|
||||||
media,
|
|
||||||
extra: [
|
|
||||||
{
|
|
||||||
name: 'Requested Seasons',
|
|
||||||
value: this.seasons
|
|
||||||
.map((season) => season.seasonNumber)
|
|
||||||
.join(', '),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
request: this,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +210,11 @@ export class MediaRequest {
|
|||||||
relations: ['requests'],
|
relations: ['requests'],
|
||||||
});
|
});
|
||||||
if (!media) {
|
if (!media) {
|
||||||
logger.error('No parent media!', { label: 'Media Request' });
|
logger.error('Media data not found', {
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const seasonRequestRepository = getRepository(SeasonRequest);
|
const seasonRequestRepository = getRepository(SeasonRequest);
|
||||||
@@ -395,8 +302,12 @@ export class MediaRequest {
|
|||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
if (settings.radarr.length === 0 && !settings.radarr[0]) {
|
if (settings.radarr.length === 0 && !settings.radarr[0]) {
|
||||||
logger.info(
|
logger.info(
|
||||||
'Skipped Radarr request as there is no Radarr server configured',
|
'No Radarr server configured, skipping request processing',
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -415,18 +326,26 @@ export class MediaRequest {
|
|||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
`Request has an override server: ${radarrSettings?.name}`,
|
`Request has an override server: ${radarrSettings?.name}`,
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!radarrSettings) {
|
if (!radarrSettings) {
|
||||||
logger.info(
|
logger.warn(
|
||||||
`There is no default ${
|
`There is no default ${
|
||||||
this.is4k ? '4K ' : ''
|
this.is4k ? '4K ' : ''
|
||||||
}Radarr server configured. Did you set any of your ${
|
}Radarr server configured. Did you set any of your ${
|
||||||
this.is4k ? '4K ' : ''
|
this.is4k ? '4K ' : ''
|
||||||
}Radarr servers as default?`,
|
}Radarr servers as default?`,
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -443,6 +362,8 @@ export class MediaRequest {
|
|||||||
rootFolder = this.rootFolder;
|
rootFolder = this.rootFolder;
|
||||||
logger.info(`Request has an override root folder: ${rootFolder}`, {
|
logger.info(`Request has an override root folder: ${rootFolder}`, {
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,15 +372,22 @@ export class MediaRequest {
|
|||||||
this.profileId !== radarrSettings.activeProfileId
|
this.profileId !== radarrSettings.activeProfileId
|
||||||
) {
|
) {
|
||||||
qualityProfile = this.profileId;
|
qualityProfile = this.profileId;
|
||||||
logger.info(`Request has an override profile id: ${qualityProfile}`, {
|
logger.info(
|
||||||
label: 'Media Request',
|
`Request has an override quality profile ID: ${qualityProfile}`,
|
||||||
});
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tags && !isEqual(this.tags, radarrSettings.tags)) {
|
if (this.tags && !isEqual(this.tags, radarrSettings.tags)) {
|
||||||
tags = this.tags;
|
tags = this.tags;
|
||||||
logger.info(`Request has override tags`, {
|
logger.info(`Request has override tags`, {
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
tagIds: tags,
|
tagIds: tags,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -476,7 +404,11 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!media) {
|
if (!media) {
|
||||||
logger.error('Media not present');
|
logger.error('Media data not found', {
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,20 +418,22 @@ export class MediaRequest {
|
|||||||
throw new Error('Media already available');
|
throw new Error('Media already available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const radarrMovieOptions: RadarrMovieOptions = {
|
||||||
|
profileId: qualityProfile,
|
||||||
|
qualityProfileId: qualityProfile,
|
||||||
|
rootFolderPath: rootFolder,
|
||||||
|
minimumAvailability: radarrSettings.minimumAvailability,
|
||||||
|
title: movie.title,
|
||||||
|
tmdbId: movie.id,
|
||||||
|
year: Number(movie.release_date.slice(0, 4)),
|
||||||
|
monitored: true,
|
||||||
|
tags,
|
||||||
|
searchNow: !radarrSettings.preventSearch,
|
||||||
|
};
|
||||||
|
|
||||||
// Run this asynchronously so we don't wait for it on the UI side
|
// Run this asynchronously so we don't wait for it on the UI side
|
||||||
radarr
|
radarr
|
||||||
.addMovie({
|
.addMovie(radarrMovieOptions)
|
||||||
profileId: qualityProfile,
|
|
||||||
qualityProfileId: qualityProfile,
|
|
||||||
rootFolderPath: rootFolder,
|
|
||||||
minimumAvailability: radarrSettings.minimumAvailability,
|
|
||||||
title: movie.title,
|
|
||||||
tmdbId: movie.id,
|
|
||||||
year: Number(movie.release_date.slice(0, 4)),
|
|
||||||
monitored: true,
|
|
||||||
tags,
|
|
||||||
searchNow: !radarrSettings.preventSearch,
|
|
||||||
})
|
|
||||||
.then(async (radarrMovie) => {
|
.then(async (radarrMovie) => {
|
||||||
// We grab media again here to make sure we have the latest version of it
|
// We grab media again here to make sure we have the latest version of it
|
||||||
const media = await mediaRepository.findOne({
|
const media = await mediaRepository.findOne({
|
||||||
@@ -507,7 +441,7 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error('Media data is missing');
|
throw new Error('Media data not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
||||||
@@ -521,36 +455,30 @@ export class MediaRequest {
|
|||||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
||||||
await mediaRepository.save(media);
|
await mediaRepository.save(media);
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Newly added movie request failed to add to Radarr, marking as unknown',
|
'Something went wrong sending movie request to Radarr, marking status as UNKNOWN',
|
||||||
{
|
{
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
radarrMovieOptions,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
notificationManager.sendNotification(Notification.MEDIA_FAILED, {
|
this.sendNotification(media, Notification.MEDIA_FAILED);
|
||||||
event: `${this.is4k ? '4K ' : ''}Movie Request Failed`,
|
|
||||||
subject: `${movie.title}${
|
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
|
||||||
}`,
|
|
||||||
message: truncate(movie.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
media,
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
|
||||||
request: this,
|
|
||||||
notifyAdmin: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
logger.info('Sent request to Radarr', { label: 'Media Request' });
|
logger.info('Sent request to Radarr', {
|
||||||
} catch (e) {
|
|
||||||
const errorMessage = `Request failed to send to Radarr: ${e.message}`;
|
|
||||||
logger.error('Request failed to send to Radarr', {
|
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
errorMessage,
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
});
|
});
|
||||||
throw new Error(errorMessage);
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong sending request to Radarr', {
|
||||||
|
label: 'Media Request',
|
||||||
|
errorMessage: e.message,
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
|
throw new Error(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -564,9 +492,13 @@ export class MediaRequest {
|
|||||||
const mediaRepository = getRepository(Media);
|
const mediaRepository = getRepository(Media);
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
if (settings.sonarr.length === 0 && !settings.sonarr[0]) {
|
if (settings.sonarr.length === 0 && !settings.sonarr[0]) {
|
||||||
logger.info(
|
logger.warn(
|
||||||
'Skipped Sonarr request as there is no Sonarr server configured',
|
'No Sonarr server configured, skipping request processing',
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -585,18 +517,26 @@ export class MediaRequest {
|
|||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
`Request has an override server: ${sonarrSettings?.name}`,
|
`Request has an override server: ${sonarrSettings?.name}`,
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sonarrSettings) {
|
if (!sonarrSettings) {
|
||||||
logger.info(
|
logger.warn(
|
||||||
`There is no default ${
|
`There is no default ${
|
||||||
this.is4k ? '4K ' : ''
|
this.is4k ? '4K ' : ''
|
||||||
}Sonarr server configured. Did you set any of your ${
|
}Sonarr server configured. Did you set any of your ${
|
||||||
this.is4k ? '4K ' : ''
|
this.is4k ? '4K ' : ''
|
||||||
}Sonarr servers as default?`,
|
}Sonarr servers as default?`,
|
||||||
{ label: 'Media Request' }
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -607,7 +547,7 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error('Media data is missing');
|
throw new Error('Media data not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -628,7 +568,7 @@ export class MediaRequest {
|
|||||||
const requestRepository = getRepository(MediaRequest);
|
const requestRepository = getRepository(MediaRequest);
|
||||||
await mediaRepository.remove(media);
|
await mediaRepository.remove(media);
|
||||||
await requestRepository.remove(this);
|
await requestRepository.remove(this);
|
||||||
throw new Error('Series was missing tvdb id');
|
throw new Error('TVDB ID not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
let seriesType: SonarrSeries['seriesType'] = 'standard';
|
let seriesType: SonarrSeries['seriesType'] = 'standard';
|
||||||
@@ -650,12 +590,10 @@ export class MediaRequest {
|
|||||||
seriesType === 'anime' && sonarrSettings.activeAnimeProfileId
|
seriesType === 'anime' && sonarrSettings.activeAnimeProfileId
|
||||||
? sonarrSettings.activeAnimeProfileId
|
? sonarrSettings.activeAnimeProfileId
|
||||||
: sonarrSettings.activeProfileId;
|
: sonarrSettings.activeProfileId;
|
||||||
|
|
||||||
let languageProfile =
|
let languageProfile =
|
||||||
seriesType === 'anime' && sonarrSettings.activeAnimeLanguageProfileId
|
seriesType === 'anime' && sonarrSettings.activeAnimeLanguageProfileId
|
||||||
? sonarrSettings.activeAnimeLanguageProfileId
|
? sonarrSettings.activeAnimeLanguageProfileId
|
||||||
: sonarrSettings.activeLanguageProfileId;
|
: sonarrSettings.activeLanguageProfileId;
|
||||||
|
|
||||||
let tags =
|
let tags =
|
||||||
seriesType === 'anime'
|
seriesType === 'anime'
|
||||||
? sonarrSettings.animeTags
|
? sonarrSettings.animeTags
|
||||||
@@ -669,14 +607,21 @@ export class MediaRequest {
|
|||||||
rootFolder = this.rootFolder;
|
rootFolder = this.rootFolder;
|
||||||
logger.info(`Request has an override root folder: ${rootFolder}`, {
|
logger.info(`Request has an override root folder: ${rootFolder}`, {
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.profileId && this.profileId !== qualityProfile) {
|
if (this.profileId && this.profileId !== qualityProfile) {
|
||||||
qualityProfile = this.profileId;
|
qualityProfile = this.profileId;
|
||||||
logger.info(`Request has an override profile ID: ${qualityProfile}`, {
|
logger.info(
|
||||||
label: 'Media Request',
|
`Request has an override quality profile ID: ${qualityProfile}`,
|
||||||
});
|
{
|
||||||
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -685,9 +630,11 @@ export class MediaRequest {
|
|||||||
) {
|
) {
|
||||||
languageProfile = this.languageProfileId;
|
languageProfile = this.languageProfileId;
|
||||||
logger.info(
|
logger.info(
|
||||||
`Request has an override Language Profile: ${languageProfile}`,
|
`Request has an override language profile ID: ${languageProfile}`,
|
||||||
{
|
{
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -696,25 +643,29 @@ export class MediaRequest {
|
|||||||
tags = this.tags;
|
tags = this.tags;
|
||||||
logger.info(`Request has override tags`, {
|
logger.info(`Request has override tags`, {
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
tagIds: tags,
|
tagIds: tags,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sonarrSeriesOptions: AddSeriesOptions = {
|
||||||
|
profileId: qualityProfile,
|
||||||
|
languageProfileId: languageProfile,
|
||||||
|
rootFolderPath: rootFolder,
|
||||||
|
title: series.name,
|
||||||
|
tvdbid: tvdbId,
|
||||||
|
seasons: this.seasons.map((season) => season.seasonNumber),
|
||||||
|
seasonFolder: sonarrSettings.enableSeasonFolders,
|
||||||
|
seriesType,
|
||||||
|
tags,
|
||||||
|
monitored: true,
|
||||||
|
searchNow: !sonarrSettings.preventSearch,
|
||||||
|
};
|
||||||
|
|
||||||
// Run this asynchronously so we don't wait for it on the UI side
|
// Run this asynchronously so we don't wait for it on the UI side
|
||||||
sonarr
|
sonarr
|
||||||
.addSeries({
|
.addSeries(sonarrSeriesOptions)
|
||||||
profileId: qualityProfile,
|
|
||||||
languageProfileId: languageProfile,
|
|
||||||
rootFolderPath: rootFolder,
|
|
||||||
title: series.name,
|
|
||||||
tvdbid: tvdbId,
|
|
||||||
seasons: this.seasons.map((season) => season.seasonNumber),
|
|
||||||
seasonFolder: sonarrSettings.enableSeasonFolders,
|
|
||||||
seriesType,
|
|
||||||
tags,
|
|
||||||
monitored: true,
|
|
||||||
searchNow: !sonarrSettings.preventSearch,
|
|
||||||
})
|
|
||||||
.then(async (sonarrSeries) => {
|
.then(async (sonarrSeries) => {
|
||||||
// We grab media again here to make sure we have the latest version of it
|
// We grab media again here to make sure we have the latest version of it
|
||||||
const media = await mediaRepository.findOne({
|
const media = await mediaRepository.findOne({
|
||||||
@@ -723,7 +674,7 @@ export class MediaRequest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!media) {
|
if (!media) {
|
||||||
throw new Error('Media data is missing');
|
throw new Error('Media data not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
media[this.is4k ? 'externalServiceId4k' : 'externalServiceId'] =
|
||||||
@@ -737,47 +688,116 @@ export class MediaRequest {
|
|||||||
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
media[this.is4k ? 'status4k' : 'status'] = MediaStatus.UNKNOWN;
|
||||||
await mediaRepository.save(media);
|
await mediaRepository.save(media);
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'Newly added series request failed to add to Sonarr, marking as unknown',
|
'Something went wrong sending series request to Sonarr, marking status as UNKNOWN',
|
||||||
{
|
{
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
sonarrSeriesOptions,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
notificationManager.sendNotification(Notification.MEDIA_FAILED, {
|
this.sendNotification(media, Notification.MEDIA_FAILED);
|
||||||
event: `${this.is4k ? '4K ' : ''}Series Request Failed`,
|
|
||||||
subject: `${series.name}${
|
|
||||||
series.first_air_date
|
|
||||||
? ` (${series.first_air_date.slice(0, 4)})`
|
|
||||||
: ''
|
|
||||||
}`,
|
|
||||||
message: truncate(series.overview, {
|
|
||||||
length: 500,
|
|
||||||
separator: /\s/,
|
|
||||||
omission: '…',
|
|
||||||
}),
|
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${series.poster_path}`,
|
|
||||||
media,
|
|
||||||
extra: [
|
|
||||||
{
|
|
||||||
name: 'Requested Seasons',
|
|
||||||
value: this.seasons
|
|
||||||
.map((season) => season.seasonNumber)
|
|
||||||
.join(', '),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
request: this,
|
|
||||||
notifyAdmin: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
logger.info('Sent request to Sonarr', { label: 'Media Request' });
|
logger.info('Sent request to Sonarr', {
|
||||||
} catch (e) {
|
|
||||||
const errorMessage = `Request failed to send to Sonarr: ${e.message}`;
|
|
||||||
logger.error('Request failed to send to Sonarr', {
|
|
||||||
label: 'Media Request',
|
label: 'Media Request',
|
||||||
errorMessage,
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
});
|
});
|
||||||
throw new Error(errorMessage);
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong sending request to Sonarr', {
|
||||||
|
label: 'Media Request',
|
||||||
|
errorMessage: e.message,
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
|
throw new Error(e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async sendNotification(media: Media, type: Notification) {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mediaType = this.type === MediaType.MOVIE ? 'Movie' : 'Series';
|
||||||
|
let event: string | undefined;
|
||||||
|
let notifyAdmin = true;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Notification.MEDIA_APPROVED:
|
||||||
|
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Approved`;
|
||||||
|
notifyAdmin = false;
|
||||||
|
break;
|
||||||
|
case Notification.MEDIA_DECLINED:
|
||||||
|
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Declined`;
|
||||||
|
notifyAdmin = false;
|
||||||
|
break;
|
||||||
|
case Notification.MEDIA_PENDING:
|
||||||
|
event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`;
|
||||||
|
break;
|
||||||
|
case Notification.MEDIA_AUTO_APPROVED:
|
||||||
|
event = `${
|
||||||
|
this.is4k ? '4K ' : ''
|
||||||
|
}${mediaType} Request Automatically Approved`;
|
||||||
|
break;
|
||||||
|
case Notification.MEDIA_FAILED:
|
||||||
|
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Failed`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.type === MediaType.MOVIE) {
|
||||||
|
const movie = await tmdb.getMovie({ movieId: media.tmdbId });
|
||||||
|
notificationManager.sendNotification(type, {
|
||||||
|
media,
|
||||||
|
request: this,
|
||||||
|
notifyAdmin,
|
||||||
|
notifyUser: notifyAdmin ? undefined : this.requestedBy,
|
||||||
|
event,
|
||||||
|
subject: `${movie.title}${
|
||||||
|
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
||||||
|
}`,
|
||||||
|
message: truncate(movie.overview, {
|
||||||
|
length: 500,
|
||||||
|
separator: /\s/,
|
||||||
|
omission: '…',
|
||||||
|
}),
|
||||||
|
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
||||||
|
});
|
||||||
|
} else if (this.type === MediaType.TV) {
|
||||||
|
const tv = await tmdb.getTvShow({ tvId: media.tmdbId });
|
||||||
|
notificationManager.sendNotification(type, {
|
||||||
|
media,
|
||||||
|
request: this,
|
||||||
|
notifyAdmin,
|
||||||
|
notifyUser: notifyAdmin ? undefined : this.requestedBy,
|
||||||
|
event,
|
||||||
|
subject: `${tv.name}${
|
||||||
|
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
||||||
|
}`,
|
||||||
|
message: truncate(tv.overview, {
|
||||||
|
length: 500,
|
||||||
|
separator: /\s/,
|
||||||
|
omission: '…',
|
||||||
|
}),
|
||||||
|
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
||||||
|
extra: [
|
||||||
|
{
|
||||||
|
name: 'Requested Seasons',
|
||||||
|
value: this.seasons
|
||||||
|
.map((season) => season.seasonNumber)
|
||||||
|
.join(', '),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong sending media notification(s)', {
|
||||||
|
label: 'Notifications',
|
||||||
|
errorMessage: e.message,
|
||||||
|
requestId: this.id,
|
||||||
|
mediaId: this.media.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import TheMovieDb from '../api/themoviedb';
|
import TheMovieDb from '../api/themoviedb';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
|
import logger from '../logger';
|
||||||
import { mapCollection } from '../models/Collection';
|
import { mapCollection } from '../models/Collection';
|
||||||
|
|
||||||
const collectionRoutes = Router();
|
const collectionRoutes = Router();
|
||||||
@@ -20,7 +21,15 @@ collectionRoutes.get<{ id: string }>('/:id', async (req, res, next) => {
|
|||||||
|
|
||||||
return res.status(200).json(mapCollection(collection, media));
|
return res.status(200).json(mapCollection(collection, media));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next({ status: 404, message: 'Collection does not exist' });
|
logger.debug('Something went wrong retrieving collection', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
collectionId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve collection.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -37,54 +37,15 @@ export const createTmdbWithRegionLanguage = (user?: User): TheMovieDb => {
|
|||||||
|
|
||||||
const discoverRoutes = Router();
|
const discoverRoutes = Router();
|
||||||
|
|
||||||
discoverRoutes.get('/movies', async (req, res) => {
|
discoverRoutes.get('/movies', async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverMovies({
|
try {
|
||||||
page: Number(req.query.page),
|
|
||||||
language: req.locale ?? (req.query.language as string),
|
|
||||||
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
|
||||||
studio: req.query.studio ? Number(req.query.studio) : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
|
||||||
data.results.map((result) => result.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
page: data.page,
|
|
||||||
totalPages: data.total_pages,
|
|
||||||
totalResults: data.total_results,
|
|
||||||
results: data.results.map((result) =>
|
|
||||||
mapMovieResult(
|
|
||||||
result,
|
|
||||||
media.find(
|
|
||||||
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
discoverRoutes.get<{ language: string }>(
|
|
||||||
'/movies/language/:language',
|
|
||||||
async (req, res, next) => {
|
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
|
||||||
|
|
||||||
const languages = await tmdb.getLanguages();
|
|
||||||
|
|
||||||
const language = languages.find(
|
|
||||||
(lang) => lang.iso_639_1 === req.params.language
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!language) {
|
|
||||||
return next({ status: 404, message: 'Unable to retrieve language' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverMovies({
|
const data = await tmdb.getDiscoverMovies({
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
originalLanguage: req.params.language,
|
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
||||||
|
studio: req.query.studio ? Number(req.query.studio) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
@@ -95,7 +56,6 @@ discoverRoutes.get<{ language: string }>(
|
|||||||
page: data.page,
|
page: data.page,
|
||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
language,
|
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapMovieResult(
|
mapMovieResult(
|
||||||
result,
|
result,
|
||||||
@@ -106,6 +66,70 @@ discoverRoutes.get<{ language: string }>(
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving popular movies', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve popular movies.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
discoverRoutes.get<{ language: string }>(
|
||||||
|
'/movies/language/:language',
|
||||||
|
async (req, res, next) => {
|
||||||
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const languages = await tmdb.getLanguages();
|
||||||
|
|
||||||
|
const language = languages.find(
|
||||||
|
(lang) => lang.iso_639_1 === req.params.language
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!language) {
|
||||||
|
return next({ status: 404, message: 'Language not found.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await tmdb.getDiscoverMovies({
|
||||||
|
page: Number(req.query.page),
|
||||||
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
originalLanguage: req.params.language,
|
||||||
|
});
|
||||||
|
|
||||||
|
const media = await Media.getRelatedMedia(
|
||||||
|
data.results.map((result) => result.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
page: data.page,
|
||||||
|
totalPages: data.total_pages,
|
||||||
|
totalResults: data.total_results,
|
||||||
|
language,
|
||||||
|
results: data.results.map((result) =>
|
||||||
|
mapMovieResult(
|
||||||
|
result,
|
||||||
|
media.find(
|
||||||
|
(req) =>
|
||||||
|
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movies by language', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
language: req.params.language,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movies by language.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -114,43 +138,55 @@ discoverRoutes.get<{ genreId: string }>(
|
|||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const genres = await tmdb.getMovieGenres({
|
try {
|
||||||
language: req.locale ?? (req.query.language as string),
|
const genres = await tmdb.getMovieGenres({
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const genre = genres.find(
|
const genre = genres.find(
|
||||||
(genre) => genre.id === Number(req.params.genreId)
|
(genre) => genre.id === Number(req.params.genreId)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!genre) {
|
if (!genre) {
|
||||||
return next({ status: 404, message: 'Unable to retrieve genre' });
|
return next({ status: 404, message: 'Genre not found.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverMovies({
|
const data = await tmdb.getDiscoverMovies({
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
genre: Number(req.params.genreId),
|
genre: Number(req.params.genreId),
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
data.results.map((result) => result.id)
|
data.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: data.page,
|
page: data.page,
|
||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
genre,
|
genre,
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapMovieResult(
|
mapMovieResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(req) =>
|
(req) =>
|
||||||
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movies by genre', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
genreId: req.params.genreId,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movies by genre.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,12 +224,20 @@ discoverRoutes.get<{ studioId: string }>(
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next({ status: 404, message: 'Unable to retrieve studio' });
|
logger.debug('Something went wrong retrieving movies by studio', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
studioId: req.params.studioId,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movies by studio.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
discoverRoutes.get('/movies/upcoming', async (req, res) => {
|
discoverRoutes.get('/movies/upcoming', async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -202,79 +246,52 @@ discoverRoutes.get('/movies/upcoming', async (req, res) => {
|
|||||||
.toISOString()
|
.toISOString()
|
||||||
.split('T')[0];
|
.split('T')[0];
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverMovies({
|
try {
|
||||||
page: Number(req.query.page),
|
const data = await tmdb.getDiscoverMovies({
|
||||||
language: req.locale ?? (req.query.language as string),
|
|
||||||
primaryReleaseDateGte: date,
|
|
||||||
});
|
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
|
||||||
data.results.map((result) => result.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
page: data.page,
|
|
||||||
totalPages: data.total_pages,
|
|
||||||
totalResults: data.total_results,
|
|
||||||
results: data.results.map((result) =>
|
|
||||||
mapMovieResult(
|
|
||||||
result,
|
|
||||||
media.find(
|
|
||||||
(med) => med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
discoverRoutes.get('/tv', async (req, res) => {
|
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverTv({
|
|
||||||
page: Number(req.query.page),
|
|
||||||
language: req.locale ?? (req.query.language as string),
|
|
||||||
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
|
||||||
network: req.query.network ? Number(req.query.network) : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
|
||||||
data.results.map((result) => result.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
page: data.page,
|
|
||||||
totalPages: data.total_pages,
|
|
||||||
totalResults: data.total_results,
|
|
||||||
results: data.results.map((result) =>
|
|
||||||
mapTvResult(
|
|
||||||
result,
|
|
||||||
media.find(
|
|
||||||
(med) => med.tmdbId === result.id && med.mediaType === MediaType.TV
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
discoverRoutes.get<{ language: string }>(
|
|
||||||
'/tv/language/:language',
|
|
||||||
async (req, res, next) => {
|
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
|
||||||
|
|
||||||
const languages = await tmdb.getLanguages();
|
|
||||||
|
|
||||||
const language = languages.find(
|
|
||||||
(lang) => lang.iso_639_1 === req.params.language
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!language) {
|
|
||||||
return next({ status: 404, message: 'Unable to retrieve language' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverTv({
|
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
originalLanguage: req.params.language,
|
primaryReleaseDateGte: date,
|
||||||
|
});
|
||||||
|
|
||||||
|
const media = await Media.getRelatedMedia(
|
||||||
|
data.results.map((result) => result.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
page: data.page,
|
||||||
|
totalPages: data.total_pages,
|
||||||
|
totalResults: data.total_results,
|
||||||
|
results: data.results.map((result) =>
|
||||||
|
mapMovieResult(
|
||||||
|
result,
|
||||||
|
media.find(
|
||||||
|
(med) =>
|
||||||
|
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving upcoming movies', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve upcoming movies.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
discoverRoutes.get('/tv', async (req, res, next) => {
|
||||||
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await tmdb.getDiscoverTv({
|
||||||
|
page: Number(req.query.page),
|
||||||
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
genre: req.query.genre ? Number(req.query.genre) : undefined,
|
||||||
|
network: req.query.network ? Number(req.query.network) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
@@ -285,7 +302,6 @@ discoverRoutes.get<{ language: string }>(
|
|||||||
page: data.page,
|
page: data.page,
|
||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
language,
|
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapTvResult(
|
mapTvResult(
|
||||||
result,
|
result,
|
||||||
@@ -295,6 +311,70 @@ discoverRoutes.get<{ language: string }>(
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving popular series', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve popular series.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
discoverRoutes.get<{ language: string }>(
|
||||||
|
'/tv/language/:language',
|
||||||
|
async (req, res, next) => {
|
||||||
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const languages = await tmdb.getLanguages();
|
||||||
|
|
||||||
|
const language = languages.find(
|
||||||
|
(lang) => lang.iso_639_1 === req.params.language
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!language) {
|
||||||
|
return next({ status: 404, message: 'Language not found.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await tmdb.getDiscoverTv({
|
||||||
|
page: Number(req.query.page),
|
||||||
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
originalLanguage: req.params.language,
|
||||||
|
});
|
||||||
|
|
||||||
|
const media = await Media.getRelatedMedia(
|
||||||
|
data.results.map((result) => result.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
page: data.page,
|
||||||
|
totalPages: data.total_pages,
|
||||||
|
totalResults: data.total_results,
|
||||||
|
language,
|
||||||
|
results: data.results.map((result) =>
|
||||||
|
mapTvResult(
|
||||||
|
result,
|
||||||
|
media.find(
|
||||||
|
(med) =>
|
||||||
|
med.tmdbId === result.id && med.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving series by language', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
language: req.params.language,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series by language.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -303,42 +383,55 @@ discoverRoutes.get<{ genreId: string }>(
|
|||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const genres = await tmdb.getTvGenres({
|
try {
|
||||||
language: req.locale ?? (req.query.language as string),
|
const genres = await tmdb.getTvGenres({
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const genre = genres.find(
|
const genre = genres.find(
|
||||||
(genre) => genre.id === Number(req.params.genreId)
|
(genre) => genre.id === Number(req.params.genreId)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!genre) {
|
if (!genre) {
|
||||||
return next({ status: 404, message: 'Unable to retrieve genre' });
|
return next({ status: 404, message: 'Genre not found.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverTv({
|
const data = await tmdb.getDiscoverTv({
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
genre: Number(req.params.genreId),
|
genre: Number(req.params.genreId),
|
||||||
});
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
data.results.map((result) => result.id)
|
data.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: data.page,
|
page: data.page,
|
||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
genre,
|
genre,
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapTvResult(
|
mapTvResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(med) => med.tmdbId === result.id && med.mediaType === MediaType.TV
|
(med) =>
|
||||||
|
med.tmdbId === result.id && med.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving series by genre', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
genreId: req.params.genreId,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series by genre.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -376,12 +469,20 @@ discoverRoutes.get<{ networkId: string }>(
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next({ status: 404, message: 'Unable to retrieve network' });
|
logger.debug('Something went wrong retrieving series by network', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
networkId: req.params.networkId,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series by network.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
discoverRoutes.get('/tv/upcoming', async (req, res) => {
|
discoverRoutes.get('/tv/upcoming', async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -390,76 +491,47 @@ discoverRoutes.get('/tv/upcoming', async (req, res) => {
|
|||||||
.toISOString()
|
.toISOString()
|
||||||
.split('T')[0];
|
.split('T')[0];
|
||||||
|
|
||||||
const data = await tmdb.getDiscoverTv({
|
try {
|
||||||
page: Number(req.query.page),
|
const data = await tmdb.getDiscoverTv({
|
||||||
language: req.locale ?? (req.query.language as string),
|
page: Number(req.query.page),
|
||||||
firstAirDateGte: date,
|
language: req.locale ?? (req.query.language as string),
|
||||||
});
|
firstAirDateGte: date,
|
||||||
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
data.results.map((result) => result.id)
|
data.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: data.page,
|
page: data.page,
|
||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapTvResult(
|
mapTvResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(med) => med.tmdbId === result.id && med.mediaType === MediaType.TV
|
(med) => med.tmdbId === result.id && med.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving upcoming series', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve upcoming series.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
discoverRoutes.get('/trending', async (req, res) => {
|
discoverRoutes.get('/trending', async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage(req.user);
|
const tmdb = createTmdbWithRegionLanguage(req.user);
|
||||||
|
|
||||||
const data = await tmdb.getAllTrending({
|
try {
|
||||||
page: Number(req.query.page),
|
const data = await tmdb.getAllTrending({
|
||||||
language: req.locale ?? (req.query.language as string),
|
|
||||||
});
|
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
|
||||||
data.results.map((result) => result.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
page: data.page,
|
|
||||||
totalPages: data.total_pages,
|
|
||||||
totalResults: data.total_results,
|
|
||||||
results: data.results.map((result) =>
|
|
||||||
isMovie(result)
|
|
||||||
? mapMovieResult(
|
|
||||||
result,
|
|
||||||
media.find(
|
|
||||||
(med) =>
|
|
||||||
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: isPerson(result)
|
|
||||||
? mapPersonResult(result)
|
|
||||||
: mapTvResult(
|
|
||||||
result,
|
|
||||||
media.find(
|
|
||||||
(med) =>
|
|
||||||
med.tmdbId === result.id && med.mediaType === MediaType.TV
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
discoverRoutes.get<{ keywordId: string }>(
|
|
||||||
'/keyword/:keywordId/movies',
|
|
||||||
async (req, res) => {
|
|
||||||
const tmdb = new TheMovieDb();
|
|
||||||
|
|
||||||
const data = await tmdb.getMoviesByKeyword({
|
|
||||||
keywordId: Number(req.params.keywordId),
|
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
});
|
});
|
||||||
@@ -473,15 +545,78 @@ discoverRoutes.get<{ keywordId: string }>(
|
|||||||
totalPages: data.total_pages,
|
totalPages: data.total_pages,
|
||||||
totalResults: data.total_results,
|
totalResults: data.total_results,
|
||||||
results: data.results.map((result) =>
|
results: data.results.map((result) =>
|
||||||
mapMovieResult(
|
isMovie(result)
|
||||||
result,
|
? mapMovieResult(
|
||||||
media.find(
|
result,
|
||||||
(med) =>
|
media.find(
|
||||||
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
(med) =>
|
||||||
)
|
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
: isPerson(result)
|
||||||
|
? mapPersonResult(result)
|
||||||
|
: mapTvResult(
|
||||||
|
result,
|
||||||
|
media.find(
|
||||||
|
(med) =>
|
||||||
|
med.tmdbId === result.id && med.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving trending items', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve trending items.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
discoverRoutes.get<{ keywordId: string }>(
|
||||||
|
'/keyword/:keywordId/movies',
|
||||||
|
async (req, res, next) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await tmdb.getMoviesByKeyword({
|
||||||
|
keywordId: Number(req.params.keywordId),
|
||||||
|
page: Number(req.query.page),
|
||||||
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
|
const media = await Media.getRelatedMedia(
|
||||||
|
data.results.map((result) => result.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
page: data.page,
|
||||||
|
totalPages: data.total_pages,
|
||||||
|
totalResults: data.total_results,
|
||||||
|
results: data.results.map((result) =>
|
||||||
|
mapMovieResult(
|
||||||
|
result,
|
||||||
|
media.find(
|
||||||
|
(med) =>
|
||||||
|
med.tmdbId === result.id && med.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movies by keyword', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
keywordId: req.params.keywordId,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movies by keyword.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -515,7 +650,8 @@ discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
|
|||||||
|
|
||||||
return res.status(200).json(sortedData);
|
return res.status(200).json(sortedData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Something went wrong retrieving the movie genre slider', {
|
logger.debug('Something went wrong retrieving the movie genre slider', {
|
||||||
|
label: 'API',
|
||||||
errorMessage: e.message,
|
errorMessage: e.message,
|
||||||
});
|
});
|
||||||
return next({
|
return next({
|
||||||
@@ -556,12 +692,13 @@ discoverRoutes.get<{ language: string }, GenreSliderItem[]>(
|
|||||||
|
|
||||||
return res.status(200).json(sortedData);
|
return res.status(200).json(sortedData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Something went wrong retrieving the tv genre slider', {
|
logger.debug('Something went wrong retrieving the series genre slider', {
|
||||||
|
label: 'API',
|
||||||
errorMessage: e.message,
|
errorMessage: e.message,
|
||||||
});
|
});
|
||||||
return next({
|
return next({
|
||||||
status: 500,
|
status: 500,
|
||||||
message: 'Unable to retrieve tv genre slider.',
|
message: 'Unable to retrieve series genre slider.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import { TmdbMovieResult, TmdbTvResult } from '../api/themoviedb/interfaces';
|
|||||||
import { StatusResponse } from '../interfaces/api/settingsInterfaces';
|
import { StatusResponse } from '../interfaces/api/settingsInterfaces';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
import { getSettings } from '../lib/settings';
|
import { getSettings } from '../lib/settings';
|
||||||
|
import logger from '../logger';
|
||||||
import { checkUser, isAuthenticated } from '../middleware/auth';
|
import { checkUser, isAuthenticated } from '../middleware/auth';
|
||||||
import { mapProductionCompany } from '../models/Movie';
|
import { mapProductionCompany } from '../models/Movie';
|
||||||
import { mapNetwork } from '../models/Tv';
|
import { mapNetwork } from '../models/Tv';
|
||||||
@@ -114,78 +115,157 @@ router.use('/issue', isAuthenticated(), issueRoutes);
|
|||||||
router.use('/issueComment', isAuthenticated(), issueCommentRoutes);
|
router.use('/issueComment', isAuthenticated(), issueCommentRoutes);
|
||||||
router.use('/auth', authRoutes);
|
router.use('/auth', authRoutes);
|
||||||
|
|
||||||
router.get('/regions', isAuthenticated(), async (req, res) => {
|
router.get('/regions', isAuthenticated(), async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const regions = await tmdb.getRegions();
|
try {
|
||||||
|
const regions = await tmdb.getRegions();
|
||||||
|
|
||||||
return res.status(200).json(regions);
|
return res.status(200).json(regions);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving regions', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve regions.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/languages', isAuthenticated(), async (req, res) => {
|
router.get('/languages', isAuthenticated(), async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const languages = await tmdb.getLanguages();
|
try {
|
||||||
|
const languages = await tmdb.getLanguages();
|
||||||
|
|
||||||
return res.status(200).json(languages);
|
return res.status(200).json(languages);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving languages', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve languages.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get<{ id: string }>('/studio/:id', async (req, res) => {
|
router.get<{ id: string }>('/studio/:id', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const studio = await tmdb.getStudio(Number(req.params.id));
|
try {
|
||||||
|
const studio = await tmdb.getStudio(Number(req.params.id));
|
||||||
|
|
||||||
return res.status(200).json(mapProductionCompany(studio));
|
return res.status(200).json(mapProductionCompany(studio));
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving studio', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
studioId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve studio.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get<{ id: string }>('/network/:id', async (req, res) => {
|
router.get<{ id: string }>('/network/:id', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const network = await tmdb.getNetwork(Number(req.params.id));
|
try {
|
||||||
|
const network = await tmdb.getNetwork(Number(req.params.id));
|
||||||
|
|
||||||
return res.status(200).json(mapNetwork(network));
|
return res.status(200).json(mapNetwork(network));
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving network', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
networkId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve network.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/genres/movie', isAuthenticated(), async (req, res) => {
|
router.get('/genres/movie', isAuthenticated(), async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const genres = await tmdb.getMovieGenres({
|
try {
|
||||||
language: req.locale ?? (req.query.language as string),
|
const genres = await tmdb.getMovieGenres({
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json(genres);
|
return res.status(200).json(genres);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movie genres', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movie genres.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/genres/tv', isAuthenticated(), async (req, res) => {
|
router.get('/genres/tv', isAuthenticated(), async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const genres = await tmdb.getTvGenres({
|
try {
|
||||||
language: req.locale ?? (req.query.language as string),
|
const genres = await tmdb.getTvGenres({
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json(genres);
|
return res.status(200).json(genres);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving series genres', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series genres.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/backdrops', async (req, res) => {
|
router.get('/backdrops', async (req, res, next) => {
|
||||||
const tmdb = createTmdbWithRegionLanguage();
|
const tmdb = createTmdbWithRegionLanguage();
|
||||||
|
|
||||||
const data = (
|
try {
|
||||||
await tmdb.getAllTrending({
|
const data = (
|
||||||
page: 1,
|
await tmdb.getAllTrending({
|
||||||
timeWindow: 'week',
|
page: 1,
|
||||||
})
|
timeWindow: 'week',
|
||||||
).results.filter((result) => !isPerson(result)) as (
|
})
|
||||||
| TmdbMovieResult
|
).results.filter((result) => !isPerson(result)) as (
|
||||||
| TmdbTvResult
|
| TmdbMovieResult
|
||||||
)[];
|
| TmdbTvResult
|
||||||
|
)[];
|
||||||
|
|
||||||
return res
|
return res
|
||||||
.status(200)
|
.status(200)
|
||||||
.json(
|
.json(
|
||||||
data
|
data
|
||||||
.map((result) => result.backdrop_path)
|
.map((result) => result.backdrop_path)
|
||||||
.filter((backdropPath) => !!backdropPath)
|
.filter((backdropPath) => !!backdropPath)
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving backdrops', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve backdrops.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/', (_req, res) => {
|
router.get('/', (_req, res) => {
|
||||||
|
@@ -22,75 +22,105 @@ movieRoutes.get('/:id', async (req, res, next) => {
|
|||||||
|
|
||||||
return res.status(200).json(mapMovieDetails(tmdbMovie, media));
|
return res.status(200).json(mapMovieDetails(tmdbMovie, media));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Something went wrong getting movie', {
|
logger.debug('Something went wrong retrieving movie', {
|
||||||
label: 'Movie',
|
label: 'API',
|
||||||
message: e.message,
|
errorMessage: e.message,
|
||||||
|
movieId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movie.',
|
||||||
});
|
});
|
||||||
return next({ status: 404, message: 'Movie does not exist' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
movieRoutes.get('/:id/recommendations', async (req, res) => {
|
movieRoutes.get('/:id/recommendations', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const results = await tmdb.getMovieRecommendations({
|
try {
|
||||||
movieId: Number(req.params.id),
|
const results = await tmdb.getMovieRecommendations({
|
||||||
page: Number(req.query.page),
|
movieId: Number(req.params.id),
|
||||||
language: req.locale ?? (req.query.language as string),
|
page: Number(req.query.page),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
results.results.map((result) => result.id)
|
results.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: results.page,
|
page: results.page,
|
||||||
totalPages: results.total_pages,
|
totalPages: results.total_pages,
|
||||||
totalResults: results.total_results,
|
totalResults: results.total_results,
|
||||||
results: results.results.map((result) =>
|
results: results.results.map((result) =>
|
||||||
mapMovieResult(
|
mapMovieResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
(req) =>
|
||||||
|
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movie recommendations', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
movieId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movie recommendations.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
movieRoutes.get('/:id/similar', async (req, res) => {
|
movieRoutes.get('/:id/similar', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const results = await tmdb.getMovieSimilar({
|
try {
|
||||||
movieId: Number(req.params.id),
|
const results = await tmdb.getMovieSimilar({
|
||||||
page: Number(req.query.page),
|
movieId: Number(req.params.id),
|
||||||
language: req.locale ?? (req.query.language as string),
|
page: Number(req.query.page),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
results.results.map((result) => result.id)
|
results.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: results.page,
|
page: results.page,
|
||||||
totalPages: results.total_pages,
|
totalPages: results.total_pages,
|
||||||
totalResults: results.total_results,
|
totalResults: results.total_results,
|
||||||
results: results.results.map((result) =>
|
results: results.results.map((result) =>
|
||||||
mapMovieResult(
|
mapMovieResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(req) => req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
(req) =>
|
||||||
|
req.tmdbId === result.id && req.mediaType === MediaType.MOVIE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving similar movies', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
movieId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve similar movies.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
||||||
try {
|
const tmdb = new TheMovieDb();
|
||||||
const tmdb = new TheMovieDb();
|
const rtapi = new RottenTomatoes();
|
||||||
const rtapi = new RottenTomatoes();
|
|
||||||
|
|
||||||
|
try {
|
||||||
const movie = await tmdb.getMovie({
|
const movie = await tmdb.getMovie({
|
||||||
movieId: Number(req.params.id),
|
movieId: Number(req.params.id),
|
||||||
});
|
});
|
||||||
@@ -101,12 +131,23 @@ movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!rtratings) {
|
if (!rtratings) {
|
||||||
return next({ status: 404, message: 'Unable to retrieve ratings' });
|
return next({
|
||||||
|
status: 404,
|
||||||
|
message: 'Rotten Tomatoes ratings not found.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(rtratings);
|
return res.status(200).json(rtratings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next({ status: 404, message: 'Movie does not exist' });
|
logger.debug('Something went wrong retrieving movie ratings', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
movieId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movie ratings.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -20,52 +20,71 @@ personRoutes.get('/:id', async (req, res, next) => {
|
|||||||
});
|
});
|
||||||
return res.status(200).json(mapPersonDetails(person));
|
return res.status(200).json(mapPersonDetails(person));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(e.message);
|
logger.debug('Something went wrong retrieving person', {
|
||||||
next({ status: 404, message: 'Person not found' });
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
personId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve person.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
personRoutes.get('/:id/combined_credits', async (req, res) => {
|
personRoutes.get('/:id/combined_credits', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const combinedCredits = await tmdb.getPersonCombinedCredits({
|
try {
|
||||||
personId: Number(req.params.id),
|
const combinedCredits = await tmdb.getPersonCombinedCredits({
|
||||||
language: req.locale ?? (req.query.language as string),
|
personId: Number(req.params.id),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const castMedia = await Media.getRelatedMedia(
|
const castMedia = await Media.getRelatedMedia(
|
||||||
combinedCredits.cast.map((result) => result.id)
|
combinedCredits.cast.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
const crewMedia = await Media.getRelatedMedia(
|
const crewMedia = await Media.getRelatedMedia(
|
||||||
combinedCredits.crew.map((result) => result.id)
|
combinedCredits.crew.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
cast: combinedCredits.cast
|
cast: combinedCredits.cast
|
||||||
.map((result) =>
|
.map((result) =>
|
||||||
mapCastCredits(
|
mapCastCredits(
|
||||||
result,
|
result,
|
||||||
castMedia.find(
|
castMedia.find(
|
||||||
(med) =>
|
(med) =>
|
||||||
med.tmdbId === result.id && med.mediaType === result.media_type
|
med.tmdbId === result.id && med.mediaType === result.media_type
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
.filter((item) => !item.adult),
|
||||||
.filter((item) => !item.adult),
|
crew: combinedCredits.crew
|
||||||
crew: combinedCredits.crew
|
.map((result) =>
|
||||||
.map((result) =>
|
mapCrewCredits(
|
||||||
mapCrewCredits(
|
result,
|
||||||
result,
|
crewMedia.find(
|
||||||
crewMedia.find(
|
(med) =>
|
||||||
(med) =>
|
med.tmdbId === result.id && med.mediaType === result.media_type
|
||||||
med.tmdbId === result.id && med.mediaType === result.media_type
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
.filter((item) => !item.adult),
|
||||||
.filter((item) => !item.adult),
|
id: combinedCredits.id,
|
||||||
id: combinedCredits.id,
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving combined credits', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
personId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve combined credits.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default personRoutes;
|
export default personRoutes;
|
||||||
|
@@ -3,43 +3,56 @@ import TheMovieDb from '../api/themoviedb';
|
|||||||
import { TmdbSearchMultiResponse } from '../api/themoviedb/interfaces';
|
import { TmdbSearchMultiResponse } from '../api/themoviedb/interfaces';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
import { findSearchProvider } from '../lib/search';
|
import { findSearchProvider } from '../lib/search';
|
||||||
|
import logger from '../logger';
|
||||||
import { mapSearchResults } from '../models/Search';
|
import { mapSearchResults } from '../models/Search';
|
||||||
|
|
||||||
const searchRoutes = Router();
|
const searchRoutes = Router();
|
||||||
|
|
||||||
searchRoutes.get('/', async (req, res) => {
|
searchRoutes.get('/', async (req, res, next) => {
|
||||||
const queryString = req.query.query as string;
|
const queryString = req.query.query as string;
|
||||||
const searchProvider = findSearchProvider(queryString.toLowerCase());
|
const searchProvider = findSearchProvider(queryString.toLowerCase());
|
||||||
let results: TmdbSearchMultiResponse;
|
let results: TmdbSearchMultiResponse;
|
||||||
|
|
||||||
if (searchProvider) {
|
try {
|
||||||
const [id] = queryString
|
if (searchProvider) {
|
||||||
.toLowerCase()
|
const [id] = queryString
|
||||||
.match(searchProvider.pattern) as RegExpMatchArray;
|
.toLowerCase()
|
||||||
results = await searchProvider.search(
|
.match(searchProvider.pattern) as RegExpMatchArray;
|
||||||
id,
|
results = await searchProvider.search(
|
||||||
req.locale ?? (req.query.language as string)
|
id,
|
||||||
);
|
req.locale ?? (req.query.language as string)
|
||||||
} else {
|
);
|
||||||
const tmdb = new TheMovieDb();
|
} else {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
results = await tmdb.searchMulti({
|
results = await tmdb.searchMulti({
|
||||||
query: queryString,
|
query: queryString,
|
||||||
page: Number(req.query.page),
|
page: Number(req.query.page),
|
||||||
language: req.locale ?? (req.query.language as string),
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const media = await Media.getRelatedMedia(
|
||||||
|
results.results.map((result) => result.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
page: results.page,
|
||||||
|
totalPages: results.total_pages,
|
||||||
|
totalResults: results.total_results,
|
||||||
|
results: mapSearchResults(results.results, media),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving search results', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
query: req.query.query,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve search results.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
|
||||||
results.results.map((result) => result.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
page: results.page,
|
|
||||||
totalPages: results.total_pages,
|
|
||||||
totalResults: results.total_results,
|
|
||||||
results: mapSearchResults(results.results, media),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default searchRoutes;
|
export default searchRoutes;
|
||||||
|
@@ -243,52 +243,63 @@ settingsRoutes.post('/tautulli', async (req, res) => {
|
|||||||
settingsRoutes.get(
|
settingsRoutes.get(
|
||||||
'/plex/users',
|
'/plex/users',
|
||||||
isAuthenticated(Permission.MANAGE_USERS),
|
isAuthenticated(Permission.MANAGE_USERS),
|
||||||
async (req, res) => {
|
async (req, res, next) => {
|
||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const qb = userRepository.createQueryBuilder('user');
|
const qb = userRepository.createQueryBuilder('user');
|
||||||
|
|
||||||
const admin = await userRepository.findOneOrFail({
|
try {
|
||||||
select: ['id', 'plexToken'],
|
const admin = await userRepository.findOneOrFail({
|
||||||
order: { id: 'ASC' },
|
select: ['id', 'plexToken'],
|
||||||
});
|
order: { id: 'ASC' },
|
||||||
const plexApi = new PlexTvAPI(admin.plexToken ?? '');
|
});
|
||||||
const plexUsers = (await plexApi.getUsers()).MediaContainer.User.map(
|
const plexApi = new PlexTvAPI(admin.plexToken ?? '');
|
||||||
(user) => user.$
|
const plexUsers = (await plexApi.getUsers()).MediaContainer.User.map(
|
||||||
).filter((user) => user.email);
|
(user) => user.$
|
||||||
|
).filter((user) => user.email);
|
||||||
|
|
||||||
const unimportedPlexUsers: {
|
const unimportedPlexUsers: {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
thumb: string;
|
thumb: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
const existingUsers = await qb
|
const existingUsers = await qb
|
||||||
.where('user.plexId IN (:...plexIds)', {
|
.where('user.plexId IN (:...plexIds)', {
|
||||||
plexIds: plexUsers.map((plexUser) => plexUser.id),
|
plexIds: plexUsers.map((plexUser) => plexUser.id),
|
||||||
})
|
})
|
||||||
.orWhere('user.email IN (:...plexEmails)', {
|
.orWhere('user.email IN (:...plexEmails)', {
|
||||||
plexEmails: plexUsers.map((plexUser) => plexUser.email.toLowerCase()),
|
plexEmails: plexUsers.map((plexUser) => plexUser.email.toLowerCase()),
|
||||||
})
|
})
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
plexUsers.map(async (plexUser) => {
|
plexUsers.map(async (plexUser) => {
|
||||||
if (
|
if (
|
||||||
!existingUsers.find(
|
!existingUsers.find(
|
||||||
(user) =>
|
(user) =>
|
||||||
user.plexId === parseInt(plexUser.id) ||
|
user.plexId === parseInt(plexUser.id) ||
|
||||||
user.email === plexUser.email.toLowerCase()
|
user.email === plexUser.email.toLowerCase()
|
||||||
) &&
|
) &&
|
||||||
(await plexApi.checkUserAccess(parseInt(plexUser.id)))
|
(await plexApi.checkUserAccess(parseInt(plexUser.id)))
|
||||||
) {
|
) {
|
||||||
unimportedPlexUsers.push(plexUser);
|
unimportedPlexUsers.push(plexUser);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json(sortBy(unimportedPlexUsers, 'username'));
|
return res.status(200).json(sortBy(unimportedPlexUsers, 'username'));
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong getting unimported Plex users', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
});
|
||||||
|
next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve unimported Plex users.',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -21,104 +21,156 @@ tvRoutes.get('/:id', async (req, res, next) => {
|
|||||||
|
|
||||||
return res.status(200).json(mapTvDetails(tv, media));
|
return res.status(200).json(mapTvDetails(tv, media));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Failed to get tv show', {
|
logger.debug('Something went wrong retrieving series', {
|
||||||
label: 'API',
|
label: 'API',
|
||||||
errorMessage: e.message,
|
errorMessage: e.message,
|
||||||
|
tvId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series.',
|
||||||
});
|
});
|
||||||
return next({ status: 404, message: 'TV Show does not exist' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tvRoutes.get('/:id/season/:seasonNumber', async (req, res) => {
|
tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const season = await tmdb.getTvSeason({
|
try {
|
||||||
tvId: Number(req.params.id),
|
const season = await tmdb.getTvSeason({
|
||||||
seasonNumber: Number(req.params.seasonNumber),
|
tvId: Number(req.params.id),
|
||||||
language: req.locale ?? (req.query.language as string),
|
seasonNumber: Number(req.params.seasonNumber),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json(mapSeasonWithEpisodes(season));
|
return res.status(200).json(mapSeasonWithEpisodes(season));
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving season', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
tvId: req.params.id,
|
||||||
|
seasonNumber: req.params.seasonNumber,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve season.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tvRoutes.get('/:id/recommendations', async (req, res) => {
|
tvRoutes.get('/:id/recommendations', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const results = await tmdb.getTvRecommendations({
|
try {
|
||||||
tvId: Number(req.params.id),
|
const results = await tmdb.getTvRecommendations({
|
||||||
page: Number(req.query.page),
|
tvId: Number(req.params.id),
|
||||||
language: req.locale ?? (req.query.language as string),
|
page: Number(req.query.page),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
results.results.map((result) => result.id)
|
results.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: results.page,
|
page: results.page,
|
||||||
totalPages: results.total_pages,
|
totalPages: results.total_pages,
|
||||||
totalResults: results.total_results,
|
totalResults: results.total_results,
|
||||||
results: results.results.map((result) =>
|
results: results.results.map((result) =>
|
||||||
mapTvResult(
|
mapTvResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
|
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving series recommendations', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
tvId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series recommendations.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tvRoutes.get('/:id/similar', async (req, res) => {
|
tvRoutes.get('/:id/similar', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const results = await tmdb.getTvSimilar({
|
try {
|
||||||
tvId: Number(req.params.id),
|
const results = await tmdb.getTvSimilar({
|
||||||
page: Number(req.query.page),
|
tvId: Number(req.params.id),
|
||||||
language: req.locale ?? (req.query.language as string),
|
page: Number(req.query.page),
|
||||||
});
|
language: req.locale ?? (req.query.language as string),
|
||||||
|
});
|
||||||
|
|
||||||
const media = await Media.getRelatedMedia(
|
const media = await Media.getRelatedMedia(
|
||||||
results.results.map((result) => result.id)
|
results.results.map((result) => result.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
page: results.page,
|
page: results.page,
|
||||||
totalPages: results.total_pages,
|
totalPages: results.total_pages,
|
||||||
totalResults: results.total_results,
|
totalResults: results.total_results,
|
||||||
results: results.results.map((result) =>
|
results: results.results.map((result) =>
|
||||||
mapTvResult(
|
mapTvResult(
|
||||||
result,
|
result,
|
||||||
media.find(
|
media.find(
|
||||||
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
|
(req) => req.tmdbId === result.id && req.mediaType === MediaType.TV
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
),
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving similar series', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
tvId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve similar series.',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tvRoutes.get('/:id/ratings', async (req, res, next) => {
|
tvRoutes.get('/:id/ratings', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
const rtapi = new RottenTomatoes();
|
const rtapi = new RottenTomatoes();
|
||||||
|
|
||||||
const tv = await tmdb.getTvShow({
|
try {
|
||||||
tvId: Number(req.params.id),
|
const tv = await tmdb.getTvShow({
|
||||||
});
|
tvId: Number(req.params.id),
|
||||||
|
});
|
||||||
|
|
||||||
if (!tv) {
|
const rtratings = await rtapi.getTVRatings(
|
||||||
return next({ status: 404, message: 'TV Show does not exist' });
|
tv.name,
|
||||||
|
tv.first_air_date ? Number(tv.first_air_date.slice(0, 4)) : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rtratings) {
|
||||||
|
return next({
|
||||||
|
status: 404,
|
||||||
|
message: 'Rotten Tomatoes ratings not found.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json(rtratings);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving series ratings', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
tvId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve series ratings.',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const rtratings = await rtapi.getTVRatings(
|
|
||||||
tv.name,
|
|
||||||
tv.first_air_date ? Number(tv.first_air_date.slice(0, 4)) : undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!rtratings) {
|
|
||||||
return next({ status: 404, message: 'Unable to retrieve ratings' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).json(rtratings);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tvRoutes;
|
export default tvRoutes;
|
||||||
|
@@ -12,6 +12,7 @@ import IssueComment from '../entity/IssueComment';
|
|||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
import notificationManager, { Notification } from '../lib/notifications';
|
import notificationManager, { Notification } from '../lib/notifications';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class IssueCommentSubscriber
|
export class IssueCommentSubscriber
|
||||||
@@ -26,62 +27,67 @@ export class IssueCommentSubscriber
|
|||||||
let image: string;
|
let image: string;
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
const issue = (
|
try {
|
||||||
await getRepository(IssueComment).findOne({
|
const issue = (
|
||||||
where: { id: entity.id },
|
await getRepository(IssueComment).findOneOrFail({
|
||||||
relations: ['issue'],
|
where: { id: entity.id },
|
||||||
})
|
relations: ['issue'],
|
||||||
)?.issue;
|
})
|
||||||
if (!issue) {
|
).issue;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const media = await getRepository(Media).findOne({
|
const media = await getRepository(Media).findOneOrFail({
|
||||||
where: { id: issue.media.id },
|
where: { id: issue.media.id },
|
||||||
});
|
|
||||||
if (!media) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media.mediaType === MediaType.MOVIE) {
|
|
||||||
const movie = await tmdb.getMovie({ movieId: media.tmdbId });
|
|
||||||
|
|
||||||
title = `${movie.title}${
|
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
|
||||||
}`;
|
|
||||||
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`;
|
|
||||||
} else {
|
|
||||||
const tvshow = await tmdb.getTvShow({ tvId: media.tmdbId });
|
|
||||||
|
|
||||||
title = `${tvshow.name}${
|
|
||||||
tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : ''
|
|
||||||
}`;
|
|
||||||
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [firstComment] = sortBy(issue.comments, 'id');
|
|
||||||
|
|
||||||
if (entity.id !== firstComment.id) {
|
|
||||||
// Send notifications to all issue managers
|
|
||||||
notificationManager.sendNotification(Notification.ISSUE_COMMENT, {
|
|
||||||
event: `New Comment on ${
|
|
||||||
issue.issueType !== IssueType.OTHER
|
|
||||||
? `${IssueTypeName[issue.issueType]} `
|
|
||||||
: ''
|
|
||||||
}Issue`,
|
|
||||||
subject: title,
|
|
||||||
message: firstComment.message,
|
|
||||||
comment: entity,
|
|
||||||
issue,
|
|
||||||
media,
|
|
||||||
image,
|
|
||||||
notifyAdmin: true,
|
|
||||||
notifyUser:
|
|
||||||
!issue.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
|
|
||||||
issue.createdBy.id !== entity.user.id
|
|
||||||
? issue.createdBy
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (media.mediaType === MediaType.MOVIE) {
|
||||||
|
const movie = await tmdb.getMovie({ movieId: media.tmdbId });
|
||||||
|
|
||||||
|
title = `${movie.title}${
|
||||||
|
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
||||||
|
}`;
|
||||||
|
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`;
|
||||||
|
} else {
|
||||||
|
const tvshow = await tmdb.getTvShow({ tvId: media.tmdbId });
|
||||||
|
|
||||||
|
title = `${tvshow.name}${
|
||||||
|
tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : ''
|
||||||
|
}`;
|
||||||
|
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [firstComment] = sortBy(issue.comments, 'id');
|
||||||
|
|
||||||
|
if (entity.id !== firstComment.id) {
|
||||||
|
// Send notifications to all issue managers
|
||||||
|
notificationManager.sendNotification(Notification.ISSUE_COMMENT, {
|
||||||
|
event: `New Comment on ${
|
||||||
|
issue.issueType !== IssueType.OTHER
|
||||||
|
? `${IssueTypeName[issue.issueType]} `
|
||||||
|
: ''
|
||||||
|
}Issue`,
|
||||||
|
subject: title,
|
||||||
|
message: firstComment.message,
|
||||||
|
comment: entity,
|
||||||
|
issue,
|
||||||
|
media,
|
||||||
|
image,
|
||||||
|
notifyAdmin: true,
|
||||||
|
notifyUser:
|
||||||
|
!issue.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
|
||||||
|
issue.createdBy.id !== entity.user.id
|
||||||
|
? issue.createdBy
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
'Something went wrong sending issue comment notification(s)',
|
||||||
|
{
|
||||||
|
label: 'Notifications',
|
||||||
|
errorMessage: e.message,
|
||||||
|
commentId: entity.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ import { MediaType } from '../constants/media';
|
|||||||
import Issue from '../entity/Issue';
|
import Issue from '../entity/Issue';
|
||||||
import notificationManager, { Notification } from '../lib/notifications';
|
import notificationManager, { Notification } from '../lib/notifications';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class IssueSubscriber implements EntitySubscriberInterface<Issue> {
|
export class IssueSubscriber implements EntitySubscriberInterface<Issue> {
|
||||||
@@ -22,72 +23,81 @@ export class IssueSubscriber implements EntitySubscriberInterface<Issue> {
|
|||||||
let title: string;
|
let title: string;
|
||||||
let image: string;
|
let image: string;
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
if (entity.media.mediaType === MediaType.MOVIE) {
|
|
||||||
const movie = await tmdb.getMovie({ movieId: entity.media.tmdbId });
|
|
||||||
|
|
||||||
title = `${movie.title}${
|
try {
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
if (entity.media.mediaType === MediaType.MOVIE) {
|
||||||
}`;
|
const movie = await tmdb.getMovie({ movieId: entity.media.tmdbId });
|
||||||
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`;
|
|
||||||
} else {
|
|
||||||
const tvshow = await tmdb.getTvShow({ tvId: entity.media.tmdbId });
|
|
||||||
|
|
||||||
title = `${tvshow.name}${
|
title = `${movie.title}${
|
||||||
tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : ''
|
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
||||||
}`;
|
}`;
|
||||||
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`;
|
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`;
|
||||||
}
|
} else {
|
||||||
|
const tvshow = await tmdb.getTvShow({ tvId: entity.media.tmdbId });
|
||||||
|
|
||||||
const [firstComment] = sortBy(entity.comments, 'id');
|
title = `${tvshow.name}${
|
||||||
const extra: { name: string; value: string }[] = [];
|
tvshow.first_air_date ? ` (${tvshow.first_air_date.slice(0, 4)})` : ''
|
||||||
|
}`;
|
||||||
if (entity.media.mediaType === MediaType.TV && entity.problemSeason > 0) {
|
image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`;
|
||||||
extra.push({
|
|
||||||
name: 'Affected Season',
|
|
||||||
value: entity.problemSeason.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (entity.problemEpisode > 0) {
|
|
||||||
extra.push({
|
|
||||||
name: 'Affected Episode',
|
|
||||||
value: entity.problemEpisode.toString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.sendNotification(type, {
|
const [firstComment] = sortBy(entity.comments, 'id');
|
||||||
event:
|
const extra: { name: string; value: string }[] = [];
|
||||||
type === Notification.ISSUE_CREATED
|
|
||||||
? `New ${
|
if (entity.media.mediaType === MediaType.TV && entity.problemSeason > 0) {
|
||||||
entity.issueType !== IssueType.OTHER
|
extra.push({
|
||||||
? `${IssueTypeName[entity.issueType]} `
|
name: 'Affected Season',
|
||||||
: ''
|
value: entity.problemSeason.toString(),
|
||||||
}Issue Reported`
|
});
|
||||||
: type === Notification.ISSUE_RESOLVED
|
|
||||||
? `${
|
if (entity.problemEpisode > 0) {
|
||||||
entity.issueType !== IssueType.OTHER
|
extra.push({
|
||||||
? `${IssueTypeName[entity.issueType]} `
|
name: 'Affected Episode',
|
||||||
: ''
|
value: entity.problemEpisode.toString(),
|
||||||
}Issue Resolved`
|
});
|
||||||
: `${
|
}
|
||||||
entity.issueType !== IssueType.OTHER
|
}
|
||||||
? `${IssueTypeName[entity.issueType]} `
|
|
||||||
: ''
|
notificationManager.sendNotification(type, {
|
||||||
}Issue Reopened`,
|
event:
|
||||||
subject: title,
|
type === Notification.ISSUE_CREATED
|
||||||
message: firstComment.message,
|
? `New ${
|
||||||
issue: entity,
|
entity.issueType !== IssueType.OTHER
|
||||||
media: entity.media,
|
? `${IssueTypeName[entity.issueType]} `
|
||||||
image,
|
: ''
|
||||||
extra,
|
}Issue Reported`
|
||||||
notifyAdmin: true,
|
: type === Notification.ISSUE_RESOLVED
|
||||||
notifyUser:
|
? `${
|
||||||
!entity.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
|
entity.issueType !== IssueType.OTHER
|
||||||
(type === Notification.ISSUE_RESOLVED ||
|
? `${IssueTypeName[entity.issueType]} `
|
||||||
type === Notification.ISSUE_REOPENED)
|
: ''
|
||||||
? entity.createdBy
|
}Issue Resolved`
|
||||||
: undefined,
|
: `${
|
||||||
});
|
entity.issueType !== IssueType.OTHER
|
||||||
|
? `${IssueTypeName[entity.issueType]} `
|
||||||
|
: ''
|
||||||
|
}Issue Reopened`,
|
||||||
|
subject: title,
|
||||||
|
message: firstComment.message,
|
||||||
|
issue: entity,
|
||||||
|
media: entity.media,
|
||||||
|
image,
|
||||||
|
extra,
|
||||||
|
notifyAdmin: true,
|
||||||
|
notifyUser:
|
||||||
|
!entity.createdBy.hasPermission(Permission.MANAGE_ISSUES) &&
|
||||||
|
(type === Notification.ISSUE_RESOLVED ||
|
||||||
|
type === Notification.ISSUE_REOPENED)
|
||||||
|
? entity.createdBy
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong sending issue notification(s)', {
|
||||||
|
label: 'Notifications',
|
||||||
|
errorMessage: e.message,
|
||||||
|
issueId: entity.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public afterInsert(event: InsertEvent<Issue>): void {
|
public afterInsert(event: InsertEvent<Issue>): void {
|
||||||
|
@@ -12,6 +12,7 @@ import Media from '../entity/Media';
|
|||||||
import { MediaRequest } from '../entity/MediaRequest';
|
import { MediaRequest } from '../entity/MediaRequest';
|
||||||
import Season from '../entity/Season';
|
import Season from '../entity/Season';
|
||||||
import notificationManager, { Notification } from '../lib/notifications';
|
import notificationManager, { Notification } from '../lib/notifications';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
@EventSubscriber()
|
@EventSubscriber()
|
||||||
export class MediaSubscriber implements EntitySubscriberInterface<Media> {
|
export class MediaSubscriber implements EntitySubscriberInterface<Media> {
|
||||||
@@ -36,26 +37,40 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
|
|||||||
|
|
||||||
if (relatedRequests.length > 0) {
|
if (relatedRequests.length > 0) {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
const movie = await tmdb.getMovie({ movieId: entity.tmdbId });
|
|
||||||
|
|
||||||
relatedRequests.forEach((request) => {
|
try {
|
||||||
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
|
const movie = await tmdb.getMovie({ movieId: entity.tmdbId });
|
||||||
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
|
|
||||||
notifyAdmin: false,
|
relatedRequests.forEach((request) => {
|
||||||
notifyUser: request.requestedBy,
|
notificationManager.sendNotification(
|
||||||
subject: `${movie.title}${
|
Notification.MEDIA_AVAILABLE,
|
||||||
movie.release_date ? ` (${movie.release_date.slice(0, 4)})` : ''
|
{
|
||||||
}`,
|
event: `${is4k ? '4K ' : ''}Movie Request Now Available`,
|
||||||
message: truncate(movie.overview, {
|
notifyAdmin: false,
|
||||||
length: 500,
|
notifyUser: request.requestedBy,
|
||||||
separator: /\s/,
|
subject: `${movie.title}${
|
||||||
omission: '…',
|
movie.release_date
|
||||||
}),
|
? ` (${movie.release_date.slice(0, 4)})`
|
||||||
media: entity,
|
: ''
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`,
|
}`,
|
||||||
request,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,31 +129,40 @@ export class MediaSubscriber implements EntitySubscriberInterface<Media> {
|
|||||||
processedSeasons.push(
|
processedSeasons.push(
|
||||||
...request.seasons.map((season) => season.seasonNumber)
|
...request.seasons.map((season) => season.seasonNumber)
|
||||||
);
|
);
|
||||||
const tv = await tmdb.getTvShow({ tvId: entity.tmdbId });
|
|
||||||
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
|
try {
|
||||||
event: `${is4k ? '4K ' : ''}Series Request Now Available`,
|
const tv = await tmdb.getTvShow({ tvId: entity.tmdbId });
|
||||||
subject: `${tv.name}${
|
notificationManager.sendNotification(Notification.MEDIA_AVAILABLE, {
|
||||||
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
event: `${is4k ? '4K ' : ''}Series Request Now Available`,
|
||||||
}`,
|
subject: `${tv.name}${
|
||||||
message: truncate(tv.overview, {
|
tv.first_air_date ? ` (${tv.first_air_date.slice(0, 4)})` : ''
|
||||||
length: 500,
|
}`,
|
||||||
separator: /\s/,
|
message: truncate(tv.overview, {
|
||||||
omission: '…',
|
length: 500,
|
||||||
}),
|
separator: /\s/,
|
||||||
notifyAdmin: false,
|
omission: '…',
|
||||||
notifyUser: request.requestedBy,
|
}),
|
||||||
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
notifyAdmin: false,
|
||||||
media: entity,
|
notifyUser: request.requestedBy,
|
||||||
extra: [
|
image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`,
|
||||||
{
|
media: entity,
|
||||||
name: 'Requested Seasons',
|
extra: [
|
||||||
value: request.seasons
|
{
|
||||||
.map((season) => season.seasonNumber)
|
name: 'Requested Seasons',
|
||||||
.join(', '),
|
value: request.seasons
|
||||||
},
|
.map((season) => season.seasonNumber)
|
||||||
],
|
.join(', '),
|
||||||
request,
|
},
|
||||||
});
|
],
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Something went wrong sending media notification(s)', {
|
||||||
|
label: 'Notifications',
|
||||||
|
errorMessage: e.message,
|
||||||
|
mediaId: entity.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ confinement: strict
|
|||||||
parts:
|
parts:
|
||||||
overseerr:
|
overseerr:
|
||||||
plugin: nodejs
|
plugin: nodejs
|
||||||
nodejs-version: '14.18.1'
|
nodejs-version: '16.13.1'
|
||||||
nodejs-package-manager: 'yarn'
|
nodejs-package-manager: 'yarn'
|
||||||
nodejs-yarn-version: v1.22.10
|
nodejs-yarn-version: v1.22.10
|
||||||
build-packages:
|
build-packages:
|
||||||
|
Reference in New Issue
Block a user