From 4ee78ab2fe0359df6baa58f0986687f05a8392a2 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Thu, 18 Mar 2021 22:07:22 -0400 Subject: [PATCH] feat(notif): include requested season numbers in notifications (#1211) --- server/entity/MediaRequest.ts | 19 +- server/lib/notifications/agents/agent.ts | 10 +- server/lib/notifications/agents/discord.ts | 4 +- server/lib/notifications/agents/email.ts | 249 ++++++++++-------- server/lib/notifications/agents/pushbullet.ts | 6 +- server/lib/notifications/agents/pushover.ts | 44 ++-- server/lib/notifications/agents/slack.ts | 11 +- server/lib/notifications/agents/telegram.ts | 8 +- server/lib/notifications/index.ts | 2 +- server/templates/email/media-request/html.pug | 17 +- 10 files changed, 209 insertions(+), 161 deletions(-) diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 78d8ee963..606ccb4c2 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -179,7 +179,7 @@ export class MediaRequest { subject: movie.title, message: movie.overview, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, - notifyUser: this.requestedBy, + notifyUser: autoApproved ? undefined : this.requestedBy, media, request: this, } @@ -444,15 +444,10 @@ export class MediaRequest { label: 'Media Request', } ); - const userRepository = getRepository(User); - const admin = await userRepository.findOneOrFail({ - select: ['id', 'plexToken'], - order: { id: 'ASC' }, - }); + notificationManager.sendNotification(Notification.MEDIA_FAILED, { subject: movie.title, - message: 'Movie failed to add to Radarr', - notifyUser: admin, + message: movie.overview, media, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`, request: this, @@ -641,14 +636,10 @@ export class MediaRequest { label: 'Media Request', } ); - const userRepository = getRepository(User); - const admin = await userRepository.findOneOrFail({ - order: { id: 'ASC' }, - }); + notificationManager.sendNotification(Notification.MEDIA_FAILED, { subject: series.name, - message: 'Series failed to add to Sonarr', - notifyUser: admin, + message: series.overview, image: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${series.poster_path}`, media, extra: [ diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts index 517913222..132683e5f 100644 --- a/server/lib/notifications/agents/agent.ts +++ b/server/lib/notifications/agents/agent.ts @@ -6,7 +6,7 @@ import { NotificationAgentConfig } from '../../settings'; export interface NotificationPayload { subject: string; - notifyUser: User; + notifyUser?: User; media?: Media; image?: string; message?: string; @@ -21,15 +21,9 @@ export abstract class BaseAgent { } protected abstract getSettings(): T; - - protected userNotificationTypes: Notification[] = [ - Notification.MEDIA_APPROVED, - Notification.MEDIA_DECLINED, - Notification.MEDIA_AVAILABLE, - ]; } export interface NotificationAgent { - shouldSend(type: Notification, payload: NotificationPayload): boolean; + shouldSend(type: Notification): boolean; send(type: Notification, payload: NotificationPayload): Promise; } diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index 5c02240e9..fa4d7d91d 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -107,7 +107,7 @@ class DiscordAgent if (payload.request) { fields.push({ name: 'Requested By', - value: payload.notifyUser.displayName ?? '', + value: payload.request?.requestedBy.displayName ?? '', inline: true, }); } @@ -217,7 +217,7 @@ class DiscordAgent let content = undefined; if ( - this.userNotificationTypes.includes(type) && + payload.notifyUser && payload.notifyUser.settings?.enableNotifications && payload.notifyUser.settings?.discordId ) { diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index 64483c13c..8fbb803af 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -22,13 +22,12 @@ class EmailAgent return settings.notifications.agents.email; } - public shouldSend(type: Notification, payload: NotificationPayload): boolean { + public shouldSend(type: Notification): boolean { const settings = this.getSettings(); if ( settings.enabled && - hasNotificationType(type, this.getSettings().types) && - (payload.notifyUser.settings?.enableNotifications ?? true) + hasNotificationType(type, this.getSettings().types) ) { return true; } @@ -45,9 +44,13 @@ class EmailAgent // Send to all users with the manage requests permission (or admins) users - .filter((user) => user.hasPermission(Permission.MANAGE_REQUESTS)) + .filter( + (user) => + user.hasPermission(Permission.MANAGE_REQUESTS) && + (user.settings?.enableNotifications ?? true) + ) .forEach((user) => { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + const email = new PreparedEmail(user.settings?.pgpKey); email.send({ template: path.join( @@ -62,9 +65,11 @@ class EmailAgent payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' }!`, mediaName: payload.subject, + mediaPlot: payload.message, + mediaExtra: payload.extra ?? [], imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, + requestedBy: payload.request?.requestedBy.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -95,9 +100,13 @@ class EmailAgent // Send to all users with the manage requests permission (or admins) users - .filter((user) => user.hasPermission(Permission.MANAGE_REQUESTS)) + .filter( + (user) => + user.hasPermission(Permission.MANAGE_REQUESTS) && + (user.settings?.enableNotifications ?? true) + ) .forEach((user) => { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + const email = new PreparedEmail(user.settings?.pgpKey); email.send({ template: path.join( @@ -112,11 +121,12 @@ class EmailAgent payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' } could not be added to ${ payload.media?.mediaType === MediaType.TV ? 'Sonarr' : 'Radarr' - }`, + }:`, mediaName: payload.subject, + mediaPlot: payload.message, imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, + requestedBy: payload.request?.requestedBy.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -142,34 +152,41 @@ class EmailAgent // This is getting main settings for the whole app const { applicationUrl, applicationTitle } = getSettings().main; try { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + if ( + payload.notifyUser && + payload.notifyUser.settings?.enableNotifications + ) { + const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + + await email.send({ + template: path.join( + __dirname, + '../../../templates/email/media-request' + ), + message: { + to: payload.notifyUser.email, + }, + locals: { + body: `Your request for the following ${ + payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' + } has been approved:`, + mediaName: payload.subject, + mediaExtra: payload.extra ?? [], + imageUrl: payload.image, + timestamp: new Date().toTimeString(), + requestedBy: payload.request?.requestedBy.displayName, + actionUrl: applicationUrl + ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` + : undefined, + applicationUrl, + applicationTitle, + requestType: `${ + payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' + } Request Approved`, + }, + }); + } - await email.send({ - template: path.join( - __dirname, - '../../../templates/email/media-request' - ), - message: { - to: payload.notifyUser.email, - }, - locals: { - body: `Your request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } has been approved:`, - mediaName: payload.subject, - imageUrl: payload.image, - timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, - actionUrl: applicationUrl - ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` - : undefined, - applicationUrl, - applicationTitle, - requestType: `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Approved`, - }, - }); return true; } catch (e) { logger.error('Email notification failed to send', { @@ -189,7 +206,11 @@ class EmailAgent // Send to all users with the manage requests permission (or admins) users - .filter((user) => user.hasPermission(Permission.MANAGE_REQUESTS)) + .filter( + (user) => + user.hasPermission(Permission.MANAGE_REQUESTS) && + (user.settings?.enableNotifications ?? true) + ) .forEach((user) => { const email = new PreparedEmail(); @@ -206,9 +227,10 @@ class EmailAgent payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' } has been automatically approved:`, mediaName: payload.subject, + mediaExtra: payload.extra ?? [], imageUrl: payload.image, timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, + requestedBy: payload.request?.requestedBy.displayName, actionUrl: applicationUrl ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` : undefined, @@ -234,34 +256,41 @@ class EmailAgent // This is getting main settings for the whole app const { applicationUrl, applicationTitle } = getSettings().main; try { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + if ( + payload.notifyUser && + payload.notifyUser.settings?.enableNotifications + ) { + const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + + await email.send({ + template: path.join( + __dirname, + '../../../templates/email/media-request' + ), + message: { + to: payload.notifyUser.email, + }, + locals: { + body: `Your request for the following ${ + payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' + } was declined:`, + mediaName: payload.subject, + mediaExtra: payload.extra ?? [], + imageUrl: payload.image, + timestamp: new Date().toTimeString(), + requestedBy: payload.request?.requestedBy.displayName, + actionUrl: applicationUrl + ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` + : undefined, + applicationUrl, + applicationTitle, + requestType: `${ + payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' + } Request Declined`, + }, + }); + } - await email.send({ - template: path.join( - __dirname, - '../../../templates/email/media-request' - ), - message: { - to: payload.notifyUser.email, - }, - locals: { - body: `Your request for the following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } was declined:`, - mediaName: payload.subject, - imageUrl: payload.image, - timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, - actionUrl: applicationUrl - ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` - : undefined, - applicationUrl, - applicationTitle, - requestType: `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Request Declined`, - }, - }); return true; } catch (e) { logger.error('Email notification failed to send', { @@ -276,34 +305,41 @@ class EmailAgent // This is getting main settings for the whole app const { applicationUrl, applicationTitle } = getSettings().main; try { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + if ( + payload.notifyUser && + payload.notifyUser.settings?.enableNotifications + ) { + const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + + await email.send({ + template: path.join( + __dirname, + '../../../templates/email/media-request' + ), + message: { + to: payload.notifyUser.email, + }, + locals: { + body: `The following ${ + payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' + } you requested is now available!`, + mediaName: payload.subject, + mediaExtra: payload.extra ?? [], + imageUrl: payload.image, + timestamp: new Date().toTimeString(), + requestedBy: payload.request?.requestedBy.displayName, + actionUrl: applicationUrl + ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` + : undefined, + applicationUrl, + applicationTitle, + requestType: `${ + payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' + } Now Available`, + }, + }); + } - await email.send({ - template: path.join( - __dirname, - '../../../templates/email/media-request' - ), - message: { - to: payload.notifyUser.email, - }, - locals: { - body: `The following ${ - payload.media?.mediaType === MediaType.TV ? 'series' : 'movie' - } you requested is now available!`, - mediaName: payload.subject, - imageUrl: payload.image, - timestamp: new Date().toTimeString(), - requestedBy: payload.notifyUser.displayName, - actionUrl: applicationUrl - ? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}` - : undefined, - applicationUrl, - applicationTitle, - requestType: `${ - payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie' - } Now Available`, - }, - }); return true; } catch (e) { logger.error('Email notification failed to send', { @@ -318,19 +354,22 @@ class EmailAgent // This is getting main settings for the whole app const { applicationUrl, applicationTitle } = getSettings().main; try { - const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + if (payload.notifyUser) { + const email = new PreparedEmail(payload.notifyUser.settings?.pgpKey); + + await email.send({ + template: path.join(__dirname, '../../../templates/email/test-email'), + message: { + to: payload.notifyUser.email, + }, + locals: { + body: payload.message, + applicationUrl, + applicationTitle, + }, + }); + } - await email.send({ - template: path.join(__dirname, '../../../templates/email/test-email'), - message: { - to: payload.notifyUser.email, - }, - locals: { - body: payload.message, - applicationUrl, - applicationTitle, - }, - }); return true; } catch (e) { logger.error('Email notification failed to send', { diff --git a/server/lib/notifications/agents/pushbullet.ts b/server/lib/notifications/agents/pushbullet.ts index ef40bff31..f0c0f757e 100644 --- a/server/lib/notifications/agents/pushbullet.ts +++ b/server/lib/notifications/agents/pushbullet.ts @@ -47,7 +47,7 @@ class PushbulletAgent const title = payload.subject; const plot = payload.message; - const username = payload.notifyUser.displayName; + const username = payload.request?.requestedBy.displayName; switch (type) { case Notification.MEDIA_PENDING: @@ -122,6 +122,10 @@ class PushbulletAgent break; } + for (const extra of payload.extra ?? []) { + message += `\n${extra.name}: ${extra.value}`; + } + return { title: messageTitle, body: message, diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index 588b46c7b..3b5d3f873 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -61,7 +61,7 @@ class PushoverAgent const title = payload.subject; const plot = payload.message; - const username = payload.notifyUser.displayName; + const username = payload.request?.requestedBy.displayName; switch (type) { case Notification.MEDIA_PENDING: @@ -70,10 +70,10 @@ class PushoverAgent } Request`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nPending Approval`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nPending Approval`; break; case Notification.MEDIA_APPROVED: messageTitle = `${ @@ -81,10 +81,10 @@ class PushoverAgent } Request Approved`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nProcessing`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nProcessing`; break; case Notification.MEDIA_AUTO_APPROVED: messageTitle = `${ @@ -92,10 +92,10 @@ class PushoverAgent } Request Automatically Approved`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nProcessing`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nProcessing`; break; case Notification.MEDIA_AVAILABLE: messageTitle = `${ @@ -103,10 +103,10 @@ class PushoverAgent } Now Available`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nAvailable`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nAvailable`; break; case Notification.MEDIA_DECLINED: messageTitle = `${ @@ -114,10 +114,10 @@ class PushoverAgent } Request Declined`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nDeclined`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nDeclined`; priority = 1; break; case Notification.MEDIA_FAILED: @@ -126,18 +126,22 @@ class PushoverAgent } Request`; message += `${title}`; if (plot) { - message += `\n${plot}`; + message += `\n${plot}`; } - message += `\n\nRequested By\n${username}`; - message += `\n\nStatus\nFailed`; + message += `\n\nRequested By\n${username}`; + message += `\n\nStatus\nFailed`; priority = 1; break; case Notification.TEST_NOTIFICATION: messageTitle = 'Test Notification'; - message += `${plot}`; + message += `${plot}`; break; } + for (const extra of payload.extra ?? []) { + message += `\n\n${extra.name}\n${extra.value}`; + } + if (settings.main.applicationUrl && payload.media) { url = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`; url_title = `Open in ${settings.main.applicationTitle}`; diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index fc6643d6a..b52347854 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -67,7 +67,9 @@ class SlackAgent if (payload.request) { fields.push({ type: 'mrkdwn', - text: `*Requested By*\n${payload.notifyUser.displayName ?? ''}`, + text: `*Requested By*\n${ + payload.request?.requestedBy.displayName ?? '' + }`, }); } @@ -131,6 +133,13 @@ class SlackAgent break; } + for (const extra of payload.extra ?? []) { + fields.push({ + type: 'mrkdwn', + text: `*${extra.name}*\n${extra.value}`, + }); + } + if (settings.main.applicationUrl && payload.media) { actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`; } diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index f26c5cbb8..2445c7fdb 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -61,7 +61,7 @@ class TelegramAgent const title = this.escapeText(payload.subject); const plot = this.escapeText(payload.message); - const user = this.escapeText(payload.notifyUser.displayName); + const user = this.escapeText(payload.request?.requestedBy.displayName); const applicationTitle = this.escapeText(settings.main.applicationTitle); /* eslint-disable no-useless-escape */ @@ -138,6 +138,10 @@ class TelegramAgent break; } + for (const extra of payload.extra ?? []) { + message += `\n\n\*${extra.name}\*\n${extra.value}`; + } + if (settings.main.applicationUrl && payload.media) { const actionUrl = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`; message += `\n\n\[Open in ${applicationTitle}\]\(${actionUrl}\)`; @@ -175,7 +179,7 @@ class TelegramAgent // Send user notification if ( - this.userNotificationTypes.includes(type) && + payload.notifyUser && payload.notifyUser.settings?.enableNotifications && payload.notifyUser.settings?.telegramChatId && payload.notifyUser.settings?.telegramChatId !== diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts index b9cc84a9b..7d5b68003 100644 --- a/server/lib/notifications/index.ts +++ b/server/lib/notifications/index.ts @@ -50,7 +50,7 @@ class NotificationManager { label: 'Notifications', }); this.activeAgents.forEach((agent) => { - if (settings.enabled && agent.shouldSend(type, payload)) { + if (settings.enabled && agent.shouldSend(type)) { agent.send(type, payload); } }); diff --git a/server/templates/email/media-request/html.pug b/server/templates/email/media-request/html.pug index 814fcab06..73985a3e7 100644 --- a/server/templates/email/media-request/html.pug +++ b/server/templates/email/media-request/html.pug @@ -42,7 +42,6 @@ div(role='article' aria-roledescription='email' aria-label='' lang='en') table(style='width: 100%' width='100%' cellpadding='0' cellspacing='0' role='presentation') tr td(align='center' style='\ - font-size: 16px;\ padding-top: 25px;\ padding-bottom: 25px;\ text-align: center;\ @@ -50,7 +49,7 @@ div(role='article' aria-roledescription='email' aria-label='' lang='en') a(href=applicationUrl style='\ text-shadow: 0 1px 0 #ffffff;\ font-weight: 700;\ - font-size: 16px;\ + font-size: 24px;\ color: #a8aaaf;\ text-decoration: none;\ ') @@ -70,13 +69,17 @@ div(role='article' aria-roledescription='email' aria-label='' lang='en') br br p(style='margin-top: 4px; text-align: center') - | #{mediaName} - table(cellpadding='0' cellspacing='0' role='presentation') + b + | #{mediaName} + each extra in mediaExtra + br + | #{extra.name}:  + | #{extra.value} + table(align='center' cellpadding='0' cellspacing='0' role='presentation') tr td - table(cellpadding='0' cellspacing='0' role='presentation') - a(href=actionUrl style='color: #3869d4') - img(src=imageUrl alt='') + a(href=actionUrl style='color: #3869d4') + img(src=imageUrl alt='') p(style='\ font-size: 16px;\ line-height: 24px;\