From 53297304a4439e33af078b34cb0941e05a0e8f3f Mon Sep 17 00:00:00 2001 From: Hayden Bleasel Date: Mon, 16 Mar 2026 12:27:04 -0700 Subject: [PATCH 1/2] Resolves #246 --- packages/adapter-discord/src/index.test.ts | 3 +++ packages/adapter-discord/src/index.ts | 10 ++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/adapter-discord/src/index.test.ts b/packages/adapter-discord/src/index.test.ts index c20e6118..f0ad76f0 100644 --- a/packages/adapter-discord/src/index.test.ts +++ b/packages/adapter-discord/src/index.test.ts @@ -2551,9 +2551,12 @@ describe("postChannelMessage", () => { await adapter.postChannelMessage("discord:guild1:channel456", cardMessage); const calledPayload = spy.mock.calls[0]?.[2] as { + content?: string; embeds?: unknown[]; components?: unknown[]; }; + // Should NOT include content text when card is present (avoids duplicate display) + expect(calledPayload.content).toBeUndefined(); expect(calledPayload.embeds).toBeDefined(); expect(Array.isArray(calledPayload.embeds)).toBe(true); expect((calledPayload.embeds ?? []).length).toBeGreaterThan(0); diff --git a/packages/adapter-discord/src/index.ts b/packages/adapter-discord/src/index.ts index 1b6f84a4..af6ee06b 100644 --- a/packages/adapter-discord/src/index.ts +++ b/packages/adapter-discord/src/index.ts @@ -56,7 +56,7 @@ import { InteractionResponseType as DiscordInteractionResponseType, verifyKey, } from "discord-interactions"; -import { cardToDiscordPayload, cardToFallbackText } from "./cards"; +import { cardToDiscordPayload } from "./cards"; import { DiscordFormatConverter } from "./markdown"; import { type DiscordActionRow, @@ -811,8 +811,7 @@ export class DiscordAdapter implements Adapter { const cardPayload = cardToDiscordPayload(card); embeds.push(...cardPayload.embeds); components.push(...cardPayload.components); - // Fallback text (truncated to Discord's limit) - payload.content = this.truncateContent(cardToFallbackText(card)); + // Don't include text - Discord shows both text and card if text is present } else { // Regular text message (truncated to Discord's limit) payload.content = this.truncateContent( @@ -1157,8 +1156,7 @@ export class DiscordAdapter implements Adapter { const cardPayload = cardToDiscordPayload(card); embeds.push(...cardPayload.embeds); components.push(...cardPayload.components); - // Fallback text (truncated to Discord's limit) - payload.content = this.truncateContent(cardToFallbackText(card)); + // Don't include text - Discord shows both text and card if text is present } else { // Regular text message (truncated to Discord's limit) payload.content = this.truncateContent( @@ -2378,7 +2376,7 @@ export class DiscordAdapter implements Adapter { const cardPayload = cardToDiscordPayload(card); embeds.push(...cardPayload.embeds); components.push(...cardPayload.components); - payload.content = this.truncateContent(cardToFallbackText(card)); + // Don't include text - Discord shows both text and card if text is present } else { payload.content = this.truncateContent( convertEmojiPlaceholders( From 427213f89cc087e7380f1299f74f33c2dbf7e67a Mon Sep 17 00:00:00 2001 From: Ben Sabic Date: Fri, 20 Mar 2026 16:02:22 +1100 Subject: [PATCH 2/2] Add card test coverage for postMessage/editMessage and changeset --- .../fix-discord-duplicate-card-content.md | 5 ++ packages/adapter-discord/src/index.test.ts | 76 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 .changeset/fix-discord-duplicate-card-content.md diff --git a/.changeset/fix-discord-duplicate-card-content.md b/.changeset/fix-discord-duplicate-card-content.md new file mode 100644 index 00000000..3ecc527f --- /dev/null +++ b/.changeset/fix-discord-duplicate-card-content.md @@ -0,0 +1,5 @@ +--- +"@chat-adapter/discord": patch +--- + +Fix duplicate content display when sending card messages on Discord diff --git a/packages/adapter-discord/src/index.test.ts b/packages/adapter-discord/src/index.test.ts index f0ad76f0..f9bda007 100644 --- a/packages/adapter-discord/src/index.test.ts +++ b/packages/adapter-discord/src/index.test.ts @@ -1536,6 +1536,42 @@ describe("postMessage", () => { spy.mockRestore(); }); + + it("does not include content text when posting a card message", async () => { + const mockResponse = new Response( + JSON.stringify({ + id: "msg005", + channel_id: "channel456", + content: "", + timestamp: "2021-01-01T00:00:00.000Z", + author: { id: "test-app-id", username: "bot" }, + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + const spy = vi + .spyOn(adapter as any, "discordFetch") + .mockResolvedValue(mockResponse); + + const cardMessage = { + card: Card({ + title: "Test Card", + children: [Actions([Button({ id: "btn1", label: "Click me" })])], + }), + }; + + await adapter.postMessage("discord:guild1:channel456", cardMessage); + + const calledPayload = spy.mock.calls[0]?.[2] as { + content?: string; + embeds?: unknown[]; + components?: unknown[]; + }; + expect(calledPayload.content).toBeUndefined(); + expect(calledPayload.embeds).toBeDefined(); + expect(calledPayload.components).toBeDefined(); + + spy.mockRestore(); + }); }); // ============================================================================ @@ -1642,6 +1678,46 @@ describe("editMessage", () => { spy.mockRestore(); }); + + it("does not include content text when editing to a card message", async () => { + const mockResponse = new Response( + JSON.stringify({ + id: "msg004", + channel_id: "channel456", + content: "", + timestamp: "2021-01-01T00:00:00.000Z", + author: { id: "test-app-id", username: "bot" }, + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ); + const spy = vi + .spyOn(adapter as any, "discordFetch") + .mockResolvedValue(mockResponse); + + const cardMessage = { + card: Card({ + title: "Test Card", + children: [Actions([Button({ id: "btn1", label: "Click me" })])], + }), + }; + + await adapter.editMessage( + "discord:guild1:channel456", + "msg004", + cardMessage + ); + + const calledPayload = spy.mock.calls[0]?.[2] as { + content?: string; + embeds?: unknown[]; + components?: unknown[]; + }; + expect(calledPayload.content).toBeUndefined(); + expect(calledPayload.embeds).toBeDefined(); + expect(calledPayload.components).toBeDefined(); + + spy.mockRestore(); + }); }); // ============================================================================