summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Server.ts28
-rw-r--r--src/routes/channels/#channel_id/messages/#message_id/reactions.ts142
-rw-r--r--src/routes/channels/#channel_id/messages/index.ts13
3 files changed, 168 insertions, 15 deletions
diff --git a/src/Server.ts b/src/Server.ts

index f17d4c9d..e6d3d9c9 100644 --- a/src/Server.ts +++ b/src/Server.ts
@@ -37,13 +37,17 @@ export class FosscordServer extends Server { } async setupSchema() { - await db.collection("users").createIndex({ id: 1 }, { unique: true }); - await db.collection("messages").createIndex({ id: 1 }, { unique: true }); - await db.collection("channels").createIndex({ id: 1 }, { unique: true }); - await db.collection("guilds").createIndex({ id: 1 }, { unique: true }); - await db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true }); - await db.collection("roles").createIndex({ id: 1 }, { unique: true }); - await db.collection("emojis").createIndex({ id: 1 }, { unique: true }); + return Promise.all([ + db.collection("users").createIndex({ id: 1 }, { unique: true }), + db.collection("messages").createIndex({ id: 1 }, { unique: true }), + db.collection("channels").createIndex({ id: 1 }, { unique: true }), + db.collection("guilds").createIndex({ id: 1 }, { unique: true }), + db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true }), + db.collection("roles").createIndex({ id: 1 }, { unique: true }), + db.collection("emojis").createIndex({ id: 1 }, { unique: true }), + db.collection("invites").createIndex({ code: 1 }, { unique: true }), + db.collection("invites").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }) // after 0 seconds of expires_at the invite will get delete + ]); } async start() { @@ -70,9 +74,9 @@ export class FosscordServer extends Server { fallbackLng: "en", ns, backend: { - loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json", + loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json" }, - load: "all", + load: "all" }); this.app.use(i18nextMiddleware.handle(i18next, {})); @@ -92,8 +96,8 @@ export class FosscordServer extends Server { const response = await fetch(`https://discord.com/assets/${req.params.file}`, { // @ts-ignore headers: { - ...req.headers, - }, + ...req.headers + } }); const buffer = await response.buffer(); @@ -107,7 +111,7 @@ export class FosscordServer extends Server { "transfer-encoding", "expect-ct", "access-control-allow-origin", - "content-encoding", + "content-encoding" ].includes(name.toLowerCase()) ) { return; diff --git a/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
index 014daee7..be635197 100644 --- a/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -1,6 +1,144 @@ -import { Router } from "express"; +import { + ChannelModel, + EmojiModel, + getPermission, + MemberModel, + MessageModel, + MessageReactionAddEvent, + MessageReactionRemoveEvent, + PartialEmoji, + PublicUserProjection, + toObject, + UserModel +} from "@fosscord/server-util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { emitEvent } from "../../../../../util/Event"; const router = Router(); -// TODO: +// TODO: check if emoji is really an unicode emoji or a prperly encoded external emoji + +function getEmoji(emoji: string): PartialEmoji { + emoji = decodeURIComponent(emoji); + const parts = emoji.includes(":") && emoji.split(":"); + if (parts) + return { + name: parts[0], + id: parts[1] + }; + + return { + id: undefined, + name: emoji + }; +} + +router.get("/:emoji", async (req, res) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); + + const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + if (!message) throw new HTTPError("Message not found", 404); + 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 permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("VIEW_CHANNEL"); + + const users = await UserModel.find({ id: { $in: reaction.user_ids } }, PublicUserProjection).exec(); + + res.json(toObject(users)); +}); + +router.put("/:emoji/:user_id", async (req, res) => { + 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 ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); + if (!channel) throw new HTTPError("Channel not found", 404); + + const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + if (!message) throw new HTTPError("Message not found", 404); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("READ_MESSAGE_HISTORY"); + if (!already_added) permissions.hasThrow("ADD_REACTIONS"); + + if (emoji.id) { + const external_emoji = await EmojiModel.findOne({ id: emoji.id }).exec(); + if (!external_emoji) throw new HTTPError("Emoji not found", 404); + if (!already_added) permissions.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 MessageModel.updateOne({ id: message_id, channel_id }, message).exec(); + + const member = channel.guild_id && (await MemberModel.findOne({ id: req.user_id }).exec()); + + await emitEvent({ + event: "MESSAGE_REACTION_ADD", + channel_id, + guild_id: channel.guild_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + member + } + } as MessageReactionAddEvent); + + res.sendStatus(204); +}); + +router.delete("/:emoji/:user_id", async (req, res) => { + var { message_id, channel_id, user_id } = req.params; + + const emoji = getEmoji(req.params.emoji); + + const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); + if (!channel) throw new HTTPError("Channel not found", 404); + + const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + if (!message) throw new HTTPError("Message not found", 404); + + const permissions = await getPermission(req.user_id, undefined, channel_id); + + if (user_id === "@me") user_id = req.user_id; + else 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 MessageModel.updateOne({ id: message_id, channel_id }, message).exec(); + + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", + channel_id, + guild_id: channel.guild_id, + 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/routes/channels/#channel_id/messages/index.ts b/src/routes/channels/#channel_id/messages/index.ts
index 7bf6412d..7fdff809 100644 --- a/src/routes/channels/#channel_id/messages/index.ts +++ b/src/routes/channels/#channel_id/messages/index.ts
@@ -76,7 +76,18 @@ router.get("/", async (req, res) => { const messages = await query.limit(limit).exec(); - return res.json(toObject(messages)); + return res.json( + toObject(messages).map((x) => { + (x.reactions || []).forEach((x) => { + // @ts-ignore + if ((x.user_ids || []).includes(req.user_id)) x.me = true; + // @ts-ignore + delete x.user_ids; + }); + + return x; + }) + ); }); // TODO: config max upload size