diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 5553b2f4a..cf0903e8f 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -138,8 +138,11 @@ export class MediaRequest { * auto approved content */ @AfterUpdate() - private async _notifyApproved() { - if (this.status === MediaRequestStatus.APPROVED) { + public async notifyApprovedOrDeclined(): Promise { + if ( + this.status === MediaRequestStatus.APPROVED || + this.status === MediaRequestStatus.DECLINED + ) { const mediaRepository = getRepository(Media); const media = await mediaRepository.findOne({ where: { id: this.media.id }, @@ -151,30 +154,40 @@ export class MediaRequest { const tmdb = new TheMovieDb(); if (this.media.mediaType === MediaType.MOVIE) { const movie = await tmdb.getMovie({ movieId: this.media.tmdbId }); - notificationManager.sendNotification(Notification.MEDIA_APPROVED, { - subject: movie.title, - message: movie.overview, - image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, - notifyUser: this.requestedBy, - media, - }); + notificationManager.sendNotification( + this.status === MediaRequestStatus.APPROVED + ? Notification.MEDIA_APPROVED + : Notification.MEDIA_DECLINED, + { + subject: movie.title, + message: movie.overview, + image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, + notifyUser: this.requestedBy, + media, + } + ); } else if (this.media.mediaType === MediaType.TV) { const tv = await tmdb.getTvShow({ tvId: this.media.tmdbId }); - notificationManager.sendNotification(Notification.MEDIA_APPROVED, { - subject: tv.name, - message: tv.overview, - image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`, - notifyUser: this.requestedBy, - media, - extra: [ - { - name: 'Seasons', - value: this.seasons - .map((season) => season.seasonNumber) - .join(', '), - }, - ], - }); + notificationManager.sendNotification( + this.status === MediaRequestStatus.APPROVED + ? Notification.MEDIA_APPROVED + : Notification.MEDIA_DECLINED, + { + subject: tv.name, + message: tv.overview, + image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tv.poster_path}`, + notifyUser: this.requestedBy, + media, + extra: [ + { + name: 'Seasons', + value: this.seasons + .map((season) => season.seasonNumber) + .join(', '), + }, + ], + } + ); } } } diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index 9c1897d16..de6ed7ba4 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -158,6 +158,28 @@ class DiscordAgent } ); + if (settings.main.applicationUrl) { + fields.push({ + name: 'View Media', + value: `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`, + }); + } + break; + case Notification.MEDIA_DECLINED: + color = EmbedColors.RED; + fields.push( + { + name: 'Requested By', + value: payload.notifyUser.username ?? '', + inline: true, + }, + { + name: 'Status', + value: 'Declined', + inline: true, + } + ); + if (settings.main.applicationUrl) { fields.push({ name: 'View Media', diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index 9b60f4589..6bb2a2aa0 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -162,6 +162,43 @@ class EmailAgent } } + private async sendMediaDeclinedEmail(payload: NotificationPayload) { + // This is getting main settings for the whole app + const applicationUrl = getSettings().main.applicationUrl; + try { + const email = new PreparedEmail(); + + await email.send({ + template: path.join( + __dirname, + '../../../templates/email/media-request' + ), + message: { + to: payload.notifyUser.email, + }, + locals: { + body: 'Your request for the following media was declined:', + mediaName: payload.subject, + imageUrl: payload.image, + timestamp: new Date().toTimeString(), + requestedBy: payload.notifyUser.username, + actionUrl: applicationUrl + ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` + : undefined, + applicationUrl, + requestType: 'Request Declined', + }, + }); + return true; + } catch (e) { + logger.error('Mail notification failed to send', { + label: 'Notifications', + message: e.message, + }); + return false; + } + } + private async sendMediaAvailableEmail(payload: NotificationPayload) { // This is getting main settings for the whole app const applicationUrl = getSettings().main.applicationUrl; diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index 072352ab7..158d01c71 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -72,6 +72,13 @@ class PushoverAgent message += `Requested By\n${user}\n\n`; message += `Status\nAvailable\n`; break; + case Notification.MEDIA_DECLINED: + messageTitle = 'Request Declined'; + message += `${title}\n\n`; + message += `${plot}\n\n`; + message += `Requested By\n${user}\n\n`; + message += `Status\nDeclined\n`; + break; case Notification.TEST_NOTIFICATION: messageTitle = 'Test Notification'; message += `${title}\n\n`; diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index 03f901b2b..df338884f 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -96,6 +96,22 @@ class SlackAgent actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`; } break; + case Notification.MEDIA_DECLINED: + header = 'Request Declined'; + fields.push( + { + type: 'mrkdwn', + text: `*Requested By*\n${payload.notifyUser.username ?? ''}`, + }, + { + type: 'mrkdwn', + text: '*Status*\nDeclined', + } + ); + if (settings.main.applicationUrl) { + actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`; + } + break; case Notification.MEDIA_AVAILABLE: header = 'Now available!'; fields.push( diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index 2b5a9fdfc..62de93ec0 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -70,6 +70,14 @@ class TelegramAgent message += `\*Requested By\*\n${user}\n\n`; message += `\*Status\*\nProcessing Request\n`; + break; + case Notification.MEDIA_DECLINED: + message += `\*Request Declined\*\n`; + message += `${title}\n\n`; + message += `${plot}\n\n`; + message += `\*Requested By\*\n${user}\n\n`; + message += `\*Status\*\nDeclined\n`; + break; case Notification.MEDIA_AVAILABLE: message += `\*Now available\\!\*\n`; diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts index 07127cf2f..23fba1161 100644 --- a/server/lib/notifications/index.ts +++ b/server/lib/notifications/index.ts @@ -7,6 +7,7 @@ export enum Notification { MEDIA_AVAILABLE = 8, MEDIA_FAILED = 16, TEST_NOTIFICATION = 32, + MEDIA_DECLINED = 64, } export const hasNotificationType = ( diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 7b8b7f3fa..b6591edcd 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -14,6 +14,8 @@ const messages = defineMessages({ mediafailed: 'Media Failed', mediafailedDescription: 'Sends a notification when media fails to be added to services (Radarr/Sonarr). For certain agents, this will only send the notification to admins or users with the "Manage Requests" permission.', + mediadeclined: 'Media Declined', + mediadeclinedDescription: 'Sends a notification when a request is declined.', }); export const hasNotificationType = ( @@ -41,6 +43,7 @@ export enum Notification { MEDIA_AVAILABLE = 8, MEDIA_FAILED = 16, TEST_NOTIFICATION = 32, + MEDIA_DECLINED = 64, } export interface NotificationItem { @@ -75,6 +78,12 @@ const NotificationTypeSelector: React.FC = ({ description: intl.formatMessage(messages.mediaapprovedDescription), value: Notification.MEDIA_APPROVED, }, + { + id: 'media-declined', + name: intl.formatMessage(messages.mediadeclined), + description: intl.formatMessage(messages.mediadeclinedDescription), + value: Notification.MEDIA_DECLINED, + }, { id: 'media-available', name: intl.formatMessage(messages.mediaavailable), diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 3e51a16d8..2e7c2e645 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -72,6 +72,8 @@ "components.NotificationTypeSelector.mediaapprovedDescription": "Sends a notification when media is approved.", "components.NotificationTypeSelector.mediaavailable": "Media Available", "components.NotificationTypeSelector.mediaavailableDescription": "Sends a notification when media becomes available.", + "components.NotificationTypeSelector.mediadeclined": "Media Declined", + "components.NotificationTypeSelector.mediadeclinedDescription": "Sends a notification when a request is declined.", "components.NotificationTypeSelector.mediafailed": "Media Failed", "components.NotificationTypeSelector.mediafailedDescription": "Sends a notification when media fails to be added to services (Radarr/Sonarr). For certain agents, this will only send the notification to admins or users with the \"Manage Requests\" permission.", "components.NotificationTypeSelector.mediarequested": "Media Requested",