mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(notifications): added ability to send test notifications
closes #309
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Notification } from '..';
|
||||
import { User } from '../../../entity/User';
|
||||
import { NotificationAgentConfig } from '../../settings';
|
||||
|
||||
export interface NotificationPayload {
|
||||
subject: string;
|
||||
@@ -9,6 +10,15 @@ export interface NotificationPayload {
|
||||
extra?: { name: string; value: string }[];
|
||||
}
|
||||
|
||||
export abstract class BaseAgent<T extends NotificationAgentConfig> {
|
||||
protected settings?: T;
|
||||
public constructor(settings?: T) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
protected abstract getSettings(): T;
|
||||
}
|
||||
|
||||
export interface NotificationAgent {
|
||||
shouldSend(type: Notification): boolean;
|
||||
send(type: Notification, payload: NotificationPayload): Promise<boolean>;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { Notification } from '..';
|
||||
import logger from '../../../logger';
|
||||
import { getSettings } from '../../settings';
|
||||
import type { NotificationAgent, NotificationPayload } from './agent';
|
||||
import { getSettings, NotificationAgentDiscord } from '../../settings';
|
||||
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||
|
||||
enum EmbedColors {
|
||||
DEFAULT = 0,
|
||||
@@ -37,6 +37,11 @@ interface DiscordImageEmbed {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
value: string;
|
||||
inline?: boolean;
|
||||
}
|
||||
interface DiscordRichEmbed {
|
||||
title?: string;
|
||||
type?: 'rich'; // Always rich for webhooks
|
||||
@@ -61,11 +66,7 @@ interface DiscordRichEmbed {
|
||||
icon_url?: string;
|
||||
proxy_icon_url?: string;
|
||||
};
|
||||
fields?: {
|
||||
name: string;
|
||||
value: string;
|
||||
inline?: boolean;
|
||||
}[];
|
||||
fields?: Field[];
|
||||
}
|
||||
|
||||
interface DiscordWebhookPayload {
|
||||
@@ -75,27 +76,75 @@ interface DiscordWebhookPayload {
|
||||
tts: boolean;
|
||||
}
|
||||
|
||||
class DiscordAgent implements NotificationAgent {
|
||||
class DiscordAgent
|
||||
extends BaseAgent<NotificationAgentDiscord>
|
||||
implements NotificationAgent {
|
||||
protected getSettings(): NotificationAgentDiscord {
|
||||
if (this.settings) {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
return settings.notifications.agents.discord;
|
||||
}
|
||||
|
||||
public buildEmbed(
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): DiscordRichEmbed {
|
||||
let color = EmbedColors.DEFAULT;
|
||||
let status = 'Unknown';
|
||||
|
||||
const fields: Field[] = [];
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
color = EmbedColors.ORANGE;
|
||||
status = 'Pending Approval';
|
||||
fields.push(
|
||||
{
|
||||
name: 'Requested By',
|
||||
value: payload.notifyUser.username ?? '',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Status',
|
||||
value: 'Pending Approval',
|
||||
inline: true,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
color = EmbedColors.PURPLE;
|
||||
status = 'Processing Request';
|
||||
fields.push(
|
||||
{
|
||||
name: 'Requested By',
|
||||
value: payload.notifyUser.username ?? '',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Status',
|
||||
value: 'Processing Request',
|
||||
inline: true,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
color = EmbedColors.GREEN;
|
||||
status = 'Available';
|
||||
fields.push(
|
||||
{
|
||||
name: 'Requested By',
|
||||
value: payload.notifyUser.username ?? '',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Status',
|
||||
value: 'Available',
|
||||
inline: true,
|
||||
}
|
||||
);
|
||||
break;
|
||||
default:
|
||||
color = EmbedColors.DARK_PURPLE;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -105,16 +154,7 @@ class DiscordAgent implements NotificationAgent {
|
||||
timestamp: new Date().toISOString(),
|
||||
author: { name: 'Overseerr' },
|
||||
fields: [
|
||||
{
|
||||
name: 'Requested By',
|
||||
value: payload.notifyUser.username ?? '',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Status',
|
||||
value: status,
|
||||
inline: true,
|
||||
},
|
||||
...fields,
|
||||
// If we have extra data, map it to fields for discord notifications
|
||||
...(payload.extra ?? []).map((extra) => ({
|
||||
name: extra.name,
|
||||
@@ -130,12 +170,7 @@ class DiscordAgent implements NotificationAgent {
|
||||
// TODO: Add checking for type here once we add notification type filters for agents
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public shouldSend(_type: Notification): boolean {
|
||||
const settings = getSettings();
|
||||
|
||||
if (
|
||||
settings.notifications.agents.discord?.enabled &&
|
||||
settings.notifications.agents.discord?.options?.webhookUrl
|
||||
) {
|
||||
if (this.getSettings().enabled && this.getSettings().options.webhookUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -146,11 +181,9 @@ class DiscordAgent implements NotificationAgent {
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): Promise<boolean> {
|
||||
const settings = getSettings();
|
||||
logger.debug('Sending discord notification', { label: 'Notifications' });
|
||||
try {
|
||||
const webhookUrl =
|
||||
settings.notifications.agents.discord?.options?.webhookUrl;
|
||||
const webhookUrl = this.getSettings().options.webhookUrl;
|
||||
|
||||
if (!webhookUrl) {
|
||||
return false;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { NotificationAgent, NotificationPayload } from './agent';
|
||||
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||
import { Notification } from '..';
|
||||
import path from 'path';
|
||||
import { getSettings } from '../../settings';
|
||||
import { getSettings, NotificationAgentEmail } from '../../settings';
|
||||
import nodemailer from 'nodemailer';
|
||||
import Email from 'email-templates';
|
||||
import logger from '../../../logger';
|
||||
@@ -9,13 +9,25 @@ import { getRepository } from 'typeorm';
|
||||
import { User } from '../../../entity/User';
|
||||
import { Permission } from '../../permissions';
|
||||
|
||||
class EmailAgent implements NotificationAgent {
|
||||
class EmailAgent
|
||||
extends BaseAgent<NotificationAgentEmail>
|
||||
implements NotificationAgent {
|
||||
protected getSettings(): NotificationAgentEmail {
|
||||
if (this.settings) {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
return settings.notifications.agents.email;
|
||||
}
|
||||
|
||||
// TODO: Add checking for type here once we add notification type filters for agents
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public shouldSend(_type: Notification): boolean {
|
||||
const settings = getSettings();
|
||||
const settings = this.getSettings();
|
||||
|
||||
if (settings.notifications.agents.email.enabled) {
|
||||
if (settings.enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,7 +35,7 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
|
||||
private getSmtpTransport() {
|
||||
const emailSettings = getSettings().notifications.agents.email.options;
|
||||
const emailSettings = this.getSettings().options;
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host: emailSettings.smtpHost,
|
||||
@@ -40,7 +52,7 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
|
||||
private getNewEmail() {
|
||||
const settings = getSettings().notifications.agents.email;
|
||||
const settings = this.getSettings();
|
||||
return new Email({
|
||||
message: {
|
||||
from: settings.options.emailFrom,
|
||||
@@ -51,7 +63,8 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
|
||||
private async sendMediaRequestEmail(payload: NotificationPayload) {
|
||||
const settings = getSettings().main;
|
||||
// This is getting main settings for the whole app
|
||||
const applicationUrl = getSettings().main.applicationUrl;
|
||||
try {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
@@ -76,7 +89,7 @@ class EmailAgent implements NotificationAgent {
|
||||
imageUrl: payload.image,
|
||||
timestamp: new Date().toTimeString(),
|
||||
requestedBy: payload.notifyUser.username,
|
||||
actionUrl: settings.applicationUrl,
|
||||
actionUrl: applicationUrl,
|
||||
requestType: 'New Request',
|
||||
},
|
||||
});
|
||||
@@ -92,7 +105,8 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
|
||||
private async sendMediaApprovedEmail(payload: NotificationPayload) {
|
||||
const settings = getSettings().main;
|
||||
// This is getting main settings for the whole app
|
||||
const applicationUrl = getSettings().main.applicationUrl;
|
||||
try {
|
||||
const email = this.getNewEmail();
|
||||
|
||||
@@ -110,7 +124,7 @@ class EmailAgent implements NotificationAgent {
|
||||
imageUrl: payload.image,
|
||||
timestamp: new Date().toTimeString(),
|
||||
requestedBy: payload.notifyUser.username,
|
||||
actionUrl: settings.applicationUrl,
|
||||
actionUrl: applicationUrl,
|
||||
requestType: 'Request Approved',
|
||||
},
|
||||
});
|
||||
@@ -125,7 +139,8 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
|
||||
private async sendMediaAvailableEmail(payload: NotificationPayload) {
|
||||
const settings = getSettings().main;
|
||||
// This is getting main settings for the whole app
|
||||
const applicationUrl = getSettings().main.applicationUrl;
|
||||
try {
|
||||
const email = this.getNewEmail();
|
||||
|
||||
@@ -143,7 +158,7 @@ class EmailAgent implements NotificationAgent {
|
||||
imageUrl: payload.image,
|
||||
timestamp: new Date().toTimeString(),
|
||||
requestedBy: payload.notifyUser.username,
|
||||
actionUrl: settings.applicationUrl,
|
||||
actionUrl: applicationUrl,
|
||||
requestType: 'Now Available',
|
||||
},
|
||||
});
|
||||
@@ -157,6 +172,32 @@ class EmailAgent implements NotificationAgent {
|
||||
}
|
||||
}
|
||||
|
||||
private async sendTestEmail(payload: NotificationPayload) {
|
||||
// This is getting main settings for the whole app
|
||||
const applicationUrl = getSettings().main.applicationUrl;
|
||||
try {
|
||||
const email = this.getNewEmail();
|
||||
|
||||
email.send({
|
||||
template: path.join(__dirname, '../../../templates/email/test-email'),
|
||||
message: {
|
||||
to: payload.notifyUser.email,
|
||||
},
|
||||
locals: {
|
||||
body: payload.message,
|
||||
actionUrl: applicationUrl,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.error('Mail notification failed to send', {
|
||||
label: 'Notifications',
|
||||
message: e.message,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async send(
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
@@ -173,6 +214,9 @@ class EmailAgent implements NotificationAgent {
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
this.sendMediaAvailableEmail(payload);
|
||||
break;
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
this.sendTestEmail(payload);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
Reference in New Issue
Block a user