d->m: Try too-large PNGs as JPEG for upload
Large PNGs were almost certainly originally JPEG photos that were converted to PNG by the user copy-pasting. If the PNG is just supposed to be that large, you can still get the original through the external URL option. Had to change _removeExpiryParams to be based on URL parsing instead of regexp because regexp didn't work with &format=jpeg on the end.
This commit is contained in:
@@ -55,7 +55,7 @@ async function sendMessage(message, channel, guild) {
|
||||
}
|
||||
}
|
||||
|
||||
const events = await messageToEvent.messageToEvent(message, guild, {}, {api, snow: discord.snow})
|
||||
const events = await messageToEvent.messageToEvent(message, guild, {}, {api, snow: discord.snow, fetch})
|
||||
const eventIDs = []
|
||||
if (events.length) {
|
||||
db.prepare("INSERT OR IGNORE INTO message_room (message_id, historical_room_index) VALUES (?, ?)").run(message.id, historicalRoomIndex)
|
||||
|
||||
@@ -69,8 +69,8 @@ async function editToChanges(message, guild, api) {
|
||||
|
||||
// Figure out what we will be replacing them with
|
||||
|
||||
const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api})
|
||||
const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api})
|
||||
const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api, fetch})
|
||||
const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api, fetch})
|
||||
assert.ok(newFallbackContent.length === newInnerContent.length)
|
||||
|
||||
// Match the new events to the old events
|
||||
|
||||
@@ -109,10 +109,11 @@ const embedTitleParser = markdown.markdownEngine.parserFor({
|
||||
|
||||
/**
|
||||
* @param {{room?: boolean, user_ids?: string[]}} mentions
|
||||
* @param {Omit<DiscordTypes.APIAttachment, "id" | "proxy_url">} attachment
|
||||
* @param {Omit<DiscordTypes.APIAttachment, "id" | "proxy_url"> & {proxy_url?: string}} attachment
|
||||
* @param {boolean} [alwaysLink]
|
||||
* @param {{fetch?: typeof fetch}} [di]
|
||||
*/
|
||||
async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
async function attachmentToEvent(mentions, attachment, alwaysLink, di) {
|
||||
const external_url = dUtils.getPublicUrlForCdn(attachment.url)
|
||||
const emoji =
|
||||
attachment.content_type?.startsWith("image/jp") ? "📸"
|
||||
@@ -132,8 +133,37 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
formatted_body: `<blockquote>${emoji} Uploaded SPOILER file: <a href="${external_url}">${external_url}</a> (${pb(attachment.size)})</blockquote>`
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
if (
|
||||
attachment.content_type === "image/png" && attachment.size > reg.ooye.max_file_size
|
||||
&& !alwaysLink && attachment.proxy_url && attachment.width && attachment.height && di?.fetch
|
||||
) {
|
||||
const proxyUrl = new URL(attachment.proxy_url)
|
||||
proxyUrl.searchParams.set("format", "jpeg")
|
||||
const jpegUrl = proxyUrl.toString()
|
||||
const res = await di.fetch(jpegUrl, {method: "HEAD"})
|
||||
const newFilename = attachment.filename.replace(/\.png$/, ".jpg")
|
||||
const newSize = Number(res.headers.get("content-length"))
|
||||
if (res.ok && res.headers.get("content-type") === "image/jpeg" && !res.headers.has("content-encoding") && newSize <= reg.ooye.max_file_size) {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
msgtype: "m.image",
|
||||
url: await file.uploadDiscordFileToMxc(jpegUrl),
|
||||
external_url,
|
||||
body: attachment.description || newFilename,
|
||||
filename: newFilename,
|
||||
info: {
|
||||
mimetype: "image/jpeg",
|
||||
w: attachment.width,
|
||||
h: attachment.height,
|
||||
size: newSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// for large files, always link them instead of uploading so I don't use up all the space in the content repo
|
||||
else if (alwaysLink || attachment.size > reg.ooye.max_file_size) {
|
||||
if (alwaysLink || attachment.size > reg.ooye.max_file_size) {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
@@ -142,7 +172,8 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: `${emoji} Uploaded file: <a href="${external_url}">${attachment.filename}</a> (${pb(attachment.size)})`
|
||||
}
|
||||
} else if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) {
|
||||
}
|
||||
if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
@@ -158,7 +189,8 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
size: attachment.size
|
||||
}
|
||||
}
|
||||
} else if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) {
|
||||
}
|
||||
if (attachment.content_type?.startsWith("video/") && attachment.width && attachment.height) {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
@@ -174,7 +206,8 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
size: attachment.size
|
||||
}
|
||||
}
|
||||
} else if (attachment.content_type?.startsWith("audio/")) {
|
||||
}
|
||||
if (attachment.content_type?.startsWith("audio/")) {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
@@ -189,19 +222,19 @@ async function attachmentToEvent(mentions, attachment, alwaysLink) {
|
||||
duration: attachment.duration_secs && Math.round(attachment.duration_secs * 1000)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
msgtype: "m.file",
|
||||
url: await file.uploadDiscordFileToMxc(attachment.url),
|
||||
external_url,
|
||||
body: attachment.description || attachment.filename,
|
||||
filename: attachment.filename,
|
||||
info: {
|
||||
mimetype: attachment.content_type,
|
||||
size: attachment.size
|
||||
}
|
||||
}
|
||||
// else
|
||||
return {
|
||||
$type: "m.room.message",
|
||||
"m.mentions": mentions,
|
||||
msgtype: "m.file",
|
||||
url: await file.uploadDiscordFileToMxc(attachment.url),
|
||||
external_url,
|
||||
body: attachment.description || attachment.filename,
|
||||
filename: attachment.filename,
|
||||
info: {
|
||||
mimetype: attachment.content_type,
|
||||
size: attachment.size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +328,7 @@ function mergeTextEvents(newEvents, events, forceSameMsgtype, forceMerge = false
|
||||
* - includeEditFallbackStar: false
|
||||
* - alwaysReturnFormattedBody: false - formatted_body will be skipped if it is the same as body because the message is plaintext. if you want the formatted_body to be returned anyway, for example to merge it with another message, then set this to true.
|
||||
* - scanTextForMentions: true - needs to be set to false when converting forwarded messages etc which may be from a different channel that can't be scanned.
|
||||
* @param {{api: import("../../matrix/api"), snow?: import("snowtransfer").SnowTransfer}} di simple-as-nails dependency injection for the matrix API
|
||||
* @param {{api: import("../../matrix/api"), snow?: import("snowtransfer").SnowTransfer, fetch?: typeof fetch}} di simple-as-nails dependency injection for the matrix API
|
||||
* @returns {Promise<{$type: string, $sender?: string, [x: string]: any}[]>}
|
||||
*/
|
||||
async function messageToEvent(message, guild, options = {}, di) {
|
||||
@@ -882,7 +915,7 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||
|
||||
// Then attachments
|
||||
if (message.attachments) {
|
||||
const attachmentEvents = await Promise.all(message.attachments.map(attachment => attachmentToEvent(mentions, attachment)))
|
||||
const attachmentEvents = await Promise.all(message.attachments.map(attachment => attachmentToEvent(mentions, attachment, false, {fetch: di?.fetch})))
|
||||
|
||||
// Try to merge attachment events with the previous event
|
||||
// This means that if the attachments ended up as a text link, and especially if there were many of them, the events will be joined together.
|
||||
@@ -913,7 +946,7 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||
url: file.url,
|
||||
height: file.height,
|
||||
width: file.width,
|
||||
}, true)
|
||||
}, true, {fetch: di?.fetch})
|
||||
stack.msb.addLine(ev.body, ev.formatted_body)
|
||||
}
|
||||
else if (component.type === DiscordTypes.ComponentType.MediaGallery) {
|
||||
|
||||
@@ -1103,6 +1103,45 @@ test("message2event: very large attachment is linked instead of being uploaded",
|
||||
}])
|
||||
})
|
||||
|
||||
test("message2event: very large png is converted to jpeg for upload", async t => {
|
||||
const events = await messageToEvent({
|
||||
content: "",
|
||||
attachments: [{
|
||||
filename: "855064e9-bdae-487a-a497-c662bd510487.png",
|
||||
url: "https://cdn.discordapp.com/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png",
|
||||
proxy_url: "https://media.discordapp.net/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png",
|
||||
content_type: "image/png",
|
||||
width: 5472,
|
||||
height: 3648,
|
||||
size: 14967919
|
||||
}]
|
||||
}, data.guild.general, {}, {
|
||||
async fetch(url, init) {
|
||||
t.equal(url, "https://media.discordapp.net/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png?format=jpeg")
|
||||
t.equal(init.method, "HEAD")
|
||||
return new Response("wa", {headers: {
|
||||
"Content-Type": "image/jpeg",
|
||||
"Content-Length": 483085
|
||||
}})
|
||||
}
|
||||
})
|
||||
t.deepEqual(events, [{
|
||||
$type: "m.room.message",
|
||||
"m.mentions": {},
|
||||
msgtype: "m.image",
|
||||
body: "855064e9-bdae-487a-a497-c662bd510487.jpg",
|
||||
info: {
|
||||
w: 5472,
|
||||
h: 3648,
|
||||
mimetype: "image/jpeg",
|
||||
size: 483085
|
||||
},
|
||||
external_url: "https://bridge.example.org/download/discordcdn/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png",
|
||||
filename: "855064e9-bdae-487a-a497-c662bd510487.jpg",
|
||||
url: "mxc://cadence.moe/zXNXRvJwRDMIzZZVqHUXtGeD"
|
||||
}])
|
||||
})
|
||||
|
||||
test("message2event: multiple attachments are combined into the same event where possible", async t => {
|
||||
const events = await messageToEvent({
|
||||
content: "hey",
|
||||
|
||||
+8
-3
@@ -15,10 +15,15 @@ const IMAGE_SIZE = 1024
|
||||
const inflight = new Map()
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string} urlString
|
||||
*/
|
||||
function _removeExpiryParams(url) {
|
||||
return url.replace(/\?(?:(?:ex|is|sg|hm)=[a-f0-9]+&?)*$/, "")
|
||||
function _removeExpiryParams(urlString) {
|
||||
const url = new URL(urlString)
|
||||
url.searchParams.delete("ex")
|
||||
url.searchParams.delete("is")
|
||||
url.searchParams.delete("sg")
|
||||
url.searchParams.delete("hm")
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,3 +20,9 @@ test("removeExpiryParams: rearranged params are removed", t => {
|
||||
const result = file._removeExpiryParams(url)
|
||||
t.equal(result, "https://cdn.discordapp.com/attachments/112760669178241024/1157363960518029322/image.png")
|
||||
})
|
||||
|
||||
test("removeExpiryParams: works on media proxy and keeps quality", t => {
|
||||
const url = "https://media.discordapp.net/attachments/1160894080998461480/1548665030898094240/855064e9-bdae-487a-a497-c662bd510487.png?ex=6aa7e234&is=6aa690b4&hm=7c153efcdd1feb1d36d0bb99955a2bfe12f26abea377fed02980521766f51620&format=jpeg"
|
||||
const result = file._removeExpiryParams(url)
|
||||
t.equal(result, "https://media.discordapp.net/attachments/1160894080998461480/1548665030898094240/855064e9-bdae-487a-a497-c662bd510487.png?format=jpeg")
|
||||
})
|
||||
|
||||
@@ -168,7 +168,8 @@ INSERT INTO file (discord_url, mxc_url) VALUES
|
||||
('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'),
|
||||
('https://cdn.discordapp.com/attachments/123/456/my_enemies.txt', 'mxc://cadence.moe/y89EOTRp2lbeOkgdsEleGOge'),
|
||||
('https://cdn.discordapp.com/emojis/1254940125948022915.webp', 'mxc://cadence.moe/bvVJFgOIyNcAknKCbmaHDktG');
|
||||
('https://cdn.discordapp.com/emojis/1254940125948022915.webp', 'mxc://cadence.moe/bvVJFgOIyNcAknKCbmaHDktG'),
|
||||
('https://media.discordapp.net/attachments/1160894080998461480/1548656385292509274/855064e9-bdae-487a-a497-c662bd510487.png?format=jpeg', 'mxc://cadence.moe/zXNXRvJwRDMIzZZVqHUXtGeD');
|
||||
|
||||
INSERT INTO emoji (emoji_id, name, animated, mxc_url) VALUES
|
||||
('230201364309868544', 'hippo', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'),
|
||||
|
||||
Reference in New Issue
Block a user