From 1a1173413b27c9182914341d404e6c4daf7cde3f Mon Sep 17 00:00:00 2001 From: Guzio Date: Mon, 19 Oct 2026 13:43:39 +0200 Subject: [PATCH 1/4] spoiler fix --- src/d2m/converters/message-to-event.js | 9 +++++---- src/discord/utils.js | 9 ++++++--- src/web/routes/download-discord.js | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index a291a5e..62eed46 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -135,7 +135,8 @@ const embedTitleParser = markdown.markdownEngine.parserFor({ * @param {{fetch?: typeof fetch}} [di] */ async function attachmentToEvent(mentions, attachment, alwaysLink, di) { - const external_url = dUtils.getPublicUrlForCdn(attachment.url) + const spoiler = attachment.filename.startsWith("SPOILER_") || !!((attachment.flags || 0) & DiscordTypes.AttachmentFlags.IsSpoiler); + const external_url = dUtils.getPublicUrlForCdn(attachment.url, spoiler ? "spoiler/" : "") const emoji = attachment.content_type?.startsWith("image/jp") ? "šŸ“ø" : attachment.content_type?.startsWith("image/") ? "šŸ–¼ļø" @@ -143,15 +144,15 @@ async function attachmentToEvent(mentions, attachment, alwaysLink, di) { : attachment.content_type?.startsWith("text/") ? "šŸ“" : attachment.content_type?.startsWith("audio/") ? "šŸŽ¶" : "šŸ“„" - // no native media spoilers in Element, so we'll post a link instead, forcing it to not preview using a blockquote - if (attachment.filename.startsWith("SPOILER_") || !!((attachment.flags || 0) & DiscordTypes.AttachmentFlags.IsSpoiler)) { + // no native media spoilers in Element, so we'll post a spoiler (unpreviewable) link instead + if (spoiler) { return { $type: "m.room.message", "m.mentions": mentions, msgtype: "m.text", body: `${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})`, format: "org.matrix.custom.html", - formatted_body: `
${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})
` + formatted_body: `${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})` } } // attempt to convert large PNG image files to JPEG, since it's almost certainly a photo that was forced into PNG by the user copy-pasting. diff --git a/src/discord/utils.js b/src/discord/utils.js index 6fb0b43..2f7a7e0 100644 --- a/src/discord/utils.js +++ b/src/discord/utils.js @@ -134,14 +134,17 @@ function timestampToSnowflakeInexact(timestamp) { return String((timestamp - EPOCH) * 2**22) } -/** @param {string} url */ -function getPublicUrlForCdn(url) { +/** + * @param {string} url + * @param {""|"spoiler/"} spoiler + */ +function getPublicUrlForCdn(url, spoiler="") { const match = url.match(/https:\/\/(cdn|media)\.discordapp\.(?:com|net)\/attachments\/([0-9]+)\/([0-9]+)\/([-A-Za-z0-9_.,]+)/) if (!match) return url const unsignedHash = hasher.h64(match[3]) // attachment ID const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash) - return `${reg.ooye.bridge_origin}/download/discord${match[1]}/${match[2]}/${match[3]}/${match[4]}` + return `${reg.ooye.bridge_origin}/download/discord${match[1]}${spoiler}/${match[2]}/${match[3]}/${match[4]}` } /** diff --git a/src/web/routes/download-discord.js b/src/web/routes/download-discord.js index 769fc9c..f802d17 100644 --- a/src/web/routes/download-discord.js +++ b/src/web/routes/download-discord.js @@ -9,7 +9,10 @@ let hasher = null // @ts-ignore require("xxhash-wasm")().then(h => hasher = h) -const {discord, as, select} = require("../../passthrough") +const {discord, as, select, sync} = require("../../passthrough") + +/** @type {import("../web-utils")} */ +const webUtils = sync.require("../web-utils") const schema = { params: z.object({ @@ -40,10 +43,16 @@ function timeUntilExpiry(url) { if (time > 0) return time } -function defineMediaProxyHandler(domain) { +/** + * @param {string} domain + * @param {boolean} spoiler + */ +function defineMediaProxyHandler(domain, spoiler) { return defineEventHandler(async event => { const params = await getValidatedRouterParams(event, schema.params.parse) + if (spoiler) webUtils.rejectUrlPreviewer(event) + const unsignedHash = hasher.h64(params.attachment_id) const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range @@ -81,7 +90,9 @@ function defineMediaProxyHandler(domain) { }) } -as.router.get(`/download/discordcdn/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("cdn.discordapp.com")) -as.router.get(`/download/discordmedia/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("media.discordapp.net")) +as.router.get(`/download/discordcdn/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("cdn.discordapp.com", false)) +as.router.get(`/download/discordmedia/:channel_id/:attachment_id/:file_name`, defineMediaProxyHandler("media.discordapp.net", false)) +as.router.get(`/download/discordcdn/:channel_id/spoiler/:attachment_id/:file_name`, defineMediaProxyHandler("cdn.discordapp.com", true)) +as.router.get(`/download/discordmedia/:channel_id/spoiler/:attachment_id/:file_name`, defineMediaProxyHandler("media.discordapp.net", true)) module.exports._cache = cache -- 2.34.1 From 966095c3610b5565c2dd902805ebfb3b00dd2187 Mon Sep 17 00:00:00 2001 From: Guzio Date: Mon, 19 Oct 2026 13:57:48 +0200 Subject: [PATCH 2/4] of course, I fucked up where the / goes... --- src/discord/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/utils.js b/src/discord/utils.js index 2f7a7e0..9d1775f 100644 --- a/src/discord/utils.js +++ b/src/discord/utils.js @@ -144,7 +144,7 @@ function getPublicUrlForCdn(url, spoiler="") { const unsignedHash = hasher.h64(match[3]) // attachment ID const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash) - return `${reg.ooye.bridge_origin}/download/discord${match[1]}${spoiler}/${match[2]}/${match[3]}/${match[4]}` + return `${reg.ooye.bridge_origin}/download/discord${match[1]}/${spoiler}${match[2]}/${match[3]}/${match[4]}` } /** -- 2.34.1 From 4350bff202e83151b25c35ba8068537d75c06bf4 Mon Sep 17 00:00:00 2001 From: Guzio Date: Mon, 19 Oct 2026 14:06:33 +0200 Subject: [PATCH 3/4] kill me --- src/discord/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/discord/utils.js b/src/discord/utils.js index 9d1775f..8effd88 100644 --- a/src/discord/utils.js +++ b/src/discord/utils.js @@ -144,7 +144,7 @@ function getPublicUrlForCdn(url, spoiler="") { const unsignedHash = hasher.h64(match[3]) // attachment ID const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash) - return `${reg.ooye.bridge_origin}/download/discord${match[1]}/${spoiler}${match[2]}/${match[3]}/${match[4]}` + return `${reg.ooye.bridge_origin}/download/discord${match[1]}/${match[2]}/${spoiler}${match[3]}/${match[4]}` } /** -- 2.34.1 From 727b5a3e4f97c59de6d0cd69d6adc00c6eed6d81 Mon Sep 17 00:00:00 2001 From: Guzio Date: Mon, 19 Oct 2026 15:33:54 +0200 Subject: [PATCH 4/4] update tests --- src/d2m/converters/message-to-event.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/d2m/converters/message-to-event.test.js b/src/d2m/converters/message-to-event.test.js index 44b9fe6..233a4ba 100644 --- a/src/d2m/converters/message-to-event.test.js +++ b/src/d2m/converters/message-to-event.test.js @@ -366,9 +366,9 @@ test("message2event: spoiler attachment", async t => { $type: "m.room.message", "m.mentions": {}, msgtype: "m.text", - body: "šŸ“„ Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci (74 KB)", + body: "šŸ“„ Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/spoiler/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci (74 KB)", format: "org.matrix.custom.html", - formatted_body: "
šŸ“„ Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci (74 KB)
" + formatted_body: "šŸ“„ Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/spoiler/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci (74 KB)" }]) }) @@ -1205,13 +1205,13 @@ test("message2event: multiple attachments are combined into the same event where msgtype: "m.text", body: "hey" + "\nšŸ“„ Uploaded file: https://bridge.example.org/download/discordcdn/123/456/789.mega (100 MB)" - + "\nšŸ“ø Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg (38 KB)" + + "\nšŸ“ø Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/spoiler/456/SPOILER_secret.jpg (38 KB)" + "\nšŸ“„ Uploaded file: https://bridge.example.org/download/discordcdn/123/456/789.mega (100 MB)", format: "org.matrix.custom.html", formatted_body: "hey" + `
šŸ“„ Uploaded file: hey.jpg (100 MB)` - + `
šŸ“ø Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg (38 KB)
` - + `šŸ“„ Uploaded file: hey.jpg (100 MB)` + + `
šŸ“ø Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/spoiler/456/SPOILER_secret.jpg (38 KB)` + + `
šŸ“„ Uploaded file: hey.jpg (100 MB)` }, { $type: "m.room.message", "m.mentions": {}, -- 2.34.1