summary refs log tree commit diff
path: root/src/api/util/handlers
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2026-04-17 19:55:36 +0200
committerRory& <root@rory.gay>2026-04-18 18:26:34 +0200
commita3d8a427aa7b5e586f9e25de8cd436f21ff63472 (patch)
treed70983e171f3dd33e4e5be4f0d49fcfdfecdb054 /src/api/util/handlers
parentAttempt to set channel/message id for attachments in more places (diff)
downloadserver-ts-a3d8a427aa7b5e586f9e25de8cd436f21ff63472.tar.xz
Split out attachment handling from message handler
Diffstat (limited to 'src/api/util/handlers')
-rw-r--r--src/api/util/handlers/Message.ts159
1 files changed, 76 insertions, 83 deletions
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts

index 964f48f0..ea42283f 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts
@@ -50,6 +50,7 @@ import { MessageFlags, FieldErrors, Snowflake, + getDatabase, } from "@spacebar/util"; import { HTTPError } from "lambert-server"; import { In, Or, Equal, IsNull } from "typeorm"; @@ -321,16 +322,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { } const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined; - // cloud attachments with indexes - const cloudAttachments = opts.attachments?.reduce( - (acc, att, index) => { - if ("uploaded_filename" in att) { - acc.push({ attachment: att, index }); - } - return acc; - }, - [] as { attachment: MessageCreateCloudAttachment; index: number }[], - ); const message = Message.create({ ...opts, @@ -339,7 +330,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { sticker_items: stickers, guild_id: channel.guild_id, channel_id: opts.channel_id, - attachments: opts.attachments || [], + attachments: [], embeds: opts.embeds || [], reactions: opts.reactions || [], type: opts.type ?? 0, @@ -347,10 +338,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { components: opts.components ?? undefined, // Fix Discord-Go? }); message.channel = channel; - message.attachments?.forEach((att) => { - att.message_id = message.id; - att.save(); - }); + await processMessageOptionAttachments(opts, message); if (opts.author_id) { message.author = await User.findOneOrFail({ @@ -372,52 +360,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { await channel.save(); } - if (cloudAttachments && cloudAttachments.length > 0) { - console.log("[Message] Processing attachments for message", message.id, ":", message.attachments); - handle?.(message.id, message.author as User, message.channel); - const uploadedAttachments = await Promise.all( - cloudAttachments.map(async (att) => { - const cAtt = att.attachment; - const attEnt = await CloudAttachment.findOneOrFail({ - where: { - uploadFilename: cAtt.uploaded_filename, - }, - }); - - const cloneResponse = await fetch(`${conf.cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, { - method: "POST", - headers: { - signature: conf.security.requestSignature || "", - }, - }); - - if (!cloneResponse.ok) { - console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`); - throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); - } - - const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; - - const realAtt = Attachment.create({ - filename: attEnt.userFilename, - size: attEnt.size, - height: attEnt.height, - width: attEnt.width, - content_type: attEnt.contentType || attEnt.userOriginalContentType, - channel_id: channel.id, - message_id: message.id, - }); - await realAtt.save(); - return { attachment: realAtt, index: att.index }; - }), - ); - console.log("[Message] Processed attachments for message", message.id, ":", message.attachments); - - for (const att of uploadedAttachments) { - message.attachments![att.index] = att.attachment; - } - } - // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments); + // TODO: Removed cloud attachment handling being inline - handle components! if (message.content && message.content.length > conf.limits.message.maxCharacters) { throw new HTTPError("Content length over max character limit"); @@ -562,6 +505,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { if (content) { // TODO: explicit-only mentions + // TODO: make mentions lazy message.content = content.trim(); content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. @@ -596,6 +540,10 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { id: message.message_reference.message_id, channel_id: message.channel_id, }, + relations: { + mentions: true, + mention_roles: true, + }, }); if (referencedMessage && referencedMessage.author_id !== message.author_id) { message.mentions.push( @@ -609,27 +557,8 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { message.type = MessageType.DEFAULT; if (message.referenced_message) { - const mention_roles: string[] = []; - const mentions: string[] = []; - // TODO: mention_roles and mentions arrays - not needed it seems, but discord still returns that - - message.message_snapshots = [ - { - message: { - attachments: message.referenced_message.attachments, - components: message.referenced_message.components, - content: message.referenced_message.content!, - edited_timestamp: message.referenced_message.edited_timestamp, - embeds: message.referenced_message.embeds, - flags: message.referenced_message.flags, - mention_roles, - mentions, - timestamp: message.referenced_message.timestamp, - type: message.referenced_message.type, - }, - }, - ]; + message.message_snapshots = [message.referenced_message.toSnapshot()]; } } } @@ -792,9 +721,12 @@ export async function sendMessage(opts: MessageOptions) { const message = await handleMessage({ ...opts, timestamp: new Date() }); const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0; + await getDatabase()?.transaction(async (entityManager) => { + await entityManager.save(message); + await entityManager.save(message.channel); + if (message.attachments && message.attachments.length > 0) await entityManager.save(message.attachments); + }); await Promise.all([ - message.insert(), - message.channel.save(), emitEvent({ event: "MESSAGE_CREATE", ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }), @@ -808,6 +740,7 @@ export async function sendMessage(opts: MessageOptions) { return message; } +type MessageOptionAttachment = MessageCreateAttachment | MessageCreateCloudAttachment | Attachment; interface MessageOptions extends MessageCreateSchema { id?: string; type?: MessageType; @@ -824,3 +757,63 @@ interface MessageOptions extends MessageCreateSchema { username?: string; avatar_url?: string; } + +// Makes for concise code, inspired by Nix' lib.trace +function logPassthru<T>(obj: T, ...data: unknown[]) { + console.log(...data); + return obj; +} +export async function processMessageOptionAttachments(source: MessageOptions, destination: Message) { + if (!source.attachments || source.attachments.length == 0) return; + const logp = `[Message/${destination.id}/Attachments]`; + console.log("[Message] Processing attachments for message", source.id, "->", source.attachments); + const tasks = source.attachments?.map(async (src): Promise<Attachment> => { + if (src instanceof Attachment) return logPassthru(src, logp, `Got Attachment instance`); + if (isCloudAttachment(src)) + return logPassthru(await convertCloudAttachmentToAttachment(src, destination.channel_id!, destination.id), logp, "Got MessageCreateCloudAttachment contents"); + throw new Error(logp + " Unhandled attachment: " + JSON.stringify(src)); + }); + + destination.attachments = []; + for (const task of tasks) { + destination.attachments.push(await task); + } +} + +export function isCloudAttachment(attachment: MessageOptionAttachment) { + return "uploaded_filename" in attachment; +} + +export async function convertCloudAttachmentToAttachment(cAtt: MessageCreateCloudAttachment, destinationChannelId: string, destinationMessageId: string) { + const attEnt = await CloudAttachment.findOneOrFail({ + where: { + uploadFilename: cAtt.uploaded_filename, + }, + }); + + const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${destinationMessageId}`, { + method: "POST", + headers: { + signature: Config.get().security.requestSignature || "", + }, + }); + + if (!cloneResponse.ok) { + console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${destinationMessageId}`); + throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); + } + + const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; + + const realAtt = Attachment.create({ + filename: attEnt.userFilename, + size: attEnt.size, + height: attEnt.height, + width: attEnt.width, + content_type: attEnt.contentType || attEnt.userOriginalContentType, + channel_id: destinationChannelId, + message_id: destinationMessageId, + }); + console.log("[Message] Converted cloud attachment to", realAtt); + return realAtt; +}