import axios from 'axios'; import { Notification } from '..'; import logger from '../../../logger'; import { getSettings } from '../../settings'; import type { NotificationAgent, NotificationPayload } from './agent'; enum EmbedColors { DEFAULT = 0, AQUA = 1752220, GREEN = 3066993, BLUE = 3447003, PURPLE = 10181046, GOLD = 15844367, ORANGE = 15105570, RED = 15158332, GREY = 9807270, DARKER_GREY = 8359053, NAVY = 3426654, DARK_AQUA = 1146986, DARK_GREEN = 2067276, DARK_BLUE = 2123412, DARK_PURPLE = 7419530, DARK_GOLD = 12745742, DARK_ORANGE = 11027200, DARK_RED = 10038562, DARK_GREY = 9936031, LIGHT_GREY = 12370112, DARK_NAVY = 2899536, LUMINOUS_VIVID_PINK = 16580705, DARK_VIVID_PINK = 12320855, } interface DiscordImageEmbed { url?: string; proxy_url?: string; height?: number; width?: number; } interface DiscordRichEmbed { title?: string; type?: 'rich'; // Always rich for webhooks description?: string; url?: string; timestamp?: string; color?: number; footer?: { text: string; icon_url?: string; proxy_icon_url?: string; }; image?: DiscordImageEmbed; thumbnail?: DiscordImageEmbed; provider?: { name?: string; url?: string; }; author?: { name?: string; url?: string; icon_url?: string; proxy_icon_url?: string; }; fields?: { name: string; value: string; inline?: boolean; }[]; } interface DiscordWebhookPayload { embeds: DiscordRichEmbed[]; username: string; avatar_url?: string; tts: boolean; } class DiscordAgent implements NotificationAgent { public buildEmbed( type: Notification, payload: NotificationPayload ): DiscordRichEmbed { let color = EmbedColors.DEFAULT; let status = 'Unknown'; switch (type) { case Notification.MEDIA_PENDING: color = EmbedColors.ORANGE; status = 'Pending Approval'; break; case Notification.MEDIA_APPROVED: color = EmbedColors.PURPLE; status = 'Processing Request'; break; case Notification.MEDIA_AVAILABLE: color = EmbedColors.GREEN; status = 'Available'; break; } return { title: payload.subject, description: payload.message, color, timestamp: new Date().toISOString(), author: { name: 'Overseerr' }, fields: [ { name: 'Requested By', value: payload.notifyUser.username ?? '', inline: true, }, { name: 'Status', value: status, inline: true, }, // If we have extra data, map it to fields for discord notifications ...(payload.extra ?? []).map((extra) => ({ name: extra.name, value: extra.value, })), ], thumbnail: { url: payload.image, }, }; } public shouldSend(type: Notification): boolean { const settings = getSettings(); if ( settings.notifications.agents.discord?.enabled && settings.notifications.agents.discord?.options?.webhookUrl ) { return true; } return false; } public async send( type: Notification, payload: NotificationPayload ): Promise { const settings = getSettings(); logger.debug('Sending discord notification', { label: 'Notifications' }); try { const webhookUrl = settings.notifications.agents.discord?.options?.webhookUrl; if (!webhookUrl) { return false; } await axios.post(webhookUrl, { username: 'Overseerr', embeds: [this.buildEmbed(type, payload)], } as DiscordWebhookPayload); return true; } catch (e) { logger.error('Error sending Discord notification', { label: 'Notifications', message: e.message, }); return false; } } } export default DiscordAgent;