import { route } from "@spacebar/api";
import {
Config,
DiscordApiErrors,
getPermission,
Webhook,
WebhooksUpdateEvent,
emitEvent,
Channel,
handleFile,
ValidateName,
Message,
MessageDeleteBulkEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { WebhookUpdateSchema } from "@spacebar/schemas";
import { In } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
"/",
route({
description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.",
responses: {
200: {
body: "APIWebhook",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { webhook_id } = req.params as { [key: string]: string };
const webhook = await Webhook.findOneOrFail({
where: { id: webhook_id },
relations: { user: true, channel: true, source_channel: true, guild: true, source_guild: true, application: true },
});
if (webhook.guild_id) {
const permission = await getPermission(req.user_id, webhook.guild_id);
if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
} else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
return res.json({
...webhook,
url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
});
},
);
router.delete(
"/",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { webhook_id } = req.params as { [key: string]: string };
const webhook = await Webhook.findOneOrFail({
where: { id: webhook_id },
relations: { user: true, channel: true, source_channel: true, guild: true, source_guild: true, application: true },
});
if (webhook.guild_id) {
const permission = await getPermission(req.user_id, webhook.guild_id);
if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
} else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
const channel_id = webhook.channel_id;
const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
// work around foreign key constraint
while (await Message.count({ where: { webhook_id, channel_id } })) {
const ids = (await Message.find({ where: { webhook_id, channel_id }, select: { id: true }, order: { id: "asc" }, take: 100 })).map((x) => x.id);
await Message.delete({ id: In(ids) });
await emitEvent({
event: "MESSAGE_DELETE_BULK",
channel_id,
origin: "webhook delete",
data: {
channel_id,
guild_id: channel.guild_id,
ids,
},
} satisfies MessageDeleteBulkEvent);
}
await Webhook.delete({ id: webhook_id });
await emitEvent({
event: "WEBHOOKS_UPDATE",
channel_id,
data: {
channel_id,
guild_id: webhook.guild_id!, // TODO: is this even the right fix?
},
} satisfies WebhooksUpdateEvent);
res.sendStatus(204);
},
);
router.patch(
"/",
route({
requestBody: "WebhookUpdateSchema",
responses: {
200: {
body: "WebhookCreateResponse",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { webhook_id } = req.params as { [key: string]: string };
const body = req.body as WebhookUpdateSchema;
const webhook = await Webhook.findOneOrFail({
where: { id: webhook_id },
relations: { user: true, channel: true, source_channel: true, guild: true, source_guild: true, application: true },
});
if (webhook.guild_id) {
const permission = await getPermission(req.user_id, webhook.guild_id);
if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
} else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
if (!body.name && !body.avatar && !body.channel_id) {
throw new HTTPError("Empty webhook updates are not allowed", 50006);
}
if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
if (body.name) {
ValidateName(body.name);
}
const channel_id = body.channel_id || webhook.channel_id;
webhook.assign(body);
if (body.channel_id)
webhook.assign({
channel: await Channel.findOneOrFail({
where: { id: channel_id },
}),
});
await Promise.all([
webhook.save(),
emitEvent({
event: "WEBHOOKS_UPDATE",
channel_id,
data: {
channel_id,
guild_id: webhook.guild_id!, //TODO: is this even the right fix?
},
} satisfies WebhooksUpdateEvent),
]);
res.json(webhook);
},
);
export default router;