Move calls from getPublicUrlForMxc to getMedia
This commit is contained in:
parent
b45d0f3038
commit
c0d92ea66d
7 changed files with 64 additions and 52 deletions
|
@ -8,6 +8,8 @@ const {sync} = require("../../passthrough")
|
|||
|
||||
/** @type {import("../converters/emoji-sheet")} */
|
||||
const emojiSheetConverter = sync.require("../converters/emoji-sheet")
|
||||
/** @type {import("../../matrix/api")} */
|
||||
const api = sync.require("../../matrix/api")
|
||||
|
||||
/**
|
||||
* Downloads the emoji from the web and converts to uncompressed PNG data.
|
||||
|
@ -16,16 +18,12 @@ const emojiSheetConverter = sync.require("../converters/emoji-sheet")
|
|||
*/
|
||||
async function getAndConvertEmoji(mxc) {
|
||||
const abortController = new AbortController()
|
||||
|
||||
const url = utils.getPublicUrlForMxc(mxc)
|
||||
assert(url)
|
||||
|
||||
/** @type {import("node-fetch").Response} */
|
||||
// If it turns out to be a GIF, we want to abandon the connection without downloading the whole thing.
|
||||
// If we were using connection pooling, we would be forced to download the entire GIF.
|
||||
// So we set no agent to ensure we are not connection pooling.
|
||||
// @ts-ignore the signal is slightly different from the type it wants (still works fine)
|
||||
const res = await fetch(url, {agent: false, signal: abortController.signal})
|
||||
const res = await api.getMedia(mxc, {agent: false, signal: abortController.signal})
|
||||
return emojiSheetConverter.convertImageStream(res.body, () => {
|
||||
abortController.abort()
|
||||
res.body.pause()
|
||||
|
|
|
@ -23,7 +23,7 @@ const editMessage = sync.require("../../d2m/actions/edit-message")
|
|||
const emojiSheet = sync.require("../actions/emoji-sheet")
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[], pendingFiles?: ({name: string, url: string} | {name: string, url: string, key: string, iv: string} | {name: string, buffer: Buffer | Readable})[]}} message
|
||||
* @param {DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[], pendingFiles?: ({name: string, mxc: string} | {name: string, mxc: string, key: string, iv: string} | {name: string, buffer: Buffer | Readable})[]}} message
|
||||
* @returns {Promise<DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer | Readable}[]}>}
|
||||
*/
|
||||
async function resolvePendingFiles(message) {
|
||||
|
@ -39,7 +39,7 @@ async function resolvePendingFiles(message) {
|
|||
// Encrypted file
|
||||
const d = crypto.createDecipheriv("aes-256-ctr", Buffer.from(p.key, "base64url"), Buffer.from(p.iv, "base64url"))
|
||||
// @ts-ignore
|
||||
fetch(p.url).then(res => res.body.pipe(d))
|
||||
await api.getMedia(p.mxc).then(res => res.body.pipe(d))
|
||||
return {
|
||||
name: p.name,
|
||||
file: d
|
||||
|
@ -47,7 +47,7 @@ async function resolvePendingFiles(message) {
|
|||
} else {
|
||||
// Unencrypted file
|
||||
/** @type {Readable} */ // @ts-ignore
|
||||
const body = await fetch(p.url).then(res => res.body)
|
||||
const body = await api.getMedia(p.mxc).then(res => res.body)
|
||||
return {
|
||||
name: p.name,
|
||||
file: body
|
||||
|
@ -79,7 +79,7 @@ async function sendEvent(event) {
|
|||
|
||||
// no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it
|
||||
|
||||
let {messagesToEdit, messagesToSend, messagesToDelete, ensureJoined} = await eventToMessage.eventToMessage(event, guild, {api, snow: discord.snow, fetch, mxcDownloader: emojiSheet.getAndConvertEmoji})
|
||||
let {messagesToEdit, messagesToSend, messagesToDelete, ensureJoined} = await eventToMessage.eventToMessage(event, guild, {api, snow: discord.snow, mxcDownloader: emojiSheet.getAndConvertEmoji})
|
||||
|
||||
messagesToEdit = await Promise.all(messagesToEdit.map(async e => {
|
||||
e.message = await resolvePendingFiles(e.message)
|
||||
|
|
|
@ -305,7 +305,7 @@ function getUserOrProxyOwnerID(mxid) {
|
|||
* This function will strip them from the content and generate the correct pending file of the sprite sheet.
|
||||
* @param {string} content
|
||||
* @param {{id: string, name: string}[]} attachments
|
||||
* @param {({name: string, url: string} | {name: string, url: string, key: string, iv: string} | {name: string, buffer: Buffer})[]} pendingFiles
|
||||
* @param {({name: string, mxc: string} | {name: string, mxc: string, key: string, iv: string} | {name: string, buffer: Buffer})[]} pendingFiles
|
||||
* @param {(mxc: string) => Promise<Buffer | undefined>} mxcDownloader function that will download the mxc URLs and convert to uncompressed PNG data. use `getAndConvertEmoji` or a mock.
|
||||
*/
|
||||
async function uploadEndOfMessageSpriteSheet(content, attachments, pendingFiles, mxcDownloader) {
|
||||
|
@ -389,7 +389,7 @@ async function handleRoomOrMessageLinks(input, di) {
|
|||
* @param {string} senderMxid
|
||||
* @param {string} roomID
|
||||
* @param {DiscordTypes.APIGuild} guild
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, fetch: import("node-fetch")["default"]}} di
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer}} di
|
||||
*/
|
||||
async function checkWrittenMentions(content, senderMxid, roomID, guild, di) {
|
||||
let writtenMentionMatch = content.match(/(?:^|[^"[<>/A-Za-z0-9])@([A-Za-z][A-Za-z0-9._\[\]\(\)-]+):?/d) // /d flag for indices requires node.js 16+
|
||||
|
@ -440,7 +440,7 @@ const attachmentEmojis = new Map([
|
|||
/**
|
||||
* @param {Ty.Event.Outer_M_Room_Message | Ty.Event.Outer_M_Room_Message_File | Ty.Event.Outer_M_Sticker | Ty.Event.Outer_M_Room_Message_Encrypted_File} event
|
||||
* @param {import("discord-api-types/v10").APIGuild} guild
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, fetch: import("node-fetch")["default"], mxcDownloader: (mxc: string) => Promise<Buffer | undefined>}} di simple-as-nails dependency injection for the matrix API
|
||||
* @param {{api: import("../../matrix/api"), snow: import("snowtransfer").SnowTransfer, mxcDownloader: (mxc: string) => Promise<Buffer | undefined>}} di simple-as-nails dependency injection for the matrix API
|
||||
*/
|
||||
async function eventToMessage(event, guild, di) {
|
||||
let displayName = event.sender
|
||||
|
@ -466,7 +466,7 @@ async function eventToMessage(event, guild, di) {
|
|||
|
||||
let content = event.content.body // ultimate fallback
|
||||
const attachments = []
|
||||
/** @type {({name: string, url: string} | {name: string, url: string, key: string, iv: string} | {name: string, buffer: Buffer})[]} */
|
||||
/** @type {({name: string, mxc: string} | {name: string, mxc: string, key: string, iv: string} | {name: string, buffer: Buffer})[]} */
|
||||
const pendingFiles = []
|
||||
/** @type {DiscordTypes.APIUser[]} */
|
||||
const ensureJoined = []
|
||||
|
@ -767,29 +767,23 @@ async function eventToMessage(event, guild, di) {
|
|||
const description = (event.content.body !== event.content.filename && event.content.filename && event.content.body) || undefined
|
||||
if ("url" in event.content) {
|
||||
// Unencrypted
|
||||
const url = mxUtils.getPublicUrlForMxc(event.content.url)
|
||||
assert(url)
|
||||
attachments.push({id: "0", description, filename})
|
||||
pendingFiles.push({name: filename, url})
|
||||
pendingFiles.push({name: filename, mxc: event.content.url})
|
||||
} else {
|
||||
// Encrypted
|
||||
const url = mxUtils.getPublicUrlForMxc(event.content.file.url)
|
||||
assert(url)
|
||||
assert.equal(event.content.file.key.alg, "A256CTR")
|
||||
attachments.push({id: "0", description, filename})
|
||||
pendingFiles.push({name: filename, url, key: event.content.file.key.k, iv: event.content.file.iv})
|
||||
pendingFiles.push({name: filename, mxc: event.content.file.url, key: event.content.file.key.k, iv: event.content.file.iv})
|
||||
}
|
||||
} else if (event.type === "m.sticker") {
|
||||
content = ""
|
||||
const url = mxUtils.getPublicUrlForMxc(event.content.url)
|
||||
assert(url)
|
||||
let filename = event.content.body
|
||||
if (event.type === "m.sticker") {
|
||||
let mimetype
|
||||
if (event.content.info?.mimetype?.includes("/")) {
|
||||
mimetype = event.content.info.mimetype
|
||||
} else {
|
||||
const res = await di.fetch(url, {method: "HEAD"})
|
||||
const res = await di.api.getMedia(event.content.url, {method: "HEAD"})
|
||||
if (res.status === 200) {
|
||||
mimetype = res.headers.get("content-type")
|
||||
}
|
||||
|
@ -798,7 +792,7 @@ async function eventToMessage(event, guild, di) {
|
|||
filename += "." + mimetype.split("/")[1]
|
||||
}
|
||||
attachments.push({id: "0", filename})
|
||||
pendingFiles.push({name: filename, url})
|
||||
pendingFiles.push({name: filename, mxc: event.content.url})
|
||||
}
|
||||
|
||||
content = displayNameRunoff + replyLine + content
|
||||
|
|
|
@ -3550,7 +3550,7 @@ test("event2message: text attachments work", async t => {
|
|||
content: "",
|
||||
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU",
|
||||
attachments: [{id: "0", description: undefined, filename: "chiki-powerups.txt"}],
|
||||
pendingFiles: [{name: "chiki-powerups.txt", url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/zyThGlYQxvlvBVbVgKDDbiHH"}]
|
||||
pendingFiles: [{name: "chiki-powerups.txt", mxc: "mxc://cadence.moe/zyThGlYQxvlvBVbVgKDDbiHH"}]
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -3586,7 +3586,7 @@ test("event2message: image attachments work", async t => {
|
|||
content: "",
|
||||
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU",
|
||||
attachments: [{id: "0", description: undefined, filename: "cool cat.png"}],
|
||||
pendingFiles: [{name: "cool cat.png", url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/IvxVJFLEuksCNnbojdSIeEvn"}]
|
||||
pendingFiles: [{name: "cool cat.png", mxc: "mxc://cadence.moe/IvxVJFLEuksCNnbojdSIeEvn"}]
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -3622,7 +3622,7 @@ test("event2message: image attachments can have a custom description", async t =
|
|||
content: "",
|
||||
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU",
|
||||
attachments: [{id: "0", description: "Cat emoji surrounded by pink hearts", filename: "cool cat.png"}],
|
||||
pendingFiles: [{name: "cool cat.png", url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/IvxVJFLEuksCNnbojdSIeEvn"}]
|
||||
pendingFiles: [{name: "cool cat.png", url: "mxc://cadence.moe/IvxVJFLEuksCNnbojdSIeEvn"}]
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -3674,7 +3674,7 @@ test("event2message: encrypted image attachments work", async t => {
|
|||
attachments: [{id: "0", description: undefined, filename: "image.png"}],
|
||||
pendingFiles: [{
|
||||
name: "image.png",
|
||||
url: "https://matrix.cadence.moe/_matrix/media/r0/download/heyquark.com/LOGkUTlVFrqfiExlGZNgCJJX",
|
||||
mxc: "mxc://heyquark.com/LOGkUTlVFrqfiExlGZNgCJJX",
|
||||
key: "QTo-oMPnN1Rbc7vBFg9WXMgoctscdyxdFEIYm8NYceo",
|
||||
iv: "Va9SHZpIn5kAAAAAAAAAAA"
|
||||
}]
|
||||
|
@ -3717,7 +3717,7 @@ test("event2message: stickers work", async t => {
|
|||
content: "",
|
||||
avatar_url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/azCAhThKTojXSZJRoWwZmhvU",
|
||||
attachments: [{id: "0", filename: "get_real2.gif"}],
|
||||
pendingFiles: [{name: "get_real2.gif", url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/NyMXQFAAdniImbHzsygScbmN"}]
|
||||
pendingFiles: [{name: "get_real2.gif", mxc: "mxc://cadence.moe/NyMXQFAAdniImbHzsygScbmN"}]
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -3736,15 +3736,17 @@ test("event2message: stickers fetch mimetype from server when mimetype not provi
|
|||
event_id: "$mL-eEVWCwOvFtoOiivDP7gepvf-fTYH6_ioK82bWDI0",
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||
}, {}, {
|
||||
async fetch(url, options) {
|
||||
called++
|
||||
t.equal(url, "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf")
|
||||
t.equal(options.method, "HEAD")
|
||||
return {
|
||||
status: 200,
|
||||
headers: new Map([
|
||||
["content-type", "image/gif"]
|
||||
])
|
||||
api: {
|
||||
async getMedia(mxc, options) {
|
||||
called++
|
||||
t.equal(mxc, "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf")
|
||||
t.equal(options.method, "HEAD")
|
||||
return {
|
||||
status: 200,
|
||||
headers: new Map([
|
||||
["content-type", "image/gif"]
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -3757,7 +3759,7 @@ test("event2message: stickers fetch mimetype from server when mimetype not provi
|
|||
content: "",
|
||||
avatar_url: undefined,
|
||||
attachments: [{id: "0", filename: "YESYESYES.gif"}],
|
||||
pendingFiles: [{name: "YESYESYES.gif", url: "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf"}]
|
||||
pendingFiles: [{name: "YESYESYES.gif", mxc: "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJf"}]
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -3777,15 +3779,17 @@ test("event2message: stickers with unknown mimetype are not allowed", async t =>
|
|||
event_id: "$mL-eEVWCwOvFtoOiivDP7gepvf-fTYH6_ioK82bWDI0",
|
||||
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||
}, {}, {
|
||||
async fetch(url, options) {
|
||||
called++
|
||||
t.equal(url, "https://matrix.cadence.moe/_matrix/media/r0/download/cadence.moe/ybOWQCaXysnyUGuUCaQlTGJe")
|
||||
t.equal(options.method, "HEAD")
|
||||
return {
|
||||
status: 404,
|
||||
headers: new Map([
|
||||
["content-type", "application/json"]
|
||||
])
|
||||
api: {
|
||||
async getMedia(mxc, options) {
|
||||
called++
|
||||
t.equal(mxc, "mxc://cadence.moe/ybOWQCaXysnyUGuUCaQlTGJe")
|
||||
t.equal(options.method, "HEAD")
|
||||
return {
|
||||
status: 404,
|
||||
headers: new Map([
|
||||
["content-type", "application/json"]
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -223,10 +223,10 @@ async function getViaServersQuery(roomID, api) {
|
|||
*/
|
||||
function getPublicUrlForMxc(mxc) {
|
||||
assert(hasher, "xxhash is not ready yet")
|
||||
const avatarURLParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
if (!avatarURLParts) return null
|
||||
const mediaParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
if (!mediaParts) return null
|
||||
|
||||
const serverAndMediaID = `${avatarURLParts[1]}/${avatarURLParts[2]}`
|
||||
const serverAndMediaID = `${mediaParts[1]}/${mediaParts[2]}`
|
||||
const unsignedHash = hasher.h64(serverAndMediaID)
|
||||
const signedHash = unsignedHash - 0x8000000000000000n // shifting down to signed 64-bit range
|
||||
db.prepare("INSERT OR IGNORE INTO media_proxy (permitted_hash) VALUES (?)").run(signedHash)
|
||||
|
|
|
@ -297,6 +297,7 @@ async function setUserPowerCascade(roomID, mxid, power) {
|
|||
}
|
||||
|
||||
async function ping() {
|
||||
// not using mreq so that we can read the status code
|
||||
const res = await fetch(`${mreq.baseUrl}/client/v1/appservice/${reg.id}/ping`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -312,6 +313,21 @@ async function ping() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} mxc
|
||||
* @param {RequestInit} [init]
|
||||
*/
|
||||
function getMedia(mxc, init = {}) {
|
||||
const mediaParts = mxc?.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
assert(mediaParts)
|
||||
return fetch(`${mreq.baseUrl}/client/v1/media/download/${mediaParts[1]}/${mediaParts[2]}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${reg.as_token}`
|
||||
},
|
||||
...init
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.path = path
|
||||
module.exports.register = register
|
||||
module.exports.createRoom = createRoom
|
||||
|
@ -336,3 +352,4 @@ module.exports.profileSetAvatarUrl = profileSetAvatarUrl
|
|||
module.exports.setUserPower = setUserPower
|
||||
module.exports.setUserPowerCascade = setUserPowerCascade
|
||||
module.exports.ping = ping
|
||||
module.exports.getMedia = getMedia
|
||||
|
|
|
@ -217,9 +217,8 @@ const commands = [{
|
|||
} else {
|
||||
// Upload it to Discord and have the bridge sync it back to Matrix again
|
||||
for (const e of toUpload) {
|
||||
const publicUrl = mxUtils.getPublicUrlForMxc(e.url)
|
||||
// @ts-ignore
|
||||
const resizeInput = await fetch(publicUrl, {agent: false}).then(res => res.arrayBuffer())
|
||||
const resizeInput = await api.getMedia(e.url, {agent: false}).then(res => res.arrayBuffer())
|
||||
const resizeOutput = await sharp(resizeInput)
|
||||
.resize(EMOJI_SIZE, EMOJI_SIZE, {fit: "inside", withoutEnlargement: true, background: {r: 0, g: 0, b: 0, alpha: 0}})
|
||||
.png()
|
||||
|
|
Loading…
Reference in a new issue