From c035e49d5b8beebb00bc90d36fa76129a0651c1f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 27 Jul 2026 23:14:05 +1200 Subject: [PATCH 1/2] Add (bot) to Matrix side displayname --- scripts/setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/setup.js b/scripts/setup.js index 6ce4c9f..191ca3e 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -364,7 +364,7 @@ function defineEchoHandler() { // set profile data on homeserver... console.log("⏩ Updating Matrix profile... (If you've joined lots of rooms, this is slow. Please allow at least 30 seconds.)") - await api.profileSetDisplayname(mxid, "Out Of Your Element") + await api.profileSetDisplayname(mxid, "Out Of Your Element (bot)") await api.profileSetAvatarUrl(mxid, avatarUrl) console.log("✅ Matrix profile updated...") From b765f564b34ff2f2d2e6a0c52504363a63ea5796 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 27 Jul 2026 23:45:17 +1200 Subject: [PATCH 2/2] Remove rather than truncate links in reply preview --- src/m2d/converters/event-to-message.js | 18 ++++++-- src/m2d/converters/event-to-message.test.js | 51 +++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index fbaf769..18b9848 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -773,14 +773,14 @@ async function eventToMessage(event, guild, channel, di) { repliedToEvent.content = repliedToEvent.content["m.new_content"] } /** @type {string} */ - let repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body + let originalRepliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body const fileReplyContentAlternative = attachmentEmojis.get(repliedToEvent.content.msgtype) let contentPreview if (fileReplyContentAlternative) { contentPreview = " " + fileReplyContentAlternative } else if (repliedToEvent.unsigned?.redacted_because) { contentPreview = " (in reply to a deleted message)" - } else if (typeof repliedToContent !== "string") { + } else if (typeof originalRepliedToContent !== "string") { // in reply to a weird metadata event like m.room.name, m.room.member... // I'm not implementing text fallbacks for arbitrary room events. this should cover most cases // this has never ever happened in the wild anyway @@ -788,6 +788,7 @@ async function eventToMessage(event, guild, channel, di) { contentPreview = " (channel details edited)" } else { // Generate a reply preview for a standard message + let repliedToContent = originalRepliedToContent repliedToContent = repliedToContent.replace(/.*<\/mx-reply>/s, "") // Remove everything before replies, so just use the actual message body repliedToContent = repliedToContent.replace(/^\s*
.*?<\/blockquote>(.....)/s, "$1") // If the message starts with a blockquote, don't count it and use the message body afterwards repliedToContent = repliedToContent.replace(/(?:\n|
)+/g, " ") // Should all be on one line @@ -802,7 +803,18 @@ async function eventToMessage(event, guild, channel, di) { const contentPreviewChunks = chunk(repliedToContent, 50) if (contentPreviewChunks.length) { contentPreview = ": " + contentPreviewChunks[0] - contentPreview = contentPreview.replace(/\bhttps?:\/\/[^ )]*/g, "<$&>") + contentPreview = contentPreview.replace(/\bhttps?:\/\/[^ )]*/g, url => { + const originalUrlIndex = originalRepliedToContent.indexOf(url) + if (originalUrlIndex !== -1 && originalRepliedToContent[originalUrlIndex + url.length]?.match(/[^ )>"'`]/)) { // URL was truncated by chunking, replace it + try { + const u = new URL(url) + return `` + } catch (e) { + return `` + } + } + return `<${url}>` // Full URL is present, escape it + }) if (contentPreviewChunks.length > 1) contentPreview = contentPreview.replace(/[,.']$/, "") + "..." } else { contentPreview = "" diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index 158c2ef..5dd0af7 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -2117,6 +2117,57 @@ test("event2message: should suppress embeds for links in reply preview", async t ) }) +test("event2message: truncated links in reply preview should be unlinked to prevent broken links", async t => { + t.deepEqual( + await eventToMessage({ + type: "m.room.message", + sender: "@cadence:cadence.moe", + content: { + msgtype: "m.text", + body: `> <@_ooye_.wing.:cadence.moe> https://huggingface.co/blog/security-incident-july-2026\n\nthis is either written by claude or somebody who's too claude-brained and I just can't get through it`, + format: "org.matrix.custom.html", + formatted_body: `
In reply to @_ooye_.wing.:cadence.moe
https://huggingface.co/blog/security-incident-july-2026
this is either written by claude or somebody who's too claude-brained and I just can't get through it`, + "m.relates_to": { + "m.in_reply_to": { + event_id: "$qmyjr-ISJtnOM5WTWLI0fT7uSlqRLgpyin2d2NCglCU" + } + } + }, + event_id: "$0Bs3rbsXaeZmSztGMx1NIsqvOrkXOpIWebN-dqr09i4", + room_id: "!fGgIymcYWOqjbSRUdV:cadence.moe" + }, data.guild.general, data.channel.general, { + api: { + getEvent: mockGetEvent(t, "!fGgIymcYWOqjbSRUdV:cadence.moe", "$qmyjr-ISJtnOM5WTWLI0fT7uSlqRLgpyin2d2NCglCU", { + "type": "m.room.message", + "sender": "@_ooye_.wing.:cadence.moe", + "content": { + "m.mentions": {}, + "msgtype": "m.text", + "body": "", + "format": "org.matrix.custom.html", + "formatted_body": "https://huggingface.co/blog/security-incident-july-2026" + } + }) + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "cadence [they]", + content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647>https://discord.com/channels/112760669178241024/687028734322147344/1273204543739396116 <@112890272819507200>:" + + " ..." + + `\nthis is either written by claude or somebody who's too claude-brained and I just can't get through it`, + avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU?preset=avatar", + allowed_mentions: { + parse: ["users", "roles"] + } + }] + } + ) +}) + test("event2message: should include a reply preview when message ends with a blockquote", async t => { t.deepEqual( await eventToMessage({