From a52103d9fe0d3b97dfd00c0057320e7d4044b76a Mon Sep 17 00:00:00 2001 From: Matthew Jensen Date: Fri, 29 May 2026 07:27:12 +0000 Subject: [PATCH] fix(discord): render bare URLs as bare links, not [url](url) masked links Discord only renders masked links `[text](url)` inside embeds. In a normal message, a bare URL converted to `[url](url)` shows up as literal text rather than a clickable link. In nodeToDiscordMarkdown's link branch, return the bare URL when the link's label equals its target (the bare-URL / autolink case); labeled links are unchanged. Adds regression tests for both bare URLs and . --- .changeset/discord-bare-url-links.md | 5 +++++ packages/adapter-discord/src/markdown.test.ts | 20 +++++++++++++++++++ packages/adapter-discord/src/markdown.ts | 6 ++++++ 3 files changed, 31 insertions(+) create mode 100644 .changeset/discord-bare-url-links.md diff --git a/.changeset/discord-bare-url-links.md b/.changeset/discord-bare-url-links.md new file mode 100644 index 000000000..c6704f8ee --- /dev/null +++ b/.changeset/discord-bare-url-links.md @@ -0,0 +1,5 @@ +--- +"@chat-adapter/discord": patch +--- + +fix: render bare URLs and autolinks as bare URLs instead of `[url](url)` masked links, which Discord only renders inside embeds (in normal messages they showed up as literal text) diff --git a/packages/adapter-discord/src/markdown.test.ts b/packages/adapter-discord/src/markdown.test.ts index 0d48cbb98..99e96b584 100644 --- a/packages/adapter-discord/src/markdown.test.ts +++ b/packages/adapter-discord/src/markdown.test.ts @@ -29,6 +29,26 @@ describe("DiscordFormatConverter", () => { expect(result).toContain("[link text](https://example.com)"); }); + it("should render a bare URL as a bare URL, not a masked link", () => { + // Discord renders masked links `[text](url)` only in embeds, so wrapping + // a bare URL as `[url](url)` shows up as literal text in a normal message. + const ast = converter.toAst("https://example.com"); + const result = converter.fromAst(ast); + expect(result).toContain("https://example.com"); + expect(result).not.toContain( + "[https://example.com](https://example.com)" + ); + }); + + it("should render an autolink as a bare URL, not a masked link", () => { + const ast = converter.toAst(""); + const result = converter.fromAst(ast); + expect(result).toContain("https://example.com"); + expect(result).not.toContain( + "[https://example.com](https://example.com)" + ); + }); + it("should preserve inline code", () => { const ast = converter.toAst("Use `const x = 1`"); const result = converter.fromAst(ast); diff --git a/packages/adapter-discord/src/markdown.ts b/packages/adapter-discord/src/markdown.ts index 02366c6d7..22795f8a7 100644 --- a/packages/adapter-discord/src/markdown.ts +++ b/packages/adapter-discord/src/markdown.ts @@ -146,6 +146,12 @@ export class DiscordFormatConverter extends BaseFormatConverter { const linkText = getNodeChildren(node) .map((child) => this.nodeToDiscordMarkdown(child)) .join(""); + // Bare URLs / autolinks (label === url) must stay bare: Discord only + // renders masked links `[text](url)` inside embeds, so `[url](url)` in a + // normal message shows up as literal text instead of a clickable link. + if (linkText === node.url) { + return node.url; + } // Standard markdown [text](url) return `[${linkText}](${node.url})`; }