From 1ded5cc9a937ae07476b8bdd2f3e29b8b1bc3381 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 3 Mar 2026 17:00:40 +0530 Subject: [PATCH] fix: guard malformed Telegram replies and pass hook accountId --- src/telegram/bot-message-dispatch.ts | 1 + src/telegram/bot-native-commands.ts | 6 +++- src/telegram/bot/delivery.replies.ts | 8 +++-- src/telegram/bot/delivery.test.ts | 50 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index 5a7d795c1f9..7dfcc80e6a8 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -385,6 +385,7 @@ export const dispatchTelegramMessage = async ({ }; const deliveryBaseOptions = { chatId: String(chatId), + accountId: route.accountId, token: opts.token, runtime, bot, diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 1c6ec8767e9..efe5821005a 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -445,12 +445,14 @@ export const registerTelegramNativeCommands = ({ }; const buildCommandDeliveryBaseOptions = (params: { chatId: string | number; + accountId: string; mediaLocalRoots?: readonly string[]; threadSpec: ReturnType; tableMode: ReturnType; chunkMode: ReturnType; }) => ({ chatId: String(params.chatId), + accountId: params.accountId, token: opts.token, runtime, bot, @@ -513,6 +515,7 @@ export const registerTelegramNativeCommands = ({ }); const deliveryBaseOptions = buildCommandDeliveryBaseOptions({ chatId, + accountId: route.accountId, mediaLocalRoots, threadSpec, tableMode, @@ -726,7 +729,7 @@ export const registerTelegramNativeCommands = ({ return; } const { senderId, commandAuthorized, isGroup, isForum, resolvedThreadId } = auth; - const { threadSpec, mediaLocalRoots, tableMode, chunkMode } = + const { threadSpec, route, mediaLocalRoots, tableMode, chunkMode } = resolveCommandRuntimeContext({ msg, isGroup, @@ -735,6 +738,7 @@ export const registerTelegramNativeCommands = ({ }); const deliveryBaseOptions = buildCommandDeliveryBaseOptions({ chatId, + accountId: route.accountId, mediaLocalRoots, threadSpec, tableMode, diff --git a/src/telegram/bot/delivery.replies.ts b/src/telegram/bot/delivery.replies.ts index b6fef8dd91b..2b9c941a6bd 100644 --- a/src/telegram/bot/delivery.replies.ts +++ b/src/telegram/bot/delivery.replies.ts @@ -428,6 +428,7 @@ async function deliverMediaReply(params: { export async function deliverReplies(params: { replies: ReplyPayload[]; chatId: string; + accountId?: string; token: string; runtime: RuntimeEnv; bot: Bot; @@ -459,9 +460,9 @@ export async function deliverReplies(params: { }); for (const originalReply of params.replies) { let reply = originalReply; - const mediaList = reply.mediaUrls?.length + const mediaList = reply?.mediaUrls?.length ? reply.mediaUrls - : reply.mediaUrl + : reply?.mediaUrl ? [reply.mediaUrl] : []; const hasMedia = mediaList.length > 0; @@ -488,6 +489,7 @@ export async function deliverReplies(params: { }, { channelId: "telegram", + accountId: params.accountId, conversationId: params.chatId, }, ); @@ -555,6 +557,7 @@ export async function deliverReplies(params: { }, { channelId: "telegram", + accountId: params.accountId, conversationId: params.chatId, }, ); @@ -570,6 +573,7 @@ export async function deliverReplies(params: { }, { channelId: "telegram", + accountId: params.accountId, conversationId: params.chatId, }, ); diff --git a/src/telegram/bot/delivery.test.ts b/src/telegram/bot/delivery.test.ts index 50704b0cc4a..122c697183d 100644 --- a/src/telegram/bot/delivery.test.ts +++ b/src/telegram/bot/delivery.test.ts @@ -126,6 +126,22 @@ describe("deliverReplies", () => { expect(runtime.error).not.toHaveBeenCalled(); }); + it("skips malformed replies and continues with valid entries", async () => { + const runtime = createRuntime(false); + const sendMessage = vi.fn().mockResolvedValue({ message_id: 1, chat: { id: "123" } }); + const bot = createBot({ sendMessage }); + + await deliverWith({ + replies: [undefined, { text: "hello" }] as unknown as DeliverRepliesParams["replies"], + runtime, + bot, + }); + + expect(runtime.error).toHaveBeenCalledTimes(1); + expect(sendMessage).toHaveBeenCalledTimes(1); + expect(sendMessage.mock.calls[0]?.[1]).toBe("hello"); + }); + it("reports message_sent success=false when hooks blank out a text-only reply", async () => { messageHookRunner.hasHooks.mockImplementation( (name: string) => name === "message_sending" || name === "message_sent", @@ -149,6 +165,40 @@ describe("deliverReplies", () => { ); }); + it("passes accountId into message hooks", async () => { + messageHookRunner.hasHooks.mockImplementation( + (name: string) => name === "message_sending" || name === "message_sent", + ); + + const runtime = createRuntime(false); + const sendMessage = vi.fn().mockResolvedValue({ message_id: 9, chat: { id: "123" } }); + const bot = createBot({ sendMessage }); + + await deliverWith({ + accountId: "work", + replies: [{ text: "hello" }], + runtime, + bot, + }); + + expect(messageHookRunner.runMessageSending).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + channelId: "telegram", + accountId: "work", + conversationId: "123", + }), + ); + expect(messageHookRunner.runMessageSent).toHaveBeenCalledWith( + expect.objectContaining({ success: true }), + expect.objectContaining({ + channelId: "telegram", + accountId: "work", + conversationId: "123", + }), + ); + }); + it("passes media metadata to message_sending hooks", async () => { messageHookRunner.hasHooks.mockImplementation((name: string) => name === "message_sending");