From 479be0daeb2384bf17facca510425d7ee43eda90 Mon Sep 17 00:00:00 2001 From: Ishan Jain Date: Mon, 8 Sep 2025 17:41:31 +0530 Subject: [PATCH] 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 --- server/lib/notifications/agents/discord.ts | 12 ++++++++---- server/lib/notifications/agents/email.ts | 8 +++++--- server/lib/notifications/agents/ntfy.ts | 6 ++++-- server/lib/notifications/agents/pushover.ts | 6 ++++-- server/lib/notifications/agents/slack.ts | 19 +++++++++++-------- server/lib/notifications/agents/telegram.ts | 8 +++++--- server/lib/notifications/agents/webpush.ts | 4 +++- server/lib/settings/index.ts | 11 +++++++++++ server/routes/settings/notifications.ts | 3 +++ server/templates/email/media-request/html.pug | 9 +++++---- .../Notifications/NotificationsDiscord.tsx | 12 ++++++++++++ .../Notifications/NotificationsEmail.tsx | 12 ++++++++++++ .../Notifications/NotificationsNtfy/index.tsx | 11 +++++++++++ .../NotificationsPushover/index.tsx | 12 ++++++++++++ .../NotificationsSlack/index.tsx | 12 ++++++++++++ .../Notifications/NotificationsTelegram.tsx | 11 +++++++++++ .../NotificationsWebPush/index.tsx | 15 ++++++++++++++- src/i18n/locale/en.json | 6 ++++++ 18 files changed, 149 insertions(+), 28 deletions(-) diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index cabd332de..149a7fbc1 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -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, }; } diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index 59c5b4aa7..c33cc8949 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -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}` diff --git a/server/lib/notifications/agents/ntfy.ts b/server/lib/notifications/agents/ntfy.ts index 005e9aa15..b533cd173 100644 --- a/server/lib/notifications/agents/ntfy.ts +++ b/server/lib/notifications/agents/ntfy.ts @@ -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) { diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index db5176a76..c95b1d063 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -78,7 +78,9 @@ class PushoverAgent type: Notification, payload: NotificationPayload ): Promise> { - 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 ? `${payload.subject}` : ''; @@ -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; diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts index 8f1f0c953..f5dd2442d 100644 --- a/server/lib/notifications/agents/slack.ts +++ b/server/lib/notifications/agents/slack.ts @@ -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, }); } diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index 01d4de497..0a1ad1005 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -65,7 +65,9 @@ class TelegramAgent type: Notification, payload: NotificationPayload ): Partial { - 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 { 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); diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index 56472018e..fcb7416e0 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -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, diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 60e4769d8..29c7ed041 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -207,6 +207,7 @@ interface FullPublicSettings extends PublicSettings { export interface NotificationAgentConfig { enabled: boolean; + embedPoster: boolean; types?: number; options: Record; } @@ -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: '', diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts index cee96b7d7..122ef017c 100644 --- a/server/routes/settings/notifications.ts +++ b/server/routes/settings/notifications.ts @@ -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( diff --git a/server/templates/email/media-request/html.pug b/server/templates/email/media-request/html.pug index 334095dfe..cd698c205 100644 --- a/server/templates/email/media-request/html.pug +++ b/server/templates/email/media-request/html.pug @@ -53,10 +53,11 @@ div(style='display: block; background-color: #111827; padding: 2.5rem 0;') b(style='color: #9ca3af; font-weight: 700;') | #{extra.name}  | #{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 diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index 6abea6a2b..d6e66c73d 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -15,6 +15,7 @@ import * as Yup from 'yup'; const messages = defineMessages('components.Settings.Notifications', { agentenabled: 'Enable Agent', + embedPoster: 'Embed Poster', botUsername: 'Bot Username', botAvatarUrl: 'Bot Avatar URL', webhookUrl: 'Webhook URL', @@ -74,6 +75,7 @@ const NotificationsDiscord = () => { { try { await axios.post('/api/v1/settings/notifications/discord', { enabled: values.enabled, + embedPoster: values.embedPoster, types: values.types, options: { botUsername: values.botUsername, @@ -135,6 +138,7 @@ const NotificationsDiscord = () => { ); await axios.post('/api/v1/settings/notifications/discord/test', { enabled: true, + embedPoster: values.embedPoster, types: values.types, options: { botUsername: values.botUsername, @@ -176,6 +180,14 @@ const NotificationsDiscord = () => { +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 0049d4d75..63a207c17 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -624,6 +624,7 @@ "components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "You must provide a valid URL", "components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.Notifications.NotificationsNtfy.agentenabled": "Enable Agent", + "components.Settings.Notifications.NotificationsNtfy.embedPoster": "Embed Poster", "components.Settings.Notifications.NotificationsNtfy.ntfysettingsfailed": "Ntfy notification settings failed to save.", "components.Settings.Notifications.NotificationsNtfy.ntfysettingssaved": "Ntfy notification settings saved successfully!", "components.Settings.Notifications.NotificationsNtfy.password": "Password", @@ -654,6 +655,7 @@ "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Register an application for use with Jellyseerr", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Device Default", + "components.Settings.Notifications.NotificationsPushover.embedPoster": "Embed Poster", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.", "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notification settings saved successfully!", "components.Settings.Notifications.NotificationsPushover.sound": "Notification Sound", @@ -666,6 +668,7 @@ "components.Settings.Notifications.NotificationsPushover.validationTypes": "You must select at least one notification type", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user or group key", "components.Settings.Notifications.NotificationsSlack.agentenabled": "Enable Agent", + "components.Settings.Notifications.NotificationsSlack.embedPoster": "Embed Poster", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved successfully!", "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack test notification failed to send.", @@ -691,6 +694,7 @@ "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notification settings failed to save.", "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook notification settings saved successfully!", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent", + "components.Settings.Notifications.NotificationsWebPush.embedPoster": "Embed Poster", "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…", @@ -713,6 +717,7 @@ "components.Settings.Notifications.emailsender": "Sender Address", "components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.", "components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!", + "components.Settings.Notifications.embedPoster": "Embed Poster", "components.Settings.Notifications.enableMentions": "Enable Mentions", "components.Settings.Notifications.encryption": "Encryption Method", "components.Settings.Notifications.encryptionDefault": "Use STARTTLS if available", @@ -1158,6 +1163,7 @@ "components.Settings.menuServices": "Services", "components.Settings.menuUsers": "Users", "components.Settings.metadataProviderSelection": "Metadata Provider Selection", + "components.Settings.metadataProviderSettings": "Metadata Providers", "components.Settings.metadataSettings": "Settings for metadata provider", "components.Settings.metadataSettingsSaved": "Metadata provider settings saved", "components.Settings.no": "No",