diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index 7687d8bea..f842a3a55 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -502,15 +502,11 @@ router.post( // no await as it shouldnt block the message send function and silently catch error postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - - return res.json( - message.withSignedAttachments( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ); + const sign = new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }); + return res.json(await Message.prototype.withSignedComponents.call(message.withSignedAttachments(sign), sign)); }, ); diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index 0a50809a3..54da86a87 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -163,8 +163,20 @@ async function processMedia(media: UnfurledMediaItem, messageId: string, batchId } const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; + + const realAtt = Attachment.create({ + filename: attEnt.userFilename, + url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + size: attEnt.size, + height: attEnt.height, + width: attEnt.width, + content_type: attEnt.contentType || attEnt.userOriginalContentType, + }); + await realAtt.save(); + //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.id = realAtt.id; media.proxy_url = `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`; if (url.protocol !== "attachment") media.url = media.proxy_url; media.height = attEnt.height; diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts index 112f55bbf..e758849b0 100644 --- a/src/gateway/listener/listener.ts +++ b/src/gateway/listener/listener.ts @@ -352,6 +352,17 @@ async function consume(this: WebSocket, opts: EventOpts) { userAgent: this.userAgent, }), ).attachments; + if (data["components"]) { + data["components"] = ( + await Message.prototype.withSignedComponents.call( + data, + new NewUrlUserSignatureData({ + ip: this.ipAddress, + userAgent: this.userAgent, + }), + ) + ).components; + } break; default: break; diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts index 05e7dea50..cd89c6e2e 100644 --- a/src/util/entities/Message.ts +++ b/src/util/entities/Message.ts @@ -29,7 +29,18 @@ import { Webhook } from "./Webhook"; import { Sticker } from "./Sticker"; import { Attachment } from "./Attachment"; import { NewUrlUserSignatureData } from "../Signing"; -import { ApplicationCommandType, BaseMessageComponents, Embed, MessageSnapshot, MessageType, PartialMessage, Poll, Reaction } from "@spacebar/schemas"; +import { + ApplicationCommandType, + BaseMessageComponents, + Embed, + MessageComponentType, + MessageSnapshot, + MessageType, + PartialMessage, + Poll, + Reaction, + UnfurledMediaItem, +} from "@spacebar/schemas"; import { MessageFlags } from "@spacebar/util"; import { JsonRemoveEmpty } from "../util/Decorators"; @@ -304,6 +315,55 @@ export class Message extends BaseClass { attachments: this.attachments?.map((attachment: Attachment) => Attachment.prototype.signUrls.call(attachment, data)), }; } + async withSignedComponents(data: NewUrlUserSignatureData) { + if (!this.components || !(this.flags & Number(MessageFlags.FLAGS.IS_COMPONENTS_V2))) return { ...this }; + function signMedia(media: UnfurledMediaItem) { + Object.assign(media, Attachment.prototype.signUrls.call(media, data)); + } + return { + ...this, + components: this.components.map((comp) => { + comp = structuredClone(comp); + if (comp.type === MessageComponentType.Section) { + const accessory = comp.accessory; + if (accessory.type === MessageComponentType.Thumbnail) { + signMedia(accessory.media); + } + } else if (comp.type === MessageComponentType.MediaGallery) { + comp.items.forEach(({ media }) => signMedia(media)); + } else if (comp.type === MessageComponentType.File) { + signMedia(comp.file); + } else if (comp.type === MessageComponentType.Container) { + for (const elm of comp.components) { + switch (elm.type) { + case MessageComponentType.Separator: + case MessageComponentType.TextDisplay: + case MessageComponentType.ActionRow: + break; + case MessageComponentType.Section: { + const accessory = elm.accessory; + if (accessory.type === MessageComponentType.Thumbnail) { + signMedia(accessory.media); + } + break; + } + case MessageComponentType.MediaGallery: + elm.items.forEach(({ media }) => signMedia(media)); + break; + case MessageComponentType.File: { + signMedia(elm.file); + break; + } + + default: + elm satisfies never; + } + } + } + return comp; + }), + }; + } static async createWithDefaults(opts: Partial): Promise { const message = Message.create();