more changes

This commit is contained in:
MathMan05
2026-02-03 17:09:33 -06:00
parent 3d921cbcec
commit df20d6551e
10 changed files with 294 additions and 33 deletions
@@ -92,6 +92,16 @@ router.post(
},
author_id: user.id,
});
sendMessage({
channel_id: channel.id,
type: MessageType.THREAD_CREATED,
content: thread.name,
message_reference: {
channel_id: channel.id,
guild_id: channel.guild_id,
},
author_id: user.id,
});
await Promise.all([
emitEvent({
event: "THREAD_CREATE",
@@ -270,10 +270,10 @@ router.get(
// polyfill message references for old messages
await Promise.all(
ret
.filter((msg) => msg.message_reference && !msg.referenced_message?.id)
.filter((msg) => msg.message_reference && !msg.referenced_message?.id && msg.message_reference.message_id)
.map(async (msg) => {
const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
id: msg.message_reference!.message_id,
id: msg.message_reference!.message_id as string,
};
if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
@@ -290,7 +290,7 @@ router.get(
);
// TODO: config max upload size
const messageUpload = multer({
export const messageUpload = multer({
limits: {
fileSize: Config.get().limits.message.maxAttachmentSize,
fields: 10,
@@ -0,0 +1,173 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 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 { handleMessage, postHandleMessage, route, sendMessage } from "@spacebar/api";
import { Message, Channel, emitEvent, User, MessageUpdateEvent, Recipient, uploadFile, Attachment, Member, ReadState, MessageCreateEvent } from "@spacebar/util";
import { MessageThreadCreationSchema, ChannelType, MessageType, ThreadCreationSchema, MessageCreateAttachment, MessageCreateCloudAttachment } from "@spacebar/schemas";
import { Request, Response, Router } from "express";
import { messageUpload } from "./messages";
const router = Router({ mergeParams: true });
// TODO: public read receipts & privacy scoping
// TODO: send read state event to all channel members
// TODO: advance-only notification cursor
router.post(
"/",
messageUpload.any(),
(req, res, next) => {
if (req.body.payload_json) {
req.body = JSON.parse(req.body.payload_json);
}
next();
},
route({
requestBody: "ThreadCreationSchema",
permission: "CREATE_PUBLIC_THREADS",
responses: {
200: {},
403: {},
},
}),
async (req: Request, res: Response) => {
// TODO: check for differences with https://github.com/spacebarchat/server/pull/876/files#diff-95be9c4cdfd8ba6f67361cd40b9abc8226b35d83e2bb44bf5b4682f1d66155e9
const { channel_id } = req.params;
const body = req.body as ThreadCreationSchema;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
});
const user = await User.findOneOrFail({ where: { id: req.user_id } });
const thread = await Channel.createChannel(
{
owner: user,
parent: channel,
guild: channel.guild,
member_count: 1,
message_count: body.message ? 1 : 0,
total_message_sent: body.message ? 1 : 0,
name: body.name,
guild_id: channel.guild_id,
rate_limit_per_user: body.rate_limit_per_user,
type: body.type,
recipients: [],
thread_metadata: {
archived: false,
auto_archive_duration: body.auto_archive_duration || channel.default_auto_archive_duration || 4320,
archive_timestamp: new Date().toISOString(),
locked: false,
create_timestamp: new Date().toISOString(),
},
},
void 0,
{ skipPermissionCheck: true, keepId: true, skipEventEmit: true },
);
const recipient = Recipient.create({ channel_id: channel.id, user });
await recipient.save();
await Promise.all([
emitEvent({
event: "THREAD_CREATE",
channel_id,
data: {
...thread.toJSON(),
newly_created: true,
},
}),
]);
if (body.type !== ChannelType.GUILD_PRIVATE_THREAD)
sendMessage({
channel_id: channel.id,
type: MessageType.THREAD_CREATED,
content: thread.name,
message_reference: {
channel_id: channel.id,
guild_id: channel.guild_id,
},
author_id: user.id,
});
if (body.message) {
const files = (req.files as Express.Multer.File[]) ?? [];
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.message.attachments ?? [];
for (const currFile of files) {
try {
const file = await uploadFile(`/attachments/${channel.id}`, currFile);
attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
}
const embeds = body.message.embeds || [];
const message = await handleMessage({
...body,
type: 0,
pinned: false,
author_id: req.user_id,
embeds,
channel_id,
attachments,
timestamp: new Date(),
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore wrong type but idk why it's mad
message.edited_timestamp = null;
if (message.guild_id) {
// handleMessage will fetch the Member, but only if they are not guild owner.
// have to fetch ourselves otherwise.
if (!message.member) {
message.member = await Member.findOneOrFail({
where: { id: req.user_id, guild_id: message.guild_id },
relations: { roles: true },
});
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
}
let read_state = await ReadState.findOne({
where: { user_id: req.user_id, channel_id },
});
if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
read_state.last_message_id = message.id;
//It's a little more complicated than this but this'll do
read_state.mention_count = 0;
await Promise.all([
read_state.save(),
message.save(),
emitEvent({
event: "MESSAGE_CREATE",
channel_id: channel_id,
data: message,
} as MessageCreateEvent),
message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
]);
postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
}
return res.json(thread.toJSON());
},
);
export default router;
+30 -28
View File
@@ -248,38 +248,40 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
}
message.message_reference = opts.message_reference;
message.referenced_message = await Message.findOneOrFail({
where: {
id: opts.message_reference.message_id,
},
relations: {
author: true,
webhook: true,
application: true,
mentions: true,
mention_roles: true,
mention_channels: true,
sticker_items: true,
attachments: true,
},
});
if (message.message_reference.message_id) {
message.referenced_message = await Message.findOneOrFail({
where: {
id: opts.message_reference.message_id,
},
relations: {
author: true,
webhook: true,
application: true,
mentions: true,
mention_roles: true,
mention_channels: true,
sticker_items: true,
attachments: true,
},
});
if (
message.referenced_message.channel_id &&
message.referenced_message.channel_id !== opts.message_reference.channel_id &&
opts.type !== MessageType.THREAD_STARTER_MESSAGE
)
throw new HTTPError("Referenced message not found in the specified channel", 404);
if (
message.referenced_message.guild_id &&
message.referenced_message.guild_id !== opts.message_reference.guild_id &&
opts.type !== MessageType.THREAD_STARTER_MESSAGE
)
throw new HTTPError("Referenced message not found in the specified channel", 404);
if (
message.referenced_message.channel_id &&
message.referenced_message.channel_id !== opts.message_reference.channel_id &&
opts.type !== MessageType.THREAD_STARTER_MESSAGE
)
throw new HTTPError("Referenced message not found in the specified channel", 404);
if (
message.referenced_message.guild_id &&
message.referenced_message.guild_id !== opts.message_reference.guild_id &&
opts.type !== MessageType.THREAD_STARTER_MESSAGE
)
throw new HTTPError("Referenced message not found in the specified channel", 404);
}
}
/** Q: should be checked if the referenced message exists? ANSWER: NO
otherwise backfilling won't work **/
if (MessageType.THREAD_STARTER_MESSAGE !== message.type) message.type = MessageType.REPLY;
if (MessageType.THREAD_STARTER_MESSAGE !== message.type && MessageType.THREAD_CREATED !== message.type) message.type = MessageType.REPLY;
}
}
@@ -0,0 +1,25 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 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/>.
*/
export interface MessageActivity {
type: 1 | 3 | 5;
session_id?: string;
party_id?: string;
name_override?: string;
icon_override?: string;
}
@@ -49,7 +49,7 @@ export interface MessageCreateSchema {
replied_user?: boolean;
};
message_reference?: {
message_id: string;
message_id?: string;
channel_id?: string;
guild_id?: string;
fail_if_not_exists?: boolean;
@@ -20,4 +20,6 @@ export interface MessageThreadCreationSchema {
auto_archive_duration?: number;
rate_limit_per_user?: number;
name: string;
location?: string; //ignore it
type?: number;
}
@@ -0,0 +1,47 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 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 { ActionRowComponent, ChannelType, Embed } from "#schemas/api";
import { MessageActivity } from "./MessageActivity";
import { MessageCreateAttachment, MessageCreateCloudAttachment } from "./MessageCreateSchema";
export interface ThreadCreationSchema {
auto_archive_duration?: number;
rate_limit_per_user?: number;
name: string;
type: ChannelType.GUILD_PUBLIC_THREAD | ChannelType.GUILD_PRIVATE_THREAD;
invitable?: boolean;
applied_tags?: string[];
location?: string; //Ignore it
message?: {
content?: string;
embeds?: Embed[];
allowed_mentions?: {
parse?: string[];
roles?: string[];
users?: string[];
replied_user?: boolean;
};
components?: ActionRowComponent[] | null;
sticker_ids?: string[];
activity?: MessageActivity;
application_id?: string;
flags?: number;
attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment)[];
};
}
+2
View File
@@ -92,3 +92,5 @@ export * from "./WebhookExecuteSchema";
export * from "./WebhookUpdateSchema";
export * from "./WidgetModifySchema";
export * from "./MessageThreadCreationSchema";
export * from "./ThreadCreationSchema";
export * from "./MessageActivity";
+1 -1
View File
@@ -173,7 +173,7 @@ export class Message extends BaseClass {
@Column({ type: "simple-json", nullable: true })
message_reference?: {
message_id: string;
message_id?: string;
channel_id?: string;
guild_id?: string;
type?: number; // 0 = DEFAULT, 1 = FORWARD