Spoiler Fix #90

Manually merged
cadence merged 11 commits from Guzio/out-of-your-element:spoilerfix into main 2026-10-20 12:45:17 +00:00
4 changed files with 122 additions and 116 deletions
Showing only changes of commit 15055b8ee0 - Show all commits

Use less hacky way of setting the params

Guzio 2026-10-20 13:22:18 +02:00

View file

@ -136,7 +136,7 @@ const embedTitleParser = markdown.markdownEngine.parserFor({
*/ */
async function attachmentToEvent(mentions, attachment, alwaysLink, di) { async function attachmentToEvent(mentions, attachment, alwaysLink, di) {
const spoiler = attachment.filename.startsWith("SPOILER_") || !!((attachment.flags || 0) & DiscordTypes.AttachmentFlags.IsSpoiler); const spoiler = attachment.filename.startsWith("SPOILER_") || !!((attachment.flags || 0) & DiscordTypes.AttachmentFlags.IsSpoiler);
const external_url = dUtils.getPublicUrlForCdn(attachment.url, spoiler ? "?spoiler" : "") const external_url = dUtils.getPublicUrlForCdn(attachment.url, spoiler)
const emoji = const emoji =
attachment.content_type?.startsWith("image/jp") ? "📸" attachment.content_type?.startsWith("image/jp") ? "📸"
: attachment.content_type?.startsWith("image/") ? "🖼️" : attachment.content_type?.startsWith("image/") ? "🖼️"

View file

@ -366,9 +366,9 @@ test("message2event: spoiler attachment", async t => {
$type: "m.room.message", $type: "m.room.message",
"m.mentions": {}, "m.mentions": {},
msgtype: "m.text", msgtype: "m.text",
body: "📄 Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler (74 KB)", body: "📄 Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler= (74 KB)",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
formatted_body: "📄 Uploaded <strong>SPOILER</strong> file: <a href=\"https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler\">https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler</a> (74 KB)" formatted_body: "📄 Uploaded <strong>SPOILER</strong> file: <a href=\"https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler=\">https://bridge.example.org/download/discordcdn/1100319550446252084/1147465564307079258/SPOILER_69-GNDP-CADENCE.nfs.gci?spoiler=</a> (74 KB)"
}]) }])
}) })
@ -1151,10 +1151,12 @@ test("message2event: very large png is converted to jpeg for upload", async t =>
async fetch(url, init) { async fetch(url, init) {
t.equal(url, "https://media.discordapp.net/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png?format=jpeg") t.equal(url, "https://media.discordapp.net/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png?format=jpeg")
t.equal(init.method, "HEAD") t.equal(init.method, "HEAD")
return new Response("wa", {headers: { return new Response("wa", {
headers: {
"Content-Type": "image/jpeg", "Content-Type": "image/jpeg",
"Content-Length": 483085 "Content-Length": 483085
}}) }
})
} }
}) })
t.deepEqual(events, [{ t.deepEqual(events, [{
@ -1205,12 +1207,12 @@ test("message2event: multiple attachments are combined into the same event where
msgtype: "m.text", msgtype: "m.text",
body: "hey" body: "hey"
+ "\n📄 Uploaded file: https://bridge.example.org/download/discordcdn/123/456/789.mega (100 MB)" + "\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?spoiler (38 KB)" + "\n📸 Uploaded SPOILER file: https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg?spoiler= (38 KB)"
+ "\n📄 Uploaded file: https://bridge.example.org/download/discordcdn/123/456/789.mega (100 MB)", + "\n📄 Uploaded file: https://bridge.example.org/download/discordcdn/123/456/789.mega (100 MB)",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
formatted_body: "hey" formatted_body: "hey"
+ `<br>📄 Uploaded file: <a href="https://bridge.example.org/download/discordcdn/123/456/789.mega">hey.jpg</a> (100 MB)` + `<br>📄 Uploaded file: <a href="https://bridge.example.org/download/discordcdn/123/456/789.mega">hey.jpg</a> (100 MB)`
+ `<br>📸 Uploaded <strong>SPOILER</strong> file: <a href=\"https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg?spoiler\">https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg?spoiler</a> (38 KB)` + `<br>📸 Uploaded <strong>SPOILER</strong> file: <a href=\"https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg?spoiler=\">https://bridge.example.org/download/discordcdn/123/456/SPOILER_secret.jpg?spoiler=</a> (38 KB)`
+ `<br>📄 Uploaded file: <a href="https://bridge.example.org/download/discordcdn/123/456/789.mega">hey.jpg</a> (100 MB)` + `<br>📄 Uploaded file: <a href="https://bridge.example.org/download/discordcdn/123/456/789.mega">hey.jpg</a> (100 MB)`
}, { }, {
$type: "m.room.message", $type: "m.room.message",
@ -1783,7 +1785,8 @@ test("message2event: cross-room reply", async t => {
} }
}, },
content: "cross-room reply" content: "cross-room reply"
}, {}, {}, {api: { }, {}, {}, {
api: {
async getEvent(roomID, eventID) { async getEvent(roomID, eventID) {
called++ called++
t.equal(roomID, "!mHmhQQPwXNananaOLD:cadence.moe") t.equal(roomID, "!mHmhQQPwXNananaOLD:cadence.moe")
@ -1807,7 +1810,8 @@ test("message2event: cross-room reply", async t => {
} }
} }
} }
}}) }
})
t.deepEqual(events, [ t.deepEqual(events, [
{ {
$type: "m.room.message", $type: "m.room.message",

View file

@ -136,15 +136,17 @@ function timestampToSnowflakeInexact(timestamp) {
/** /**
* @param {string} url * @param {string} url
* @param {""|"?spoiler"} spoiler * @param {boolean} spoiler
*/ */
function getPublicUrlForCdn(url, spoiler="") { function getPublicUrlForCdn(url, spoiler=false) {
const match = url.match(/https:\/\/(cdn|media)\.discordapp\.(?:com|net)\/attachments\/([0-9]+)\/([0-9]+)\/([-A-Za-z0-9_.,]+)/) const match = url.match(/https:\/\/(cdn|media)\.discordapp\.(?:com|net)\/attachments\/([0-9]+)\/([0-9]+)\/([-A-Za-z0-9_.,]+)/)
if (!match) return url if (!match) return url
const unsignedHash = hasher.h64(match[3]) // attachment ID const unsignedHash = hasher.h64(match[3]) // attachment ID
const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range
db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash) 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]}${spoiler}` const transformedUrl = new URL(`${reg.ooye.bridge_origin}/download/discord${match[1]}/${match[2]}/${match[3]}/${match[4]}`)
if (spoiler) transformedUrl.searchParams.set("spoiler", "")
return transformedUrl.toString()
} }
/** /**

View file

@ -21,7 +21,7 @@ const schema = {
file_name: z.string().regex(/^[-A-Za-z0-9_.,]+$/) file_name: z.string().regex(/^[-A-Za-z0-9_.,]+$/)
}), }),
query: z.object({ query: z.object({
spoiler: z.optional(z.string().max(0)) // Lets you choose between no query param, and a ?spoiler query param (H3 considers it an empty string). Booleans do NOT work here, unfortunately. spoiler: z.string().optional()
}) })
} }