Spoiler Fix #94

Closed
Guzio wants to merge 4 commits from Guzio/out-of-your-element:spoilerfix into main
3 changed files with 26 additions and 11 deletions
Showing only changes of commit 1a1173413b - Show all commits

spoiler fix

Guzio 2026-10-19 13:43:39 +02:00

View file

@ -135,7 +135,8 @@ const embedTitleParser = markdown.markdownEngine.parserFor({
* @param {{fetch?: typeof fetch}} [di] * @param {{fetch?: typeof fetch}} [di]
*/ */
async function attachmentToEvent(mentions, attachment, alwaysLink, 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 = const emoji =
attachment.content_type?.startsWith("image/jp") ? "📸" attachment.content_type?.startsWith("image/jp") ? "📸"
: attachment.content_type?.startsWith("image/") ? "🖼️" : 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("text/") ? "📝"
: attachment.content_type?.startsWith("audio/") ? "🎶" : 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 // no native media spoilers in Element, so we'll post a spoiler (unpreviewable) link instead
if (attachment.filename.startsWith("SPOILER_") || !!((attachment.flags || 0) & DiscordTypes.AttachmentFlags.IsSpoiler)) { if (spoiler) {
return { return {
$type: "m.room.message", $type: "m.room.message",
"m.mentions": mentions, "m.mentions": mentions,
msgtype: "m.text", msgtype: "m.text",
body: `${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})`, body: `${emoji} Uploaded SPOILER file: ${external_url} (${pb(attachment.size)})`,
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
formatted_body: `<blockquote>${emoji} Uploaded SPOILER file: <a href="${external_url}">${external_url}</a> (${pb(attachment.size)})</blockquote>` formatted_body: `${emoji} Uploaded <strong>SPOILER</strong> file: <a href="${external_url}">${external_url}</a> (${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. // 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.

View file

@ -134,14 +134,17 @@ function timestampToSnowflakeInexact(timestamp) {
return String((timestamp - EPOCH) * 2**22) 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_.,]+)/) 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]}` return `${reg.ooye.bridge_origin}/download/discord${match[1]}${spoiler}/${match[2]}/${match[3]}/${match[4]}`
} }
/** /**

View file

@ -9,7 +9,10 @@ let hasher = null
// @ts-ignore // @ts-ignore
require("xxhash-wasm")().then(h => hasher = h) 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 = { const schema = {
params: z.object({ params: z.object({
@ -40,10 +43,16 @@ function timeUntilExpiry(url) {
if (time > 0) return time if (time > 0) return time
} }
function defineMediaProxyHandler(domain) { /**
* @param {string} domain
* @param {boolean} spoiler
*/
function defineMediaProxyHandler(domain, spoiler) {
return defineEventHandler(async event => { return defineEventHandler(async event => {
const params = await getValidatedRouterParams(event, schema.params.parse) const params = await getValidatedRouterParams(event, schema.params.parse)
if (spoiler) webUtils.rejectUrlPreviewer(event)
const unsignedHash = hasher.h64(params.attachment_id) const unsignedHash = hasher.h64(params.attachment_id)
const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range 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/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")) 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 module.exports._cache = cache