diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js
index 7229d3d..83fab1b 100644
--- a/src/d2m/converters/message-to-event.js
+++ b/src/d2m/converters/message-to-event.js
@@ -265,8 +265,9 @@ function getFormattedInteraction(interaction, isThinkingInteraction) {
* @param {any} newEvents merge into events
* @param {any} events will be modified
* @param {boolean} forceSameMsgtype whether m.text may only be combined with m.text, etc
+ * @param {boolean} [forceMerge] if true, must merge event, will error if it had to append
*/
-function mergeTextEvents(newEvents, events, forceSameMsgtype) {
+function mergeTextEvents(newEvents, events, forceSameMsgtype, forceMerge = false) {
let prev = events.at(-1)
for (const ne of newEvents) {
const isAllText = prev?.body && prev?.formatted_body && ["m.text", "m.notice"].includes(ne.msgtype) && ["m.text", "m.notice"].includes(prev?.msgtype)
@@ -278,6 +279,8 @@ function mergeTextEvents(newEvents, events, forceSameMsgtype) {
rep.addLine(ne.body, ne.formatted_body)
prev.body = rep.body
prev.formatted_body = rep.formattedBody
+ } else if (forceMerge) {
+ throw new Error("Unable to merge events")
} else {
events.push(ne)
}
@@ -967,7 +970,8 @@ async function messageToEvent(message, guild, options = {}, di) {
// May only be a section accessory or in an action row (up to 5)
if (component.style === DiscordTypes.ButtonStyle.Link) {
assert(component.label) // required for Discord to validate link buttons
- stack.msb.add(`[${component.label} ${component.url}] `, tag`${component.label} `)
+ const link = await transformContentMessageLinks(component.url)
+ stack.msb.add(`[${component.label} ${link}] `, tag`${component.label} `)
}
}
@@ -980,7 +984,19 @@ async function messageToEvent(message, guild, options = {}, di) {
const {body, formatted_body} = stack.msb.get()
if (body.trim().length) {
- await addTextEvent(body, formatted_body, "m.text")
+ // Create new message if Components V2 (cannot have regular content)
+ if ((message.flags ?? 0) & DiscordTypes.MessageFlags.IsComponentsV2) {
+ await addTextEvent(body, formatted_body, "m.text")
+ }
+ // Add to existing message if legacy components https://docs.discord.com/developers/components/reference#legacy-message-component-behavior
+ else {
+ mergeTextEvents([{
+ msgtype: "m.text",
+ body,
+ format: "org.matrix.custom.html",
+ formatted_body
+ }], events, false, true)
+ }
}
}
diff --git a/src/d2m/converters/message-to-event.test.components.js b/src/d2m/converters/message-to-event.test.components.js
index 137b63b..1ef83c3 100644
--- a/src/d2m/converters/message-to-event.test.components.js
+++ b/src/d2m/converters/message-to-event.test.components.js
@@ -1,6 +1,7 @@
const {test} = require("supertape")
const {messageToEvent} = require("./message-to-event")
const data = require("../../../test/data")
+const {mockGetEffectivePower} = require("../../matrix/utils.test")
test("message2event components: pk question mark output", async t => {
const events = await messageToEvent(data.message_with_components.pk_question_mark_response, data.guild.general, {})
@@ -77,3 +78,24 @@ test("message2event components: pk question mark output", async t => {
msgtype: "m.text",
}])
})
+
+test("message2event components: pk ping message legacy components", async t => {
+ const events = await messageToEvent(data.message_with_components.pk_ping_components_v1, data.guild.general, {}, {
+ api: {
+ async getJoinedMembers() {
+ return {joined: {}}
+ },
+ getEffectivePower: mockGetEffectivePower()
+ }
+ })
+ t.deepEqual(events, [{
+ $type: "m.room.message",
+ msgtype: "m.text",
+ body: "โญ cadence used `/๐ Ping author`"
+ + "\nPsst, **Red** (@cadence.worm:), you have been pinged by @cadence.worm:."
+ + "\n[Jump https://matrix.to/#/!TqlyQmifxGUggEmdBN:cadence.moe/$l9FMmsEbh9K0NUReeEpWOMZYGRlUOE8yLcm6P-TYHSM?via=cadence.moe] ",
+ format: "org.matrix.custom.html",
+ formatted_body: "
โญ cadence used /๐ Ping author
Psst, Red (@cadence.worm), you have been pinged by @cadence.worm.
Jump ",
+ "m.mentions": {}
+ }])
+})
diff --git a/test/data.js b/test/data.js
index f3092bc..eab9a63 100644
--- a/test/data.js
+++ b/test/data.js
@@ -5473,6 +5473,189 @@ module.exports = {
content: '-# Original Message ID: 1466556003645657118 ยท '
}
]
+ },
+ pk_ping_components_v1: {
+ type: 23,
+ content: "Psst, **Red** (<@772659086046658620>), you have been pinged by <@772659086046658620>.",
+ mentions: [
+ {
+ id: "772659086046658620",
+ username: "cadence.worm",
+ avatar: "466df0c98b1af1e1388f595b4c1ad1b9",
+ discriminator: "0",
+ public_flags: 0,
+ flags: 0,
+ banner: null,
+ accent_color: null,
+ global_name: "cadence",
+ avatar_decoration_data: null,
+ collectibles: null,
+ display_name_styles: null,
+ banner_color: null,
+ clan: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ },
+ primary_guild: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ }
+ }
+ ],
+ mention_roles: [],
+ attachments: [],
+ embeds: [],
+ timestamp: "2026-03-25T07:07:02.626000+00:00",
+ edited_timestamp: null,
+ flags: 0,
+ components: [
+ {
+ type: 1,
+ id: 1,
+ components: [
+ {
+ type: 2,
+ id: 2,
+ style: 5,
+ label: "Jump",
+ url: "https://discord.com/channels/1160893336324931584/1160894080998461480/1440549403667468320"
+ }
+ ]
+ }
+ ],
+ id: "1486260105908457653",
+ channel_id: "1160894080998461480",
+ author: {
+ id: "466378653216014359",
+ username: "PluralKit",
+ avatar: "b78ef67a081737a830b60aa47d9ebcd9",
+ discriminator: "4020",
+ public_flags: 65536,
+ flags: 65536,
+ bot: true,
+ banner: null,
+ accent_color: null,
+ global_name: null,
+ avatar_decoration_data: null,
+ collectibles: null,
+ display_name_styles: null,
+ banner_color: null,
+ clan: null,
+ primary_guild: null
+ },
+ pinned: false,
+ mention_everyone: false,
+ tts: false,
+ application_id: "466378653216014359",
+ interaction: {
+ id: "1486260103928614932",
+ type: 2,
+ name: "๐ Ping author",
+ user: {
+ id: "772659086046658620",
+ username: "cadence.worm",
+ avatar: "466df0c98b1af1e1388f595b4c1ad1b9",
+ discriminator: "0",
+ public_flags: 0,
+ flags: 0,
+ banner: null,
+ accent_color: null,
+ global_name: "cadence",
+ avatar_decoration_data: null,
+ collectibles: null,
+ display_name_styles: null,
+ banner_color: null,
+ clan: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ },
+ primary_guild: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ }
+ }
+ },
+ webhook_id: "466378653216014359",
+ message_reference: {
+ type: 0,
+ channel_id: "1160894080998461480",
+ message_id: "1440549403667468320",
+ guild_id: "1160893336324931584"
+ },
+ interaction_metadata: {
+ id: "1486260103928614932",
+ type: 2,
+ user: {
+ id: "772659086046658620",
+ username: "cadence.worm",
+ avatar: "466df0c98b1af1e1388f595b4c1ad1b9",
+ discriminator: "0",
+ public_flags: 0,
+ flags: 0,
+ banner: null,
+ accent_color: null,
+ global_name: "cadence",
+ avatar_decoration_data: null,
+ collectibles: null,
+ display_name_styles: null,
+ banner_color: null,
+ clan: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ },
+ primary_guild: {
+ identity_guild_id: "532245108070809601",
+ identity_enabled: true,
+ tag: "doll",
+ badge: "dba08126b4e810a0e096cc7cd5bc37f0"
+ }
+ },
+ authorizing_integration_owners: { "0": "1160893336324931584" },
+ name: "๐ Ping author",
+ command_type: 3,
+ target_message_id: "1440549403667468320"
+ },
+ referenced_message: {
+ type: 0,
+ content: "test",
+ mentions: [],
+ mention_roles: [],
+ attachments: [],
+ embeds: [],
+ timestamp: "2025-11-19T03:49:01.948000+00:00",
+ edited_timestamp: null,
+ flags: 0,
+ components: [],
+ id: "1440549403667468320",
+ channel_id: "1160894080998461480",
+ author: {
+ id: "1195662438662680720",
+ username: "special name",
+ avatar: "a82347890f2739e5880cd82b8c1a708e",
+ discriminator: "0000",
+ public_flags: 0,
+ flags: 0,
+ bot: true,
+ global_name: null,
+ clan: null,
+ primary_guild: null
+ },
+ pinned: false,
+ mention_everyone: false,
+ tts: false,
+ application_id: "466378653216014359",
+ webhook_id: "1195662438662680720"
+ }
}
},
message_update: {
diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql
index 8dd71cd..1662320 100644
--- a/test/ooye-test-data.sql
+++ b/test/ooye-test-data.sql
@@ -95,7 +95,8 @@ WITH a (message_id, channel_id) AS (VALUES
('1381212840957972480', '112760669178241024'),
('1401760355339862066', '112760669178241024'),
('1439351590262800565', '1438284564815548418'),
-('1404133238414376971', '112760669178241024'))
+('1404133238414376971', '112760669178241024'),
+('1440549403667468320', '1160894080998461480'))
SELECT message_id, max(historical_room_index) as historical_room_index FROM a INNER JOIN historical_channel_room ON historical_channel_room.reference_channel_id = a.channel_id GROUP BY message_id;
INSERT INTO event_message (event_id, event_type, event_subtype, message_id, part, reaction_part, source) VALUES
@@ -143,7 +144,8 @@ INSERT INTO event_message (event_id, event_type, event_subtype, message_id, part
('$7P2O_VTQNHvavX5zNJ35DV-dbJB1Ag80tGQP_JzGdhk', 'm.room.message', 'm.text', '1401760355339862066', 0, 0, 0),
('$ielAnR6geu0P1Tl5UXfrbxlIf-SV9jrNprxrGXP3v7M', 'm.room.message', 'm.image', '1439351590262800565', 0, 0, 0),
('$uUKLcTQvik5tgtTGDKuzn0Ci4zcCvSoUcYn2X7mXm9I', 'm.room.message', 'm.text', '1404133238414376971', 0, 1, 1),
-('$LhmoWWvYyn5_AHkfb6FaXmLI6ZOC1kloql5P40YDmIk', 'm.room.message', 'm.notice', '1404133238414376971', 1, 0, 1);
+('$LhmoWWvYyn5_AHkfb6FaXmLI6ZOC1kloql5P40YDmIk', 'm.room.message', 'm.notice', '1404133238414376971', 1, 0, 1),
+('$l9FMmsEbh9K0NUReeEpWOMZYGRlUOE8yLcm6P-TYHSM', 'm.room.message', 'm.text', '1440549403667468320', 0, 0, 1);
INSERT INTO file (discord_url, mxc_url) VALUES
('https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png', 'mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM'),