diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-09-26 22:29:30 +1000 |
---|---|---|
committer | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-09-26 22:41:21 +1000 |
commit | 99ee7e9400f06e8718612d8b52d15215dc620774 (patch) | |
tree | 08de8c5d3985b9c2eaa419f5198f891ecd82d012 /src/api/routes/channels/#channel_id | |
parent | Remove the cdn storage location log (diff) | |
download | server-99ee7e9400f06e8718612d8b52d15215dc620774.tar.xz |
Prettier
Diffstat (limited to 'src/api/routes/channels/#channel_id')
-rw-r--r-- | src/api/routes/channels/#channel_id/index.ts | 117 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/invites.ts | 65 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/#message_id/ack.ts | 62 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts | 52 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/#message_id/index.ts | 164 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts | 327 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/bulk-delete.ts | 85 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/messages/index.ts | 138 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/permissions.ts | 79 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/pins.ts | 163 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/purge.ts | 105 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/recipients.ts | 42 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/typing.ts | 54 | ||||
-rw-r--r-- | src/api/routes/channels/#channel_id/webhooks.ts | 41 |
14 files changed, 945 insertions, 549 deletions
diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts index 8dbefe1b..a164fff6 100644 --- a/src/api/routes/channels/#channel_id/index.ts +++ b/src/api/routes/channels/#channel_id/index.ts @@ -6,7 +6,7 @@ import { emitEvent, Recipient, handleFile, - ChannelModifySchema + ChannelModifySchema, } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; @@ -15,56 +15,89 @@ const router: Router = Router(); // TODO: delete channel // TODO: Get channel -router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { - const { channel_id } = req.params; +router.get( + "/", + route({ permission: "VIEW_CHANNEL" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - return res.send(channel); -}); + return res.send(channel); + }, +); -router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - const { channel_id } = req.params; +router.delete( + "/", + route({ permission: "MANAGE_CHANNELS" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); - if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }); - recipient.closed = true; - await Promise.all([ - recipient.save(), - emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent) - ]); - } else if (channel.type === ChannelType.GROUP_DM) { - await Channel.removeRecipientFromChannel(channel, req.user_id); - } else { - await Promise.all([ - Channel.delete({ id: channel_id }), - emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent) - ]); - } + if (channel.type === ChannelType.DM) { + const recipient = await Recipient.findOneOrFail({ + where: { channel_id: channel_id, user_id: req.user_id }, + }); + recipient.closed = true; + await Promise.all([ + recipient.save(), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + user_id: req.user_id, + } as ChannelDeleteEvent), + ]); + } else if (channel.type === ChannelType.GROUP_DM) { + await Channel.removeRecipientFromChannel(channel, req.user_id); + } else { + await Promise.all([ + Channel.delete({ id: channel_id }), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + channel_id, + } as ChannelDeleteEvent), + ]); + } - res.send(channel); -}); + res.send(channel); + }, +); -router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - var payload = req.body as ChannelModifySchema; - const { channel_id } = req.params; - if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); +router.patch( + "/", + route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), + async (req: Request, res: Response) => { + var payload = req.body as ChannelModifySchema; + const { channel_id } = req.params; + if (payload.icon) + payload.icon = await handleFile( + `/channel-icons/${channel_id}`, + payload.icon, + ); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - channel.assign(payload); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + channel.assign(payload); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id - } as ChannelUpdateEvent) - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id, + } as ChannelUpdateEvent), + ]); - res.send(channel); -}); + res.send(channel); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts index 246a2c69..afaabf47 100644 --- a/src/api/routes/channels/#channel_id/invites.ts +++ b/src/api/routes/channels/#channel_id/invites.ts @@ -2,16 +2,33 @@ import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import { random } from "@fosscord/api"; -import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util"; +import { + Channel, + Invite, + InviteCreateEvent, + emitEvent, + User, + Guild, + PublicInviteRelation, +} from "@fosscord/util"; import { isTextChannel } from "./messages"; const router: Router = Router(); -router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }), +router.post( + "/", + route({ + body: "InviteCreateSchema", + permission: "CREATE_INSTANT_INVITE", + right: "CREATE_INVITES", + }), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + select: ["id", "name", "type", "guild_id"], + }); isTextChannel(channel.type); if (!channel.guild_id) { @@ -31,30 +48,44 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT created_at: new Date(), guild_id, channel_id: channel_id, - inviter_id: user_id + inviter_id: user_id, }).save(); const data = invite.toJSON(); data.inviter = await User.getPublicUser(req.user_id); data.guild = await Guild.findOne({ where: { id: guild_id } }); data.channel = channel; - await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent); + await emitEvent({ + event: "INVITE_CREATE", + data, + guild_id, + } as InviteCreateEvent); res.status(201).send(data); - }); + }, +); -router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - const { user_id } = req; - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); +router.get( + "/", + route({ permission: "MANAGE_CHANNELS" }), + async (req: Request, res: Response) => { + const { user_id } = req; + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) { - throw new HTTPError("This channel doesn't exist", 404); - } - const { guild_id } = channel; + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; - const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); + const invites = await Invite.find({ + where: { guild_id }, + relations: PublicInviteRelation, + }); - res.status(200).send(invites); -}); + res.status(200).send(invites); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts index bedd453c..1a30143f 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,4 +1,9 @@ -import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; +import { + emitEvent, + getPermission, + MessageAckEvent, + ReadState, +} from "@fosscord/util"; import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; @@ -8,29 +13,40 @@ const router = Router(); // TODO: send read state event to all channel members // TODO: advance-only notification cursor -router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; +router.post( + "/", + route({ body: "MessageAcknowledgeSchema" }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const permission = await getPermission(req.user_id, undefined, channel_id); - permission.hasThrow("VIEW_CHANNEL"); - - 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; - - await read_state.save(); - - await emitEvent({ - event: "MESSAGE_ACK", - user_id: req.user_id, - data: { + const permission = await getPermission( + req.user_id, + undefined, channel_id, - message_id, - version: 3763 - } - } as MessageAckEvent); - - res.json({ token: null }); -}); + ); + permission.hasThrow("VIEW_CHANNEL"); + + 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; + + await read_state.save(); + + await emitEvent({ + event: "MESSAGE_ACK", + user_id: req.user_id, + data: { + channel_id, + message_id, + version: 3763, + }, + } as MessageAckEvent); + + res.json({ token: null }); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts index b2cb6763..d8b55ccd 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts @@ -3,26 +3,36 @@ import { route } from "@fosscord/api"; const router = Router(); -router.post("/", route({ permission: "MANAGE_MESSAGES" }), (req: Request, res: Response) => { - // TODO: - res.json({ - id: "", - type: 0, - content: "", - channel_id: "", - author: { id: "", username: "", avatar: "", discriminator: "", public_flags: 64 }, - attachments: [], - embeds: [], - mentions: [], - mention_roles: [], - pinned: false, - mention_everyone: false, - tts: false, - timestamp: "", - edited_timestamp: null, - flags: 1, - components: [] - }).status(200); -}); +router.post( + "/", + route({ permission: "MANAGE_MESSAGES" }), + (req: Request, res: Response) => { + // TODO: + res.json({ + id: "", + type: 0, + content: "", + channel_id: "", + author: { + id: "", + username: "", + avatar: "", + discriminator: "", + public_flags: 64, + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "", + edited_timestamp: null, + flags: 1, + components: [], + }).status(200); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts index 46b0d6bd..3abfebe8 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts @@ -26,55 +26,69 @@ const messageUpload = multer({ limits: { fileSize: 1024 * 1024 * 100, fields: 10, - files: 1 + files: 1, }, - storage: multer.memoryStorage() + storage: multer.memoryStorage(), }); // max upload 50 mb -router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - var body = req.body as MessageCreateSchema; +router.patch( + "/", + route({ + body: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + var body = req.body as MessageCreateSchema; - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission( + req.user_id, + undefined, + channel_id, + ); - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - if ((req.user_id !== message.author_id)) { - if (!rights.has("MANAGE_MESSAGES")) { - permissions.hasThrow("MANAGE_MESSAGES"); - body = { flags: body.flags }; - // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins - } - } else rights.hasThrow("SELF_EDIT_MESSAGES"); - - const new_message = await handleMessage({ - ...message, - // TODO: should message_reference be overridable? - // @ts-ignore - message_reference: message.message_reference, - ...body, - author_id: message.author_id, - channel_id, - id: message_id, - edited_timestamp: new Date() - }); - - await Promise.all([ - new_message!.save(), - await emitEvent({ - event: "MESSAGE_UPDATE", + if (req.user_id !== message.author_id) { + if (!rights.has("MANAGE_MESSAGES")) { + permissions.hasThrow("MANAGE_MESSAGES"); + body = { flags: body.flags }; + // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins + } + } else rights.hasThrow("SELF_EDIT_MESSAGES"); + + const new_message = await handleMessage({ + ...message, + // TODO: should message_reference be overridable? + // @ts-ignore + message_reference: message.message_reference, + ...body, + author_id: message.author_id, channel_id, - data: { ...new_message, nonce: undefined } - } as MessageUpdateEvent) - ]); + id: message_id, + edited_timestamp: new Date(), + }); - postHandleMessage(message); + await Promise.all([ + new_message!.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { ...new_message, nonce: undefined }, + } as MessageUpdateEvent), + ]); - return res.json(message); -}); + postHandleMessage(message); + return res.json(message); + }, +); // Backfill message with specific timestamp router.put( @@ -87,7 +101,11 @@ router.put( next(); }, - route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_BACKDATED_EVENTS" }), + route({ + body: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_BACKDATED_EVENTS", + }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; var body = req.body as MessageCreateSchema; @@ -107,20 +125,30 @@ router.put( throw FosscordApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; } - const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id } }); + const exists = await Message.findOne({ + where: { id: message_id, channel_id: channel_id }, + }); if (exists) { throw FosscordApiErrors.CANNOT_REPLACE_BY_BACKFILL; } if (req.file) { try { - const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + const file = await uploadFile( + `/attachments/${req.params.channel_id}`, + req.file, + ); + attachments.push( + Attachment.create({ ...file, proxy_url: file.url }), + ); } catch (error) { return res.status(400).json(error); } } - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); const embeds = body.embeds || []; if (body.embed) embeds.push(body.embed); @@ -142,27 +170,43 @@ router.put( await Promise.all([ message.save(), - emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent), - channel.save() + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: channel_id, + data: message, + } as MessageCreateEvent), + channel.save(), ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); - } + }, ); -router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; +router.get( + "/", + route({ permission: "VIEW_CHANNEL" }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission( + req.user_id, + undefined, + channel_id, + ); - if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); + if (message.author_id !== req.user_id) + permissions.hasThrow("READ_MESSAGE_HISTORY"); - return res.json(message); -}); + return res.json(message); + }, +); router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; @@ -172,9 +216,13 @@ router.delete("/", route({}), async (req: Request, res: Response) => { const rights = await getRights(req.user_id); - if ((message.author_id !== req.user_id)) { + if (message.author_id !== req.user_id) { if (!rights.has("MANAGE_MESSAGES")) { - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + const permission = await getPermission( + req.user_id, + channel.guild_id, + channel_id, + ); permission.hasThrow("MANAGE_MESSAGES"); } } else rights.hasThrow("SELF_DELETE_MESSAGES"); @@ -187,8 +235,8 @@ router.delete("/", route({}), async (req: Request, res: Response) => { data: { id: message_id, channel_id, - guild_id: channel.guild_id - } + guild_id: channel.guild_id, + }, } as MessageDeleteEvent); res.sendStatus(204); diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts index c3cca05d..9f774682 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -11,7 +11,7 @@ import { MessageReactionRemoveEvent, PartialEmoji, PublicUserProjection, - User + User, } from "@fosscord/util"; import { route } from "@fosscord/api"; import { Router, Response, Request } from "express"; @@ -27,159 +27,224 @@ function getEmoji(emoji: string): PartialEmoji { if (parts) return { name: parts[0], - id: parts[1] + id: parts[1], }; return { id: undefined, - name: emoji + name: emoji, }; } -router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; +router.delete( + "/", + route({ permission: "MANAGE_MESSAGES" }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - await Message.update({ id: message_id, channel_id }, { reactions: [] }); + await Message.update({ id: message_id, channel_id }, { reactions: [] }); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE_ALL", - channel_id, - data: { + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE_ALL", channel_id, - message_id, - guild_id: channel.guild_id + data: { + channel_id, + message_id, + guild_id: channel.guild_id, + }, + } as MessageReactionRemoveAllEvent); + + res.sendStatus(204); + }, +); + +router.delete( + "/:emoji", + route({ permission: "MANAGE_MESSAGES" }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); + + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + + const already_added = message.reactions.find( + (x) => + (x.emoji.id === emoji.id && emoji.id) || + x.emoji.name === emoji.name, + ); + if (!already_added) throw new HTTPError("Reaction not found", 404); + message.reactions.remove(already_added); + + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_REACTION_REMOVE_EMOJI", + channel_id, + data: { + channel_id, + message_id, + guild_id: message.guild_id, + emoji, + }, + } as MessageReactionRemoveEmojiEvent), + ]); + + res.sendStatus(204); + }, +); + +router.get( + "/:emoji", + route({ permission: "VIEW_CHANNEL" }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); + + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const reaction = message.reactions.find( + (x) => + (x.emoji.id === emoji.id && emoji.id) || + x.emoji.name === emoji.name, + ); + if (!reaction) throw new HTTPError("Reaction not found", 404); + + const users = await User.find({ + where: { + id: In(reaction.user_ids), + }, + select: PublicUserProjection, + }); + + res.json(users); + }, +); + +router.put( + "/:emoji/:user_id", + route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), + async (req: Request, res: Response) => { + const { message_id, channel_id, user_id } = req.params; + if (user_id !== "@me") throw new HTTPError("Invalid user"); + const emoji = getEmoji(req.params.emoji); + + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const already_added = message.reactions.find( + (x) => + (x.emoji.id === emoji.id && emoji.id) || + x.emoji.name === emoji.name, + ); + + if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); + + if (emoji.id) { + const external_emoji = await Emoji.findOneOrFail({ + where: { id: emoji.id }, + }); + if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); + emoji.animated = external_emoji.animated; + emoji.name = external_emoji.name; } - } as MessageReactionRemoveAllEvent); - - res.sendStatus(204); -}); -router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); - - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); - - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added) throw new HTTPError("Reaction not found", 404); - message.reactions.remove(already_added); - - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_REACTION_REMOVE_EMOJI", + if (already_added) { + if (already_added.user_ids.includes(req.user_id)) + return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error + already_added.count++; + } else + message.reactions.push({ + count: 1, + emoji, + user_ids: [req.user_id], + }); + + await message.save(); + + const member = + channel.guild_id && + (await Member.findOneOrFail({ where: { id: req.user_id } })); + + await emitEvent({ + event: "MESSAGE_REACTION_ADD", channel_id, data: { + user_id: req.user_id, channel_id, message_id, - guild_id: message.guild_id, - emoji - } - } as MessageReactionRemoveEmojiEvent) - ]); - - res.sendStatus(204); -}); - -router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); - - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); - const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!reaction) throw new HTTPError("Reaction not found", 404); - - const users = await User.find({ - where: { - id: In(reaction.user_ids) - }, - select: PublicUserProjection - }); - - res.json(users); -}); - -router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), async (req: Request, res: Response) => { - const { message_id, channel_id, user_id } = req.params; - if (user_id !== "@me") throw new HTTPError("Invalid user"); - const emoji = getEmoji(req.params.emoji); - - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - - if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); - - if (emoji.id) { - const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id } }); - if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); - emoji.animated = external_emoji.animated; - emoji.name = external_emoji.name; - } - - if (already_added) { - if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error - already_added.count++; - } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] }); - - await message.save(); - - const member = channel.guild_id && (await Member.findOneOrFail({ where: { id: req.user_id } })); - - await emitEvent({ - event: "MESSAGE_REACTION_ADD", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - member + guild_id: channel.guild_id, + emoji, + member, + }, + } as MessageReactionAddEvent); + + res.sendStatus(204); + }, +); + +router.delete( + "/:emoji/:user_id", + route({}), + async (req: Request, res: Response) => { + var { message_id, channel_id, user_id } = req.params; + + const emoji = getEmoji(req.params.emoji); + + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + + if (user_id === "@me") user_id = req.user_id; + else { + const permissions = await getPermission( + req.user_id, + undefined, + channel_id, + ); + permissions.hasThrow("MANAGE_MESSAGES"); } - } as MessageReactionAddEvent); - res.sendStatus(204); -}); + const already_added = message.reactions.find( + (x) => + (x.emoji.id === emoji.id && emoji.id) || + x.emoji.name === emoji.name, + ); + if (!already_added || !already_added.user_ids.includes(user_id)) + throw new HTTPError("Reaction not found", 404); -router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => { - var { message_id, channel_id, user_id } = req.params; + already_added.count--; - const emoji = getEmoji(req.params.emoji); + if (already_added.count <= 0) message.reactions.remove(already_added); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); + await message.save(); - if (user_id === "@me") user_id = req.user_id; - else { - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - } - - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); - - already_added.count--; - - if (already_added.count <= 0) message.reactions.remove(already_added); - - await message.save(); - - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE", - channel_id, - data: { - user_id: req.user_id, + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", channel_id, - message_id, - guild_id: channel.guild_id, - emoji - } - } as MessageReactionRemoveEvent); - - res.sendStatus(204); -}); + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + }, + } as MessageReactionRemoveEvent); + + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts index 6493c16a..553ab17e 100644 --- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts @@ -1,5 +1,13 @@ import { Router, Response, Request } from "express"; -import { Channel, Config, emitEvent, getPermission, getRights, MessageDeleteBulkEvent, Message } from "@fosscord/util"; +import { + Channel, + Config, + emitEvent, + getPermission, + getRights, + MessageDeleteBulkEvent, + Message, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; @@ -10,33 +18,48 @@ export default router; // should users be able to bulk delete messages or only bots? ANSWER: all users // should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO // https://discord.com/developers/docs/resources/channel#bulk-delete-messages -router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); - - const rights = await getRights(req.user_id); - rights.hasThrow("SELF_DELETE_MESSAGES"); - - let superuser = rights.has("MANAGE_MESSAGES"); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - - const { maxBulkDelete } = Config.get().limits.message; - - const { messages } = req.body as { messages: string[] }; - if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete"); - if (!superuser) { - permission.hasThrow("MANAGE_MESSAGES"); - if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - } - - await Message.delete(messages); - - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { ids: messages, channel_id, guild_id: channel.guild_id } - } as MessageDeleteBulkEvent); - - res.sendStatus(204); -}); +router.post( + "/", + route({ body: "BulkDeleteSchema" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) + throw new HTTPError("Can't bulk delete dm channel messages", 400); + + const rights = await getRights(req.user_id); + rights.hasThrow("SELF_DELETE_MESSAGES"); + + let superuser = rights.has("MANAGE_MESSAGES"); + const permission = await getPermission( + req.user_id, + channel?.guild_id, + channel_id, + ); + + const { maxBulkDelete } = Config.get().limits.message; + + const { messages } = req.body as { messages: string[] }; + if (messages.length === 0) + throw new HTTPError("You must specify messages to bulk delete"); + if (!superuser) { + permission.hasThrow("MANAGE_MESSAGES"); + if (messages.length > maxBulkDelete) + throw new HTTPError( + `You cannot delete more than ${maxBulkDelete} messages`, + ); + } + + await Message.delete(messages); + + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { ids: messages, channel_id, guild_id: channel.guild_id }, + } as MessageDeleteBulkEvent); + + res.sendStatus(204); + }, +); diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index bee93e80..631074c6 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -61,36 +61,50 @@ router.get("/", async (req: Request, res: Response) => { const before = req.query.before ? `${req.query.before}` : undefined; const after = req.query.after ? `${req.query.after}` : undefined; const limit = Number(req.query.limit) || 50; - if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + if (limit < 1 || limit > 100) + throw new HTTPError("limit must be between 1 and 100", 422); var halfLimit = Math.floor(limit / 2); - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + const permissions = await getPermission( + req.user_id, + channel.guild_id, + channel_id, + ); permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: FindManyOptions<Message> & { where: { id?: any; }; } = { + var query: FindManyOptions<Message> & { where: { id?: any } } = { order: { timestamp: "DESC" }, take: limit, where: { channel_id }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + ], }; if (after) { - if (BigInt(after) > BigInt(Snowflake.generate())) return res.status(422); + if (BigInt(after) > BigInt(Snowflake.generate())) + return res.status(422); query.where.id = MoreThan(after); - } - else if (before) { - if (BigInt(before) < BigInt(req.params.channel_id)) return res.status(422); + } else if (before) { + if (BigInt(before) < BigInt(req.params.channel_id)) + return res.status(422); query.where.id = LessThan(before); - } - else if (around) { + } else if (around) { query.where.id = [ MoreThan((BigInt(around) - BigInt(halfLimit)).toString()), - LessThan((BigInt(around) + BigInt(halfLimit)).toString()) + LessThan((BigInt(around) + BigInt(halfLimit)).toString()), ]; - return res.json([]); // TODO: fix around + return res.json([]); // TODO: fix around } const messages = await Message.find(query); @@ -105,11 +119,22 @@ router.get("/", async (req: Request, res: Response) => { delete x.user_ids; }); // @ts-ignore - if (!x.author) x.author = { id: "4", discriminator: "0000", username: "Fosscord Ghost", public_flags: "0", avatar: null }; + if (!x.author) + x.author = { + id: "4", + discriminator: "0000", + username: "Fosscord Ghost", + public_flags: "0", + avatar: null, + }; x.attachments?.forEach((y: any) => { // dynamically set attachment proxy_url in case the endpoint changed - const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; - y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`; + const uri = y.proxy_url.startsWith("http") + ? y.proxy_url + : `https://example.org${y.proxy_url}`; + y.proxy_url = `${endpoint == null ? "" : endpoint}${ + new URL(uri).pathname + }`; }); /** @@ -123,7 +148,7 @@ router.get("/", async (req: Request, res: Response) => { // } return x; - }) + }), ); }); @@ -134,7 +159,7 @@ const messageUpload = multer({ fields: 10, // files: 1 }, - storage: multer.memoryStorage() + storage: multer.memoryStorage(), }); // max upload 50 mb /** TODO: dynamically change limit of MessageCreateSchema with config @@ -155,24 +180,38 @@ router.post( next(); }, - route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), + route({ + body: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + }), async (req: Request, res: Response) => { const { channel_id } = req.params; var body = req.body as MessageCreateSchema; const attachments: Attachment[] = []; - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); if (!channel.isWritable()) { - throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); + throw new HTTPError( + `Cannot send messages to channel of type ${channel.type}`, + 400, + ); } - const files = req.files as Express.Multer.File[] ?? []; + const files = (req.files as Express.Multer.File[]) ?? []; for (var currFile of files) { try { - const file = await uploadFile(`/attachments/${channel.id}`, currFile); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } - catch (error) { + const file = await uploadFile( + `/attachments/${channel.id}`, + currFile, + ); + attachments.push( + Attachment.create({ ...file, proxy_url: file.url }), + ); + } catch (error) { return res.status(400).json(error); } } @@ -188,7 +227,7 @@ router.post( channel_id, attachments, edited_timestamp: undefined, - timestamp: new Date() + timestamp: new Date(), }); channel.last_message_id = message.id; @@ -205,32 +244,47 @@ router.post( recipient.save(), emitEvent({ event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id - }) + data: channel_dto.excludedRecipients([ + recipient.user_id, + ]), + user_id: recipient.user_id, + }), ]); } - }) + }), ); } - const member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] }); - member.roles = member.roles.filter((role: Role) => { - return role.id !== role.guild_id; - }).map((role: Role) => { - return role.id; - }) as any; + const member = await Member.findOneOrFail({ + where: { id: req.user_id }, + relations: ["roles"], + }); + member.roles = member.roles + .filter((role: Role) => { + return role.id !== role.guild_id; + }) + .map((role: Role) => { + return role.id; + }) as any; await Promise.all([ 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, - channel.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, + channel.save(), ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); - } + }, ); - diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts index e74a0255..89be843f 100644 --- a/src/api/routes/channels/#channel_id/permissions.ts +++ b/src/api/routes/channels/#channel_id/permissions.ts @@ -6,7 +6,7 @@ import { emitEvent, getPermission, Member, - Role + Role, } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -16,69 +16,90 @@ const router: Router = Router(); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) -export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite { } +export interface ChannelPermissionOverwriteSchema + extends ChannelPermissionOverwrite {} router.put( "/:overwrite_id", - route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }), + route({ + body: "ChannelPermissionOverwriteSchema", + permission: "MANAGE_ROLES", + }), async (req: Request, res: Response) => { const { channel_id, overwrite_id } = req.params; const body = req.body as ChannelPermissionOverwriteSchema; - var channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + var channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (body.type === 0) { - if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); + if (!(await Role.count({ where: { id: overwrite_id } }))) + throw new HTTPError("role not found", 404); } else if (body.type === 1) { - if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); + if (!(await Member.count({ where: { id: overwrite_id } }))) + throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); - // @ts-ignore - var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); + //@ts-ignore + var overwrite: ChannelPermissionOverwrite = + channel.permission_overwrites?.find((x) => x.id === overwrite_id); if (!overwrite) { // @ts-ignore overwrite = { id: overwrite_id, - type: body.type + type: body.type, }; channel.permission_overwrites!.push(overwrite); } - overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || BigInt("0"))); - overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || BigInt("0"))); + overwrite.allow = String( + req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")), + ); + overwrite.deny = String( + req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")), + ); await Promise.all([ channel.save(), emitEvent({ event: "CHANNEL_UPDATE", channel_id, - data: channel - } as ChannelUpdateEvent) + data: channel, + } as ChannelUpdateEvent), ]); return res.sendStatus(204); - } + }, ); // TODO: check permission hierarchy -router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; +router.delete( + "/:overwrite_id", + route({ permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { channel_id, overwrite_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id); + channel.permission_overwrites = channel.permission_overwrites!.filter( + (x) => x.id === overwrite_id, + ); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel - } as ChannelUpdateEvent) - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel, + } as ChannelUpdateEvent), + ]); - return res.sendStatus(204); -}); + return res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 30507c71..d3f6960a 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -6,7 +6,7 @@ import { getPermission, Message, MessageUpdateEvent, - DiscordApiErrors + DiscordApiErrors, } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -14,77 +14,100 @@ import { route } from "@fosscord/api"; const router: Router = Router(); -router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; - - const message = await Message.findOneOrFail({ where: { id: message_id } }); - - // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); - - const pinned_count = await Message.count({ where: { channel: { id: channel_id }, pinned: true } }); - const { maxPins } = Config.get().limits.channel; - if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); - - await Promise.all([ - Message.update({ id: message_id }, { pinned: true }), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { +router.put( + "/:message_id", + route({ permission: "VIEW_CHANNEL" }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + + const message = await Message.findOneOrFail({ + where: { id: message_id }, + }); + + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); + + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned: true }, + }); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) + throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + + await Promise.all([ + Message.update({ id: message_id }, { pinned: true }), + emitEvent({ + event: "MESSAGE_UPDATE", channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent) - ]); - - res.sendStatus(204); -}); - -router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; - - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); - - const message = await Message.findOneOrFail({ where: { id: message_id } }); - message.pinned = false; - - await Promise.all([ - message.save(), - - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent), - - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", channel_id, - guild_id: channel.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent) - ]); - - res.sendStatus(204); -}); - -router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => { - const { channel_id } = req.params; - - let pins = await Message.find({ where: { channel_id: channel_id, pinned: true } }); + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); + + res.sendStatus(204); + }, +); + +router.delete( + "/:message_id", + route({ permission: "VIEW_CHANNEL" }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); + + const message = await Message.findOneOrFail({ + where: { id: message_id }, + }); + message.pinned = false; + + await Promise.all([ + message.save(), + + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), - res.send(pins); -}); + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: channel.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); + + res.sendStatus(204); + }, +); + +router.get( + "/", + route({ permission: ["READ_MESSAGE_HISTORY"] }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + + let pins = await Message.find({ + where: { channel_id: channel_id, pinned: true }, + }); + + res.send(pins); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts index 9fe6b658..a9f88662 100644 --- a/src/api/routes/channels/#channel_id/purge.ts +++ b/src/api/routes/channels/#channel_id/purge.ts @@ -21,52 +21,79 @@ export default router; /** TODO: apply the delete bit by bit to prevent client and database stress **/ -router.post("/", route({ /*body: "PurgeSchema",*/ }), async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); +router.post( + "/", + route({ + /*body: "PurgeSchema",*/ + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); - isTextChannel(channel.type); + if (!channel.guild_id) + throw new HTTPError("Can't purge dm channels", 400); + isTextChannel(channel.type); - const rights = await getRights(req.user_id); - if (!rights.has("MANAGE_MESSAGES")) { - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - permissions.hasThrow("MANAGE_CHANNELS"); - } + const rights = await getRights(req.user_id); + if (!rights.has("MANAGE_MESSAGES")) { + const permissions = await getPermission( + req.user_id, + channel.guild_id, + channel_id, + ); + permissions.hasThrow("MANAGE_MESSAGES"); + permissions.hasThrow("MANAGE_CHANNELS"); + } - const { before, after } = req.body as PurgeSchema; + const { before, after } = req.body as PurgeSchema; - // TODO: send the deletion event bite-by-bite to prevent client stress - - var query: FindManyOptions<Message> & { where: { id?: any; }; } = { - order: { id: "ASC" }, - // take: limit, - where: { - channel_id, - id: Between(after, before), // the right way around - author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id) - // if you lack the right of self-deletion, you can't delete your own messages, even in purges - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] - }; + // TODO: send the deletion event bite-by-bite to prevent client stress + var query: FindManyOptions<Message> & { where: { id?: any } } = { + order: { id: "ASC" }, + // take: limit, + where: { + channel_id, + id: Between(after, before), // the right way around + author_id: rights.has("SELF_DELETE_MESSAGES") + ? undefined + : Not(req.user_id), + // if you lack the right of self-deletion, you can't delete your own messages, even in purges + }, + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + ], + }; - const messages = await Message.find(query); - const endpoint = Config.get().cdn.endpointPublic; + const messages = await Message.find(query); + const endpoint = Config.get().cdn.endpointPublic; - if (messages.length == 0) { - res.sendStatus(304); - return; - } + if (messages.length == 0) { + res.sendStatus(304); + return; + } - await Message.delete(messages.map((x) => x.id)); + await Message.delete(messages.map((x) => x.id)); - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { ids: messages.map(x => x.id), channel_id, guild_id: channel.guild_id } - } as MessageDeleteBulkEvent); + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { + ids: messages.map((x) => x.id), + channel_id, + guild_id: channel.guild_id, + }, + } as MessageDeleteBulkEvent); - res.sendStatus(204); -}); + res.sendStatus(204); + }, +); diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts index 25854415..cc7e5756 100644 --- a/src/api/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts @@ -8,7 +8,7 @@ import { emitEvent, PublicUserProjection, Recipient, - User + User, } from "@fosscord/util"; import { route } from "@fosscord/api"; @@ -16,34 +16,48 @@ const router: Router = Router(); router.put("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); if (channel.type !== ChannelType.GROUP_DM) { - const recipients = [...channel.recipients!.map((r) => r.user_id), user_id].unique(); + const recipients = [ + ...channel.recipients!.map((r) => r.user_id), + user_id, + ].unique(); - const new_channel = await Channel.createDMChannel(recipients, req.user_id); + const new_channel = await Channel.createDMChannel( + recipients, + req.user_id, + ); return res.status(201).json(new_channel); } else { if (channel.recipients!.map((r) => r.user_id).includes(user_id)) { throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? } - channel.recipients!.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); + channel.recipients!.push( + Recipient.create({ channel_id: channel_id, user_id: user_id }), + ); await channel.save(); await emitEvent({ event: "CHANNEL_CREATE", data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id + user_id: user_id, }); await emitEvent({ event: "CHANNEL_RECIPIENT_ADD", data: { channel_id: channel_id, - user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + user: await User.findOneOrFail({ + where: { id: user_id }, + select: PublicUserProjection, + }), }, - channel_id: channel_id + channel_id: channel_id, } as ChannelRecipientAddEvent); return res.sendStatus(204); } @@ -51,8 +65,16 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => { router.delete("/:user_id", route({}), async (req: Request, res: Response) => { const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); - if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); + if ( + !( + channel.type === ChannelType.GROUP_DM && + (channel.owner_id === req.user_id || user_id === req.user_id) + ) + ) throw DiscordApiErrors.MISSING_PERMISSIONS; if (!channel.recipients!.map((r) => r.user_id).includes(user_id)) { diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts index 99460f6e..03f76205 100644 --- a/src/api/routes/channels/#channel_id/typing.ts +++ b/src/api/routes/channels/#channel_id/typing.ts @@ -4,26 +4,42 @@ import { Router, Request, Response } from "express"; const router: Router = Router(); -router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { - const { channel_id } = req.params; - const user_id = req.user_id; - const timestamp = Date.now(); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - const member = await Member.findOne({ where: { id: user_id, guild_id: channel.guild_id }, relations: ["roles", "user"] }); +router.post( + "/", + route({ permission: "SEND_MESSAGES" }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const user_id = req.user_id; + const timestamp = Date.now(); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const member = await Member.findOne({ + where: { id: user_id, guild_id: channel.guild_id }, + relations: ["roles", "user"], + }); - await emitEvent({ - event: "TYPING_START", - channel_id: channel_id, - data: { - ...(member ? { member: { ...member, roles: member?.roles?.map((x) => x.id) } } : null), - channel_id, - timestamp, - user_id, - guild_id: channel.guild_id - } - } as TypingStartEvent); + await emitEvent({ + event: "TYPING_START", + channel_id: channel_id, + data: { + ...(member + ? { + member: { + ...member, + roles: member?.roles?.map((x) => x.id), + }, + } + : null), + channel_id, + timestamp, + user_id, + guild_id: channel.guild_id, + }, + } as TypingStartEvent); - res.sendStatus(204); -}); + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts index 99c104ca..da8fe73c 100644 --- a/src/api/routes/channels/#channel_id/webhooks.ts +++ b/src/api/routes/channels/#channel_id/webhooks.ts @@ -13,22 +13,29 @@ router.get("/", route({}), async (req: Request, res: Response) => { }); // TODO: use Image Data Type for avatar instead of String -router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - - isTextChannel(channel.type); - if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); - - const webhook_count = await Webhook.count({ where: { channel_id } }); - const { maxWebhooks } = Config.get().limits.channel; - if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - - var { avatar, name } = req.body as { name: string; avatar?: string }; - name = trimSpecial(name); - if (name === "clyde") throw new HTTPError("Invalid name", 400); - - // TODO: save webhook in database and send response -}); +router.post( + "/", + route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), + async (req: Request, res: Response) => { + const channel_id = req.params.channel_id; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + + isTextChannel(channel.type); + if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); + + const webhook_count = await Webhook.count({ where: { channel_id } }); + const { maxWebhooks } = Config.get().limits.channel; + if (webhook_count > maxWebhooks) + throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); + + var { avatar, name } = req.body as { name: string; avatar?: string }; + name = trimSpecial(name); + if (name === "clyde") throw new HTTPError("Invalid name", 400); + + // TODO: save webhook in database and send response + }, +); export default router; |