diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index a98c326d7..790f06a69 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -4,6 +4,47 @@ const axios = require("axios"); class Telegram extends NotificationProvider { name = "telegram"; + /** + * Escapes special characters for Telegram MarkdownV2 format + * @param {string} text Text to escape + * @returns {string} Escaped text + */ + escapeMarkdownV2(text) { + if (!text) { + return text; + } + + // Characters that need to be escaped in MarkdownV2 + // https://core.telegram.org/bots/api#markdownv2-style + return String(text).replace(/[_*[\]()~>#+\-=|{}.!\\]/g, "\\$&"); + } + + /** + * Recursively escapes string properties of an object for Telegram MarkdownV2 + * @param {object|string} obj Object or string to escape + * @returns {object|string} Escaped object or string + */ + escapeObjectRecursive(obj) { + if (typeof obj === "string") { + return this.escapeMarkdownV2(obj); + } + if (typeof obj === "object" && obj !== null) { + // Check if array + if (Array.isArray(obj)) { + return obj.map(item => this.escapeObjectRecursive(item)); + } + + const newObj = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + newObj[key] = this.escapeObjectRecursive(obj[key]); + } + } + return newObj; + } + return obj; + } + /** * @inheritdoc */ @@ -24,7 +65,29 @@ class Telegram extends NotificationProvider { } if (notification.telegramUseTemplate) { - params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON); + let monitorJSONCopy = monitorJSON; + let heartbeatJSONCopy = heartbeatJSON; + + if (notification.telegramTemplateParseMode === "MarkdownV2") { + msg = this.escapeMarkdownV2(msg); + + if (monitorJSONCopy) { + monitorJSONCopy = this.escapeObjectRecursive(monitorJSONCopy); + } else { + // for testing monitorJSON is null, provide escaped defaults + monitorJSONCopy = { + name: this.escapeMarkdownV2("Monitor Name not available"), + hostname: this.escapeMarkdownV2("testing.hostname"), + url: this.escapeMarkdownV2("testing.hostname"), + }; + } + + if (heartbeatJSONCopy) { + heartbeatJSONCopy = this.escapeObjectRecursive(heartbeatJSONCopy); + } + } + + params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSONCopy, heartbeatJSONCopy); if (notification.telegramTemplateParseMode !== "plain") { params.parse_mode = notification.telegramTemplateParseMode;