From c3847811374f124a25e0f3d18aea1b53ee9c658a Mon Sep 17 00:00:00 2001 From: Adrian Bartnik <3998715+adrianbartnik@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:05:57 +0200 Subject: [PATCH] Add toggle for plain text message to telegram notification adapter (#299) --- lib/notification/adapter/telegram.js | 42 ++++++++++++++++--- .../NotificationAdapterMutator.jsx | 23 ++++++---- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/lib/notification/adapter/telegram.js b/lib/notification/adapter/telegram.js index d687028..ee1aad5 100644 --- a/lib/notification/adapter/telegram.js +++ b/lib/notification/adapter/telegram.js @@ -105,6 +105,32 @@ function buildText(jobName, serviceName, o) { ); } +/** + * Build a plain text Telegram photo caption (max 4096 characters). + * @param {string} jobName + * @param {string} serviceName + * @param {Object} o - Listing object + * @returns {string} + */ +function buildCaptionPlain(jobName, serviceName, o) { + const title = shorten((o.title || '').replace(/\*/g, ''), 90); + const meta = [o.address, o.price, o.size].filter(Boolean).join(' | '); + return `${jobName} (${serviceName})\n${title}\n${meta}\n\n${o.link || ''}`.slice(0, 4096); +} + +/** + * Build a plain text Telegram message. + * @param {string} jobName + * @param {string} serviceName + * @param {Object} o - Listing object + * @returns {string} + */ +function buildTextPlain(jobName, serviceName, o) { + const title = shorten((o.title || '').replace(/\*/g, ''), 90); + const meta = [o.address, o.price, o.size].filter(Boolean).join(' | '); + return `${jobName} (${serviceName})\n${title}\n${o.link || ''}\n${meta}`; +} + /** * Send new listings to Telegram. * - Respects per-chat Telegram rate limits using a lightweight throttle cache. @@ -122,7 +148,7 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey if (!adapterCfg || !adapterCfg.fields) { throw new Error(`Telegram adapter configuration missing for job '${jobKey || ''}'`); } - const { token, chatId, messageThreadId } = adapterCfg.fields; + const { token, chatId, messageThreadId, plainText } = adapterCfg.fields; if (!token || !chatId) { throw new Error("Telegram 'token' and 'chatId' must be provided in notification config"); } @@ -163,8 +189,8 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey const img = normalizeImageUrl(o.image); const textPayload = { chat_id: chatId, - text: buildText(jobName, serviceName, o), - parse_mode: 'HTML', + text: plainText ? buildTextPlain(jobName, serviceName, o) : buildText(jobName, serviceName, o), + ...(plainText ? {} : { parse_mode: 'HTML' }), disable_web_page_preview: true, ...(message_thread_id ? { message_thread_id } : {}), }; @@ -178,8 +204,8 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey return await throttledCall('sendPhoto', { chat_id: chatId, photo: img, - caption: buildCaption(jobName, serviceName, o), - parse_mode: 'HTML', + caption: plainText ? buildCaptionPlain(jobName, serviceName, o) : buildCaption(jobName, serviceName, o), + ...(plainText ? {} : { parse_mode: 'HTML' }), ...(message_thread_id ? { message_thread_id } : {}), }).catch(async (e) => { logger.error(`Error sending photo to Telegram and use a fallback: ${e.message}`); @@ -220,5 +246,11 @@ export const config = { description: 'Optional: The topic/thread id within a supergroup to post into (Telegram message_thread_id). Provide a positive integer.', }, + plainText: { + type: 'boolean', + optional: true, + label: 'Send as plain text', + description: 'Send messages as plain text instead of HTML formatted.', + }, }, }; diff --git a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx index ad432d0..581f8d6 100644 --- a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx +++ b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx @@ -156,14 +156,21 @@ export default function NotificationAdapterMutator({ return (
{uiElement.type === 'boolean' ? ( -
- { - setValue(selectedAdapter, uiElement, key, checked); - }} - /> - {uiElement.label} +
+
+ { + setValue(selectedAdapter, uiElement, key, checked); + }} + /> + {uiElement.label} +
+ {uiElement.description && ( +
+ {uiElement.description} +
+ )}
) : (