feat(notif): add "Media Automatically Approved" notification type (#1137)

pull/1154/head
TheCatLady 4 years ago committed by GitHub
parent 1be97fe7fb
commit f7d2723fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1196,9 +1196,6 @@ components:
enabled: enabled:
type: boolean type: boolean
example: true example: true
autoapprovalEnabled:
type: boolean
example: false
NotificationEmailSettings: NotificationEmailSettings:
type: object type: object
properties: properties:

@ -144,7 +144,7 @@ export class MediaRequest {
* auto approved content * auto approved content
*/ */
@AfterUpdate() @AfterUpdate()
public async notifyApprovedOrDeclined(): Promise<void> { public async notifyApprovedOrDeclined(autoApproved = false): Promise<void> {
if ( if (
this.status === MediaRequestStatus.APPROVED || this.status === MediaRequestStatus.APPROVED ||
this.status === MediaRequestStatus.DECLINED this.status === MediaRequestStatus.DECLINED
@ -171,7 +171,9 @@ export class MediaRequest {
const movie = await tmdb.getMovie({ movieId: this.media.tmdbId }); const movie = await tmdb.getMovie({ movieId: this.media.tmdbId });
notificationManager.sendNotification( notificationManager.sendNotification(
this.status === MediaRequestStatus.APPROVED this.status === MediaRequestStatus.APPROVED
? Notification.MEDIA_APPROVED ? autoApproved
? Notification.MEDIA_AUTO_APPROVED
: Notification.MEDIA_APPROVED
: Notification.MEDIA_DECLINED, : Notification.MEDIA_DECLINED,
{ {
subject: movie.title, subject: movie.title,
@ -211,13 +213,8 @@ export class MediaRequest {
@AfterInsert() @AfterInsert()
public async autoapprovalNotification(): Promise<void> { public async autoapprovalNotification(): Promise<void> {
const settings = getSettings().notifications; if (this.status === MediaRequestStatus.APPROVED) {
this.notifyApprovedOrDeclined(true);
if (
settings.autoapprovalEnabled &&
this.status === MediaRequestStatus.APPROVED
) {
this.notifyApprovedOrDeclined();
} }
} }

@ -21,6 +21,12 @@ export abstract class BaseAgent<T extends NotificationAgentConfig> {
} }
protected abstract getSettings(): T; protected abstract getSettings(): T;
protected userNotificationTypes: Notification[] = [
Notification.MEDIA_APPROVED,
Notification.MEDIA_DECLINED,
Notification.MEDIA_AVAILABLE,
];
} }
export interface NotificationAgent { export interface NotificationAgent {

@ -122,6 +122,7 @@ class DiscordAgent
}); });
break; break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
case Notification.MEDIA_AUTO_APPROVED:
color = EmbedColors.PURPLE; color = EmbedColors.PURPLE;
fields.push({ fields.push({
name: 'Status', name: 'Status',
@ -201,7 +202,7 @@ class DiscordAgent
type: Notification, type: Notification,
payload: NotificationPayload payload: NotificationPayload
): Promise<boolean> { ): Promise<boolean> {
logger.debug('Sending discord notification', { label: 'Notifications' }); logger.debug('Sending Discord notification', { label: 'Notifications' });
try { try {
const { const {
botUsername, botUsername,
@ -217,6 +218,7 @@ class DiscordAgent
let content = undefined; let content = undefined;
if ( if (
this.userNotificationTypes.includes(type) &&
payload.notifyUser.settings?.enableNotifications && payload.notifyUser.settings?.enableNotifications &&
payload.notifyUser.settings?.discordId payload.notifyUser.settings?.discordId
) { ) {

@ -7,6 +7,7 @@ import { getRepository } from 'typeorm';
import { User } from '../../../entity/User'; import { User } from '../../../entity/User';
import { Permission } from '../../permissions'; import { Permission } from '../../permissions';
import PreparedEmail from '../../email'; import PreparedEmail from '../../email';
import { MediaType } from '../../../constants/media';
class EmailAgent class EmailAgent
extends BaseAgent<NotificationAgentEmail> extends BaseAgent<NotificationAgentEmail>
@ -57,7 +58,9 @@ class EmailAgent
to: user.email, to: user.email,
}, },
locals: { locals: {
body: 'A user has requested new media!', body: `A user has requested a new ${
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
}!`,
mediaName: payload.subject, mediaName: payload.subject,
imageUrl: payload.image, imageUrl: payload.image,
timestamp: new Date().toTimeString(), timestamp: new Date().toTimeString(),
@ -67,13 +70,15 @@ class EmailAgent
: undefined, : undefined,
applicationUrl, applicationUrl,
applicationTitle, applicationTitle,
requestType: 'New Request', requestType: `New ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`,
}, },
}); });
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -103,8 +108,11 @@ class EmailAgent
to: user.email, to: user.email,
}, },
locals: { locals: {
body: body: `A new request for the following ${
"A user's new request has failed to add to Sonarr or Radarr", payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
} could not be added to ${
payload.media?.mediaType === MediaType.TV ? 'Sonarr' : 'Radarr'
}`,
mediaName: payload.subject, mediaName: payload.subject,
imageUrl: payload.image, imageUrl: payload.image,
timestamp: new Date().toTimeString(), timestamp: new Date().toTimeString(),
@ -114,13 +122,15 @@ class EmailAgent
: undefined, : undefined,
applicationUrl, applicationUrl,
applicationTitle, applicationTitle,
requestType: 'Failed Request', requestType: `Failed ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`,
}, },
}); });
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -143,7 +153,9 @@ class EmailAgent
to: payload.notifyUser.email, to: payload.notifyUser.email,
}, },
locals: { locals: {
body: 'Your request for the following media has been approved:', body: `Your request for the following ${
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
} has been approved:`,
mediaName: payload.subject, mediaName: payload.subject,
imageUrl: payload.image, imageUrl: payload.image,
timestamp: new Date().toTimeString(), timestamp: new Date().toTimeString(),
@ -153,12 +165,64 @@ class EmailAgent
: undefined, : undefined,
applicationUrl, applicationUrl,
applicationTitle, applicationTitle,
requestType: 'Request Approved', requestType: `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Approved`,
}, },
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications',
message: e.message,
});
return false;
}
}
private async sendMediaAutoApprovedEmail(payload: NotificationPayload) {
// This is getting main settings for the whole app
const { applicationUrl, applicationTitle } = getSettings().main;
try {
const userRepository = getRepository(User);
const users = await userRepository.find();
// Send to all users with the manage requests permission (or admins)
users
.filter((user) => user.hasPermission(Permission.MANAGE_REQUESTS))
.forEach((user) => {
const email = new PreparedEmail();
email.send({
template: path.join(
__dirname,
'../../../templates/email/media-request'
),
message: {
to: user.email,
},
locals: {
body: `A new request for the following ${
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
} has been automatically 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 Automatically Approved`,
},
});
});
return true;
} catch (e) {
logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -181,7 +245,9 @@ class EmailAgent
to: payload.notifyUser.email, to: payload.notifyUser.email,
}, },
locals: { locals: {
body: 'Your request for the following media was declined:', body: `Your request for the following ${
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
} was declined:`,
mediaName: payload.subject, mediaName: payload.subject,
imageUrl: payload.image, imageUrl: payload.image,
timestamp: new Date().toTimeString(), timestamp: new Date().toTimeString(),
@ -191,12 +257,14 @@ class EmailAgent
: undefined, : undefined,
applicationUrl, applicationUrl,
applicationTitle, applicationTitle,
requestType: 'Request Declined', requestType: `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Declined`,
}, },
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -219,7 +287,9 @@ class EmailAgent
to: payload.notifyUser.email, to: payload.notifyUser.email,
}, },
locals: { locals: {
body: 'Your requested media is now available!', body: `The following ${
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
} you requested is now available!`,
mediaName: payload.subject, mediaName: payload.subject,
imageUrl: payload.image, imageUrl: payload.image,
timestamp: new Date().toTimeString(), timestamp: new Date().toTimeString(),
@ -229,12 +299,14 @@ class EmailAgent
: undefined, : undefined,
applicationUrl, applicationUrl,
applicationTitle, applicationTitle,
requestType: 'Now Available', requestType: `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Now Available`,
}, },
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -261,7 +333,7 @@ class EmailAgent
}); });
return true; return true;
} catch (e) { } catch (e) {
logger.error('Mail notification failed to send', { logger.error('Email notification failed to send', {
label: 'Notifications', label: 'Notifications',
message: e.message, message: e.message,
}); });
@ -282,6 +354,9 @@ class EmailAgent
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
this.sendMediaApprovedEmail(payload); this.sendMediaApprovedEmail(payload);
break; break;
case Notification.MEDIA_AUTO_APPROVED:
this.sendMediaAutoApprovedEmail(payload);
break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
this.sendMediaDeclinedEmail(payload); this.sendMediaDeclinedEmail(payload);
break; break;

@ -3,6 +3,7 @@ import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentPushbullet } from '../../settings'; import { getSettings, NotificationAgentPushbullet } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import { MediaType } from '../../../constants/media';
interface PushbulletPayload { interface PushbulletPayload {
title: string; title: string;
@ -50,7 +51,9 @@ class PushbulletAgent
switch (type) { switch (type) {
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
messageTitle = 'New Request'; messageTitle = `New ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
message += `${title}`; message += `${title}`;
if (plot) { if (plot) {
message += `\n\n${plot}`; message += `\n\n${plot}`;
@ -59,7 +62,20 @@ class PushbulletAgent
message += `\nStatus: Pending Approval`; message += `\nStatus: Pending Approval`;
break; break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
messageTitle = 'Request Approved'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Approved`;
message += `${title}`;
if (plot) {
message += `\n\n${plot}`;
}
message += `\n\nRequested By: ${username}`;
message += `\nStatus: Processing`;
break;
case Notification.MEDIA_AUTO_APPROVED:
messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Automatically Approved`;
message += `${title}`; message += `${title}`;
if (plot) { if (plot) {
message += `\n\n${plot}`; message += `\n\n${plot}`;
@ -68,7 +84,9 @@ class PushbulletAgent
message += `\nStatus: Processing`; message += `\nStatus: Processing`;
break; break;
case Notification.MEDIA_AVAILABLE: case Notification.MEDIA_AVAILABLE:
messageTitle = 'Now Available'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Now Available`;
message += `${title}`; message += `${title}`;
if (plot) { if (plot) {
message += `\n\n${plot}`; message += `\n\n${plot}`;
@ -77,7 +95,9 @@ class PushbulletAgent
message += `\nStatus: Available`; message += `\nStatus: Available`;
break; break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
messageTitle = 'Request Declined'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Declined`;
message += `${title}`; message += `${title}`;
if (plot) { if (plot) {
message += `\n\n${plot}`; message += `\n\n${plot}`;
@ -86,7 +106,9 @@ class PushbulletAgent
message += `\nStatus: Declined`; message += `\nStatus: Declined`;
break; break;
case Notification.MEDIA_FAILED: case Notification.MEDIA_FAILED:
messageTitle = 'Failed Request'; messageTitle = `Failed ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
message += `${title}`; message += `${title}`;
if (plot) { if (plot) {
message += `\n\n${plot}`; message += `\n\n${plot}`;

@ -3,6 +3,7 @@ import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentPushover } from '../../settings'; import { getSettings, NotificationAgentPushover } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import { MediaType } from '../../../constants/media';
interface PushoverPayload { interface PushoverPayload {
token: string; token: string;
@ -64,7 +65,9 @@ class PushoverAgent
switch (type) { switch (type) {
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
messageTitle = 'New Request'; messageTitle = `New ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
message += `<b>${title}</b>`; message += `<b>${title}</b>`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -73,7 +76,20 @@ class PushoverAgent
message += `\n\n<b>Status</b>\nPending Approval`; message += `\n\n<b>Status</b>\nPending Approval`;
break; break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
messageTitle = 'Request Approved'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Approved`;
message += `<b>${title}</b>`;
if (plot) {
message += `\n${plot}`;
}
message += `\n\n<b>Requested By</b>\n${username}`;
message += `\n\n<b>Status</b>\nProcessing`;
break;
case Notification.MEDIA_AUTO_APPROVED:
messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Automatically Approved`;
message += `<b>${title}</b>`; message += `<b>${title}</b>`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -82,7 +98,9 @@ class PushoverAgent
message += `\n\n<b>Status</b>\nProcessing`; message += `\n\n<b>Status</b>\nProcessing`;
break; break;
case Notification.MEDIA_AVAILABLE: case Notification.MEDIA_AVAILABLE:
messageTitle = 'Now Available'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Now Available`;
message += `<b>${title}</b>`; message += `<b>${title}</b>`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -91,7 +109,9 @@ class PushoverAgent
message += `\n\n<b>Status</b>\nAvailable`; message += `\n\n<b>Status</b>\nAvailable`;
break; break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
messageTitle = 'Request Declined'; messageTitle = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Declined`;
message += `<b>${title}</b>`; message += `<b>${title}</b>`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -101,7 +121,9 @@ class PushoverAgent
priority = 1; priority = 1;
break; break;
case Notification.MEDIA_FAILED: case Notification.MEDIA_FAILED:
messageTitle = 'Failed Request'; messageTitle = `Failed ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
message += `<b>${title}</b>`; message += `<b>${title}</b>`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;

@ -3,6 +3,7 @@ import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentSlack } from '../../settings'; import { getSettings, NotificationAgentSlack } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import { MediaType } from '../../../constants/media';
interface EmbedField { interface EmbedField {
type: 'plain_text' | 'mrkdwn'; type: 'plain_text' | 'mrkdwn';
@ -72,35 +73,54 @@ class SlackAgent
switch (type) { switch (type) {
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
header = 'New Request'; header = `New ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
fields.push({ fields.push({
type: 'mrkdwn', type: 'mrkdwn',
text: '*Status*\nPending Approval', text: '*Status*\nPending Approval',
}); });
break; break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
header = 'Request Approved'; header = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Approved`;
fields.push({
type: 'mrkdwn',
text: '*Status*\nProcessing',
});
break;
case Notification.MEDIA_AUTO_APPROVED:
header = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Automatically Approved`;
fields.push({ fields.push({
type: 'mrkdwn', type: 'mrkdwn',
text: '*Status*\nProcessing', text: '*Status*\nProcessing',
}); });
break; break;
case Notification.MEDIA_AVAILABLE: case Notification.MEDIA_AVAILABLE:
header = 'Now Available'; header = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Now Available`;
fields.push({ fields.push({
type: 'mrkdwn', type: 'mrkdwn',
text: '*Status*\nAvailable', text: '*Status*\nAvailable',
}); });
break; break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
header = 'Request Declined'; header = `${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Declined`;
fields.push({ fields.push({
type: 'mrkdwn', type: 'mrkdwn',
text: '*Status*\nDeclined', text: '*Status*\nDeclined',
}); });
break; break;
case Notification.MEDIA_FAILED: case Notification.MEDIA_FAILED:
header = 'Failed Request'; header = `Failed ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request`;
fields.push({ fields.push({
type: 'mrkdwn', type: 'mrkdwn',
text: '*Status*\nFailed', text: '*Status*\nFailed',
@ -206,7 +226,7 @@ class SlackAgent
type: Notification, type: Notification,
payload: NotificationPayload payload: NotificationPayload
): Promise<boolean> { ): Promise<boolean> {
logger.debug('Sending slack notification', { label: 'Notifications' }); logger.debug('Sending Slack notification', { label: 'Notifications' });
try { try {
const webhookUrl = this.getSettings().options.webhookUrl; const webhookUrl = this.getSettings().options.webhookUrl;

@ -2,6 +2,7 @@ import axios from 'axios';
import { hasNotificationType, Notification } from '..'; import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentTelegram } from '../../settings'; import { getSettings, NotificationAgentTelegram } from '../../settings';
import { MediaType } from '../../../constants/media';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
interface TelegramMessagePayload { interface TelegramMessagePayload {
@ -66,7 +67,9 @@ class TelegramAgent
/* eslint-disable no-useless-escape */ /* eslint-disable no-useless-escape */
switch (type) { switch (type) {
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
message += `\*New Request\*`; message += `\*New ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request\*`;
message += `\n\n\*${title}\*`; message += `\n\n\*${title}\*`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -75,7 +78,20 @@ class TelegramAgent
message += `\n\n\*Status\*\nPending Approval`; message += `\n\n\*Status\*\nPending Approval`;
break; break;
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
message += `\*Request Approved\*`; message += `\*${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Approved\*`;
message += `\n\n\*${title}\*`;
if (plot) {
message += `\n${plot}`;
}
message += `\n\n\*Requested By\*\n${user}`;
message += `\n\n\*Status\*\nProcessing`;
break;
case Notification.MEDIA_AUTO_APPROVED:
message += `\*${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Automatically Approved\*`;
message += `\n\n\*${title}\*`; message += `\n\n\*${title}\*`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -84,7 +100,9 @@ class TelegramAgent
message += `\n\n\*Status\*\nProcessing`; message += `\n\n\*Status\*\nProcessing`;
break; break;
case Notification.MEDIA_AVAILABLE: case Notification.MEDIA_AVAILABLE:
message += `\*Now Available\*`; message += `\*${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Now Available\*`;
message += `\n\n\*${title}\*`; message += `\n\n\*${title}\*`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -93,7 +111,9 @@ class TelegramAgent
message += `\n\n\*Status\*\nAvailable`; message += `\n\n\*Status\*\nAvailable`;
break; break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
message += `\*Request Declined\*`; message += `\*${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request Declined\*`;
message += `\n\n\*${title}\*`; message += `\n\n\*${title}\*`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -102,7 +122,9 @@ class TelegramAgent
message += `\n\n\*Status\*\nDeclined`; message += `\n\n\*Status\*\nDeclined`;
break; break;
case Notification.MEDIA_FAILED: case Notification.MEDIA_FAILED:
message += `\*Failed Request\*`; message += `\*Failed ${
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
} Request\*`;
message += `\n\n\*${title}\*`; message += `\n\n\*${title}\*`;
if (plot) { if (plot) {
message += `\n${plot}`; message += `\n${plot}`;
@ -129,12 +151,13 @@ class TelegramAgent
type: Notification, type: Notification,
payload: NotificationPayload payload: NotificationPayload
): Promise<boolean> { ): Promise<boolean> {
logger.debug('Sending telegram notification', { label: 'Notifications' }); logger.debug('Sending Telegram notification', { label: 'Notifications' });
try { try {
const endpoint = `${this.baseUrl}bot${ const endpoint = `${this.baseUrl}bot${
this.getSettings().options.botAPI this.getSettings().options.botAPI
}/${payload.image ? 'sendPhoto' : 'sendMessage'}`; }/${payload.image ? 'sendPhoto' : 'sendMessage'}`;
// Send system notification
await (payload.image await (payload.image
? axios.post(endpoint, { ? axios.post(endpoint, {
photo: payload.image, photo: payload.image,
@ -150,7 +173,9 @@ class TelegramAgent
disable_notification: this.getSettings().options.sendSilently, disable_notification: this.getSettings().options.sendSilently,
} as TelegramMessagePayload)); } as TelegramMessagePayload));
// Send user notification
if ( if (
this.userNotificationTypes.includes(type) &&
payload.notifyUser.settings?.enableNotifications && payload.notifyUser.settings?.enableNotifications &&
payload.notifyUser.settings?.telegramChatId && payload.notifyUser.settings?.telegramChatId &&
payload.notifyUser.settings?.telegramChatId !== payload.notifyUser.settings?.telegramChatId !==

@ -138,7 +138,7 @@ class WebhookAgent
return true; return true;
} catch (e) { } catch (e) {
logger.error('Error sending Webhook notification', { logger.error('Error sending webhook notification', {
label: 'Notifications', label: 'Notifications',
errorMessage: e.message, errorMessage: e.message,
}); });

@ -9,6 +9,7 @@ export enum Notification {
MEDIA_FAILED = 16, MEDIA_FAILED = 16,
TEST_NOTIFICATION = 32, TEST_NOTIFICATION = 32,
MEDIA_DECLINED = 64, MEDIA_DECLINED = 64,
MEDIA_AUTO_APPROVED = 128,
} }
export const hasNotificationType = ( export const hasNotificationType = (

@ -163,7 +163,6 @@ interface NotificationAgents {
interface NotificationSettings { interface NotificationSettings {
enabled: boolean; enabled: boolean;
autoapprovalEnabled: boolean;
agents: NotificationAgents; agents: NotificationAgents;
} }
@ -213,7 +212,6 @@ class Settings {
}, },
notifications: { notifications: {
enabled: true, enabled: true,
autoapprovalEnabled: false,
agents: { agents: {
email: { email: {
enabled: false, enabled: false,

@ -15,7 +15,6 @@ notificationRoutes.get('/', (_req, res) => {
const settings = getSettings().notifications; const settings = getSettings().notifications;
return res.status(200).json({ return res.status(200).json({
enabled: settings.enabled, enabled: settings.enabled,
autoapprovalEnabled: settings.autoapprovalEnabled,
}); });
}); });
@ -24,13 +23,11 @@ notificationRoutes.post('/', (req, res) => {
Object.assign(settings.notifications, { Object.assign(settings.notifications, {
enabled: req.body.enabled, enabled: req.body.enabled,
autoapprovalEnabled: req.body.autoapprovalEnabled,
}); });
settings.save(); settings.save();
return res.status(200).json({ return res.status(200).json({
enabled: settings.notifications.enabled, enabled: settings.notifications.enabled,
autoapprovalEnabled: settings.notifications.autoapprovalEnabled,
}); });
}); });

@ -8,16 +8,19 @@ const messages = defineMessages({
'Sends a notification when media is requested and requires approval.', 'Sends a notification when media is requested and requires approval.',
mediaapproved: 'Media Approved', mediaapproved: 'Media Approved',
mediaapprovedDescription: mediaapprovedDescription:
'Sends a notification when media is approved.\ 'Sends a notification when requested media is manually approved.',
By default, automatically approved requests will not trigger notifications.', mediaAutoApproved: 'Media Automatically Approved',
mediaAutoApprovedDescription:
'Sends a notification when requested media is automatically approved.',
mediaavailable: 'Media Available', mediaavailable: 'Media Available',
mediaavailableDescription: mediaavailableDescription:
'Sends a notification when media becomes available.', 'Sends a notification when requested media becomes available.',
mediafailed: 'Media Failed', mediafailed: 'Media Failed',
mediafailedDescription: mediafailedDescription:
'Sends a notification when media fails to be added to Radarr or Sonarr.', 'Sends a notification when requested media fails to be added to Radarr or Sonarr.',
mediadeclined: 'Media Declined', mediadeclined: 'Media Declined',
mediadeclinedDescription: 'Sends a notification when a request is declined.', mediadeclinedDescription:
'Sends a notification when a media request is declined.',
}); });
export const hasNotificationType = ( export const hasNotificationType = (
@ -46,6 +49,7 @@ export enum Notification {
MEDIA_FAILED = 16, MEDIA_FAILED = 16,
TEST_NOTIFICATION = 32, TEST_NOTIFICATION = 32,
MEDIA_DECLINED = 64, MEDIA_DECLINED = 64,
MEDIA_AUTO_APPROVED = 128,
} }
export interface NotificationItem { export interface NotificationItem {
@ -74,6 +78,12 @@ const NotificationTypeSelector: React.FC<NotificationTypeSelectorProps> = ({
description: intl.formatMessage(messages.mediarequestedDescription), description: intl.formatMessage(messages.mediarequestedDescription),
value: Notification.MEDIA_PENDING, value: Notification.MEDIA_PENDING,
}, },
{
id: 'media-auto-approved',
name: intl.formatMessage(messages.mediaAutoApproved),
description: intl.formatMessage(messages.mediaAutoApprovedDescription),
value: Notification.MEDIA_AUTO_APPROVED,
},
{ {
id: 'media-approved', id: 'media-approved',
name: intl.formatMessage(messages.mediaapproved), name: intl.formatMessage(messages.mediaapproved),

@ -32,10 +32,13 @@ const messages = defineMessages({
senderName: 'Sender Name', senderName: 'Sender Name',
notificationtypes: 'Notification Types', notificationtypes: 'Notification Types',
validationEmail: 'You must provide a valid email address', validationEmail: 'You must provide a valid email address',
emailNotificationTypesAlert: 'Notification Email Recipients', emailNotificationTypesAlert: 'Email Notification Recipients',
emailNotificationTypesAlertDescription: emailNotificationTypesAlertDescription:
'For the "Media Requested" and "Media Failed" notification types,\ '<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong>\
notifications will only be sent to users with the "Manage Requests" permission.', email notifications are sent to all users with the <strong>Manage Requests</strong> permission.',
emailNotificationTypesAlertDescriptionPt2:
'<strong>Media Approved</strong>, <strong>Media Declined</strong>, and <strong>Media Available</strong>\
email notifications are sent to the user who submitted the request.',
}); });
const NotificationsEmail: React.FC = () => { const NotificationsEmail: React.FC = () => {
@ -134,9 +137,34 @@ const NotificationsEmail: React.FC = () => {
title={intl.formatMessage(messages.emailNotificationTypesAlert)} title={intl.formatMessage(messages.emailNotificationTypesAlert)}
type="info" type="info"
> >
{intl.formatMessage( <p className="mb-2">
messages.emailNotificationTypesAlertDescription {intl.formatMessage(
)} messages.emailNotificationTypesAlertDescription,
{
strong: function strong(msg) {
return (
<strong className="font-normal text-indigo-100">
{msg}
</strong>
);
},
}
)}
</p>
<p>
{intl.formatMessage(
messages.emailNotificationTypesAlertDescriptionPt2,
{
strong: function strong(msg) {
return (
<strong className="font-normal text-indigo-100">
{msg}
</strong>
);
},
}
)}
</p>
</Alert> </Alert>
<Form className="section"> <Form className="section">
<div className="form-row"> <div className="form-row">

@ -27,7 +27,7 @@ const messages = defineMessages({
settinguptelegramDescription: settinguptelegramDescription:
'To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key.\ 'To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key.\
Additionally, you will need the chat ID for the chat to which you would like to send notifications.\ Additionally, you will need the chat ID for the chat to which you would like to send notifications.\
You can get this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat.', You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.',
notificationtypes: 'Notification Types', notificationtypes: 'Notification Types',
sendSilently: 'Send Silently', sendSilently: 'Send Silently',
sendSilentlyTip: 'Send notifications with no sound', sendSilentlyTip: 'Send notifications with no sound',
@ -143,6 +143,9 @@ const NotificationsTelegram: React.FC = () => {
</a> </a>
); );
}, },
code: function code(msg) {
return <code className="bg-opacity-50">{msg}</code>;
},
})} })}
</Alert> </Alert>
<Form className="section"> <Form className="section">

@ -28,7 +28,6 @@ const messages = defineMessages({
notificationsettingssaved: 'Notification settings saved successfully!', notificationsettingssaved: 'Notification settings saved successfully!',
notificationsettingsfailed: 'Notification settings failed to save.', notificationsettingsfailed: 'Notification settings failed to save.',
enablenotifications: 'Enable Notifications', enablenotifications: 'Enable Notifications',
autoapprovedrequests: 'Enable Notifications for Automatic Approvals',
email: 'Email', email: 'Email',
webhook: 'Webhook', webhook: 'Webhook',
}); });
@ -187,14 +186,12 @@ const SettingsNotifications: React.FC = ({ children }) => {
<Formik <Formik
initialValues={{ initialValues={{
enabled: data.enabled, enabled: data.enabled,
autoapprovalEnabled: data.autoapprovalEnabled,
}} }}
enableReinitialize enableReinitialize
onSubmit={async (values) => { onSubmit={async (values) => {
try { try {
await axios.post('/api/v1/settings/notifications', { await axios.post('/api/v1/settings/notifications', {
enabled: values.enabled, enabled: values.enabled,
autoapprovalEnabled: values.autoapprovalEnabled,
}); });
addToast(intl.formatMessage(messages.notificationsettingssaved), { addToast(intl.formatMessage(messages.notificationsettingssaved), {
appearance: 'success', appearance: 'success',
@ -233,26 +230,6 @@ const SettingsNotifications: React.FC = ({ children }) => {
/> />
</div> </div>
</div> </div>
<div className="form-row">
<label htmlFor="name" className="checkbox-label">
<span>
{intl.formatMessage(messages.autoapprovedrequests)}
</span>
</label>
<div className="form-input">
<Field
type="checkbox"
id="autoapprovalEnabled"
name="autoapprovalEnabled"
onChange={() => {
setFieldValue(
'autoapprovalEnabled',
!values.autoapprovalEnabled
);
}}
/>
</div>
</div>
<div className="actions"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">

@ -95,14 +95,16 @@
"components.MovieDetails.view": "View", "components.MovieDetails.view": "View",
"components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.viewfullcrew": "View Full Crew",
"components.MovieDetails.watchtrailer": "Watch Trailer", "components.MovieDetails.watchtrailer": "Watch Trailer",
"components.NotificationTypeSelector.mediaAutoApproved": "Media Automatically Approved",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Sends a notification when requested media is automatically approved.",
"components.NotificationTypeSelector.mediaapproved": "Media Approved", "components.NotificationTypeSelector.mediaapproved": "Media Approved",
"components.NotificationTypeSelector.mediaapprovedDescription": "Sends a notification when media is approved. By default, automatically approved requests will not trigger notifications.", "components.NotificationTypeSelector.mediaapprovedDescription": "Sends a notification when requested media is manually approved.",
"components.NotificationTypeSelector.mediaavailable": "Media Available", "components.NotificationTypeSelector.mediaavailable": "Media Available",
"components.NotificationTypeSelector.mediaavailableDescription": "Sends a notification when media becomes available.", "components.NotificationTypeSelector.mediaavailableDescription": "Sends a notification when requested media becomes available.",
"components.NotificationTypeSelector.mediadeclined": "Media Declined", "components.NotificationTypeSelector.mediadeclined": "Media Declined",
"components.NotificationTypeSelector.mediadeclinedDescription": "Sends a notification when a request is declined.", "components.NotificationTypeSelector.mediadeclinedDescription": "Sends a notification when a media request is declined.",
"components.NotificationTypeSelector.mediafailed": "Media Failed", "components.NotificationTypeSelector.mediafailed": "Media Failed",
"components.NotificationTypeSelector.mediafailedDescription": "Sends a notification when media fails to be added to Radarr or Sonarr.", "components.NotificationTypeSelector.mediafailedDescription": "Sends a notification when requested media fails to be added to Radarr or Sonarr.",
"components.NotificationTypeSelector.mediarequested": "Media Requested", "components.NotificationTypeSelector.mediarequested": "Media Requested",
"components.NotificationTypeSelector.mediarequestedDescription": "Sends a notification when media is requested and requires approval.", "components.NotificationTypeSelector.mediarequestedDescription": "Sends a notification when media is requested and requires approval.",
"components.PermissionEdit.admin": "Admin", "components.PermissionEdit.admin": "Admin",
@ -318,8 +320,9 @@
"components.Settings.Notifications.chatId": "Chat ID", "components.Settings.Notifications.chatId": "Chat ID",
"components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.", "components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
"components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!", "components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
"components.Settings.Notifications.emailNotificationTypesAlert": "Notification Email Recipients", "components.Settings.Notifications.emailNotificationTypesAlert": "Email Notification Recipients",
"components.Settings.Notifications.emailNotificationTypesAlertDescription": "For the \"Media Requested\" and \"Media Failed\" notification types, notifications will only be sent to users with the \"Manage Requests\" permission.", "components.Settings.Notifications.emailNotificationTypesAlertDescription": "<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong> email notifications are sent to all users with the <strong>Manage Requests</strong> permission.",
"components.Settings.Notifications.emailNotificationTypesAlertDescriptionPt2": "<strong>Media Approved</strong>, <strong>Media Declined</strong>, and <strong>Media Available</strong> email notifications are sent to the user who submitted the request.",
"components.Settings.Notifications.emailsender": "Sender Address", "components.Settings.Notifications.emailsender": "Sender Address",
"components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.", "components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.",
"components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!", "components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!",
@ -331,7 +334,7 @@
"components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound", "components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound",
"components.Settings.Notifications.senderName": "Sender Name", "components.Settings.Notifications.senderName": "Sender Name",
"components.Settings.Notifications.settinguptelegram": "Setting Up Telegram Notifications", "components.Settings.Notifications.settinguptelegram": "Setting Up Telegram Notifications",
"components.Settings.Notifications.settinguptelegramDescription": "To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can get this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat.", "components.Settings.Notifications.settinguptelegramDescription": "To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.",
"components.Settings.Notifications.smtpHost": "SMTP Host", "components.Settings.Notifications.smtpHost": "SMTP Host",
"components.Settings.Notifications.smtpPort": "SMTP Port", "components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)", "components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)",
@ -507,7 +510,6 @@
"components.Settings.apikey": "API Key", "components.Settings.apikey": "API Key",
"components.Settings.applicationTitle": "Application Title", "components.Settings.applicationTitle": "Application Title",
"components.Settings.applicationurl": "Application URL", "components.Settings.applicationurl": "Application URL",
"components.Settings.autoapprovedrequests": "Enable Notifications for Automatic Approvals",
"components.Settings.cancelscan": "Cancel Scan", "components.Settings.cancelscan": "Cancel Scan",
"components.Settings.copied": "Copied API key to clipboard.", "components.Settings.copied": "Copied API key to clipboard.",
"components.Settings.csrfProtection": "Enable CSRF Protection", "components.Settings.csrfProtection": "Enable CSRF Protection",

@ -63,7 +63,7 @@ a.slider-title {
} }
.description { .description {
@apply max-w-2xl mt-1 text-sm leading-5 text-gray-500; @apply max-w-4xl mt-1 text-sm leading-5 text-gray-500;
} }
img.avatar-sm { img.avatar-sm {

Loading…
Cancel
Save