From 61d57be4c2a54d9727eae08c14a339252016215b Mon Sep 17 00:00:00 2001 From: Shadow Date: Thu, 12 Feb 2026 16:39:58 -0600 Subject: [PATCH] Discord: preserve media caption whitespace --- .../send.sends-basic-channel-messages.test.ts | 26 +++++++++++++++++++ src/discord/send.shared.ts | 8 ++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/discord/send.sends-basic-channel-messages.test.ts b/src/discord/send.sends-basic-channel-messages.test.ts index 0d01eff01c..a649822ade 100644 --- a/src/discord/send.sends-basic-channel-messages.test.ts +++ b/src/discord/send.sends-basic-channel-messages.test.ts @@ -246,6 +246,32 @@ describe("sendMessageDiscord", () => { ); }); + it("sends media with empty text without content field", async () => { + const { rest, postMock } = makeRest(); + postMock.mockResolvedValue({ id: "msg", channel_id: "789" }); + const res = await sendMessageDiscord("channel:789", "", { + rest, + token: "t", + mediaUrl: "file:///tmp/photo.jpg", + }); + expect(res.messageId).toBe("msg"); + const body = postMock.mock.calls[0]?.[1]?.body; + expect(body).not.toHaveProperty("content"); + expect(body).toHaveProperty("files"); + }); + + it("preserves whitespace in media captions", async () => { + const { rest, postMock } = makeRest(); + postMock.mockResolvedValue({ id: "msg", channel_id: "789" }); + await sendMessageDiscord("channel:789", " spaced ", { + rest, + token: "t", + mediaUrl: "file:///tmp/photo.jpg", + }); + const body = postMock.mock.calls[0]?.[1]?.body; + expect(body).toHaveProperty("content", " spaced "); + }); + it("includes message_reference when replying", async () => { const { rest, postMock } = makeRest(); postMock.mockResolvedValue({ id: "msg1", channel_id: "789" }); diff --git a/src/discord/send.shared.ts b/src/discord/send.shared.ts index d3e8a97593..7e3b059363 100644 --- a/src/discord/send.shared.ts +++ b/src/discord/send.shared.ts @@ -361,13 +361,17 @@ async function sendDiscordMedia( const media = await loadWebMedia(mediaUrl); const chunks = text ? buildDiscordTextChunks(text, { maxLinesPerMessage, chunkMode }) : []; const caption = chunks[0] ?? ""; + const hasCaption = caption.trim().length > 0; const messageReference = replyTo ? { message_id: replyTo, fail_if_not_exists: false } : undefined; const res = (await request( () => rest.post(Routes.channelMessages(channelId), { body: { - content: caption || undefined, - message_reference: messageReference, + // Only include content when there is actual text; Discord rejects + // media-only messages that carry an empty or undefined content field + // when sent as multipart/form-data. Preserve whitespace in captions. + ...(hasCaption ? { content: caption } : {}), + ...(messageReference ? { message_reference: messageReference } : {}), ...(embeds?.length ? { embeds } : {}), files: [ {