From ac2ede5bb1d55b4119b95f6420c55c20123783fc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 16 Feb 2026 04:18:17 +0100 Subject: [PATCH] fix(telegram): treat no-op editMessage as success --- src/telegram/send.test.ts | 16 ++++++++++++++++ src/telegram/send.ts | 26 ++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/telegram/send.test.ts b/src/telegram/send.test.ts index 690a4776db..db51b20914 100644 --- a/src/telegram/send.test.ts +++ b/src/telegram/send.test.ts @@ -1321,6 +1321,22 @@ describe("editMessageTelegram", () => { ); }); + it("treats 'message is not modified' as success", async () => { + botApi.editMessageText.mockRejectedValueOnce( + new Error( + "400: Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message", + ), + ); + + await expect( + editMessageTelegram("123", 1, "hi", { + token: "tok", + cfg: {}, + }), + ).resolves.toEqual({ ok: true, messageId: "1", chatId: "123" }); + expect(botApi.editMessageText).toHaveBeenCalledTimes(1); + }); + it("disables link previews when linkPreview is false", async () => { botApi.editMessageText.mockResolvedValue({ message_id: 1, chat: { id: "123" } }); diff --git a/src/telegram/send.ts b/src/telegram/send.ts index d4c4dd0e9b..ec451844f6 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -74,6 +74,8 @@ type TelegramReactionOpts = { const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i; const THREAD_NOT_FOUND_RE = /400:\s*Bad Request:\s*message thread not found/i; +const MESSAGE_NOT_MODIFIED_RE = + /400:\s*Bad Request:\s*message is not modified|MESSAGE_NOT_MODIFIED/i; const diagLogger = createSubsystemLogger("telegram/diagnostic"); function createTelegramHttpLogger(cfg: ReturnType) { @@ -182,6 +184,10 @@ function isTelegramThreadNotFoundError(err: unknown): boolean { return THREAD_NOT_FOUND_RE.test(formatErrorMessage(err)); } +function isTelegramMessageNotModifiedError(err: unknown): boolean { + return MESSAGE_NOT_MODIFIED_RE.test(formatErrorMessage(err)); +} + function hasMessageThreadIdParam(params?: Record): boolean { if (!params) { return false; @@ -730,10 +736,15 @@ export async function editMessageTelegram( verbose: opts.verbose, }); const logHttpError = createTelegramHttpLogger(cfg); - const requestWithDiag = (fn: () => Promise, label?: string) => + const requestWithDiag = ( + fn: () => Promise, + label?: string, + shouldLog?: (err: unknown) => boolean, + ) => withTelegramApiErrorLogging({ operation: label ?? "request", fn: () => request(fn, label), + shouldLog, }).catch((err) => { logHttpError(label ?? "request", err); throw err; @@ -768,7 +779,12 @@ export async function editMessageTelegram( await requestWithDiag( () => api.editMessageText(chatId, messageId, htmlText, editParams), "editMessage", + (err) => !isTelegramMessageNotModifiedError(err), ).catch(async (err) => { + if (isTelegramMessageNotModifiedError(err)) { + return; + } + // Telegram rejects malformed HTML. Fall back to plain text. const errText = formatErrorMessage(err); if (PARSE_ERR_RE.test(errText)) { @@ -788,7 +804,13 @@ export async function editMessageTelegram( ? api.editMessageText(chatId, messageId, text, plainParams) : api.editMessageText(chatId, messageId, text), "editMessage-plain", - ); + (plainErr) => !isTelegramMessageNotModifiedError(plainErr), + ).catch((plainErr) => { + if (isTelegramMessageNotModifiedError(plainErr)) { + return; + } + throw plainErr; + }); } throw err; });