Maybe flags for attachments? i tried

This commit is contained in:
Rory&
2026-08-23 23:33:02 +02:00
parent f44ace24c2
commit 8ab020db8d
7 changed files with 113 additions and 51 deletions
@@ -416,7 +416,7 @@ router.post(
for (const currFile of files) {
try {
const file = await uploadFile(`/attachments/${channel.id}/${messageId}`, currFile);
attachments.push(Attachment.create(file));
attachments.push(file);
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
@@ -436,7 +436,7 @@ router.post(
timestamp: new Date(),
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore dont care2
// @ts-ignore dont care
message.edited_timestamp = null;
if (channel.isDm()) {
@@ -128,7 +128,7 @@ router.post(
for (const currFile of files) {
try {
const file = await uploadFile(`/attachments/${channel.id}/${thread.id}`, currFile);
attachments.push(Attachment.create(file));
attachments.push(file);
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
+44 -39
View File
@@ -47,23 +47,21 @@ import {
} from "@spacebar/util";
import {
ActionRowComponent,
AttachmentFlags,
BaseMessageComponents,
ButtonStyle,
ChannelType,
Embed,
EmbedType,
MessageComponentType,
MessageCreateAttachment,
MessageCreateCloudAttachment,
MessageCreateSchema,
MessageReferenceType,
MessageType,
Reaction,
ReadStateType,
UnfurledMediaItem,
v1CompTypes,
} from "@spacebar/schemas";
import { addPendingPoll } from "../utility/polls";
import { MessageOptionAttachment, MessageOptions } from "@spacebar/util/dtos/MessageOptions";
const allow_empty = false;
// TODO: check webhook, application, system author, stickers
@@ -597,24 +595,6 @@ export async function sendMessage(opts: MessageOptions) {
return message;
}
type MessageOptionAttachment = MessageCreateAttachment | MessageCreateCloudAttachment | Attachment;
export interface MessageOptions extends MessageCreateSchema {
id?: string;
type?: MessageType;
pinned?: boolean;
author_id?: string;
webhook_id?: string;
application_id?: string;
embeds?: Embed[] | null;
reactions?: Reaction[];
channel_id?: string;
attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this?
edited_timestamp?: Date;
timestamp?: Date;
username?: string;
avatar_url?: string;
}
// Makes for concise code, inspired by Nix' lib.trace
function logPassthru<T>(obj: T, ...data: unknown[]) {
console.log(...data);
@@ -626,8 +606,28 @@ export async function processMessageOptionAttachments(source: MessageOptions, de
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");
if (isCloudAttachment(src)) {
const result = logPassthru(await convertCloudAttachmentToAttachment(src, destination.channel_id!, destination.id), logp, "Got MessageCreateCloudAttachment contents");
result.flags = 0 as AttachmentFlags;
result.flags &= (src.is_clip ? 1 : 0) * (AttachmentFlags.IS_CLIP as number);
result.flags &= (src.is_remix ? 1 : 0) * (AttachmentFlags.IS_REMIX as number);
result.flags &= (src.is_thumbnail ? 1 : 0) * (AttachmentFlags.IS_THUMBNAIL as number);
result.flags &= (src.is_spoiler ? 1 : 0) * (AttachmentFlags.IS_SPOILER as number);
return logPassthru(result, logp, "Got MessageCreateCloudAttachment contents");
}
if (isInternalCdnAttachment(src)) {
const result = Attachment.create({
...src,
});
// result.flags = 0 as AttachmentFlags;
// result.flags &= (src.is_clip ? 1 : 0) * (AttachmentFlags.IS_CLIP as number);
// result.flags &= (src.is_remix ? 1 : 0) * (AttachmentFlags.IS_REMIX as number);
// result.flags &= (src.is_thumbnail ? 1 : 0) * (AttachmentFlags.IS_THUMBNAIL as number);
// result.flags &= (src.is_spoiler ? 1 : 0) * (AttachmentFlags.IS_SPOILER as number);
return result;
}
throw new Error(logp + " Unhandled attachment: " + JSON.stringify(src));
});
@@ -641,14 +641,18 @@ 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({
export function isInternalCdnAttachment(attachment: MessageOptionAttachment) {
return "url" in attachment;
}
export async function convertCloudAttachmentToAttachment(cloudAttachmentReference: MessageCreateCloudAttachment, destinationChannelId: string, destinationMessageId: string) {
const cloudAttachment = await CloudAttachment.findOneOrFail({
where: {
uploadFilename: cAtt.uploaded_filename,
uploadFilename: cloudAttachmentReference.uploaded_filename,
},
});
const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${destinationMessageId}`, {
const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${cloudAttachment.uploadFilename}/clone_to_message/${destinationMessageId}`, {
method: "POST",
headers: {
signature: Config.get().security.requestSignature || "",
@@ -656,7 +660,7 @@ export async function convertCloudAttachmentToAttachment(cAtt: MessageCreateClou
});
if (!cloneResponse.ok) {
console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${destinationMessageId}`);
console.error(`[Message] Failed to clone attachment ${cloudAttachment.userFilename} to message ${destinationMessageId}`);
throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500);
}
@@ -666,18 +670,19 @@ export async function convertCloudAttachmentToAttachment(cAtt: MessageCreateClou
channel_id: destinationChannelId,
message_id: destinationMessageId,
filename: attEnt.userFilename,
size: attEnt.size,
height: attEnt.height,
width: attEnt.width,
content_type: attEnt.contentType || attEnt.userOriginalContentType,
filename: cloudAttachment.userFilename,
size: cloudAttachment.size,
height: cloudAttachment.height,
width: cloudAttachment.width,
content_type: cloudAttachment.contentType || cloudAttachment.userOriginalContentType,
title: cAtt.title,
duration_secs: cAtt.duration_secs,
clip_created_at: cAtt.clip_created_at,
description: cAtt.description,
waveform: cAtt.waveform,
title: cloudAttachmentReference.title,
duration_secs: cloudAttachmentReference.duration_secs,
clip_created_at: cloudAttachmentReference.clip_created_at,
description: cloudAttachmentReference.description,
waveform: cloudAttachmentReference.waveform,
});
console.log("[Message] Converted cloud attachment to", realAtt);
return realAtt;
}
+2 -1
View File
@@ -16,10 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { sendMessage } from "@spacebar/api";
import { EmbedType, MessageReferenceType, MessageType, PollAnswerCount } from "@spacebar/schemas";
import { pendingPolls } from "@spacebar/util";
import { MessageOptions, sendMessage } from "../handlers/Message";
import { Message } from "@spacebar/database";
import { MessageOptions } from "@spacebar/util/dtos/MessageOptions";
export async function generatePollResultsMessage(options: MessageOptions): Promise<MessageOptions> {
// TODO: shouldnt this get saved?
+10 -5
View File
@@ -23,6 +23,7 @@ import { HTTPError } from "lambert-server/HTTPError";
import { CloudAttachment } from "@spacebar/database";
import { Config, hasValidSignature, NewUrlUserSignatureData, Snowflake, UrlSignResult } from "@spacebar/util";
import { storage, multer, cache } from "../util";
import { InternalCdnAttachment } from "@spacebar/util/dtos/MessageOptions";
const router = Router({ mergeParams: true });
@@ -45,16 +46,20 @@ router.post("/:channel_id/:message_id", multer.single("file"), async (req: Reque
let width;
let height;
if (mimetype.includes("image")) {
const dimensions = imageSize(buffer);
if (dimensions) {
width = dimensions.width;
height = dimensions.height;
try {
const dimensions = imageSize(buffer);
if (dimensions) {
width = dimensions.width;
height = dimensions.height;
}
} catch (e) {
console.warn("Failed to get image size for attachment of type", mimetype, "because of", e);
}
}
const finalUrl = `${endpoint}/${path}`;
const file = {
const file: InternalCdnAttachment = {
id: Snowflake.generate(),
channel_id,
message_id,
+51
View File
@@ -0,0 +1,51 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2026 Spacebar and Spacebar Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Embed, MessageCreateAttachment, MessageCreateCloudAttachment, MessageCreateSchema, MessageType, Reaction } from "@spacebar/schemas";
export type MessageOptionAttachment = MessageCreateAttachment | MessageCreateCloudAttachment | InternalCdnAttachment;
export interface MessageOptions extends MessageCreateSchema {
id?: string;
type?: MessageType;
pinned?: boolean;
author_id?: string;
webhook_id?: string;
application_id?: string;
embeds?: Embed[] | null;
reactions?: Reaction[];
channel_id?: string;
attachments?: MessageOptionAttachment[];
edited_timestamp?: Date;
timestamp?: Date;
username?: string;
avatar_url?: string;
}
export interface InternalCdnAttachment {
id: string;
channel_id: string;
message_id: string;
content_type: string;
filename: string;
size: number;
url: string;
path: string;
width?: number;
height?: number;
}
+3 -3
View File
@@ -18,14 +18,14 @@
import FormData from "form-data";
import { HTTPError } from "lambert-server/HTTPError";
import { Attachment } from "../../database/entities";
import { InternalCdnAttachment } from "@spacebar/util/dtos/MessageOptions";
import { Config } from "./Config";
export async function uploadFile(
path: string,
// These are the only props we use, don't need to enforce the full type.
file?: Pick<Express.Multer.File, "mimetype" | "originalname" | "buffer">,
): Promise<Attachment> {
): Promise<InternalCdnAttachment> {
if (!file?.buffer) throw new HTTPError("Missing file in body");
const form = new FormData();
@@ -42,7 +42,7 @@ export async function uploadFile(
method: "POST",
body: form.getBuffer(),
});
const result = (await response.json()) as Attachment;
const result = (await response.json()) as InternalCdnAttachment;
if (response.status !== 200) throw result;
return result;