You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
overseerr/server/lib/notifications/agents/webhook.ts

209 lines
6.3 KiB

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<string, string | KeyMapFunction> = {
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<NotificationAgentWebhook>
implements NotificationAgent
{
protected getSettings(): NotificationAgentWebhook {
if (this.settings) {
return this.settings;
}
const settings = getSettings();
return settings.notifications.agents.webhook;
}
private parseKeys(
finalPayload: Record<string, unknown>,
payload: NotificationPayload,
type: Notification
): Record<string, unknown> {
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<string, unknown>,
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<boolean> {
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;