summary refs log tree commit diff
diff options
context:
space:
mode:
authorMathMan05 <mathmanrm@gmail.com>2026-02-13 21:53:51 -0600
committerRory& <root@rory.gay>2026-03-07 00:58:31 +0100
commit540e5d0d7a9991fd2828c4efefbf0ae4f6524f9a (patch)
treed8c3157754638f4dfafd6b29aa4f31df909d7c33
parentv2 init work (diff)
downloadserver-ts-540e5d0d7a9991fd2828c4efefbf0ae4f6524f9a.tar.xz
maybe processing media?
-rw-r--r--src/api/util/handlers/Message.ts102
1 files changed, 90 insertions, 12 deletions
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts

index de98e455..f73b62fb 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { EmbedHandlers } from "@spacebar/api"; +import { EmbedHandlers, randomString } from "@spacebar/api"; import { Application, Attachment, @@ -104,11 +104,86 @@ function checkActionRow(row: ActionRowComponent, knownComponentIds: string[], er } } } -async function processMedia(media: UnfurledMediaItem) { +async function processMedia(media: UnfurledMediaItem, messageId: string, batchId: string, user: User, channel: Channel, id: string) { if (Object.keys(media).length > 1) throw new HTTPError("no, you can't send those"); if (!URL.canParse(media.url)) throw new HTTPError("media URL must be a URI"); const url = new URL(media.url); if (!["http", "https", "attachment"].includes(url.protocol)) throw new HTTPError("invalid media protocol"); + let attEnt: CloudAttachment; + let delWhenDone = false; + if (url.protocol !== "attachment") { + const res = await fetch(url); + if (!res.ok) throw new HTTPError("URL did not return OK"); + const blob = await res.blob(); + const name = url.pathname.split("/").findLast((_) => _) || id; + const uploadFilename = `${channel.id}/${batchId}/${id ?? "0"}/${name}`; + attEnt = CloudAttachment.create({ + user: user, + channel: channel, + uploadFilename: uploadFilename, + userAttachmentId: "0", + userFilename: name, + userFileSize: blob.size, + userIsClip: false, + }); + await attEnt.save(); + const cdnUrl = Config.get().cdn.endpointPublic; + const fetchUrl = `${cdnUrl}/attachments/${attEnt.uploadFilename}`; + await ( + await fetch(fetchUrl, { + method: "PUT", + body: blob, + }) + ).text(); + // re-fetch due to changed DB entry + attEnt = await CloudAttachment.findOneOrFail({ + where: { + id: attEnt.id, + }, + }); + delWhenDone = true; + } else { + attEnt = await CloudAttachment.findOneOrFail({ + where: { + uploadFilename: url.hostname, + }, + }); + } + + const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${messageId}`, { + method: "POST", + headers: { + signature: Config.get().security.requestSignature || "", + }, + }); + + if (!cloneResponse.ok) { + console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${messageId}`); + throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); + } + + const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; + //TODO maybe this needs to be a new DB object? I don't see a reason to do this rn though, though this id *should* technically be different from the id of the attachment + media.id = attEnt.id; + media.proxy_url = `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`; + if (url.protocol !== "attachment") media.url = media.proxy_url; + media.height = attEnt.height; + media.width = attEnt.width; + media.content_type = attEnt.contentType; + //TODO flags? + media.attachment_id = attEnt.id; + //TODO preview stuff + + if (delWhenDone) { + fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }).then(() => { + attEnt.remove(); + }); + } } export async function handleMessage(opts: MessageOptions): Promise<Message> { const errors: Record<string, { code?: string; message: string }> = {}; @@ -195,8 +270,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { throw FieldErrors(errors); } - await Promise.all(medias.map(processMedia)); - const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: { recipients: true }, @@ -243,6 +316,19 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { mentions: [], components: opts.components ?? undefined, // Fix Discord-Go? }); + const batchId = `CLOUD_${message.author_id}_${randomString(128)}`; + + if (opts.author_id) { + message.author = await User.findOneOrFail({ + where: { id: opts.author_id }, + }); + message.author.clean_data(); + const rights = await getRights(opts.author_id); + rights.hasThrow("SEND_MESSAGES"); + } + + await Promise.all(medias.map((m, index) => processMedia(m, message.id, batchId, message.author as User, channel, index + ""))); + const ephermal = (message.flags & (1 << 6)) !== 0; if (!ephermal && channel.type === ChannelType.GUILD_PUBLIC_THREAD) { const rep = Channel.getRepository(); @@ -305,14 +391,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { throw new HTTPError("Content length over max character limit"); } - if (opts.author_id) { - message.author = await User.findOneOrFail({ - where: { id: opts.author_id }, - }); - message.author.clean_data(); - const rights = await getRights(opts.author_id); - rights.hasThrow("SEND_MESSAGES"); - } if (opts.application_id) { message.application = await Application.findOneOrFail({ where: { id: opts.application_id },