diff --git a/extensions/telegram/src/button-types.test.ts b/extensions/telegram/src/button-types.test.ts new file mode 100644 index 00000000000..849caac62ac --- /dev/null +++ b/extensions/telegram/src/button-types.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from "vitest"; +import { buildTelegramInteractiveButtons, resolveTelegramInlineButtons } from "./button-types.js"; + +describe("buildTelegramInteractiveButtons", () => { + it("maps shared buttons and selects into Telegram inline rows", () => { + expect( + buildTelegramInteractiveButtons({ + blocks: [ + { + type: "buttons", + buttons: [ + { label: "Approve", value: "approve", style: "success" }, + { label: "Reject", value: "reject", style: "danger" }, + { label: "Later", value: "later" }, + { label: "Archive", value: "archive" }, + ], + }, + { + type: "select", + options: [{ label: "Alpha", value: "alpha" }], + }, + ], + }), + ).toEqual([ + [ + { text: "Approve", callback_data: "approve", style: "success" }, + { text: "Reject", callback_data: "reject", style: "danger" }, + { text: "Later", callback_data: "later", style: undefined }, + ], + [{ text: "Archive", callback_data: "archive", style: undefined }], + [{ text: "Alpha", callback_data: "alpha", style: undefined }], + ]); + }); +}); + +describe("resolveTelegramInlineButtons", () => { + it("prefers explicit buttons over shared interactive blocks", () => { + const explicit = [[{ text: "Keep", callback_data: "keep" }]] as const; + + expect( + resolveTelegramInlineButtons({ + buttons: explicit, + interactive: { + blocks: [ + { + type: "buttons", + buttons: [{ label: "Override", value: "override" }], + }, + ], + }, + }), + ).toBe(explicit); + }); + + it("derives buttons from raw interactive payloads", () => { + expect( + resolveTelegramInlineButtons({ + interactive: { + blocks: [ + { + type: "buttons", + buttons: [{ label: "Retry", value: "retry", style: "primary" }], + }, + ], + }, + }), + ).toEqual([[{ text: "Retry", callback_data: "retry", style: "primary" }]]); + }); +}); diff --git a/extensions/telegram/src/button-types.ts b/extensions/telegram/src/button-types.ts index f9c77ac190b..a6eae71995b 100644 --- a/extensions/telegram/src/button-types.ts +++ b/extensions/telegram/src/button-types.ts @@ -1,5 +1,9 @@ import { reduceInteractiveReply } from "../../../src/channels/plugins/outbound/interactive.js"; -import type { InteractiveReply, InteractiveReplyButton } from "../../../src/interactive/payload.js"; +import { + normalizeInteractiveReply, + type InteractiveReply, + type InteractiveReplyButton, +} from "../../../src/interactive/payload.js"; export type TelegramButtonStyle = "danger" | "success" | "primary"; @@ -60,3 +64,12 @@ export function buildTelegramInteractiveButtons( ); return rows.length > 0 ? rows : undefined; } + +export function resolveTelegramInlineButtons(params: { + buttons?: TelegramInlineButtons; + interactive?: unknown; +}): TelegramInlineButtons | undefined { + return ( + params.buttons ?? buildTelegramInteractiveButtons(normalizeInteractiveReply(params.interactive)) + ); +} diff --git a/extensions/telegram/src/channel-actions.ts b/extensions/telegram/src/channel-actions.ts index 246ed45c0e3..84548374f05 100644 --- a/extensions/telegram/src/channel-actions.ts +++ b/extensions/telegram/src/channel-actions.ts @@ -15,7 +15,6 @@ import type { ChannelMessageActionName, } from "../../../src/channels/plugins/types.js"; import type { TelegramActionConfig } from "../../../src/config/types.telegram.js"; -import { normalizeInteractiveReply } from "../../../src/interactive/payload.js"; import { readBooleanParam } from "../../../src/plugin-sdk/boolean-param.js"; import { extractToolSend } from "../../../src/plugin-sdk/tool-send.js"; import { resolveTelegramPollVisibility } from "../../../src/poll-params.js"; @@ -24,7 +23,7 @@ import { listEnabledTelegramAccounts, resolveTelegramPollActionGateState, } from "./accounts.js"; -import { buildTelegramInteractiveButtons } from "./button-types.js"; +import { resolveTelegramInlineButtons } from "./button-types.js"; import { isTelegramInlineButtonsEnabled } from "./inline-buttons.js"; const providerId = "telegram"; @@ -32,9 +31,10 @@ const providerId = "telegram"; function readTelegramSendParams(params: Record) { const to = readStringParam(params, "to", { required: true }); const mediaUrl = readStringParam(params, "media", { trim: false }); - const buttons = - params.buttons ?? - buildTelegramInteractiveButtons(normalizeInteractiveReply(params.interactive)); + const buttons = resolveTelegramInlineButtons({ + buttons: params.buttons as ReturnType, + interactive: params.interactive, + }); const hasButtons = Array.isArray(buttons) && buttons.length > 0; const message = readStringParam(params, "message", { required: !mediaUrl && !hasButtons, diff --git a/extensions/telegram/src/outbound-adapter.ts b/extensions/telegram/src/outbound-adapter.ts index b5ed5ccfcb4..e8c0530d06b 100644 --- a/extensions/telegram/src/outbound-adapter.ts +++ b/extensions/telegram/src/outbound-adapter.ts @@ -10,7 +10,7 @@ import { } from "../../../src/infra/outbound/send-deps.js"; import { resolveInteractiveTextFallback } from "../../../src/interactive/payload.js"; import type { TelegramInlineButtons } from "./button-types.js"; -import { buildTelegramInteractiveButtons } from "./button-types.js"; +import { resolveTelegramInlineButtons } from "./button-types.js"; import { markdownToTelegramHtmlChunks } from "./format.js"; import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js"; import { sendMessageTelegram } from "./send.js"; @@ -67,8 +67,10 @@ export async function sendTelegramPayloadMessages(params: { interactive: params.payload.interactive, }) ?? ""; const mediaUrls = resolvePayloadMediaUrls(params.payload); - const interactiveButtons = buildTelegramInteractiveButtons(params.payload.interactive); - const buttons = telegramData?.buttons ?? interactiveButtons; + const buttons = resolveTelegramInlineButtons({ + buttons: telegramData?.buttons, + interactive: params.payload.interactive, + }); const payloadOpts = { ...params.baseOpts, quoteText,