fix: escape HTML and truncate long messages in Telegram notifications

This commit is contained in:
Yash Kumar
2026-10-16 15:53:30 +05:30
parent 53464e9096
commit 7430afd701
7 changed files with 46 additions and 7 deletions
@@ -5,6 +5,7 @@ import { render } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
formatTelegramErrorMessage,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -201,7 +202,7 @@ export const sendBuildErrorNotifications = async ({
await sendTelegramNotification(
telegram,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${formatTelegramErrorMessage(errorMessage)}</pre>`,
inlineButton,
);
}
@@ -5,6 +5,7 @@ import { render } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
formatTelegramErrorMessage,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -208,7 +209,7 @@ export const sendDatabaseBackupNotifications = async ({
const statusEmoji = type === "success" ? "✅" : "❌";
const typeStatus = type === "success" ? "Successful" : "Failed";
const errorMsg = isError
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
? `\n\n<b>Error:</b>\n<pre>${formatTelegramErrorMessage(errorMessage)}</pre>`
: "";
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Database Name:</b> ${databaseName}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
@@ -5,6 +5,7 @@ import { render } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
escapeHtml,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -143,7 +144,7 @@ export const sendDockerCleanupNotifications = async (
if (telegram) {
await sendTelegramNotification(
telegram,
`<b>✅ Docker Cleanup</b>\n\n<b>Message:</b> ${message}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
`<b>✅ Docker Cleanup</b>\n\n<b>Message:</b> ${escapeHtml(message)}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
);
}
@@ -5,6 +5,7 @@ import { render } from "@react-email/components";
import { format } from "date-fns";
import { eq } from "drizzle-orm";
import {
formatTelegramErrorMessage,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -186,7 +187,7 @@ export const sendDokployBackupNotifications = async ({
const statusEmoji = type === "success" ? "✅" : "❌";
const typeStatus = type === "success" ? "Successful" : "Failed";
const errorMsg = isError
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
? `\n\n<b>Error:</b>\n<pre>${formatTelegramErrorMessage(errorMessage)}</pre>`
: "";
const sizeInfo = backupSize
? `\n<b>Backup Size:</b> ${backupSize}`
@@ -4,6 +4,7 @@ import { db } from "../../db";
import { notifications } from "../../db/schema";
import ServerThresholdEmail from "../../emails/emails/server-threshold";
import {
escapeHtml,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -193,7 +194,7 @@ export const sendServerThresholdNotifications = async (
<b>Type:</b> ${payload.Type}
<b>Current Value:</b> ${payload.Value.toFixed(2)}%
<b>Threshold:</b> ${payload.Threshold.toFixed(2)}%
<b>Message:</b> ${payload.Message}
<b>Message:</b> ${escapeHtml(payload.Message)}
<b>Time:</b> ${date.toLocaleString()}
`,
);
@@ -101,6 +101,30 @@ export const sendDiscordNotification = async (
}
};
export const escapeHtml = (s: string) =>
s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
export const formatTelegramErrorMessage = (errorMessage: string) => {
const escaped = escapeHtml(errorMessage);
const limit = 3000;
if (escaped.length <= limit) return escaped;
let end = limit;
const ampIdx = escaped.lastIndexOf("&", end);
if (ampIdx !== -1 && escaped.indexOf(";", ampIdx) > end) {
end = ampIdx;
}
if (
end > 0 &&
end < escaped.length &&
isHighSurrogate(escaped.charCodeAt(end - 1))
) {
end--;
}
return `${escaped.substring(0, end)}…`;
};
const isHighSurrogate = (code: number) => code >= 0xd800 && code <= 0xdbff;
export const sendTelegramNotification = async (
connection: typeof telegram.$inferInsert,
messageText: string,
@@ -111,7 +135,7 @@ export const sendTelegramNotification = async (
) => {
try {
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
await fetch(url, {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -125,8 +149,17 @@ export const sendTelegramNotification = async (
},
}),
});
if (!response.ok) {
const body = await response.text();
throw new Error(
`Failed to send telegram notification: ${response.status} ${body}`,
);
}
} catch (err) {
console.log(err);
throw new Error(
`Failed to send telegram notification ${err instanceof Error ? err.message : "Unknown error"}`,
);
}
};
@@ -5,6 +5,7 @@ import { render } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
formatTelegramErrorMessage,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -223,7 +224,7 @@ export const sendVolumeBackupNotifications = async ({
const statusEmoji = type === "success" ? "✅" : "❌";
const typeStatus = type === "success" ? "Successful" : "Failed";
const errorMsg = isError
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
? `\n\n<b>Error:</b>\n<pre>${formatTelegramErrorMessage(errorMessage)}</pre>`
: "";
const sizeInfo = backupSize
? `\n<b>Backup Size:</b> ${backupSize}`