mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 04:08:45 -05:00
@@ -18,6 +18,7 @@ import notificationManager from './lib/notifications';
|
||||
import DiscordAgent from './lib/notifications/agents/discord';
|
||||
import EmailAgent from './lib/notifications/agents/email';
|
||||
import { getAppVersion } from './utils/appVersion';
|
||||
import SlackAgent from './lib/notifications/agents/slack';
|
||||
|
||||
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
||||
|
||||
@@ -42,7 +43,11 @@ app
|
||||
const settings = getSettings().load();
|
||||
|
||||
// Register Notification Agents
|
||||
notificationManager.registerAgents([new DiscordAgent(), new EmailAgent()]);
|
||||
notificationManager.registerAgents([
|
||||
new DiscordAgent(),
|
||||
new EmailAgent(),
|
||||
new SlackAgent(),
|
||||
]);
|
||||
|
||||
// Start Jobs
|
||||
startJobs();
|
||||
|
||||
225
server/lib/notifications/agents/slack.ts
Normal file
225
server/lib/notifications/agents/slack.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import axios from 'axios';
|
||||
import { Notification } from '..';
|
||||
import logger from '../../../logger';
|
||||
import { getSettings, NotificationAgentSlack } from '../../settings';
|
||||
import { BaseAgent, NotificationAgent, NotificationPayload } 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;
|
||||
value: string;
|
||||
url: string;
|
||||
action_id: 'button-action';
|
||||
}
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
interface SlackBlockEmbed {
|
||||
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 settings = getSettings();
|
||||
let header = 'Overseerr';
|
||||
let actionUrl: string | undefined;
|
||||
|
||||
const fields: EmbedField[] = [];
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
header = 'New Request';
|
||||
fields.push(
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nPending Approval',
|
||||
}
|
||||
);
|
||||
if (settings.main.applicationUrl) {
|
||||
actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`;
|
||||
}
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
header = 'Request Approved';
|
||||
fields.push(
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nProcessing Request',
|
||||
}
|
||||
);
|
||||
if (settings.main.applicationUrl) {
|
||||
actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`;
|
||||
}
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
header = 'Now available!';
|
||||
fields.push(
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: `*Requested By*\n${payload.notifyUser.username ?? ''}`,
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nAvailable',
|
||||
}
|
||||
);
|
||||
|
||||
if (settings.main.applicationUrl) {
|
||||
actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const blocks: EmbedBlock[] = [
|
||||
{
|
||||
type: 'header',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: header,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
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: [
|
||||
...fields,
|
||||
...(payload.extra ?? []).map(
|
||||
(extra): EmbedField => ({
|
||||
type: 'mrkdwn',
|
||||
text: `*${extra.name}*\n${extra.value}`,
|
||||
})
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (actionUrl) {
|
||||
blocks.push({
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
action_id: 'button-action',
|
||||
type: 'button',
|
||||
url: actionUrl,
|
||||
value: 'open_overseerr',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Open Overseerr',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
blocks,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Add checking for type here once we add notification type filters for agents
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public shouldSend(_type: Notification): boolean {
|
||||
if (this.getSettings().enabled && this.getSettings().options.webhookUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async send(
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): Promise<boolean> {
|
||||
logger.debug('Sending slack notification', { label: 'Notifications' });
|
||||
try {
|
||||
const webhookUrl = this.getSettings().options.webhookUrl;
|
||||
|
||||
if (!webhookUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await axios.post(webhookUrl, this.buildEmbed(type, payload));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.error('Error sending Slack notification', {
|
||||
label: 'Notifications',
|
||||
message: e.message,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SlackAgent;
|
||||
@@ -66,6 +66,12 @@ export interface NotificationAgentDiscord extends NotificationAgentConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotificationAgentSlack extends NotificationAgentConfig {
|
||||
options: {
|
||||
webhookUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotificationAgentEmail extends NotificationAgentConfig {
|
||||
options: {
|
||||
emailFrom: string;
|
||||
@@ -81,6 +87,7 @@ export interface NotificationAgentEmail extends NotificationAgentConfig {
|
||||
interface NotificationAgents {
|
||||
email: NotificationAgentEmail;
|
||||
discord: NotificationAgentDiscord;
|
||||
slack: NotificationAgentSlack;
|
||||
}
|
||||
|
||||
interface NotificationSettings {
|
||||
@@ -142,6 +149,13 @@ class Settings {
|
||||
webhookUrl: '',
|
||||
},
|
||||
},
|
||||
slack: {
|
||||
enabled: false,
|
||||
types: 0,
|
||||
options: {
|
||||
webhookUrl: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import { SettingsAboutResponse } from '../interfaces/api/settingsInterfaces';
|
||||
import { Notification } from '../lib/notifications';
|
||||
import DiscordAgent from '../lib/notifications/agents/discord';
|
||||
import EmailAgent from '../lib/notifications/agents/email';
|
||||
import SlackAgent from '../lib/notifications/agents/slack';
|
||||
|
||||
const settingsRoutes = Router();
|
||||
|
||||
@@ -468,6 +469,40 @@ settingsRoutes.post('/notifications/discord/test', (req, res, next) => {
|
||||
return res.status(204).send();
|
||||
});
|
||||
|
||||
settingsRoutes.get('/notifications/slack', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.slack);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/notifications/slack', (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.slack = req.body;
|
||||
settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.slack);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/notifications/slack/test', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return next({
|
||||
status: 500,
|
||||
message: 'User information missing from request',
|
||||
});
|
||||
}
|
||||
|
||||
const slackAgent = new SlackAgent(req.body);
|
||||
slackAgent.send(Notification.TEST_NOTIFICATION, {
|
||||
notifyUser: req.user,
|
||||
subject: 'Test Notification',
|
||||
message:
|
||||
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
|
||||
});
|
||||
|
||||
return res.status(204).send();
|
||||
});
|
||||
|
||||
settingsRoutes.get('/notifications/email', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user