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.
259 lines
5.7 KiB
259 lines
5.7 KiB
import axios from 'axios';
|
|
import { hasNotificationType, Notification } from '..';
|
|
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
|
import logger from '../../../logger';
|
|
import type { NotificationAgentSlack } from '../../settings';
|
|
import { getSettings } from '../../settings';
|
|
import type { NotificationAgent, NotificationPayload } from './agent';
|
|
import { BaseAgent } from './agent';
|
|
|
|
interface EmbedField {
|
|
type: 'plain_text' | 'mrkdwn';
|
|
text: string;
|
|
}
|
|
|
|
interface TextItem {
|
|
type: 'plain_text' | 'mrkdwn';
|
|
text: string;
|
|
emoji?: boolean;
|
|
}
|
|
|
|
interface Element {
|
|
type: 'button';
|
|
text?: TextItem;
|
|
action_id: string;
|
|
url?: string;
|
|
value?: string;
|
|
style?: 'primary' | 'danger';
|
|
}
|
|
|
|
interface EmbedBlock {
|
|
type: 'header' | 'actions' | 'section' | 'context';
|
|
block_id?: 'section789';
|
|
text?: TextItem;
|
|
fields?: EmbedField[];
|
|
accessory?: {
|
|
type: 'image';
|
|
image_url: string;
|
|
alt_text: string;
|
|
};
|
|
elements?: (Element | TextItem)[];
|
|
}
|
|
|
|
interface SlackBlockEmbed {
|
|
text: string;
|
|
blocks: EmbedBlock[];
|
|
}
|
|
|
|
class SlackAgent
|
|
extends BaseAgent<NotificationAgentSlack>
|
|
implements NotificationAgent
|
|
{
|
|
protected getSettings(): NotificationAgentSlack {
|
|
if (this.settings) {
|
|
return this.settings;
|
|
}
|
|
|
|
const settings = getSettings();
|
|
|
|
return settings.notifications.agents.slack;
|
|
}
|
|
|
|
public buildEmbed(
|
|
type: Notification,
|
|
payload: NotificationPayload
|
|
): SlackBlockEmbed {
|
|
const { applicationUrl, applicationTitle } = getSettings().main;
|
|
|
|
const fields: EmbedField[] = [];
|
|
|
|
if (payload.request) {
|
|
fields.push({
|
|
type: 'mrkdwn',
|
|
text: `*Requested By*\n${payload.request.requestedBy.displayName}`,
|
|
});
|
|
|
|
let status = '';
|
|
switch (type) {
|
|
case Notification.MEDIA_PENDING:
|
|
status = 'Pending Approval';
|
|
break;
|
|
case Notification.MEDIA_APPROVED:
|
|
case Notification.MEDIA_AUTO_APPROVED:
|
|
status = 'Processing';
|
|
break;
|
|
case Notification.MEDIA_AVAILABLE:
|
|
status = 'Available';
|
|
break;
|
|
case Notification.MEDIA_DECLINED:
|
|
status = 'Declined';
|
|
break;
|
|
case Notification.MEDIA_FAILED:
|
|
status = 'Failed';
|
|
break;
|
|
}
|
|
|
|
if (status) {
|
|
fields.push({
|
|
type: 'mrkdwn',
|
|
text: `*Request Status*\n${status}`,
|
|
});
|
|
}
|
|
} else if (payload.comment) {
|
|
fields.push({
|
|
type: 'mrkdwn',
|
|
text: `*Comment from ${payload.comment.user.displayName}*\n${payload.comment.message}`,
|
|
});
|
|
} else if (payload.issue) {
|
|
fields.push(
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Reported By*\n${payload.issue.createdBy.displayName}`,
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Issue Type*\n${IssueTypeName[payload.issue.issueType]}`,
|
|
},
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*Issue Status*\n${
|
|
payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
|
|
}`,
|
|
}
|
|
);
|
|
}
|
|
|
|
for (const extra of payload.extra ?? []) {
|
|
fields.push({
|
|
type: 'mrkdwn',
|
|
text: `*${extra.name}*\n${extra.value}`,
|
|
});
|
|
}
|
|
|
|
const blocks: EmbedBlock[] = [];
|
|
|
|
if (payload.event) {
|
|
blocks.push({
|
|
type: 'context',
|
|
elements: [
|
|
{
|
|
type: 'mrkdwn',
|
|
text: `*${payload.event}*`,
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
blocks.push({
|
|
type: 'header',
|
|
text: {
|
|
type: 'plain_text',
|
|
text: payload.subject,
|
|
},
|
|
});
|
|
|
|
if (payload.message) {
|
|
blocks.push({
|
|
type: 'section',
|
|
text: {
|
|
type: 'mrkdwn',
|
|
text: payload.message,
|
|
},
|
|
accessory: payload.image
|
|
? {
|
|
type: 'image',
|
|
image_url: payload.image,
|
|
alt_text: payload.subject,
|
|
}
|
|
: undefined,
|
|
});
|
|
}
|
|
|
|
if (fields.length > 0) {
|
|
blocks.push({
|
|
type: 'section',
|
|
fields,
|
|
});
|
|
}
|
|
|
|
const url = applicationUrl
|
|
? payload.issue
|
|
? `${applicationUrl}/issues/${payload.issue.id}`
|
|
: payload.media
|
|
? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
|
: undefined
|
|
: undefined;
|
|
|
|
if (url) {
|
|
blocks.push({
|
|
type: 'actions',
|
|
elements: [
|
|
{
|
|
action_id: 'open-in-overseerr',
|
|
type: 'button',
|
|
url,
|
|
text: {
|
|
type: 'plain_text',
|
|
text: `View ${
|
|
payload.issue ? 'Issue' : 'Media'
|
|
} in ${applicationTitle}`,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
return {
|
|
text: payload.event ?? payload.subject,
|
|
blocks,
|
|
};
|
|
}
|
|
|
|
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 (!hasNotificationType(type, settings.types ?? 0)) {
|
|
return true;
|
|
}
|
|
|
|
logger.debug('Sending Slack notification', {
|
|
label: 'Notifications',
|
|
type: Notification[type],
|
|
subject: payload.subject,
|
|
});
|
|
try {
|
|
await axios.post(
|
|
settings.options.webhookUrl,
|
|
this.buildEmbed(type, payload)
|
|
);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
logger.error('Error sending Slack notification', {
|
|
label: 'Notifications',
|
|
type: Notification[type],
|
|
subject: payload.subject,
|
|
errorMessage: e.message,
|
|
response: e.response?.data,
|
|
});
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default SlackAgent;
|