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;
+}
|