diff --git a/extensions/telegram/src/action-runtime.test.ts b/extensions/telegram/src/action-runtime.test.ts index a050d3c9a183..e644c97bd18c 100644 --- a/extensions/telegram/src/action-runtime.test.ts +++ b/extensions/telegram/src/action-runtime.test.ts @@ -561,6 +561,60 @@ describe("handleTelegramAction", () => { expect(options.mediaUrl).toBe("https://example.com/image.jpg"); }); + it.each(["path", "filePath"] as const)("uses top-level %s as sendMessage media", async (key) => { + const mediaPath = `/tmp/customer_support_${key}.png`; + await handleTelegramAction( + { + action: "sendMessage", + to: "telegram:-100123:topic:879", + message: "Productivity", + [key]: mediaPath, + }, + telegramConfig(), + ); + const call = mockCall(sendMessageTelegram, 0, `${key} media`); + expect(call[0]).toBe("telegram:-100123:topic:879"); + expect(call[1]).toBe("Productivity"); + const options = requireRecord(call[2], `${key} media options`); + expect(options.token).toBe("tok"); + expect(options.mediaUrl).toBe(mediaPath); + }); + + it("sends all attachment paths as sendMessage media", async () => { + await handleTelegramAction( + { + action: "sendMessage", + to: "telegram:-100123:topic:879", + message: "1/2 Productivity", + attachments: [ + { + type: "image", + path: "/tmp/customer_support_productivity.png", + name: "customer_support_productivity.png", + }, + { + type: "image", + filePath: "/tmp/customer_support_resolution.png", + name: "customer_support_resolution.png", + }, + ], + }, + telegramConfig(), + ); + const call = mockCall(sendMessageTelegram, 0, "attachment media"); + expect(call[0]).toBe("telegram:-100123:topic:879"); + expect(call[1]).toBe("1/2 Productivity"); + const options = requireRecord(call[2], "attachment media options"); + expect(options.token).toBe("tok"); + expect(options.mediaUrl).toBe("/tmp/customer_support_productivity.png"); + const followUpCall = mockCall(sendMessageTelegram, 1, "second attachment media"); + expect(followUpCall[0]).toBe("telegram:-100123:topic:879"); + expect(followUpCall[1]).toBe(""); + const followUpOptions = requireRecord(followUpCall[2], "second attachment media options"); + expect(followUpOptions.token).toBe("tok"); + expect(followUpOptions.mediaUrl).toBe("/tmp/customer_support_resolution.png"); + }); + it("sends a poll", async () => { const result = await handleTelegramAction( { diff --git a/extensions/telegram/src/action-runtime.ts b/extensions/telegram/src/action-runtime.ts index c26e745139cf..add761e0435f 100644 --- a/extensions/telegram/src/action-runtime.ts +++ b/extensions/telegram/src/action-runtime.ts @@ -16,6 +16,7 @@ import { renderMessagePresentationFallbackText, } from "openclaw/plugin-sdk/interactive-runtime"; import type { MessagePresentation } from "openclaw/plugin-sdk/interactive-runtime"; +import { sendPayloadMediaSequenceOrFallback } from "openclaw/plugin-sdk/reply-payload"; import { createTelegramActionGate, resolveTelegramPollActionGateState } from "./accounts.js"; import { resolveTelegramInlineButtons } from "./button-types.js"; import { notifyTelegramInboundEventOutboundSuccess } from "./inbound-event-delivery.js"; @@ -132,6 +133,48 @@ function readTelegramReplyToMessageId(params: Record) { ); } +function pushTelegramMediaUrl(mediaUrls: string[], seen: Set, value: unknown): void { + if (typeof value !== "string") { + return; + } + const normalized = value.trim(); + if (!normalized || seen.has(normalized)) { + return; + } + seen.add(normalized); + mediaUrls.push(normalized); +} + +function readTelegramSendMediaUrls(params: Record) { + const mediaUrls: string[] = []; + const seen = new Set(); + pushTelegramMediaUrl(mediaUrls, seen, params.mediaUrl); + pushTelegramMediaUrl(mediaUrls, seen, params.media); + pushTelegramMediaUrl(mediaUrls, seen, params.path); + pushTelegramMediaUrl(mediaUrls, seen, params.filePath); + pushTelegramMediaUrl(mediaUrls, seen, params.fileUrl); + if (Array.isArray(params.mediaUrls)) { + for (const mediaUrl of params.mediaUrls) { + pushTelegramMediaUrl(mediaUrls, seen, mediaUrl); + } + } + if (Array.isArray(params.attachments)) { + for (const attachment of params.attachments) { + if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) { + continue; + } + const record = attachment as Record; + pushTelegramMediaUrl(mediaUrls, seen, record.media); + pushTelegramMediaUrl(mediaUrls, seen, record.mediaUrl); + pushTelegramMediaUrl(mediaUrls, seen, record.path); + pushTelegramMediaUrl(mediaUrls, seen, record.filePath); + pushTelegramMediaUrl(mediaUrls, seen, record.fileUrl); + pushTelegramMediaUrl(mediaUrls, seen, record.url); + } + } + return mediaUrls; +} + function resolveTelegramButtonsFromParams( params: Record, presentation = normalizeMessagePresentation(params.presentation), @@ -350,16 +393,13 @@ export async function handleTelegramAction( throw new Error("Telegram sendMessage is disabled."); } const to = readStringParam(params, "to", { required: true }); - const mediaUrl = - readStringParam(params, "mediaUrl") ?? - readStringParam(params, "media", { - trim: false, - }); + const mediaUrls = readTelegramSendMediaUrls(params); + const firstMediaUrl = mediaUrls[0]; const presentation = normalizeMessagePresentation(params.presentation); const buttons = resolveTelegramButtonsFromParams(params, presentation); const content = readTelegramSendContent({ args: params, - mediaUrl: mediaUrl ?? undefined, + mediaUrl: firstMediaUrl, hasButtons: Array.isArray(buttons) && buttons.length > 0, interactive: params.interactive, presentation, @@ -401,15 +441,13 @@ export async function handleTelegramAction( "Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.", ); } - const result = await telegramActionRuntime.sendMessageTelegram(to, content, { + const sendOptions = { cfg, token, accountId: accountId ?? undefined, - mediaUrl: mediaUrl || undefined, mediaLocalRoots: options?.mediaLocalRoots, mediaReadFile: options?.mediaReadFile, gatewayClientScopes: options?.gatewayClientScopes, - buttons, replyToMessageId: replyToMessageId ?? undefined, messageThreadId: messageThreadId ?? undefined, quoteText: quoteText ?? undefined, @@ -419,6 +457,22 @@ export async function handleTelegramAction( readBooleanParam(params, "forceDocument") ?? readBooleanParam(params, "asDocument") ?? false, + }; + const result = await sendPayloadMediaSequenceOrFallback({ + text: content, + mediaUrls, + fallbackResult: { messageId: "unknown", chatId: to }, + sendNoMedia: async () => + await telegramActionRuntime.sendMessageTelegram(to, content, { + ...sendOptions, + buttons, + }), + send: async ({ text, mediaUrl, isFirst }) => + await telegramActionRuntime.sendMessageTelegram(to, text, { + ...sendOptions, + mediaUrl, + ...(isFirst ? { buttons } : {}), + }), }); notifyVisibleOutboundSuccess(to, messageThreadId); await maybePinTelegramActionSend({