From 69e3d64905c1a0f19b54e2577c166df6b8c678b7 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 10 Feb 2025 16:44:22 +1300 Subject: [PATCH] Handle replies to state events with no body --- src/m2d/converters/event-to-message.js | 39 ++++++++--------- src/m2d/converters/event-to-message.test.js | 46 +++++++++++++++++++++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 9a17817..24e59c9 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -476,7 +476,7 @@ async function eventToMessage(event, guild, di) { // Try to extract an accurate display name and avatar URL from the member event const member = await getMemberFromCacheOrHomeserver(event.room_id, event.sender, di?.api) if (member.displayname) displayName = member.displayname - if (member.avatar_url) avatarURL = mxUtils.getPublicUrlForMxc(member.avatar_url) || undefined + if (member.avatar_url) avatarURL = mxUtils.getPublicUrlForMxc(member.avatar_url) // If the display name is too long to be put into the webhook (80 characters is the maximum), // put the excess characters into displayNameRunoff, later to be put at the top of the message let [displayNameShortened, displayNameRunoff] = splitDisplayName(displayName) @@ -512,8 +512,7 @@ async function eventToMessage(event, guild, di) { // Is it editing a reply? We need special handling if it is. // Get the original event, then check if it was a reply const originalEvent = await di.api.getEvent(event.room_id, originalEventId) - if (!originalEvent) return - const repliedToEventId = originalEvent.content["m.relates_to"]?.["m.in_reply_to"]?.event_id + const repliedToEventId = originalEvent?.content?.["m.relates_to"]?.["m.in_reply_to"]?.event_id if (!repliedToEventId) return // After all that, it's an edit of a reply. @@ -576,34 +575,27 @@ async function eventToMessage(event, guild, di) { if (row) { replyLine += `https://discord.com/channels/${guild.id}/${row.channel_id}/${row.message_id} ` } - const sender = repliedToEvent.sender - const authorID = getUserOrProxyOwnerID(sender) - if (authorID) { - replyLine += `<@${authorID}>` - } else { - let senderName = select("member_cache", "displayname", {mxid: sender}).pluck().get() - if (!senderName) { - const match = sender.match(/@([^:]*)/) - assert(match) - senderName = match[1] - } - replyLine += `**Ⓜ${senderName}**` - } // If the event has been edited, the homeserver will include the relation in `unsigned`. if (repliedToEvent.unsigned?.["m.relations"]?.["m.replace"]?.content?.["m.new_content"]) { repliedToEvent = repliedToEvent.unsigned["m.relations"]["m.replace"] // Note: this changes which event_id is in repliedToEvent. repliedToEvent.content = repliedToEvent.content["m.new_content"] } - let contentPreview + /** @type {string} */ + let repliedToContent = 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") { + // 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 + repliedToEvent.sender = "" + contentPreview = " (channel details edited)" } else { // Generate a reply preview for a standard message - /** @type {string} */ - let repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body 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 @@ -624,6 +616,15 @@ async function eventToMessage(event, guild, di) { contentPreview = "" } } + const sender = repliedToEvent.sender + const authorID = getUserOrProxyOwnerID(sender) + if (authorID) { + replyLine += `<@${authorID}>` + } else { + let senderName = select("member_cache", "displayname", {mxid: sender}).pluck().get() + if (!senderName) senderName = sender.match(/@([^:]*)/)?.[1] + if (senderName) replyLine += `**Ⓜ${senderName}**` + } replyLine = `-# > ${replyLine}${contentPreview}\n` })() diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index cc3d19a..d3871b3 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -2625,6 +2625,52 @@ test("event2message: rich reply to a deleted event", async t => { ) }) +test("event2message: rich reply to a state event with no body", async t => { + t.deepEqual( + await eventToMessage({ + type: "m.room.message", + sender: "@ampflower:matrix.org", + content: { + msgtype: "m.text", + body: "> <@ampflower:matrix.org> changed the room topic\n\nnice room topic", + format: "org.matrix.custom.html", + formatted_body: "
In reply to @ampflower:matrix.org changed the room topic
nice room topic", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$f-noT-d-Eo_Xgpc05Ww89ErUXku4NwKWYGHLzWKo1kU" + } + } + }, + event_id: "$v_Gtr-bzv9IVlSLBO5DstzwmiDd-GSFaNfHX66IupV8", + room_id: "!TqlyQmifxGUggEmdBN:cadence.moe" + }, data.guild.general, { + api: { + getEvent: mockGetEvent(t, "!TqlyQmifxGUggEmdBN:cadence.moe", "$f-noT-d-Eo_Xgpc05Ww89ErUXku4NwKWYGHLzWKo1kU", { + type: "m.room.topic", + sender: "@ampflower:matrix.org", + content: { + topic: "you're cute" + }, + user_id: "@ampflower:matrix.org" + }) + } + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "Ampflower 🌺", + content: "-# > <:L1:1144820033948762203><:L2:1144820084079087647> (channel details edited)\nnice room topic", + avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/PRfhXYBTOalvgQYtmCLeUXko", + allowed_mentions: { + parse: ["users", "roles"] + } + }] + } + ) +}) + test("event2message: raw mentioning discord users in plaintext body works", async t => { t.deepEqual( await eventToMessage({