diff --git a/src/d2m/converters/message-to-event.embeds.test.js b/src/d2m/converters/message-to-event.embeds.test.js index ef7e9b8..4d76624 100644 --- a/src/d2m/converters/message-to-event.embeds.test.js +++ b/src/d2m/converters/message-to-event.embeds.test.js @@ -67,7 +67,7 @@ test("message2event embeds: image embed and attachment", async t => { msgtype: "m.image", url: "mxc://cadence.moe/zAXdQriaJuLZohDDmacwWWDR", body: "Screenshot_20231001_034036.jpg", - external_url: "https://cdn.discordapp.com/attachments/176333891320283136/1157854643037163610/Screenshot_20231001_034036.jpg?ex=651a1faa&is=6518ce2a&hm=eb5ca80a3fa7add8765bf404aea2028a28a2341e4a62435986bcdcf058da82f3&", + external_url: "https://bridge.example.org/download/discordcdn/176333891320283136/1157854643037163610/Screenshot_20231001_034036.jpg", filename: "Screenshot_20231001_034036.jpg", info: { h: 1170, diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index cf8d4c2..73106cd 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -103,7 +103,7 @@ const embedTitleParser = markdown.markdownEngine.parserFor({ * @param {DiscordTypes.APIAttachment} attachment */ async function attachmentToEvent(mentions, attachment) { - const publicURL = dUtils.getPublicUrlForCdn(attachment.url) + const external_url = dUtils.getPublicUrlForCdn(attachment.url) const emoji = attachment.content_type?.startsWith("image/jp") ? "๐Ÿ“ธ" : attachment.content_type?.startsWith("image/") ? "๐Ÿ–ผ๏ธ" @@ -117,9 +117,9 @@ async function attachmentToEvent(mentions, attachment) { $type: "m.room.message", "m.mentions": mentions, msgtype: "m.text", - body: `${emoji} Uploaded SPOILER file: ${publicURL} (${pb(attachment.size)})`, + body: `${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})`, format: "org.matrix.custom.html", - formatted_body: `
${emoji} Uploaded SPOILER file: ${publicURL} (${pb(attachment.size)})
` + formatted_body: `
${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})
` } } // for large files, always link them instead of uploading so I don't use up all the space in the content repo @@ -128,9 +128,9 @@ async function attachmentToEvent(mentions, attachment) { $type: "m.room.message", "m.mentions": mentions, msgtype: "m.text", - body: `${emoji} Uploaded file: ${publicURL} (${pb(attachment.size)})`, + body: `${emoji} Uploaded file: ${external_url} (${pb(attachment.size)})`, format: "org.matrix.custom.html", - formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` + formatted_body: `${emoji} Uploaded file: ${attachment.filename} (${pb(attachment.size)})` } } else if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { return { @@ -138,7 +138,7 @@ async function attachmentToEvent(mentions, attachment) { "m.mentions": mentions, msgtype: "m.image", url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, + external_url, body: attachment.description || attachment.filename, filename: attachment.filename, info: { @@ -154,7 +154,7 @@ async function attachmentToEvent(mentions, attachment) { "m.mentions": mentions, msgtype: "m.video", url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, + external_url, body: attachment.description || attachment.filename, filename: attachment.filename, info: { @@ -170,7 +170,7 @@ async function attachmentToEvent(mentions, attachment) { "m.mentions": mentions, msgtype: "m.audio", url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, + external_url, body: attachment.description || attachment.filename, filename: attachment.filename, info: { @@ -185,7 +185,7 @@ async function attachmentToEvent(mentions, attachment) { "m.mentions": mentions, msgtype: "m.file", url: await file.uploadDiscordFileToMxc(attachment.url), - external_url: attachment.url, + external_url, body: attachment.description || attachment.filename, filename: attachment.filename, info: { @@ -197,8 +197,8 @@ async function attachmentToEvent(mentions, attachment) { } /** - * @param {import("discord-api-types/v10").APIMessage} message - * @param {import("discord-api-types/v10").APIGuild} guild + * @param {DiscordTypes.APIMessage} message + * @param {DiscordTypes.APIGuild} guild * @param {{includeReplyFallback?: boolean, includeEditFallbackStar?: boolean}} options default values: * - includeReplyFallback: true * - includeEditFallbackStar: false @@ -428,6 +428,12 @@ async function messageToEvent(message, guild, options = {}, di) { return {body, html} } + /** + * After converting Discord content to Matrix plaintext and HTML content, post-process the bodies and push the resulting text event + * @param {string} body matrix event plaintext body + * @param {string} html matrix event HTML body + * @param {string} msgtype matrix event msgtype (maybe m.text or m.notice) + */ async function addTextEvent(body, html, msgtype) { // Star * prefix for fallback edits if (options.includeEditFallbackStar) { @@ -436,7 +442,7 @@ async function messageToEvent(message, guild, options = {}, di) { } const flags = message.flags || 0 - if (flags & 2) { + if (flags & DiscordTypes.MessageFlags.IsCrosspost) { body = `[๐Ÿ”€ ${message.author.username}]\n` + body html = `๐Ÿ”€ ${message.author.username}
` + html } @@ -508,7 +514,57 @@ async function messageToEvent(message, guild, options = {}, di) { message.content = "changed the channel name to **" + message.content + "**" } + // Forwarded content appears first + if (message.message_reference?.type === DiscordTypes.MessageReferenceType.Forward && message.message_snapshots?.length) { + // Forwarded notice + const eventID = select("event_message", "event_id", {message_id: message.message_reference.message_id}).pluck().get() + const room = select("channel_room", ["room_id", "name", "nick"], {channel_id: message.message_reference.channel_id}).get() + const forwardedNotice = new mxUtils.MatrixStringBuilder() + if (eventID && room) { + const via = await getViaServersMemo(room.room_id) + forwardedNotice.addLine( + `[๐Ÿ”€ Forwarded from #${room.nick || room.name}]`, + tag`๐Ÿ”€ Forwarded from ${room.nick || room.name}` + ) + } else if (room) { + const via = await getViaServersMemo(room.room_id) + forwardedNotice.addLine( + `[๐Ÿ”€ Forwarded from #${room.nick || room.name}]`, + tag`๐Ÿ”€ Forwarded from ${room.nick || room.name}` + ) + } else { + forwardedNotice.addLine( + `[๐Ÿ”€ Forwarded message]`, + tag`๐Ÿ”€ Forwarded message` + ) + } + // Forwarded content + // @ts-ignore + const forwardedEvents = await messageToEvent(message.message_snapshots[0].message, guild, {includeReplyFallback: false, includeEditFallbackStar: false}, di) + + // Indent + for (const event of forwardedEvents) { + if (["m.text", "m.notice"].includes(event.msgtype)) { + event.msgtype = "m.notice" + event.body = event.body.split("\n").map(l => "ยป " + l).join("\n") + event.formatted_body = `
${event.formatted_body}
` + } + } + + // Try to merge the forwarded content with the forwarded notice + let {body, formatted_body} = forwardedNotice.get() + if (forwardedEvents.length >= 1 && ["m.text", "m.notice"].includes(forwardedEvents[0].msgtype)) { // Try to merge the forwarded content and the forwarded notice + forwardedNotice.add("\n", "
") + forwardedEvents[0].body = body + forwardedEvents[0].body + forwardedEvents[0].formatted_body = formatted_body + forwardedEvents[0].formatted_body + } else { + await addTextEvent(body, formatted_body, "m.notice") + } + events.push(...forwardedEvents) + } + + // Then text content if (message.content) { // Mentions scenario 3: scan the message content for written @mentions of matrix users. Allows for up to one space between @ and mention. const matches = [...message.content.matchAll(/@ ?([a-z0-9._]+)\b/gi)] @@ -527,7 +583,6 @@ async function messageToEvent(message, guild, options = {}, di) { } } - // Text content appears first const {body, html} = await transformContent(message.content) await addTextEvent(body, html, msgtype) } diff --git a/src/d2m/converters/message-to-event.test.js b/src/d2m/converters/message-to-event.test.js index 20630d6..115d99a 100644 --- a/src/d2m/converters/message-to-event.test.js +++ b/src/d2m/converters/message-to-event.test.js @@ -337,7 +337,7 @@ test("message2event: attachment with no content", async t => { msgtype: "m.image", url: "mxc://cadence.moe/qXoZktDqNtEGuOCZEADAMvhM", body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/497161332244742154/1124628646431297546/image.png", + external_url: "https://bridge.example.org/download/discordcdn/497161332244742154/1124628646431297546/image.png", filename: "image.png", info: { mimetype: "image/png", @@ -373,7 +373,7 @@ test("message2event: stickers", async t => { msgtype: "m.image", url: "mxc://cadence.moe/ZDCNYnkPszxGKgObUIFmvjus", body: "image.png", - external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1106366167486038016/image.png", + external_url: "https://bridge.example.org/download/discordcdn/122155380120748034/1106366167486038016/image.png", filename: "image.png", info: { mimetype: "image/png", @@ -427,7 +427,7 @@ test("message2event: skull webp attachment with content", async t => { mimetype: "image/webp", size: 74290 }, - external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084747910918195/skull.webp", + external_url: "https://bridge.example.org/download/discordcdn/112760669178241024/1128084747910918195/skull.webp", filename: "skull.webp", url: "mxc://cadence.moe/sDxWmDErBhYBxtDcJQgBETes" }]) @@ -461,7 +461,7 @@ test("message2event: reply to skull webp attachment with content", async t => { mimetype: "image/jpeg", size: 85906 }, - external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", + external_url: "https://bridge.example.org/download/discordcdn/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg", filename: "RDT_20230704_0936184915846675925224905.jpg", url: "mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa" }]) @@ -551,7 +551,7 @@ test("message2event: reply with a video", async t => { body: "Ins_1960637570.mp4", filename: "Ins_1960637570.mp4", url: "mxc://cadence.moe/kMqLycqMURhVpwleWkmASpnU", - external_url: "https://cdn.discordapp.com/attachments/112760669178241024/1197621094786531358/Ins_1960637570.mp4?ex=65bbee8f&is=65a9798f&hm=ae14f7824c3d526c5e11c162e012e1ee405fd5776e1e9302ed80ccd86503cfda&", + external_url: "https://bridge.example.org/download/discordcdn/112760669178241024/1197621094786531358/Ins_1960637570.mp4", info: { h: 854, mimetype: "video/mp4", @@ -572,7 +572,7 @@ test("message2event: voice message", async t => { t.deepEqual(events, [{ $type: "m.room.message", body: "voice-message.ogg", - external_url: "https://cdn.discordapp.com/attachments/1099031887500034088/1112476845502365786/voice-message.ogg?ex=65c92d4c&is=65b6b84c&hm=0654bab5027474cbe23875954fa117cf44d8914c144cd151879590fa1baf8b1c&", + external_url: "https://bridge.example.org/download/discordcdn/1099031887500034088/1112476845502365786/voice-message.ogg", filename: "voice-message.ogg", info: { duration: 3960.0000381469727, @@ -595,7 +595,7 @@ test("message2event: misc file", async t => { }, { $type: "m.room.message", body: "the.yml", - external_url: "https://cdn.discordapp.com/attachments/122155380120748034/1174514575220158545/the.yml?ex=65cd6270&is=65baed70&hm=8c5f1b571784e3c7f99628492298815884e351ae0dc7c2ae40dd22d97caf27d9&", + external_url: "https://bridge.example.org/download/discordcdn/122155380120748034/1174514575220158545/the.yml", filename: "the.yml", info: { mimetype: "text/plain; charset=utf-8", @@ -1017,12 +1017,29 @@ test("message2event: @everyone within a link", async t => { test("message2event: forwarded image", async t => { const events = await messageToEvent(data.message.forwarded_image) - t.deepEqual(events, [{ - $type: "m.room.message", - msgtype: "m.text", - body: "https://github.com/@everyone", - format: "org.matrix.custom.html", - formatted_body: `https://github.com/@everyone`, - "m.mentions": {} - }]) + t.deepEqual(events, [ + { + $type: "m.room.message", + body: "[๐Ÿ”€ Forwarded message]", + format: "org.matrix.custom.html", + formatted_body: "๐Ÿ”€ Forwarded message", + "m.mentions": {}, + msgtype: "m.notice", + }, + { + $type: "m.room.message", + body: "100km.gif", + external_url: "https://bridge.example.org/download/discordcdn/112760669178241024/1296237494987133070/100km.gif", + filename: "100km.gif", + info: { + h: 300, + mimetype: "image/gif", + size: 2965649, + w: 300, + }, + "m.mentions": {}, + msgtype: "m.image", + url: "mxc://cadence.moe/qDAotmebTfEIfsAIVCEZptLh", + }, + ]) }) diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index b6f0951..7e22e9f 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -123,7 +123,8 @@ INSERT INTO file (discord_url, mxc_url) VALUES ('https://cdn.discordapp.com/emojis/288858540888686602.png', 'mxc://cadence.moe/mwZaCtRGAQQyOItagDeCocEO'), ('https://cdn.discordapp.com/attachments/112760669178241024/1197621094786531358/Ins_1960637570.mp4', 'mxc://cadence.moe/kMqLycqMURhVpwleWkmASpnU'), ('https://cdn.discordapp.com/attachments/1099031887500034088/1112476845502365786/voice-message.ogg', 'mxc://cadence.moe/MRRPDggXQMYkrUjTpxQbmcxB'), -('https://cdn.discordapp.com/attachments/122155380120748034/1174514575220158545/the.yml', 'mxc://cadence.moe/HnQIYQmmlIKwOQsbFsIGpzPP'); +('https://cdn.discordapp.com/attachments/122155380120748034/1174514575220158545/the.yml', 'mxc://cadence.moe/HnQIYQmmlIKwOQsbFsIGpzPP'), +('https://cdn.discordapp.com/attachments/112760669178241024/1296237494987133070/100km.gif', 'mxc://cadence.moe/qDAotmebTfEIfsAIVCEZptLh'); INSERT INTO emoji (emoji_id, name, animated, mxc_url) VALUES ('230201364309868544', 'hippo', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'),