mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(notif): issue notifications (#2242)
* feat(notif): issue notifications * refactor: dedupe test notification strings * fix: webhook key parsing * fix(notif): skip send for admin who requested on behalf of another user * fix(notif): send comment notifs to admins when other admins reply * fix(notif): also send resolved notifs to admins, and reopened notifs to issue creator * fix: don't send duplicate notifications * fix(lang): tweak notification description strings * fix(notif): tweak Slack notification styling * fix(notif): tweak Pushbullet & Telegram notification styling * docs: reformat webhooks page * fix(notif): add missing issue_type & issue_status variables to LunaSea notif payloads * fix: explicitly attach media & issue objects where applicable * fix(notif): correctly notify both notifyUser and managers where applicable * fix: update default webhook payload for new installs * fix(notif): add missing comment_message to LunaSea notif payload * refactor(sw): simplify notificationclick event listener logic * fix(notif): add missing event description for MEDIA_AVAILABLE notifications
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { Notification } from '..';
|
||||
import type Issue from '../../../entity/Issue';
|
||||
import type Media from '../../../entity/Media';
|
||||
import IssueComment from '../../../entity/IssueComment';
|
||||
import Media from '../../../entity/Media';
|
||||
import { MediaRequest } from '../../../entity/MediaRequest';
|
||||
import { User } from '../../../entity/User';
|
||||
import { NotificationAgentConfig } from '../../settings';
|
||||
|
||||
export interface NotificationPayload {
|
||||
event?: string;
|
||||
subject: string;
|
||||
notifyAdmin: boolean;
|
||||
notifyUser?: User;
|
||||
media?: Media;
|
||||
image?: string;
|
||||
@@ -14,6 +17,7 @@ export interface NotificationPayload {
|
||||
extra?: { name: string; value: string }[];
|
||||
request?: MediaRequest;
|
||||
issue?: Issue;
|
||||
comment?: IssueComment;
|
||||
}
|
||||
|
||||
export abstract class BaseAgent<T extends NotificationAgentConfig> {
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { IssueStatus, IssueTypeNames } from '../../../constants/issue';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import {
|
||||
hasNotificationType,
|
||||
Notification,
|
||||
shouldSendAdminNotification,
|
||||
} from '..';
|
||||
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
||||
import { User } from '../../../entity/User';
|
||||
import logger from '../../../logger';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentDiscord,
|
||||
@@ -109,9 +111,9 @@ class DiscordAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): DiscordRichEmbed {
|
||||
const settings = getSettings();
|
||||
let color = EmbedColors.DARK_PURPLE;
|
||||
const { applicationUrl } = getSettings().main;
|
||||
|
||||
let color = EmbedColors.DARK_PURPLE;
|
||||
const fields: Field[] = [];
|
||||
|
||||
if (payload.request) {
|
||||
@@ -120,19 +122,55 @@ class DiscordAgent
|
||||
value: payload.request.requestedBy.displayName,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
// If payload has an issue attached, push issue specific fields
|
||||
if (payload.issue) {
|
||||
let status = '';
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
color = EmbedColors.ORANGE;
|
||||
status = 'Pending Approval';
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
color = EmbedColors.PURPLE;
|
||||
status = 'Processing';
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
color = EmbedColors.GREEN;
|
||||
status = 'Available';
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
color = EmbedColors.RED;
|
||||
status = 'Declined';
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
color = EmbedColors.RED;
|
||||
status = 'Failed';
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
fields.push({
|
||||
name: 'Request Status',
|
||||
value: status,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
} else if (payload.comment) {
|
||||
fields.push({
|
||||
name: `Comment from ${payload.comment.user.displayName}`,
|
||||
value: payload.comment.message,
|
||||
inline: false,
|
||||
});
|
||||
} else if (payload.issue) {
|
||||
fields.push(
|
||||
{
|
||||
name: 'Created By',
|
||||
name: 'Reported By',
|
||||
value: payload.issue.createdBy.displayName,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Issue Type',
|
||||
value: IssueTypeNames[payload.issue.issueType],
|
||||
value: IssueTypeName[payload.issue.issueType],
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
@@ -143,85 +181,35 @@ class DiscordAgent
|
||||
}
|
||||
);
|
||||
|
||||
if (payload.issue.media.mediaType === MediaType.TV) {
|
||||
fields.push({
|
||||
name: 'Affected Season',
|
||||
value:
|
||||
payload.issue.problemSeason > 0
|
||||
? `Season ${payload.issue.problemSeason}`
|
||||
: 'All Seasons',
|
||||
});
|
||||
|
||||
if (payload.issue.problemSeason > 0) {
|
||||
fields.push({
|
||||
name: 'Affected Episode',
|
||||
value:
|
||||
payload.issue.problemEpisode > 0
|
||||
? `Episode ${payload.issue.problemEpisode}`
|
||||
: 'All Episodes',
|
||||
});
|
||||
}
|
||||
switch (type) {
|
||||
case Notification.ISSUE_CREATED:
|
||||
case Notification.ISSUE_REOPENED:
|
||||
color = EmbedColors.RED;
|
||||
break;
|
||||
case Notification.ISSUE_COMMENT:
|
||||
color = EmbedColors.ORANGE;
|
||||
break;
|
||||
case Notification.ISSUE_RESOLVED:
|
||||
color = EmbedColors.GREEN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
color = EmbedColors.ORANGE;
|
||||
fields.push({
|
||||
name: 'Status',
|
||||
value: 'Pending Approval',
|
||||
inline: true,
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
color = EmbedColors.PURPLE;
|
||||
fields.push({
|
||||
name: 'Status',
|
||||
value: 'Processing',
|
||||
inline: true,
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
color = EmbedColors.GREEN;
|
||||
fields.push({
|
||||
name: 'Status',
|
||||
value: 'Available',
|
||||
inline: true,
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
color = EmbedColors.RED;
|
||||
fields.push({
|
||||
name: 'Status',
|
||||
value: 'Declined',
|
||||
inline: true,
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
color = EmbedColors.RED;
|
||||
fields.push({
|
||||
name: 'Status',
|
||||
value: 'Failed',
|
||||
inline: true,
|
||||
});
|
||||
break;
|
||||
case Notification.ISSUE_CREATED:
|
||||
case Notification.ISSUE_COMMENT:
|
||||
case Notification.ISSUE_RESOLVED:
|
||||
color = EmbedColors.ORANGE;
|
||||
|
||||
if (payload.issue && payload.issue.status === IssueStatus.RESOLVED) {
|
||||
color = EmbedColors.GREEN;
|
||||
}
|
||||
|
||||
break;
|
||||
for (const extra of payload.extra ?? []) {
|
||||
fields.push({
|
||||
name: extra.name,
|
||||
value: extra.value,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
|
||||
const url =
|
||||
settings.main.applicationUrl && payload.media
|
||||
? `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined;
|
||||
const url = applicationUrl
|
||||
? payload.issue
|
||||
? `${applicationUrl}/issue/${payload.issue.id}`
|
||||
: payload.media
|
||||
? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
title: payload.subject,
|
||||
@@ -229,18 +217,12 @@ class DiscordAgent
|
||||
description: payload.message,
|
||||
color,
|
||||
timestamp: new Date().toISOString(),
|
||||
author: {
|
||||
name: settings.main.applicationTitle,
|
||||
url: settings.main.applicationUrl,
|
||||
},
|
||||
fields: [
|
||||
...fields,
|
||||
// If we have extra data, map it to fields for discord notifications
|
||||
...(payload.extra ?? []).map((extra) => ({
|
||||
name: extra.name,
|
||||
value: extra.value,
|
||||
})),
|
||||
],
|
||||
author: payload.event
|
||||
? {
|
||||
name: payload.event,
|
||||
}
|
||||
: undefined,
|
||||
fields,
|
||||
thumbnail: {
|
||||
url: payload.image,
|
||||
},
|
||||
@@ -273,54 +255,53 @@ class DiscordAgent
|
||||
subject: payload.subject,
|
||||
});
|
||||
|
||||
let content = undefined;
|
||||
const userMentions: string[] = [];
|
||||
|
||||
try {
|
||||
if (payload.notifyUser) {
|
||||
// Mention user who submitted the request
|
||||
if (
|
||||
payload.notifyUser.settings?.hasNotificationType(
|
||||
NotificationAgentKey.DISCORD,
|
||||
type
|
||||
) &&
|
||||
payload.notifyUser.settings?.discordId
|
||||
payload.notifyUser.settings.discordId
|
||||
) {
|
||||
content = `<@${payload.notifyUser.settings.discordId}>`;
|
||||
userMentions.push(`<@${payload.notifyUser.settings.discordId}>`);
|
||||
}
|
||||
} else {
|
||||
// Mention all users with the Manage Requests permission
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
|
||||
content = users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
user.settings?.hasNotificationType(
|
||||
NotificationAgentKey.DISCORD,
|
||||
type
|
||||
) &&
|
||||
user.settings?.discordId &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
)
|
||||
.map((user) => `<@${user.settings?.discordId}>`)
|
||||
.join(' ');
|
||||
userMentions.push(
|
||||
...users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.settings?.hasNotificationType(
|
||||
NotificationAgentKey.DISCORD,
|
||||
type
|
||||
) &&
|
||||
user.settings.discordId &&
|
||||
shouldSendAdminNotification(type, user, payload)
|
||||
)
|
||||
.map((user) => `<@${user.settings?.discordId}>`)
|
||||
);
|
||||
}
|
||||
|
||||
await axios.post(settings.options.webhookUrl, {
|
||||
username: settings.options.botUsername,
|
||||
username: settings.options.botUsername
|
||||
? settings.options.botUsername
|
||||
: getSettings().main.applicationTitle,
|
||||
avatar_url: settings.options.botAvatarUrl,
|
||||
embeds: [this.buildEmbed(type, payload)],
|
||||
content,
|
||||
content: userMentions.join(' '),
|
||||
} as DiscordWebhookPayload);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.error('Error sending Discord notification', {
|
||||
label: 'Notifications',
|
||||
mentions: content,
|
||||
type: Notification[type],
|
||||
subject: payload.subject,
|
||||
errorMessage: e.message,
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { EmailOptions } from 'email-templates';
|
||||
import path from 'path';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { Notification } from '..';
|
||||
import { Notification, shouldSendAdminNotification } from '..';
|
||||
import { IssueType, IssueTypeName } from '../../../constants/issue';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import { User } from '../../../entity/User';
|
||||
import logger from '../../../logger';
|
||||
import PreparedEmail from '../../email';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentEmail,
|
||||
@@ -67,59 +67,34 @@ class EmailAgent
|
||||
};
|
||||
}
|
||||
|
||||
if (payload.media) {
|
||||
let requestType = '';
|
||||
const mediaType = payload.media
|
||||
? payload.media.mediaType === MediaType.MOVIE
|
||||
? 'movie'
|
||||
: 'series'
|
||||
: undefined;
|
||||
|
||||
if (payload.request) {
|
||||
let body = '';
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
requestType = `New ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
body = `A user has requested a new ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
}!`;
|
||||
body = `A new request for the following ${mediaType} is pending approval:`;
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
requestType = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Approved`;
|
||||
body = `Your request for the following ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
} has been approved:`;
|
||||
body = `Your request for the following ${mediaType} has been approved:`;
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
requestType = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Automatically Approved`;
|
||||
body = `A new request for the following ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
} has been automatically approved:`;
|
||||
body = `A new request for the following ${mediaType} has been automatically approved:`;
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
requestType = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Now Available`;
|
||||
body = `The following ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
} you requested is now available!`;
|
||||
body = `Your request for the following ${mediaType} is now available:`;
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
requestType = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Declined`;
|
||||
body = `Your request for the following ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
} was declined:`;
|
||||
body = `Your request for the following ${mediaType} was declined:`;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
requestType = `Failed ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
body = `A new request for the following ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'series' : 'movie'
|
||||
} could not be added to ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Sonarr' : 'Radarr'
|
||||
body = `A request for the following ${mediaType} failed to be added to ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'Radarr' : 'Sonarr'
|
||||
}:`;
|
||||
break;
|
||||
}
|
||||
@@ -133,14 +108,13 @@ class EmailAgent
|
||||
to: recipientEmail,
|
||||
},
|
||||
locals: {
|
||||
requestType,
|
||||
event: payload.event,
|
||||
body,
|
||||
mediaName: payload.subject,
|
||||
mediaPlot: payload.message,
|
||||
mediaExtra: payload.extra ?? [],
|
||||
imageUrl: payload.image,
|
||||
timestamp: new Date().toTimeString(),
|
||||
requestedBy: payload.request?.requestedBy.displayName,
|
||||
requestedBy: payload.request.requestedBy.displayName,
|
||||
actionUrl: applicationUrl
|
||||
? `${applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`
|
||||
: undefined,
|
||||
@@ -150,6 +124,52 @@ class EmailAgent
|
||||
recipientEmail,
|
||||
},
|
||||
};
|
||||
} else if (payload.issue) {
|
||||
const issueType =
|
||||
payload.issue && payload.issue.issueType !== IssueType.OTHER
|
||||
? `${IssueTypeName[payload.issue.issueType].toLowerCase()} issue`
|
||||
: 'issue';
|
||||
|
||||
let body = '';
|
||||
|
||||
switch (type) {
|
||||
case Notification.ISSUE_CREATED:
|
||||
body = `A new ${issueType} has been reported by ${payload.issue.createdBy.displayName} for the ${mediaType} ${payload.subject}:`;
|
||||
break;
|
||||
case Notification.ISSUE_COMMENT:
|
||||
body = `${payload.comment?.user.displayName} commented on the ${issueType} for the ${mediaType} ${payload.subject}:`;
|
||||
break;
|
||||
case Notification.ISSUE_RESOLVED:
|
||||
body = `The ${issueType} for the ${mediaType} ${payload.subject} was marked as resolved by ${payload.issue.modifiedBy?.displayName}!`;
|
||||
break;
|
||||
case Notification.ISSUE_REOPENED:
|
||||
body = `The ${issueType} for the ${mediaType} ${payload.subject} was reopened by ${payload.issue.modifiedBy?.displayName}.`;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
template: path.join(__dirname, '../../../templates/email/media-issue'),
|
||||
message: {
|
||||
to: recipientEmail,
|
||||
},
|
||||
locals: {
|
||||
event: payload.event,
|
||||
body,
|
||||
issueDescription: payload.message,
|
||||
issueComment: payload.comment?.message,
|
||||
mediaName: payload.subject,
|
||||
extra: payload.extra ?? [],
|
||||
imageUrl: payload.image,
|
||||
timestamp: new Date().toTimeString(),
|
||||
actionUrl: applicationUrl
|
||||
? `${applicationUrl}/issue/${payload.issue.id}`
|
||||
: undefined,
|
||||
applicationUrl,
|
||||
applicationTitle,
|
||||
recipientName,
|
||||
recipientEmail,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -160,7 +180,6 @@ class EmailAgent
|
||||
payload: NotificationPayload
|
||||
): Promise<boolean> {
|
||||
if (payload.notifyUser) {
|
||||
// Send notification to the user who submitted the request
|
||||
if (
|
||||
!payload.notifyUser.settings ||
|
||||
// Check if user has email notifications enabled and fallback to true if undefined
|
||||
@@ -203,8 +222,9 @@ class EmailAgent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send notifications to all users with the Manage Requests permission
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
|
||||
@@ -212,7 +232,6 @@ class EmailAgent
|
||||
users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
(!user.settings ||
|
||||
// Check if user has email notifications enabled and fallback to true if undefined
|
||||
// since email should default to true
|
||||
@@ -221,9 +240,7 @@ class EmailAgent
|
||||
type
|
||||
) ??
|
||||
true)) &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
shouldSendAdminNotification(type, user, payload)
|
||||
)
|
||||
.map(async (user) => {
|
||||
logger.debug('Sending email notification', {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { IssueStatus, IssueType } from '../../../constants/issue';
|
||||
import { MediaStatus } from '../../../constants/media';
|
||||
import logger from '../../../logger';
|
||||
import { getSettings, NotificationAgentLunaSea } from '../../settings';
|
||||
@@ -22,17 +23,17 @@ class LunaSeaAgent
|
||||
private buildPayload(type: Notification, payload: NotificationPayload) {
|
||||
return {
|
||||
notification_type: Notification[type],
|
||||
event: payload.event,
|
||||
subject: payload.subject,
|
||||
message: payload.message,
|
||||
image: payload.image ?? null,
|
||||
email: payload.notifyUser?.email,
|
||||
username: payload.notifyUser?.username,
|
||||
username: payload.notifyUser?.displayName,
|
||||
avatar: payload.notifyUser?.avatar,
|
||||
media: payload.media
|
||||
? {
|
||||
media_type: payload.media.mediaType,
|
||||
tmdbId: payload.media.tmdbId,
|
||||
imdbId: payload.media.imdbId,
|
||||
tvdbId: payload.media.tvdbId,
|
||||
status: MediaStatus[payload.media.status],
|
||||
status4k: MediaStatus[payload.media.status4k],
|
||||
@@ -47,6 +48,24 @@ class LunaSeaAgent
|
||||
requestedBy_avatar: payload.request.requestedBy.avatar,
|
||||
}
|
||||
: null,
|
||||
issue: payload.issue
|
||||
? {
|
||||
issue_id: payload.issue.id,
|
||||
issue_type: IssueType[payload.issue.issueType],
|
||||
issue_status: IssueStatus[payload.issue.status],
|
||||
createdBy_email: payload.issue.createdBy.email,
|
||||
createdBy_username: payload.issue.createdBy.displayName,
|
||||
createdBy_avatar: payload.issue.createdBy.avatar,
|
||||
}
|
||||
: null,
|
||||
comment: payload.comment
|
||||
? {
|
||||
comment_message: payload.comment.message,
|
||||
commentedBy_email: payload.comment.user.email,
|
||||
commentedBy_username: payload.comment.user.displayName,
|
||||
commentedBy_avatar: payload.comment.user.avatar,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import {
|
||||
hasNotificationType,
|
||||
Notification,
|
||||
shouldSendAdminNotification,
|
||||
} from '..';
|
||||
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
||||
import { User } from '../../../entity/User';
|
||||
import logger from '../../../logger';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentKey,
|
||||
@@ -40,94 +43,55 @@ class PushbulletAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): PushbulletPayload {
|
||||
let messageTitle = '';
|
||||
let message = '';
|
||||
const title = payload.event
|
||||
? `${payload.event} - ${payload.subject}`
|
||||
: payload.subject;
|
||||
let body = payload.message ?? '';
|
||||
|
||||
const title = payload.subject;
|
||||
const plot = payload.message;
|
||||
const username = payload.request?.requestedBy.displayName;
|
||||
if (payload.request) {
|
||||
body += `\n\nRequested By: ${payload.request.requestedBy.displayName}`;
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
messageTitle = `New ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Pending Approval`;
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Approved`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Processing`;
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Automatically Approved`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Processing`;
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Now Available`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Available`;
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Declined`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Declined`;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
messageTitle = `Failed ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
message += `${title}`;
|
||||
if (plot) {
|
||||
message += `\n\n${plot}`;
|
||||
}
|
||||
message += `\n\nRequested By: ${username}`;
|
||||
message += `\nStatus: Failed`;
|
||||
break;
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
messageTitle = 'Test Notification';
|
||||
message += `${plot}`;
|
||||
break;
|
||||
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) {
|
||||
body += `\nRequest Status: ${status}`;
|
||||
}
|
||||
} else if (payload.comment) {
|
||||
body += `\n\nComment from ${payload.comment.user.displayName}:\n${payload.comment.message}`;
|
||||
} else if (payload.issue) {
|
||||
body += `\n\nReported By: ${payload.issue.createdBy.displayName}`;
|
||||
body += `\nIssue Type: ${IssueTypeName[payload.issue.issueType]}`;
|
||||
body += `\nIssue Status: ${
|
||||
payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
|
||||
}`;
|
||||
}
|
||||
|
||||
for (const extra of payload.extra ?? []) {
|
||||
message += `\n${extra.name}: ${extra.value}`;
|
||||
body += `\n${extra.name}: ${extra.value}`;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'note',
|
||||
title: messageTitle,
|
||||
body: message,
|
||||
title,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -171,7 +135,6 @@ class PushbulletAgent
|
||||
}
|
||||
|
||||
if (payload.notifyUser) {
|
||||
// Send notification to the user who submitted the request
|
||||
if (
|
||||
payload.notifyUser.settings?.hasNotificationType(
|
||||
NotificationAgentKey.PUSHBULLET,
|
||||
@@ -207,8 +170,9 @@ class PushbulletAgent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send notifications to all users with the Manage Requests permission
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
|
||||
@@ -216,14 +180,10 @@ class PushbulletAgent
|
||||
users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
user.settings?.hasNotificationType(
|
||||
NotificationAgentKey.PUSHBULLET,
|
||||
type
|
||||
) &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
) && shouldSendAdminNotification(type, user, payload)
|
||||
)
|
||||
.map(async (user) => {
|
||||
if (
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import {
|
||||
hasNotificationType,
|
||||
Notification,
|
||||
shouldSendAdminNotification,
|
||||
} from '..';
|
||||
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
||||
import { User } from '../../../entity/User';
|
||||
import logger from '../../../logger';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentKey,
|
||||
@@ -45,103 +48,77 @@ class PushoverAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): Partial<PushoverPayload> {
|
||||
const settings = getSettings();
|
||||
let messageTitle = '';
|
||||
let message = '';
|
||||
let url: string | undefined;
|
||||
let url_title: string | undefined;
|
||||
const { applicationUrl, applicationTitle } = getSettings().main;
|
||||
|
||||
const title = payload.event ?? payload.subject;
|
||||
let message = payload.event ? `<b>${payload.subject}</b>` : '';
|
||||
let priority = 0;
|
||||
|
||||
const title = payload.subject;
|
||||
const plot = payload.message;
|
||||
const username = payload.request?.requestedBy.displayName;
|
||||
if (payload.message) {
|
||||
message += `<small>${message ? '\n' : ''}${payload.message}</small>`;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
messageTitle = `New ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nPending Approval</small>`;
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Approved`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nProcessing</small>`;
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Automatically Approved`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nProcessing</small>`;
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Now Available`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nAvailable</small>`;
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
messageTitle = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Declined`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nDeclined</small>`;
|
||||
if (payload.request) {
|
||||
message += `<small>\n\n<b>Requested By:</b> ${payload.request.requestedBy.displayName}</small>`;
|
||||
|
||||
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';
|
||||
priority = 1;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
status = 'Failed';
|
||||
priority = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
message += `<small>\n<b>Request Status:</b> ${status}</small>`;
|
||||
}
|
||||
} else if (payload.comment) {
|
||||
message += `<small>\n\n<b>Comment from ${payload.comment.user.displayName}:</b> ${payload.comment.message}</small>`;
|
||||
} else if (payload.issue) {
|
||||
message += `<small>\n\n<b>Reported By:</b> ${payload.issue.createdBy.displayName}</small>`;
|
||||
message += `<small>\n<b>Issue Type:</b> ${
|
||||
IssueTypeName[payload.issue.issueType]
|
||||
}</small>`;
|
||||
message += `<small>\n<b>Issue Status:</b> ${
|
||||
payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
|
||||
}</small>`;
|
||||
|
||||
if (type === Notification.ISSUE_CREATED) {
|
||||
priority = 1;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
messageTitle = `Failed ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
message += `<b>${title}</b>`;
|
||||
if (plot) {
|
||||
message += `<small>\n${plot}</small>`;
|
||||
}
|
||||
message += `<small>\n\n<b>Requested By</b>\n${username}</small>`;
|
||||
message += `<small>\n\n<b>Status</b>\nFailed</small>`;
|
||||
priority = 1;
|
||||
break;
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
messageTitle = 'Test Notification';
|
||||
message += `<small>${plot}</small>`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const extra of payload.extra ?? []) {
|
||||
message += `<small>\n\n<b>${extra.name}</b>\n${extra.value}</small>`;
|
||||
message += `<small>\n<b>${extra.name}:</b> ${extra.value}</small>`;
|
||||
}
|
||||
|
||||
if (settings.main.applicationUrl && payload.media) {
|
||||
url = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`;
|
||||
url_title = `Open in ${settings.main.applicationTitle}`;
|
||||
}
|
||||
const url = applicationUrl
|
||||
? payload.issue
|
||||
? `${applicationUrl}/issue/${payload.issue.id}`
|
||||
: payload.media
|
||||
? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined
|
||||
: undefined;
|
||||
const url_title = url
|
||||
? `View ${payload.issue ? 'Issue' : 'Media'} in ${applicationTitle}`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
title: messageTitle,
|
||||
title,
|
||||
message,
|
||||
url,
|
||||
url_title,
|
||||
@@ -191,7 +168,6 @@ class PushoverAgent
|
||||
}
|
||||
|
||||
if (payload.notifyUser) {
|
||||
// Send notification to the user who submitted the request
|
||||
if (
|
||||
payload.notifyUser.settings?.hasNotificationType(
|
||||
NotificationAgentKey.PUSHOVER,
|
||||
@@ -230,8 +206,9 @@ class PushoverAgent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send notifications to all users with the Manage Requests permission
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
|
||||
@@ -239,14 +216,10 @@ class PushoverAgent
|
||||
users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
user.settings?.hasNotificationType(
|
||||
NotificationAgentKey.PUSHOVER,
|
||||
type
|
||||
) &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
) && shouldSendAdminNotification(type, user, payload)
|
||||
)
|
||||
.map(async (user) => {
|
||||
if (
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
||||
import logger from '../../../logger';
|
||||
import { getSettings, NotificationAgentSlack } from '../../settings';
|
||||
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||
@@ -19,9 +19,10 @@ interface TextItem {
|
||||
interface Element {
|
||||
type: 'button';
|
||||
text?: TextItem;
|
||||
value: string;
|
||||
url: string;
|
||||
action_id: 'button-action';
|
||||
action_id: string;
|
||||
url?: string;
|
||||
value?: string;
|
||||
style?: 'primary' | 'danger';
|
||||
}
|
||||
|
||||
interface EmbedBlock {
|
||||
@@ -34,7 +35,7 @@ interface EmbedBlock {
|
||||
image_url: string;
|
||||
alt_text: string;
|
||||
};
|
||||
elements?: Element[];
|
||||
elements?: (Element | TextItem)[];
|
||||
}
|
||||
|
||||
interface SlackBlockEmbed {
|
||||
@@ -59,9 +60,7 @@ class SlackAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): SlackBlockEmbed {
|
||||
const settings = getSettings();
|
||||
let header = '';
|
||||
let actionUrl: string | undefined;
|
||||
const { applicationUrl, applicationTitle } = getSettings().main;
|
||||
|
||||
const fields: EmbedField[] = [];
|
||||
|
||||
@@ -70,66 +69,55 @@ class SlackAgent
|
||||
type: 'mrkdwn',
|
||||
text: `*Requested By*\n${payload.request.requestedBy.displayName}`,
|
||||
});
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
header = `New ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
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: '*Status*\nPending Approval',
|
||||
text: `*Request Status*\n${status}`,
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
header = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Approved`;
|
||||
fields.push({
|
||||
}
|
||||
} 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: '*Status*\nProcessing',
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
header = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Automatically Approved`;
|
||||
fields.push({
|
||||
text: `*Reported By*\n${payload.issue.createdBy.displayName}`,
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nProcessing',
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
header = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Now Available`;
|
||||
fields.push({
|
||||
text: `*Issue Type*\n${IssueTypeName[payload.issue.issueType]}`,
|
||||
},
|
||||
{
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nAvailable',
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
header = `${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Declined`;
|
||||
fields.push({
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nDeclined',
|
||||
});
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
header = `Failed ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request`;
|
||||
fields.push({
|
||||
type: 'mrkdwn',
|
||||
text: '*Status*\nFailed',
|
||||
});
|
||||
break;
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
header = 'Test Notification';
|
||||
break;
|
||||
text: `*Issue Status*\n${
|
||||
payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
|
||||
}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const extra of payload.extra ?? []) {
|
||||
@@ -139,30 +127,28 @@ class SlackAgent
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.main.applicationUrl && payload.media) {
|
||||
actionUrl = `${settings.main.applicationUrl}/${payload.media?.mediaType}/${payload.media?.tmdbId}`;
|
||||
}
|
||||
const blocks: EmbedBlock[] = [];
|
||||
|
||||
const blocks: EmbedBlock[] = [
|
||||
{
|
||||
type: 'header',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: header,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (type !== Notification.TEST_NOTIFICATION) {
|
||||
if (payload.event) {
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `*${payload.subject}*`,
|
||||
},
|
||||
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',
|
||||
@@ -183,30 +169,31 @@ class SlackAgent
|
||||
if (fields.length > 0) {
|
||||
blocks.push({
|
||||
type: 'section',
|
||||
fields: [
|
||||
...fields,
|
||||
...(payload.extra ?? []).map(
|
||||
(extra): EmbedField => ({
|
||||
type: 'mrkdwn',
|
||||
text: `*${extra.name}*\n${extra.value}`,
|
||||
})
|
||||
),
|
||||
],
|
||||
fields,
|
||||
});
|
||||
}
|
||||
|
||||
if (actionUrl) {
|
||||
const url = applicationUrl
|
||||
? payload.issue
|
||||
? `${applicationUrl}/issue/${payload.issue.id}`
|
||||
: payload.media
|
||||
? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
if (url) {
|
||||
blocks.push({
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
action_id: 'button-action',
|
||||
action_id: 'open-in-overseerr',
|
||||
type: 'button',
|
||||
url: actionUrl,
|
||||
value: 'open_overseerr',
|
||||
url,
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: `Open in ${settings.main.applicationTitle}`,
|
||||
text: `View ${
|
||||
payload.issue ? 'Issue' : 'Media'
|
||||
} in ${applicationTitle}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { getRepository } from 'typeorm';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import {
|
||||
hasNotificationType,
|
||||
Notification,
|
||||
shouldSendAdminNotification,
|
||||
} from '..';
|
||||
import { IssueStatus, IssueTypeName } from '../../../constants/issue';
|
||||
import { User } from '../../../entity/User';
|
||||
import logger from '../../../logger';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentKey,
|
||||
@@ -61,95 +64,74 @@ class TelegramAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): Partial<TelegramMessagePayload | TelegramPhotoPayload> {
|
||||
const settings = getSettings();
|
||||
let message = '';
|
||||
|
||||
const title = this.escapeText(payload.subject);
|
||||
const plot = this.escapeText(payload.message);
|
||||
const user = this.escapeText(payload.request?.requestedBy.displayName);
|
||||
const applicationTitle = this.escapeText(settings.main.applicationTitle);
|
||||
const { applicationUrl, applicationTitle } = getSettings().main;
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
message += `\*New ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nPending Approval`;
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
message += `\*${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Approved\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nProcessing`;
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
message += `\*${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Automatically Approved\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nProcessing`;
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
message += `\*${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Now Available\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nAvailable`;
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
message += `\*${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request Declined\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nDeclined`;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
message += `\*Failed ${
|
||||
payload.media?.mediaType === MediaType.TV ? 'Series' : 'Movie'
|
||||
} Request\*`;
|
||||
message += `\n\n\*${title}\*`;
|
||||
if (plot) {
|
||||
message += `\n${plot}`;
|
||||
}
|
||||
message += `\n\n\*Requested By\*\n${user}`;
|
||||
message += `\n\n\*Status\*\nFailed`;
|
||||
break;
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
message += `\*Test Notification\*`;
|
||||
message += `\n\n${plot}`;
|
||||
break;
|
||||
let message = `\*${this.escapeText(
|
||||
payload.event ? `${payload.event} - ${payload.subject}` : payload.subject
|
||||
)}\*`;
|
||||
if (payload.message) {
|
||||
message += `\n${this.escapeText(payload.message)}`;
|
||||
}
|
||||
|
||||
if (payload.request) {
|
||||
message += `\n\n\*Requested By:\* ${this.escapeText(
|
||||
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) {
|
||||
message += `\n\*Request Status:\* ${status}`;
|
||||
}
|
||||
} else if (payload.comment) {
|
||||
message += `\n\n\*Comment from ${this.escapeText(
|
||||
payload.comment.user.displayName
|
||||
)}:\* ${this.escapeText(payload.comment.message)}`;
|
||||
} else if (payload.issue) {
|
||||
message += `\n\n\*Reported By:\* ${this.escapeText(
|
||||
payload.issue.createdBy.displayName
|
||||
)}`;
|
||||
message += `\n\*Issue Type:\* ${IssueTypeName[payload.issue.issueType]}`;
|
||||
message += `\n\*Issue Status:\* ${
|
||||
payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
|
||||
}`;
|
||||
}
|
||||
|
||||
for (const extra of payload.extra ?? []) {
|
||||
message += `\n\n\*${extra.name}\*\n${extra.value}`;
|
||||
message += `\n\*${extra.name}:\* ${extra.value}`;
|
||||
}
|
||||
|
||||
if (settings.main.applicationUrl && payload.media) {
|
||||
const actionUrl = `${settings.main.applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`;
|
||||
message += `\n\n\[Open in ${applicationTitle}\]\(${actionUrl}\)`;
|
||||
const url = applicationUrl
|
||||
? payload.issue
|
||||
? `${applicationUrl}/issue/${payload.issue.id}`
|
||||
: payload.media
|
||||
? `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
if (url) {
|
||||
message += `\n\n\[View ${
|
||||
payload.issue ? 'Issue' : 'Media'
|
||||
} in ${this.escapeText(applicationTitle)}\]\(${url}\)`;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
@@ -206,7 +188,6 @@ class TelegramAgent
|
||||
}
|
||||
|
||||
if (payload.notifyUser) {
|
||||
// Send notification to the user who submitted the request
|
||||
if (
|
||||
payload.notifyUser.settings?.hasNotificationType(
|
||||
NotificationAgentKey.TELEGRAM,
|
||||
@@ -242,8 +223,9 @@ class TelegramAgent
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send notifications to all users with the Manage Requests permission
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const userRepository = getRepository(User);
|
||||
const users = await userRepository.find();
|
||||
|
||||
@@ -251,14 +233,10 @@ class TelegramAgent
|
||||
users
|
||||
.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
user.settings?.hasNotificationType(
|
||||
NotificationAgentKey.TELEGRAM,
|
||||
type
|
||||
) &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
) && shouldSendAdminNotification(type, user, payload)
|
||||
)
|
||||
.map(async (user) => {
|
||||
if (
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import { get } from 'lodash';
|
||||
import { hasNotificationType, Notification } from '..';
|
||||
import { IssueStatus, IssueType } from '../../../constants/issue';
|
||||
import { MediaStatus } from '../../../constants/media';
|
||||
import logger from '../../../logger';
|
||||
import { getSettings, NotificationAgentWebhook } from '../../settings';
|
||||
@@ -13,6 +14,7 @@ type KeyMapFunction = (
|
||||
|
||||
const KeyMap: Record<string, string | KeyMapFunction> = {
|
||||
notification_type: (_payload, type) => Notification[type],
|
||||
event: 'event',
|
||||
subject: 'subject',
|
||||
message: 'message',
|
||||
image: 'image',
|
||||
@@ -22,13 +24,12 @@ const KeyMap: Record<string, string | KeyMapFunction> = {
|
||||
notifyuser_settings_discordId: 'notifyUser.settings.discordId',
|
||||
notifyuser_settings_telegramChatId: 'notifyUser.settings.telegramChatId',
|
||||
media_tmdbid: 'media.tmdbId',
|
||||
media_imdbid: 'media.imdbId',
|
||||
media_tvdbid: 'media.tvdbId',
|
||||
media_type: 'media.mediaType',
|
||||
media_status: (payload) =>
|
||||
payload.media?.status ? MediaStatus[payload.media?.status] : '',
|
||||
payload.media ? MediaStatus[payload.media.status] : '',
|
||||
media_status4k: (payload) =>
|
||||
payload.media?.status ? MediaStatus[payload.media?.status4k] : '',
|
||||
payload.media ? MediaStatus[payload.media.status4k] : '',
|
||||
request_id: 'request.id',
|
||||
requestedBy_username: 'request.requestedBy.displayName',
|
||||
requestedBy_email: 'request.requestedBy.email',
|
||||
@@ -36,6 +37,22 @@ const KeyMap: Record<string, string | KeyMapFunction> = {
|
||||
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
|
||||
@@ -78,6 +95,22 @@ class WebhookAgent
|
||||
}
|
||||
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') {
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { getRepository } from 'typeorm';
|
||||
import webpush from 'web-push';
|
||||
import { Notification } from '..';
|
||||
import { Notification, shouldSendAdminNotification } from '..';
|
||||
import { IssueType, IssueTypeName } from '../../../constants/issue';
|
||||
import { MediaType } from '../../../constants/media';
|
||||
import { User } from '../../../entity/User';
|
||||
import { UserPushSubscription } from '../../../entity/UserPushSubscription';
|
||||
import logger from '../../../logger';
|
||||
import { Permission } from '../../permissions';
|
||||
import {
|
||||
getSettings,
|
||||
NotificationAgentConfig,
|
||||
@@ -15,12 +15,11 @@ import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
|
||||
|
||||
interface PushNotificationPayload {
|
||||
notificationType: string;
|
||||
mediaType?: 'movie' | 'tv';
|
||||
tmdbId?: number;
|
||||
subject: string;
|
||||
message?: string;
|
||||
image?: string;
|
||||
actionUrl?: string;
|
||||
actionUrlTitle?: string;
|
||||
requestId?: number;
|
||||
}
|
||||
|
||||
@@ -42,97 +41,79 @@ class WebPushAgent
|
||||
type: Notification,
|
||||
payload: NotificationPayload
|
||||
): PushNotificationPayload {
|
||||
const mediaType = payload.media
|
||||
? payload.media.mediaType === MediaType.MOVIE
|
||||
? 'movie'
|
||||
: 'series'
|
||||
: undefined;
|
||||
|
||||
const issueType = payload.issue
|
||||
? payload.issue.issueType !== IssueType.OTHER
|
||||
? `${IssueTypeName[payload.issue.issueType].toLowerCase()} issue`
|
||||
: 'issue'
|
||||
: undefined;
|
||||
|
||||
let message: string | undefined;
|
||||
switch (type) {
|
||||
case Notification.TEST_NOTIFICATION:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: payload.message,
|
||||
};
|
||||
message = payload.message;
|
||||
break;
|
||||
case Notification.MEDIA_APPROVED:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Your ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request has been approved.`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Your ${mediaType} request has been approved.`;
|
||||
break;
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Automatically approved a new ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request from ${payload.request?.requestedBy.displayName}.`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Automatically approved a new ${mediaType} request from ${payload.request?.requestedBy.displayName}.`;
|
||||
break;
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Your ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request is now available!`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Your ${mediaType} request is now available!`;
|
||||
break;
|
||||
case Notification.MEDIA_DECLINED:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Your ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request was declined.`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Your ${mediaType} request was declined.`;
|
||||
break;
|
||||
case Notification.MEDIA_FAILED:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Failed to process ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request.`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Failed to process ${mediaType} request.`;
|
||||
break;
|
||||
case Notification.MEDIA_PENDING:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message: `Approval required for new ${
|
||||
payload.media?.mediaType === MediaType.MOVIE ? 'movie' : 'series'
|
||||
} request from ${payload.request?.requestedBy.displayName}.`,
|
||||
image: payload.image,
|
||||
mediaType: payload.media?.mediaType,
|
||||
tmdbId: payload.media?.tmdbId,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`,
|
||||
};
|
||||
message = `Approval required for a new ${mediaType} request from ${payload.request?.requestedBy.displayName}.`;
|
||||
break;
|
||||
case Notification.ISSUE_CREATED:
|
||||
message = `A new ${issueType} was reported by ${payload.issue?.createdBy.displayName}.`;
|
||||
break;
|
||||
case Notification.ISSUE_COMMENT:
|
||||
message = `${payload.comment?.user.displayName} commented on the ${issueType}.`;
|
||||
break;
|
||||
case Notification.ISSUE_RESOLVED:
|
||||
message = `The ${issueType} was marked as resolved by ${payload.issue?.modifiedBy?.displayName}!`;
|
||||
break;
|
||||
case Notification.ISSUE_REOPENED:
|
||||
message = `The ${issueType} was reopened by ${payload.issue?.modifiedBy?.displayName}.`;
|
||||
break;
|
||||
default:
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: 'Unknown',
|
||||
};
|
||||
}
|
||||
|
||||
const actionUrl = payload.issue
|
||||
? `/issue/${payload.issue.id}`
|
||||
: payload.media
|
||||
? `/${payload.media.mediaType}/${payload.media.tmdbId}`
|
||||
: undefined;
|
||||
|
||||
const actionUrlTitle = actionUrl
|
||||
? `View ${payload.issue ? 'Issue' : 'Media'}`
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
notificationType: Notification[type],
|
||||
subject: payload.subject,
|
||||
message,
|
||||
image: payload.image,
|
||||
requestId: payload.request?.id,
|
||||
actionUrl,
|
||||
actionUrlTitle,
|
||||
};
|
||||
}
|
||||
|
||||
public shouldSend(): boolean {
|
||||
@@ -151,7 +132,7 @@ class WebPushAgent
|
||||
const userPushSubRepository = getRepository(UserPushSubscription);
|
||||
const settings = getSettings();
|
||||
|
||||
let pushSubs: UserPushSubscription[] = [];
|
||||
const pushSubs: UserPushSubscription[] = [];
|
||||
|
||||
const mainUser = await userRepository.findOne({ where: { id: 1 } });
|
||||
|
||||
@@ -169,13 +150,14 @@ class WebPushAgent
|
||||
where: { user: payload.notifyUser.id },
|
||||
});
|
||||
|
||||
pushSubs = notifySubs;
|
||||
} else if (!payload.notifyUser) {
|
||||
pushSubs.push(...notifySubs);
|
||||
}
|
||||
|
||||
if (payload.notifyAdmin) {
|
||||
const users = await userRepository.find();
|
||||
|
||||
const manageUsers = users.filter(
|
||||
(user) =>
|
||||
user.hasPermission(Permission.MANAGE_REQUESTS) &&
|
||||
// Check if user has webpush notifications enabled and fallback to true if undefined
|
||||
// since web push should default to true
|
||||
(user.settings?.hasNotificationType(
|
||||
@@ -183,9 +165,7 @@ class WebPushAgent
|
||||
type
|
||||
) ??
|
||||
true) &&
|
||||
// Check if it's the user's own auto-approved request
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !== payload.request?.requestedBy.id)
|
||||
shouldSendAdminNotification(type, user, payload)
|
||||
);
|
||||
|
||||
const allSubs = await userPushSubRepository
|
||||
@@ -196,7 +176,7 @@ class WebPushAgent
|
||||
})
|
||||
.getMany();
|
||||
|
||||
pushSubs = allSubs;
|
||||
pushSubs.push(...allSubs);
|
||||
}
|
||||
|
||||
if (mainUser && pushSubs.length > 0) {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { User } from '../../entity/User';
|
||||
import logger from '../../logger';
|
||||
import { Permission } from '../permissions';
|
||||
import type { NotificationAgent, NotificationPayload } from './agents/agent';
|
||||
|
||||
export enum Notification {
|
||||
@@ -13,6 +15,7 @@ export enum Notification {
|
||||
ISSUE_CREATED = 256,
|
||||
ISSUE_COMMENT = 512,
|
||||
ISSUE_RESOLVED = 1024,
|
||||
ISSUE_REOPENED = 2048,
|
||||
}
|
||||
|
||||
export const hasNotificationType = (
|
||||
@@ -41,6 +44,50 @@ export const hasNotificationType = (
|
||||
return !!(value & total);
|
||||
};
|
||||
|
||||
export const getAdminPermission = (type: Notification): Permission => {
|
||||
switch (type) {
|
||||
case Notification.MEDIA_PENDING:
|
||||
case Notification.MEDIA_APPROVED:
|
||||
case Notification.MEDIA_AVAILABLE:
|
||||
case Notification.MEDIA_FAILED:
|
||||
case Notification.MEDIA_DECLINED:
|
||||
case Notification.MEDIA_AUTO_APPROVED:
|
||||
return Permission.MANAGE_REQUESTS;
|
||||
case Notification.ISSUE_CREATED:
|
||||
case Notification.ISSUE_COMMENT:
|
||||
case Notification.ISSUE_RESOLVED:
|
||||
case Notification.ISSUE_REOPENED:
|
||||
return Permission.MANAGE_ISSUES;
|
||||
default:
|
||||
return Permission.ADMIN;
|
||||
}
|
||||
};
|
||||
|
||||
export const shouldSendAdminNotification = (
|
||||
type: Notification,
|
||||
user: User,
|
||||
payload: NotificationPayload
|
||||
): boolean => {
|
||||
return (
|
||||
user.id !== payload.notifyUser?.id &&
|
||||
user.hasPermission(getAdminPermission(type)) &&
|
||||
// Check if the user submitted this request (on behalf of themself OR another user)
|
||||
(type !== Notification.MEDIA_AUTO_APPROVED ||
|
||||
user.id !==
|
||||
(payload.request?.modifiedBy ?? payload.request?.requestedBy)?.id) &&
|
||||
// Check if the user created this issue
|
||||
(type !== Notification.ISSUE_CREATED ||
|
||||
user.id !== payload.issue?.createdBy.id) &&
|
||||
// Check if the user submitted this issue comment
|
||||
(type !== Notification.ISSUE_COMMENT ||
|
||||
user.id !== payload.comment?.user.id) &&
|
||||
// Check if the user resolved/reopened this issue
|
||||
((type !== Notification.ISSUE_RESOLVED &&
|
||||
type !== Notification.ISSUE_REOPENED) ||
|
||||
user.id !== payload.issue?.modifiedBy?.id)
|
||||
);
|
||||
};
|
||||
|
||||
class NotificationManager {
|
||||
private activeAgents: NotificationAgent[] = [];
|
||||
|
||||
|
Reference in New Issue
Block a user