Files
seerr/server/lib/notifications/agents/ntfy.ts

188 lines
5.5 KiB
TypeScript

import { IssueStatus, IssueTypeName } from '@server/constants/issue';
import { getIntl } from '@server/i18n';
import globalMessages from '@server/i18n/globalMessages';
import type { NotificationAgentNtfy } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import type { AvailableLocale } from '@server/types/languages';
import axios from 'axios';
import { Notification, hasNotificationType } from '..';
import type { NotificationAgent, NotificationPayload } from './agent';
import { BaseAgent } from './agent';
class NtfyAgent
extends BaseAgent<NotificationAgentNtfy>
implements NotificationAgent
{
protected getSettings(): NotificationAgentNtfy {
if (this.settings) {
return this.settings;
}
const settings = getSettings();
return settings.notifications.agents.ntfy;
}
private escapeMarkdown(text: string): string {
return text.replace(/([\\`*_{}[\]()#+\-.!|>~<])/g, '\\$1');
}
private buildPayload(type: Notification, payload: NotificationPayload) {
const settings = this.getSettings();
const intl = getIntl(settings.options.locale as AvailableLocale);
const { applicationUrl } = getSettings().main;
const embedPoster = settings.embedPoster;
const topic = settings.options.topic;
const priority = settings.options.priority ?? 3;
const title = payload.event
? `${payload.event} - ${payload.subject}`
: payload.subject;
let message = payload.message ?? '';
if (payload.request) {
message += `${message ? '\n\n' : ''}**${intl.formatMessage(globalMessages.requestedBy)}:** ${this.escapeMarkdown(payload.request.requestedBy.displayName)}`;
let status = '';
switch (type) {
case Notification.MEDIA_PENDING:
status = intl.formatMessage(globalMessages.pendingApproval);
break;
case Notification.MEDIA_APPROVED:
case Notification.MEDIA_AUTO_APPROVED:
status = intl.formatMessage(globalMessages.processing);
break;
case Notification.MEDIA_AVAILABLE:
status = intl.formatMessage(globalMessages.available);
break;
case Notification.MEDIA_DECLINED:
status = intl.formatMessage(globalMessages.declined);
break;
case Notification.MEDIA_FAILED:
status = intl.formatMessage(globalMessages.failed);
break;
}
if (status) {
message += `\n**${intl.formatMessage(globalMessages.requestStatus)}:** ${status}`;
}
} else if (payload.comment) {
message += `\n**${this.escapeMarkdown(
intl.formatMessage(globalMessages.commentFrom, {
userName: payload.comment.user.displayName,
})
)}:**\n${payload.comment.message}`;
} else if (payload.issue) {
message += `\n\n**${intl.formatMessage(globalMessages.reportedBy)}:** ${this.escapeMarkdown(payload.issue.createdBy.displayName)}`;
message += `\n**${intl.formatMessage(globalMessages.issueType)}:** ${IssueTypeName[payload.issue.issueType]}`;
message += `\n**${intl.formatMessage(globalMessages.issueStatus)}:** ${
payload.issue.status === IssueStatus.OPEN
? intl.formatMessage(globalMessages.open)
: intl.formatMessage(globalMessages.resolved)
}`;
}
for (const extra of payload.extra ?? []) {
message += `\n\n**${extra.name}**\n${extra.value}`;
}
const attach = embedPoster ? payload.image : undefined;
let click;
if (applicationUrl && payload.media) {
click = `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`;
}
const ntfyPayload: Record<string, unknown> = {
topic,
priority,
title,
message,
markdown: true,
};
if (attach) {
ntfyPayload.attach = attach;
}
if (click) {
ntfyPayload.click = click;
}
return ntfyPayload;
}
public shouldSend(): boolean {
const settings = this.getSettings();
if (settings.enabled && settings.options.url && settings.options.topic) {
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 ntfy notification', {
label: 'Notifications',
type: Notification[type],
subject: payload.subject,
});
try {
let authHeader;
if (
settings.options.authMethodUsernamePassword &&
settings.options.username &&
settings.options.password
) {
const encodedAuth = Buffer.from(
`${settings.options.username}:${settings.options.password}`
).toString('base64');
authHeader = `Basic ${encodedAuth}`;
} else if (settings.options.authMethodToken) {
authHeader = `Bearer ${settings.options.token}`;
}
await axios.post(
settings.options.url,
this.buildPayload(type, payload),
authHeader
? {
headers: {
Authorization: authHeader,
},
}
: undefined
);
return true;
} catch (e) {
logger.error('Error sending ntfy notification', {
label: 'Notifications',
type: Notification[type],
subject: payload.subject,
errorMessage: e.message,
response: e?.response?.data,
});
return false;
}
}
}
export default NtfyAgent;