diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 7002c38..cbdeded 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -555,25 +555,40 @@ async function eventToMessage(event, guild, di) { // Handle images first - might need to handle their `body`/`formatted_body` as well, which will fall through to the text processor let shouldProcessTextEvent = event.type === "m.room.message" && (event.content.msgtype === "m.text" || event.content.msgtype === "m.emote") if (event.type === "m.room.message" && (event.content.msgtype === "m.file" || event.content.msgtype === "m.video" || event.content.msgtype === "m.audio" || event.content.msgtype === "m.image")) { + // Build message content in addition to the uploaded file + const fileIsSpoiler = event.content["page.codeberg.everypizza.msc4193.spoiler"] + const fileSpoilerReason = event.content["page.codeberg.everypizza.msc4193.spoiler.reason"] + content = "" + const captionContent = new mxUtils.MatrixStringBuilder() + + // Caption from Matrix message + const fileHasCaption = (event.content.body && event.content.filename && event.content.body !== event.content.filename) || event.content.formatted_body + if (fileHasCaption) { + captionContent.addLine(event.content.body || "", event.content.formatted_body || tag`${event.content.body || ""}`) + } + + // Spoiler message + if (fileIsSpoiler && typeof fileSpoilerReason === "string") { + captionContent.addLine(`(Spoiler: ${fileSpoilerReason})`) + } + + // File link as alternative to uploading if (!("file" in event.content) && event.content.info?.size > getFileSizeForGuild(guild)) { // Upload (unencrypted) file as link, because it's too large for Discord // Do this by constructing a sample Matrix message with the link and then use the text processor to convert that + the original caption. const url = mxUtils.getPublicUrlForMxc(event.content.url) assert(url) const filename = event.content.filename || event.content.body - const newText = new mxUtils.MatrixStringBuilder() const emoji = attachmentEmojis.has(event.content.msgtype) ? attachmentEmojis.get(event.content.msgtype) + " " : "" - newText.addLine(`${emoji}Uploaded file: ${url} (${pb(event.content.info.size)})`, tag`${emoji}Uploaded file: ${filename} (${pb(event.content.info.size)})`) - // Check if the event has a caption that we need to add as well - if ((event.content.body && event.content.filename && event.content.body !== event.content.filename) || event.content.formatted_body) { - newText.addLine(event.content.body || "", event.content.formatted_body || tag`${event.content.body || ""}`) + if (fileIsSpoiler) { + captionContent.addLine(`${emoji}Uploaded SPOILER file: <${url}> (${pb(event.content.info.size)})`, tag`${emoji}Uploaded SPOILER file: ${filename} (${pb(event.content.info.size)})`) // the space is necessary to work around a bug in Discord's URL previewer. the preview still gets blurred in the client. + } else { + captionContent.addLine(`${emoji}Uploaded file: ${url} (${pb(event.content.info.size)})`, tag`${emoji}Uploaded file: ${filename} (${pb(event.content.info.size)})`) } - Object.assign(event.content, newText.get()) - shouldProcessTextEvent = true } else { // Upload file as file - content = "" - const filename = event.content.filename || event.content.body + let filename = event.content.filename || event.content.body + if (fileIsSpoiler) filename = "SPOILER_" + filename if ("file" in event.content) { // Encrypted assert.equal(event.content.file.key.alg, "A256CTR") @@ -584,12 +599,16 @@ async function eventToMessage(event, guild, di) { attachments.push({id: "0", filename}) pendingFiles.push({name: filename, mxc: event.content.url}) } - // Check if we also need to process a text event for this image - if it has a caption that's different from its filename - if ((event.content.body && event.content.filename && event.content.body !== event.content.filename) || event.content.formatted_body) { - shouldProcessTextEvent = true - } + } + + // Add result to content + const result = captionContent.get() + if (result.body) { + Object.assign(event.content, {body: result.body, format: result.format, formatted_body: result.formatted_body}) + shouldProcessTextEvent = true } } + if (event.type === "m.sticker") { content = "" let filename = event.content.body diff --git a/src/m2d/converters/event-to-message.test.js b/src/m2d/converters/event-to-message.test.js index 2e347f5..439e07f 100644 --- a/src/m2d/converters/event-to-message.test.js +++ b/src/m2d/converters/event-to-message.test.js @@ -404,6 +404,135 @@ test("event2message: spoiler reasons work", async t => { ) }) +test("event2message: media spoilers work", async t => { + t.deepEqual( + await eventToMessage({ + content: { + body: "pitstop.png", + filename: "pitstop.png", + info: { + h: 870, + mimetype: "image/png", + size: 729990, + w: 674, + "xyz.amorgan.blurhash": "UqOMmRM{_Mx[xZaxR*tQ.8ayxtWBRkRkWUWB" + }, + msgtype: "m.image", + "page.codeberg.everypizza.msc4193.spoiler": true, + url: "mxc://agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT" + }, + origin_server_ts: 1764885561299, + room_id: "!zq94fae5bVKUubZLp7:agiadn.org", + sender: "@underscore_x:agiadn.org", + type: "m.room.message", + event_id: "$6P7u-lpu2u73ZrHUru2UG1rPfsh8PfYLPK21o3SNIN4", + user_id: "@underscore_x:agiadn.org" + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "underscore_x", + content: "", + avatar_url: undefined, + attachments: [{id: "0", filename: "SPOILER_pitstop.png"}], + pendingFiles: [{ + mxc: "mxc://agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT", + name: "SPOILER_pitstop.png", + }] + }] + } + ) +}) + +test("event2message: media spoilers with reason work", async t => { + t.deepEqual( + await eventToMessage({ + content: { + body: "pitstop.png", + filename: "pitstop.png", + info: { + h: 870, + mimetype: "image/png", + size: 729990, + w: 674, + "xyz.amorgan.blurhash": "UqOMmRM{_Mx[xZaxR*tQ.8ayxtWBRkRkWUWB" + }, + msgtype: "m.image", + "page.codeberg.everypizza.msc4193.spoiler": true, + "page.codeberg.everypizza.msc4193.spoiler.reason": "golden witch solutions", + url: "mxc://agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT" + }, + origin_server_ts: 1764885561299, + room_id: "!zq94fae5bVKUubZLp7:agiadn.org", + sender: "@underscore_x:agiadn.org", + type: "m.room.message", + event_id: "$6P7u-lpu2u73ZrHUru2UG1rPfsh8PfYLPK21o3SNIN4", + user_id: "@underscore_x:agiadn.org" + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "underscore_x", + allowed_mentions: { + parse: ["users", "roles"] + }, + content: "(Spoiler: golden witch solutions)", + avatar_url: undefined, + attachments: [{id: "0", filename: "SPOILER_pitstop.png"}], + pendingFiles: [{ + mxc: "mxc://agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT", + name: "SPOILER_pitstop.png", + }] + }] + } + ) +}) + +test("event2message: spoiler files too large for Discord are linked and retain reason", async t => { + t.deepEqual( + await eventToMessage({ + content: { + body: "pitstop.png", + filename: "pitstop.png", + info: { + h: 870, + mimetype: "image/png", + size: 40000000, + w: 674, + "xyz.amorgan.blurhash": "UqOMmRM{_Mx[xZaxR*tQ.8ayxtWBRkRkWUWB" + }, + msgtype: "m.image", + "page.codeberg.everypizza.msc4193.spoiler": true, + "page.codeberg.everypizza.msc4193.spoiler.reason": "golden witch secrets", + url: "mxc://agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT" + }, + origin_server_ts: 1764885561299, + room_id: "!zq94fae5bVKUubZLp7:agiadn.org", + sender: "@underscore_x:agiadn.org", + type: "m.room.message", + event_id: "$6P7u-lpu2u73ZrHUru2UG1rPfsh8PfYLPK21o3SNIN4", + user_id: "@underscore_x:agiadn.org" + }), + { + ensureJoined: [], + messagesToDelete: [], + messagesToEdit: [], + messagesToSend: [{ + username: "underscore_x", + allowed_mentions: { + parse: ["users", "roles"] + }, + content: "(Spoiler: golden witch secrets)\n🖼️ _Uploaded **SPOILER** file: ||[pitstop.png](https://bridge.example.org/download/matrix/agiadn.org/JY5NvEFojTvYDp5znjGIkkQ7Ez7GwsdT )|| (40 MB)_", + avatar_url: undefined + }] + } + ) +}) + test("event2message: markdown syntax is escaped", async t => { t.deepEqual( await eventToMessage({ @@ -4236,7 +4365,7 @@ test("event2message: files too large for Discord can have a plaintext caption", messagesToEdit: [], messagesToSend: [{ username: "cadence [they]", - content: "🖼️ _Uploaded file: [cool cat.png](https://bridge.example.org/download/matrix/cadence.moe/IvxVJFLEuksCNnbojdSIeEvn) (40 MB)_\nCat emoji surrounded by pink hearts", + content: "Cat emoji surrounded by pink hearts\n🖼️ _Uploaded file: [cool cat.png](https://bridge.example.org/download/matrix/cadence.moe/IvxVJFLEuksCNnbojdSIeEvn) (40 MB)_", avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU", allowed_mentions: { parse: ["users", "roles"] @@ -4283,7 +4412,7 @@ test("event2message: files too large for Discord can have a formatted caption", messagesToEdit: [], messagesToSend: [{ username: "cadence [they]", - content: "🖼️ _Uploaded file: [5740.jpg](https://bridge.example.org/download/matrix/thomcat.rocks/RTHsXmcMPXmuHqVNsnbKtRbh) (40 MB)_\nthis event has `formatting`", + content: "this event has `formatting`\n🖼️ _Uploaded file: [5740.jpg](https://bridge.example.org/download/matrix/thomcat.rocks/RTHsXmcMPXmuHqVNsnbKtRbh) (40 MB)_", avatar_url: "https://bridge.example.org/download/matrix/cadence.moe/azCAhThKTojXSZJRoWwZmhvU", allowed_mentions: { parse: ["users", "roles"] diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index ab11c8b..9ca3c5a 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -170,7 +170,8 @@ INSERT INTO member_cache (room_id, mxid, displayname, avatar_url, power_level) V ('!TqlyQmifxGUggEmdBN:cadence.moe', '@ampflower:matrix.org', 'Ampflower 🌺', 'mxc://cadence.moe/PRfhXYBTOalvgQYtmCLeUXko', 0), ('!TqlyQmifxGUggEmdBN:cadence.moe', '@aflower:syndicated.gay', 'Rose', 'mxc://syndicated.gay/ZkBUPXCiXTjdJvONpLJmcbKP', 0), ('!TqlyQmifxGUggEmdBN:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL, 0), -('!iSyXgNxQcEuXoXpsSn:pussthecat.org', '@austin:tchncs.de', 'Austin Huang', 'mxc://tchncs.de/090a2b5e07eed2f71e84edad5207221e6c8f8b8e', 0); +('!iSyXgNxQcEuXoXpsSn:pussthecat.org', '@austin:tchncs.de', 'Austin Huang', 'mxc://tchncs.de/090a2b5e07eed2f71e84edad5207221e6c8f8b8e', 0), +('!zq94fae5bVKUubZLp7:agiadn.org', '@underscore_x:agiadn.org', 'underscore_x', NULL, 100); INSERT INTO reaction (hashed_event_id, message_id, encoded_emoji) VALUES (5162930312280790092, '1141501302736695317', '%F0%9F%90%88');