import { IssueStatus, IssueType } from '@server/constants/issue'; import { MediaStatus } from '@server/constants/media'; import type { NotificationAgentWebhook } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; import axios from 'axios'; import { get } from 'lodash'; import { hasNotificationType, Notification } from '..'; import type { NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent } from './agent'; type KeyMapFunction = ( payload: NotificationPayload, type: Notification ) => string; const KeyMap: Record = { notification_type: (_payload, type) => Notification[type], event: 'event', subject: 'subject', message: 'message', image: 'image', notifyuser_username: 'notifyUser.displayName', notifyuser_email: 'notifyUser.email', notifyuser_avatar: 'notifyUser.avatar', notifyuser_settings_discordId: 'notifyUser.settings.discordId', notifyuser_settings_telegramChatId: 'notifyUser.settings.telegramChatId', media_tmdbid: 'media.tmdbId', media_tvdbid: 'media.tvdbId', media_type: 'media.mediaType', media_status: (payload) => payload.media ? MediaStatus[payload.media.status] : '', media_status4k: (payload) => payload.media ? MediaStatus[payload.media.status4k] : '', request_id: 'request.id', requestedBy_username: 'request.requestedBy.displayName', requestedBy_email: 'request.requestedBy.email', requestedBy_avatar: 'request.requestedBy.avatar', requestedBy_settings_discordId: 'request.requestedBy.settings.discordId', requestedBy_settings_telegramChatId: 'request.requestedBy.settings.telegramChatId', issue_id: 'issue.id', issue_type: (payload) => payload.issue ? IssueType[payload.issue.issueType] : '', issue_status: (payload) => payload.issue ? IssueStatus[payload.issue.status] : '', reportedBy_username: 'issue.createdBy.displayName', reportedBy_email: 'issue.createdBy.email', reportedBy_avatar: 'issue.createdBy.avatar', reportedBy_settings_discordId: 'issue.createdBy.settings.discordId', reportedBy_settings_telegramChatId: 'issue.createdBy.settings.telegramChatId', comment_message: 'comment.message', commentedBy_username: 'comment.user.displayName', commentedBy_email: 'comment.user.email', commentedBy_avatar: 'comment.user.avatar', commentedBy_settings_discordId: 'comment.user.settings.discordId', commentedBy_settings_telegramChatId: 'comment.user.settings.telegramChatId', }; class WebhookAgent extends BaseAgent implements NotificationAgent { protected getSettings(): NotificationAgentWebhook { if (this.settings) { return this.settings; } const settings = getSettings(); return settings.notifications.agents.webhook; } private parseKeys( finalPayload: Record, payload: NotificationPayload, type: Notification ): Record { Object.keys(finalPayload).forEach((key) => { if (key === '{{extra}}') { finalPayload.extra = payload.extra ?? []; delete finalPayload[key]; key = 'extra'; } else if (key === '{{media}}') { if (payload.media) { finalPayload.media = finalPayload[key]; } else { finalPayload.media = null; } delete finalPayload[key]; key = 'media'; } else if (key === '{{request}}') { if (payload.request) { finalPayload.request = finalPayload[key]; } else { finalPayload.request = null; } delete finalPayload[key]; key = 'request'; } else if (key === '{{issue}}') { if (payload.issue) { finalPayload.issue = finalPayload[key]; } else { finalPayload.issue = null; } delete finalPayload[key]; key = 'issue'; } else if (key === '{{comment}}') { if (payload.comment) { finalPayload.comment = finalPayload[key]; } else { finalPayload.comment = null; } delete finalPayload[key]; key = 'comment'; } if (typeof finalPayload[key] === 'string') { Object.keys(KeyMap).forEach((keymapKey) => { const keymapValue = KeyMap[keymapKey as keyof typeof KeyMap]; finalPayload[key] = (finalPayload[key] as string).replace( `{{${keymapKey}}}`, typeof keymapValue === 'function' ? keymapValue(payload, type) : get(payload, keymapValue) ?? '' ); }); } else if (finalPayload[key] && typeof finalPayload[key] === 'object') { finalPayload[key] = this.parseKeys( finalPayload[key] as Record, payload, type ); } }); return finalPayload; } private buildPayload(type: Notification, payload: NotificationPayload) { const payloadString = Buffer.from( this.getSettings().options.jsonPayload, 'base64' ).toString('ascii'); const parsedJSON = JSON.parse(JSON.parse(payloadString)); return this.parseKeys(parsedJSON, payload, type); } public shouldSend(): boolean { const settings = this.getSettings(); if (settings.enabled && settings.options.webhookUrl) { return true; } return false; } public async send( type: Notification, payload: NotificationPayload ): Promise { const settings = this.getSettings(); if ( !payload.notifySystem || !hasNotificationType(type, settings.types ?? 0) ) { return true; } logger.debug('Sending webhook notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, }); try { await axios.post( settings.options.webhookUrl, this.buildPayload(type, payload), settings.options.authHeader ? { headers: { Authorization: settings.options.authHeader, }, } : undefined ); return true; } catch (e) { logger.error('Error sending webhook notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, errorMessage: e.message, response: e.response?.data, }); return false; } } } export default WebhookAgent;