mirror of
https://github.com/spacebarchat/server.git
synced 2026-07-29 20:29:26 +00:00
fixes for pluralkit
This commit is contained in:
@@ -285,6 +285,9 @@ router.post(
|
||||
},
|
||||
route({
|
||||
requestBody: "MessageCreateSchema",
|
||||
stripNulls: {
|
||||
components: true,
|
||||
},
|
||||
permission: "VIEW_CHANNEL",
|
||||
right: "SEND_MESSAGES",
|
||||
responses: {
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ButtonStyle, InteractionCallbackSchema, InteractionCallbackType, MessageComponentType, MessageType } from "@spacebar/schemas";
|
||||
import { route, sendMessage } from "@spacebar/api";
|
||||
import { BaseMessageComponents, InteractionCallbackSchema, InteractionCallbackType, MessageType } from "@spacebar/schemas";
|
||||
import { handleComps, route, sendMessage } from "@spacebar/api";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { emitEvent, FieldErrors, InteractionSuccessEvent, pendingInteractions, User } from "@spacebar/util";
|
||||
import { Config, emitEvent, InteractionSuccessEvent, Message, MessageUpdateEvent, pendingInteractions, User } from "@spacebar/util";
|
||||
import { HTTPError } from "#util/util/lambert-server";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -110,9 +111,47 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
// TODO
|
||||
break;
|
||||
case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
|
||||
// TODO
|
||||
break;
|
||||
case InteractionCallbackType.UPDATE_MESSAGE:
|
||||
{
|
||||
if (!interaction.mesageId) throw new HTTPError("no. That was not a message");
|
||||
const message = await Message.findOneOrFail({
|
||||
relations: {
|
||||
author: true,
|
||||
webhook: true,
|
||||
application: true,
|
||||
mentions: true,
|
||||
mention_roles: true,
|
||||
mention_channels: true,
|
||||
sticker_items: true,
|
||||
attachments: true,
|
||||
thread: {
|
||||
recipients: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
channel: true,
|
||||
},
|
||||
where: {
|
||||
id: interaction.mesageId,
|
||||
},
|
||||
});
|
||||
if (body.data.content && body.data.content.length > Config.get().limits.message.maxCharacters) {
|
||||
throw new HTTPError("Content length over max character limit");
|
||||
}
|
||||
if (body.data.components) stripNull(body.data.components);
|
||||
console.log(body.data.components);
|
||||
message.embeds = body.data.embeds || [];
|
||||
console.log(message);
|
||||
const handle = body.data.components ? handleComps(body.data.components, message.flags) : undefined;
|
||||
await handle?.(message.id, message.author as User, message.channel);
|
||||
message.components = body.data.components;
|
||||
await message.save();
|
||||
emitEvent({
|
||||
event: "MESSAGE_UPDATE",
|
||||
channel_id: message.channel_id,
|
||||
data: message.toJSON(),
|
||||
} satisfies MessageUpdateEvent);
|
||||
}
|
||||
// TODO
|
||||
break;
|
||||
case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
|
||||
@@ -137,3 +176,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
export default router;
|
||||
function stripNull(components: BaseMessageComponents[]) {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
id: interactionId,
|
||||
nonce: body.nonce,
|
||||
},
|
||||
} as InteractionCreateEvent);
|
||||
} satisfies InteractionCreateEvent);
|
||||
|
||||
const user = req.user;
|
||||
|
||||
@@ -69,7 +69,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString();
|
||||
|
||||
const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } });
|
||||
const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: { user: true } });
|
||||
const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: { user: true, roles: true } });
|
||||
|
||||
interactionData.guild = {
|
||||
id: guild.id,
|
||||
@@ -91,7 +91,24 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) {
|
||||
interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: { author: true } });
|
||||
interactionData.message = await Message.findOneOrFail({
|
||||
where: { id: body.message_id, flags: undefined },
|
||||
relations: {
|
||||
author: true,
|
||||
webhook: true,
|
||||
application: true,
|
||||
mentions: true,
|
||||
mention_roles: true,
|
||||
mention_channels: true,
|
||||
sticker_items: true,
|
||||
attachments: true,
|
||||
thread: {
|
||||
recipients: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await emitEvent({
|
||||
@@ -122,6 +139,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
|
||||
type: body.type,
|
||||
commandType: body.data.type,
|
||||
commandName: body.data.name,
|
||||
mesageId: body.message_id,
|
||||
});
|
||||
|
||||
res.sendStatus(204);
|
||||
|
||||
@@ -67,6 +67,7 @@ import {
|
||||
Reaction,
|
||||
ReadStateType,
|
||||
UnfurledMediaItem,
|
||||
BaseMessageComponents,
|
||||
} from "@spacebar/schemas";
|
||||
const allow_empty = false;
|
||||
// TODO: check webhook, application, system author, stickers
|
||||
@@ -201,20 +202,19 @@ async function processMedia(media: UnfurledMediaItem, messageId: string, batchId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
export function handleComps(components: BaseMessageComponents[], flags: number) {
|
||||
const errors: Record<string, { code?: string; message: string }> = {};
|
||||
const knownComponentIds: string[] = [];
|
||||
const compv2 = (opts.flags || 0) & Number(MessageFlags.FLAGS.IS_COMPONENTS_V2);
|
||||
const compv2 = (flags || 0) & Number(MessageFlags.FLAGS.IS_COMPONENTS_V2);
|
||||
const medias: UnfurledMediaItem[] = [];
|
||||
for (const comp of opts.components || []) {
|
||||
for (const comp of components || []) {
|
||||
if (comp.type === MessageComponentType.ActionRow) {
|
||||
checkActionRow(comp, knownComponentIds, errors, opts.components!.indexOf(comp));
|
||||
checkActionRow(comp, knownComponentIds, errors, components!.indexOf(comp));
|
||||
} else if (comp.type === MessageComponentType.Section) {
|
||||
if (!compv2) throw new HTTPError("Must be comp v2");
|
||||
const accessory = comp.accessory;
|
||||
if (comp.components.length < 1 || comp.components.length > 3) {
|
||||
errors[`data.components[${opts.components!.indexOf(comp)}].components`] = {
|
||||
errors[`data.components[${components!.indexOf(comp)}].components`] = {
|
||||
code: "TOO_LONG",
|
||||
message: "Component list is too long",
|
||||
};
|
||||
@@ -227,7 +227,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
} else if (comp.type === MessageComponentType.MediaGallery) {
|
||||
if (!compv2) throw new HTTPError("Must be comp v2");
|
||||
if (comp.items.length < 1 || comp.items.length > 10) {
|
||||
errors[`data.components[${opts.components!.indexOf(comp)}].items`] = {
|
||||
errors[`data.components[${components!.indexOf(comp)}].items`] = {
|
||||
code: "TOO_LONG",
|
||||
message: "Media list is too long",
|
||||
};
|
||||
@@ -248,7 +248,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
case MessageComponentType.Section: {
|
||||
const accessory = elm.accessory;
|
||||
if (elm.components.length < 1 || elm.components.length > 3) {
|
||||
errors[`data.components[${opts.components!.indexOf(comp)}].components[${comp.components!.indexOf(elm)}].components`] = {
|
||||
errors[`data.components[${components!.indexOf(comp)}].components[${comp.components!.indexOf(elm)}].components`] = {
|
||||
code: "TOO_LONG",
|
||||
message: "Component list is too long",
|
||||
};
|
||||
@@ -260,7 +260,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
}
|
||||
case MessageComponentType.MediaGallery:
|
||||
if (elm.items.length < 1 || elm.items.length > 10) {
|
||||
errors[`data.components[${opts.components!.indexOf(comp)}].components[${comp.components!.indexOf(elm)}].items`] = {
|
||||
errors[`data.components[${components!.indexOf(comp)}].components[${comp.components!.indexOf(elm)}].items`] = {
|
||||
code: "TOO_LONG",
|
||||
message: "Media list is too long",
|
||||
};
|
||||
@@ -272,7 +272,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
break;
|
||||
}
|
||||
case MessageComponentType.ActionRow:
|
||||
checkActionRow(elm, knownComponentIds, errors, opts.components!.indexOf(elm));
|
||||
checkActionRow(elm, knownComponentIds, errors, components!.indexOf(elm));
|
||||
break;
|
||||
default:
|
||||
elm satisfies never;
|
||||
@@ -286,6 +286,13 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
if (Object.keys(errors).length > 0) {
|
||||
throw FieldErrors(errors);
|
||||
}
|
||||
return async (messageId: string, user: User, channel: Channel) => {
|
||||
const batchId = `CLOUD_compUploads_${randomString(128)}`;
|
||||
(await Promise.all(medias.map((m, index) => processMedia(m, messageId, batchId, user, channel, index + "")))).forEach((_) => _?.());
|
||||
};
|
||||
}
|
||||
export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
const handle = opts.components ? handleComps(opts.components, opts.flags || 0) : undefined;
|
||||
|
||||
const channel = await Channel.findOneOrFail({
|
||||
where: { id: opts.channel_id },
|
||||
@@ -335,7 +342,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
components: opts.components ?? undefined, // Fix Discord-Go?
|
||||
});
|
||||
message.channel = channel;
|
||||
const batchId = `CLOUD_${message.author_id}_${randomString(128)}`;
|
||||
|
||||
if (opts.author_id) {
|
||||
message.author = await User.findOneOrFail({
|
||||
@@ -345,8 +351,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
rights.hasThrow("SEND_MESSAGES");
|
||||
}
|
||||
|
||||
(await Promise.all(medias.map((m, index) => processMedia(m, message.id, batchId, message.author as User, channel, index + "")))).forEach((_) => _?.());
|
||||
|
||||
const ephermal = (message.flags & (1 << 6)) !== 0;
|
||||
if (!ephermal && channel.type === ChannelType.GUILD_PUBLIC_THREAD) {
|
||||
const rep = Channel.getRepository();
|
||||
@@ -360,6 +364,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -41,7 +41,7 @@ export type RouteResponse = {
|
||||
body?: `${string}Response`;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type stripNulls = { [key: string]: true | stripNulls };
|
||||
export interface RouteOptions {
|
||||
permission?: PermissionResolvable;
|
||||
right?: RightResolvable;
|
||||
@@ -52,6 +52,7 @@ export interface RouteOptions {
|
||||
body?: string;
|
||||
};
|
||||
};
|
||||
stripNulls?: stripNulls;
|
||||
event?: EVENT | EVENT[];
|
||||
summary?: string;
|
||||
description?: string;
|
||||
@@ -73,7 +74,31 @@ export interface RouteOptions {
|
||||
// headers?: Record<string, string>;
|
||||
// };
|
||||
}
|
||||
|
||||
export function stripNull(obj: object) {
|
||||
console.log(Object.entries(obj));
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value instanceof Object || (value && !value.__proto__)) {
|
||||
stripNull(value);
|
||||
} else if (value === null) {
|
||||
//@ts-expect-error this is fine
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
export function followNullPath(obj1: any, nullObj: stripNulls) {
|
||||
for (const [key, value] of Object.entries(nullObj)) {
|
||||
if (key in obj1)
|
||||
if (value instanceof Object) {
|
||||
if (obj1[key] instanceof Object)
|
||||
//@ts-expect-error this works lol
|
||||
followNullPath(obj1[key], nullObj[key]);
|
||||
else delete obj1[key];
|
||||
} else if (obj1[key] instanceof Object) {
|
||||
stripNull(obj1[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function route(opts: RouteOptions) {
|
||||
let validate: AnyValidateFunction | undefined;
|
||||
if (opts.requestBody) {
|
||||
@@ -111,9 +136,12 @@ export function route(opts: RouteOptions) {
|
||||
}
|
||||
|
||||
if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
|
||||
if (opts.stripNulls) {
|
||||
followNullPath(req.body, opts.stripNulls);
|
||||
}
|
||||
const valid = validate(req.body);
|
||||
if (!valid) {
|
||||
console.log(req.body);
|
||||
console.log(JSON.stringify(req.body));
|
||||
const fields: Record<string, { code?: string; message: string }> = {};
|
||||
validate.errors?.forEach(
|
||||
(x) =>
|
||||
|
||||
@@ -26,6 +26,7 @@ interface PendingInteraction {
|
||||
channelId?: string;
|
||||
guildId?: string;
|
||||
nonce?: string;
|
||||
mesageId?: string;
|
||||
type: InteractionType;
|
||||
commandType: ApplicationCommandType;
|
||||
commandName: string;
|
||||
|
||||
Reference in New Issue
Block a user