feat(notifications): make embedded posters optional (#1364)

* feat(notifications): make images optional

* fix(notifications): added en i18n config

* fix: prettify

* fix(notifications): added embedImage support for ntfy

* fix(frontend): update embedImage on form state change and submission

* fix(locale): updated locale for embedImage

* fix: renamed embedImage to embedPoster
This commit is contained in:
Ishan Jain
2025-09-08 17:41:31 +05:30
committed by GitHub
parent 6245dae3b3
commit 479be0daeb
18 changed files with 149 additions and 28 deletions

View File

@@ -109,7 +109,9 @@ class DiscordAgent
type: Notification,
payload: NotificationPayload
): DiscordRichEmbed {
const { applicationUrl } = getSettings().main;
const settings = getSettings();
const { applicationUrl } = settings.main;
const { embedPoster } = settings.notifications.agents.discord;
const appUrl =
applicationUrl || `http://localhost:${process.env.port || 5055}`;
@@ -223,9 +225,11 @@ class DiscordAgent
}
: undefined,
fields,
thumbnail: {
url: payload.image,
},
thumbnail: embedPoster
? {
url: payload.image,
}
: undefined,
};
}

View File

@@ -48,7 +48,9 @@ class EmailAgent
recipientEmail: string,
recipientName?: string
): EmailOptions | undefined {
const { applicationUrl, applicationTitle } = getSettings().main;
const settings = getSettings();
const { applicationUrl, applicationTitle } = settings.main;
const { embedPoster } = settings.notifications.agents.email;
if (type === Notification.TEST_NOTIFICATION) {
return {
@@ -129,7 +131,7 @@ class EmailAgent
body,
mediaName: payload.subject,
mediaExtra: payload.extra ?? [],
imageUrl: payload.image,
imageUrl: embedPoster ? payload.image : undefined,
timestamp: new Date().toTimeString(),
requestedBy: payload.request.requestedBy.displayName,
actionUrl: applicationUrl
@@ -176,7 +178,7 @@ class EmailAgent
issueComment: payload.comment?.message,
mediaName: payload.subject,
extra: payload.extra ?? [],
imageUrl: payload.image,
imageUrl: embedPoster ? payload.image : undefined,
timestamp: new Date().toTimeString(),
actionUrl: applicationUrl
? `${applicationUrl}/issues/${payload.issue.id}`

View File

@@ -22,7 +22,9 @@ class NtfyAgent
}
private buildPayload(type: Notification, payload: NotificationPayload) {
const { applicationUrl } = getSettings().main;
const settings = getSettings();
const { applicationUrl } = settings.main;
const { embedPoster } = settings.notifications.agents.ntfy;
const topic = this.getSettings().options.topic;
const priority = 3;
@@ -72,7 +74,7 @@ class NtfyAgent
message += `\n\n**${extra.name}**\n${extra.value}`;
}
const attach = payload.image;
const attach = embedPoster ? payload.image : undefined;
let click;
if (applicationUrl && payload.media) {

View File

@@ -78,7 +78,9 @@ class PushoverAgent
type: Notification,
payload: NotificationPayload
): Promise<Partial<PushoverPayload>> {
const { applicationUrl, applicationTitle } = getSettings().main;
const settings = getSettings();
const { applicationUrl, applicationTitle } = settings.main;
const { embedPoster } = settings.notifications.agents.pushover;
const title = payload.event ?? payload.subject;
let message = payload.event ? `<b>${payload.subject}</b>` : '';
@@ -155,7 +157,7 @@ class PushoverAgent
let attachment_base64;
let attachment_type;
if (payload.image) {
if (embedPoster && payload.image) {
const imagePayload = await this.getImagePayload(payload.image);
if (imagePayload.attachment_base64 && imagePayload.attachment_type) {
attachment_base64 = imagePayload.attachment_base64;

View File

@@ -63,7 +63,9 @@ class SlackAgent
type: Notification,
payload: NotificationPayload
): SlackBlockEmbed {
const { applicationUrl, applicationTitle } = getSettings().main;
const settings = getSettings();
const { applicationUrl, applicationTitle } = settings.main;
const { embedPoster } = settings.notifications.agents.slack;
const fields: EmbedField[] = [];
@@ -159,13 +161,14 @@ class SlackAgent
type: 'mrkdwn',
text: payload.message,
},
accessory: payload.image
? {
type: 'image',
image_url: payload.image,
alt_text: payload.subject,
}
: undefined,
accessory:
embedPoster && payload.image
? {
type: 'image',
image_url: payload.image,
alt_text: payload.subject,
}
: undefined,
});
}

View File

@@ -65,7 +65,9 @@ class TelegramAgent
type: Notification,
payload: NotificationPayload
): Partial<TelegramMessagePayload | TelegramPhotoPayload> {
const { applicationUrl, applicationTitle } = getSettings().main;
const settings = getSettings();
const { applicationUrl, applicationTitle } = settings.main;
const { embedPoster } = settings.notifications.agents.telegram;
/* eslint-disable no-useless-escape */
let message = `\*${this.escapeText(
@@ -142,7 +144,7 @@ class TelegramAgent
}
/* eslint-enable */
return payload.image
return embedPoster && payload.image
? {
photo: payload.image,
caption: message,
@@ -160,7 +162,7 @@ class TelegramAgent
): Promise<boolean> {
const settings = this.getSettings();
const endpoint = `${this.baseUrl}bot${settings.options.botAPI}/${
payload.image ? 'sendPhoto' : 'sendMessage'
settings.embedPoster && payload.image ? 'sendPhoto' : 'sendMessage'
}`;
const notificationPayload = this.getNotificationPayload(type, payload);

View File

@@ -42,6 +42,8 @@ class WebPushAgent
type: Notification,
payload: NotificationPayload
): PushNotificationPayload {
const { embedPoster } = getSettings().notifications.agents.webpush;
const mediaType = payload.media
? payload.media.mediaType === MediaType.MOVIE
? 'movie'
@@ -128,7 +130,7 @@ class WebPushAgent
notificationType: Notification[type],
subject: payload.subject,
message,
image: payload.image,
image: embedPoster ? payload.image : undefined,
requestId: payload.request?.id,
actionUrl,
actionUrlTitle,

View File

@@ -207,6 +207,7 @@ interface FullPublicSettings extends PublicSettings {
export interface NotificationAgentConfig {
enabled: boolean;
embedPoster: boolean;
types?: number;
options: Record<string, unknown>;
}
@@ -434,6 +435,7 @@ class Settings {
agents: {
email: {
enabled: false,
embedPoster: true,
options: {
userEmailRequired: false,
emailFrom: '',
@@ -448,6 +450,7 @@ class Settings {
},
discord: {
enabled: false,
embedPoster: true,
types: 0,
options: {
webhookUrl: '',
@@ -457,6 +460,7 @@ class Settings {
},
slack: {
enabled: false,
embedPoster: true,
types: 0,
options: {
webhookUrl: '',
@@ -464,6 +468,7 @@ class Settings {
},
telegram: {
enabled: false,
embedPoster: true,
types: 0,
options: {
botAPI: '',
@@ -474,6 +479,7 @@ class Settings {
},
pushbullet: {
enabled: false,
embedPoster: false,
types: 0,
options: {
accessToken: '',
@@ -481,6 +487,7 @@ class Settings {
},
pushover: {
enabled: false,
embedPoster: true,
types: 0,
options: {
accessToken: '',
@@ -490,6 +497,7 @@ class Settings {
},
webhook: {
enabled: false,
embedPoster: true,
types: 0,
options: {
webhookUrl: '',
@@ -499,10 +507,12 @@ class Settings {
},
webpush: {
enabled: false,
embedPoster: true,
options: {},
},
gotify: {
enabled: false,
embedPoster: false,
types: 0,
options: {
url: '',
@@ -512,6 +522,7 @@ class Settings {
},
ntfy: {
enabled: false,
embedPoster: true,
types: 0,
options: {
url: '',

View File

@@ -270,6 +270,7 @@ notificationRoutes.get('/webhook', (_req, res) => {
const response: typeof webhookSettings = {
enabled: webhookSettings.enabled,
embedPoster: webhookSettings.embedPoster,
types: webhookSettings.types,
options: {
...webhookSettings.options,
@@ -291,6 +292,7 @@ notificationRoutes.post('/webhook', async (req, res, next) => {
settings.notifications.agents.webhook = {
enabled: req.body.enabled,
embedPoster: req.body.embedPoster,
types: req.body.types,
options: {
jsonPayload: Buffer.from(req.body.options.jsonPayload).toString(
@@ -321,6 +323,7 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => {
const testBody = {
enabled: req.body.enabled,
embedPoster: req.body.embedPoster,
types: req.body.types,
options: {
jsonPayload: Buffer.from(req.body.options.jsonPayload).toString(

View File

@@ -53,10 +53,11 @@ div(style='display: block; background-color: #111827; padding: 2.5rem 0;')
b(style='color: #9ca3af; font-weight: 700;')
| #{extra.name}&nbsp;
| #{extra.value}
td(rowspan='2' style='width: 7rem;')
a(style='display: block; width: 7rem; overflow: hidden; border-radius: .375rem;' href=actionUrl)
div(style='overflow: hidden; box-sizing: border-box; margin: 0px;')
img(alt='' src=imageUrl style='box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%;')
if imageUrl
td(rowspan='2' style='width: 7rem;')
a(style='display: block; width: 7rem; overflow: hidden; border-radius: .375rem;' href=actionUrl)
div(style='overflow: hidden; box-sizing: border-box; margin: 0px;')
img(alt='' src=imageUrl style='box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%;')
tr
td(style='font-size: .85em; color: #9ca3af; line-height: 1em; vertical-align: bottom; margin-right: 1rem')
span