From 6675ce50868d5379bedbd6923337935529e21d32 Mon Sep 17 00:00:00 2001 From: GivenBY Date: Sat, 3 Jan 2026 20:52:39 +0530 Subject: [PATCH] Fix: escape Telegram MarkdownV2 after template rendering --- server/notification-providers/telegram.js | 78 ++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index a98c326d7..5ba971d76 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -1,9 +1,84 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); +const { Liquid } = require("liquidjs"); +const { DOWN } = require("../../src/util"); 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, "\\$&"); + } + + /** + * Renders template with optional MarkdownV2 escaping + * @param {string} template The template + * @param {string} msg Base message + * @param {?object} monitorJSON Monitor details + * @param {?object} heartbeatJSON Heartbeat details + * @param {boolean} escapeMarkdown Whether to escape for MarkdownV2 + * @returns {Promise} Rendered template + */ + async renderTemplate(template, msg, monitorJSON, heartbeatJSON, escapeMarkdown = false) { + const engine = new Liquid({ + root: "./no-such-directory-uptime-kuma", + relativeReference: false, + dynamicPartials: false, + }); + + const parsedTpl = engine.parse(template); + + // Defaults + let monitorName = "Monitor Name not available"; + let monitorHostnameOrURL = "testing.hostname"; + + if (monitorJSON !== null) { + monitorName = monitorJSON.name; + monitorHostnameOrURL = this.extractAddress(monitorJSON); + } + + let serviceStatus = "⚠️ Test"; + if (heartbeatJSON !== null) { + serviceStatus = heartbeatJSON.status === DOWN ? "🔴 Down" : "✅ Up"; + } + + // Escape values only when MarkdownV2 is enabled + if (escapeMarkdown) { + msg = this.escapeMarkdownV2(msg); + monitorName = this.escapeMarkdownV2(monitorName); + monitorHostnameOrURL = this.escapeMarkdownV2(monitorHostnameOrURL); + serviceStatus = this.escapeMarkdownV2(serviceStatus); + } + + const context = { + // v1 compatibility (remove in v3) + STATUS: serviceStatus, + NAME: monitorName, + HOSTNAME_OR_URL: monitorHostnameOrURL, + + // Official variables + status: serviceStatus, + name: monitorName, + hostnameOrURL: monitorHostnameOrURL, + monitorJSON, + heartbeatJSON, + msg, + }; + + return engine.render(parsedTpl, context); + } + /** * @inheritdoc */ @@ -24,7 +99,8 @@ class Telegram extends NotificationProvider { } if (notification.telegramUseTemplate) { - params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON); + const escapeMarkdown = notification.telegramTemplateParseMode === "MarkdownV2"; + params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON, escapeMarkdown); if (notification.telegramTemplateParseMode !== "plain") { params.parse_mode = notification.telegramTemplateParseMode;