diff options
Diffstat (limited to 'src/api/routes/guilds/#guild_id')
26 files changed, 1235 insertions, 722 deletions
diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts index b54835fc..76a11f6b 100644 --- a/src/api/routes/guilds/#guild_id/audit-logs.ts +++ b/src/api/routes/guilds/#guild_id/audit-logs.ts @@ -11,7 +11,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { webhooks: [], guild_scheduled_events: [], threads: [], - application_commands: [] + application_commands: [], }); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts index ed00f9c0..930985d7 100644 --- a/src/api/routes/guilds/#guild_id/bans.ts +++ b/src/api/routes/guilds/#guild_id/bans.ts @@ -1,5 +1,15 @@ import { Request, Response, Router } from "express"; -import { DiscordApiErrors, emitEvent, GuildBanAddEvent, GuildBanRemoveEvent, Ban, User, Member, BanRegistrySchema, BanModeratorSchema } from "@fosscord/util"; +import { + DiscordApiErrors, + emitEvent, + GuildBanAddEvent, + GuildBanRemoveEvent, + Ban, + User, + Member, + BanRegistrySchema, + BanModeratorSchema, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress, route } from "@fosscord/api"; @@ -7,150 +17,184 @@ const router: Router = Router(); /* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */ -router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; +router.get( + "/", + route({ permission: "BAN_MEMBERS" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - let bans = await Ban.find({ where: { guild_id: guild_id } }); - let promisesToAwait: object[] = []; - const bansObj: object[] = []; + let bans = await Ban.find({ where: { guild_id: guild_id } }); + let promisesToAwait: object[] = []; + const bansObj: object[] = []; - bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing + bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing - bans.forEach((ban) => { - promisesToAwait.push(User.getPublicUser(ban.user_id)); - }); - - const bannedUsers: object[] = await Promise.all(promisesToAwait); - - bans.forEach((ban, index) => { - const user = bannedUsers[index] as User; - bansObj.push({ - reason: ban.reason, - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar, - public_flags: user.public_flags - } + bans.forEach((ban) => { + promisesToAwait.push(User.getPublicUser(ban.user_id)); }); - }); - - return res.json(bansObj); -}); - -router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const user_id = req.params.ban; - - let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } }) as BanRegistrySchema; - - if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; - // pretend self-bans don't exist to prevent victim chasing - - /* Filter secret from registry. */ - - ban = ban as BanModeratorSchema; - - delete ban.ip; - - return res.json(ban); -}); - -router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const banned_user_id = req.params.user_id; - if ((req.user_id === banned_user_id) && (banned_user_id === req.permission!.cache.guild?.owner_id)) - throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - - if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - - const banned_user = await User.getPublicUser(banned_user_id); + const bannedUsers: object[] = await Promise.all(promisesToAwait); + + bans.forEach((ban, index) => { + const user = bannedUsers[index] as User; + bansObj.push({ + reason: ban.reason, + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar, + public_flags: user.public_flags, + }, + }); + }); - const ban = Ban.create({ - user_id: banned_user_id, - guild_id: guild_id, - ip: getIpAdress(req), - executor_id: req.user_id, - reason: req.body.reason // || otherwise empty - }); + return res.json(bansObj); + }, +); + +router.get( + "/:user", + route({ permission: "BAN_MEMBERS" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const user_id = req.params.ban; + + let ban = (await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + })) as BanRegistrySchema; + + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; + // pretend self-bans don't exist to prevent victim chasing + + /* Filter secret from registry. */ + + ban = ban as BanModeratorSchema; + + delete ban.ip; + + return res.json(ban); + }, +); + +router.put( + "/:user_id", + route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const banned_user_id = req.params.user_id; + + if ( + req.user_id === banned_user_id && + banned_user_id === req.permission!.cache.guild?.owner_id + ) + throw new HTTPError( + "You are the guild owner, hence can't ban yourself", + 403, + ); + + if (req.permission!.cache.guild?.owner_id === banned_user_id) + throw new HTTPError("You can't ban the owner", 400); + + const banned_user = await User.getPublicUser(banned_user_id); + + const ban = Ban.create({ + user_id: banned_user_id, + guild_id: guild_id, + ip: getIpAdress(req), + executor_id: req.user_id, + reason: req.body.reason, // || otherwise empty + }); - await Promise.all([ - Member.removeFromGuild(banned_user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user - }, - guild_id: guild_id - } as GuildBanAddEvent) - ]); - - return res.json(ban); -}); - -router.put("/@me", route({ body: "BanCreateSchema" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - - const banned_user = await User.getPublicUser(req.params.user_id); - - if (req.permission!.cache.guild?.owner_id === req.params.user_id) - throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - - const ban = Ban.create({ - user_id: req.params.user_id, - guild_id: guild_id, - ip: getIpAdress(req), - executor_id: req.params.user_id, - reason: req.body.reason // || otherwise empty - }); - - await Promise.all([ - Member.removeFromGuild(req.user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user, + }, guild_id: guild_id, - user: banned_user - }, - guild_id: guild_id - } as GuildBanAddEvent) - ]); + } as GuildBanAddEvent), + ]); + + return res.json(ban); + }, +); + +router.put( + "/@me", + route({ body: "BanCreateSchema" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + + const banned_user = await User.getPublicUser(req.params.user_id); + + if (req.permission!.cache.guild?.owner_id === req.params.user_id) + throw new HTTPError( + "You are the guild owner, hence can't ban yourself", + 403, + ); + + const ban = Ban.create({ + user_id: req.params.user_id, + guild_id: guild_id, + ip: getIpAdress(req), + executor_id: req.params.user_id, + reason: req.body.reason, // || otherwise empty + }); - return res.json(ban); -}); + await Promise.all([ + Member.removeFromGuild(req.user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user, + }, + guild_id: guild_id, + } as GuildBanAddEvent), + ]); -router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; + return res.json(ban); + }, +); - let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } }); +router.delete( + "/:user_id", + route({ permission: "BAN_MEMBERS" }), + async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; - // make self-bans irreversible and hide them from view to avoid victim chasing + let ban = await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + }); - const banned_user = await User.getPublicUser(user_id); + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; + // make self-bans irreversible and hide them from view to avoid victim chasing - await Promise.all([ - Ban.delete({ - user_id: user_id, - guild_id - }), + const banned_user = await User.getPublicUser(user_id); - emitEvent({ - event: "GUILD_BAN_REMOVE", - data: { + await Promise.all([ + Ban.delete({ + user_id: user_id, guild_id, - user: banned_user - }, - guild_id - } as GuildBanRemoveEvent) - ]); - - return res.status(204).send(); -}); + }), + + emitEvent({ + event: "GUILD_BAN_REMOVE", + data: { + guild_id, + user: banned_user, + }, + guild_id, + } as GuildBanRemoveEvent), + ]); + + return res.status(204).send(); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts index 7a5b50d1..af17465d 100644 --- a/src/api/routes/guilds/#guild_id/channels.ts +++ b/src/api/routes/guilds/#guild_id/channels.ts @@ -1,5 +1,10 @@ import { Router, Response, Request } from "express"; -import { Channel, ChannelUpdateEvent, emitEvent, ChannelModifySchema } from "@fosscord/util"; +import { + Channel, + ChannelUpdateEvent, + emitEvent, + ChannelModifySchema, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; const router = Router(); @@ -11,49 +16,77 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json(channels); }); -router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel - const { guild_id } = req.params; - const body = req.body as ChannelModifySchema; +router.post( + "/", + route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), + async (req: Request, res: Response) => { + // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel + const { guild_id } = req.params; + const body = req.body as ChannelModifySchema; - const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); + const channel = await Channel.createChannel( + { ...body, guild_id }, + req.user_id, + ); - res.status(201).json(channel); -}); + res.status(201).json(channel); + }, +); -export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string; }[]; +export type ChannelReorderSchema = { + id: string; + position?: number; + lock_permissions?: boolean; + parent_id?: string; +}[]; -router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - const body = req.body as ChannelReorderSchema; +router.patch( + "/", + route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), + async (req: Request, res: Response) => { + // changes guild channel position + const { guild_id } = req.params; + const body = req.body as ChannelReorderSchema; - await Promise.all([ - body.map(async (x) => { - if (x.position == null && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400); + await Promise.all([ + body.map(async (x) => { + if (x.position == null && !x.parent_id) + throw new HTTPError( + `You need to at least specify position or parent_id`, + 400, + ); - const opts: any = {}; - if (x.position != null) opts.position = x.position; + const opts: any = {}; + if (x.position != null) opts.position = x.position; - if (x.parent_id) { - opts.parent_id = x.parent_id; - const parent_channel = await Channel.findOneOrFail({ - where: { id: x.parent_id, guild_id }, - select: ["permission_overwrites"] - }); - if (x.lock_permissions) { - opts.permission_overwrites = parent_channel.permission_overwrites; + if (x.parent_id) { + opts.parent_id = x.parent_id; + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"], + }); + if (x.lock_permissions) { + opts.permission_overwrites = + parent_channel.permission_overwrites; + } } - } - await Channel.update({ guild_id, id: x.id }, opts); - const channel = await Channel.findOneOrFail({ where: { guild_id, id: x.id } }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ + where: { guild_id, id: x.id }, + }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); - }) - ]); + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: x.id, + guild_id, + } as ChannelUpdateEvent); + }), + ]); - res.sendStatus(204); -}); + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts index bd158c56..b951e4f4 100644 --- a/src/api/routes/guilds/#guild_id/delete.ts +++ b/src/api/routes/guilds/#guild_id/delete.ts @@ -1,4 +1,14 @@ -import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; +import { + Channel, + emitEvent, + GuildDeleteEvent, + Guild, + Member, + Message, + Role, + Invite, + Emoji, +} from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; @@ -10,18 +20,22 @@ const router = Router(); router.post("/", route({}), async (req: Request, res: Response) => { var { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); - if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id !== req.user_id) + throw new HTTPError("You are not the owner of this guild", 401); await Promise.all([ Guild.delete({ id: guild_id }), // this will also delete all guild related data emitEvent({ event: "GUILD_DELETE", data: { - id: guild_id + id: guild_id, }, - guild_id: guild_id - } as GuildDeleteEvent) + guild_id: guild_id, + } as GuildDeleteEvent), ]); return res.sendStatus(204); diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts index ad20633f..7e63c06b 100644 --- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts +++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts @@ -6,33 +6,33 @@ import { route } from "@fosscord/api"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - // TODO: - // Load from database - // Admin control, but for now it allows anyone to be discoverable + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable res.send({ guild_id: guild_id, safe_environment: true, - healthy: true, - health_score_pending: false, - size: true, - nsfw_properties: {}, - protected: true, - sufficient: true, - sufficient_without_grace_period: true, - valid_rules_channel: true, - retention_healthy: true, - engagement_healthy: true, - age: true, - minimum_age: 0, - health_score: { - avg_nonnew_participators: 0, - avg_nonnew_communicators: 0, - num_intentful_joiners: 0, - perc_ret_w1_intentful: 0 - }, - minimum_size: 0 + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0, + }, + minimum_size: 0, }); }); diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts index cf9d742a..6e8570eb 100644 --- a/src/api/routes/guilds/#guild_id/emojis.ts +++ b/src/api/routes/guilds/#guild_id/emojis.ts @@ -1,5 +1,17 @@ import { Router, Request, Response } from "express"; -import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User, EmojiCreateSchema, EmojiModifySchema } from "@fosscord/util"; +import { + Config, + DiscordApiErrors, + emitEvent, + Emoji, + GuildEmojisUpdateEvent, + handleFile, + Member, + Snowflake, + User, + EmojiCreateSchema, + EmojiModifySchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; const router = Router(); @@ -9,7 +21,10 @@ router.get("/", route({}), async (req: Request, res: Response) => { await Member.IsInGuildOrFail(req.user_id, guild_id); - const emojis = await Emoji.find({ where: { guild_id: guild_id }, relations: ["user"] }); + const emojis = await Emoji.find({ + where: { guild_id: guild_id }, + relations: ["user"], + }); return res.json(emojis); }); @@ -19,89 +34,115 @@ router.get("/:emoji_id", route({}), async (req: Request, res: Response) => { await Member.IsInGuildOrFail(req.user_id, guild_id); - const emoji = await Emoji.findOneOrFail({ where: { guild_id: guild_id, id: emoji_id }, relations: ["user"] }); + const emoji = await Emoji.findOneOrFail({ + where: { guild_id: guild_id, id: emoji_id }, + relations: ["user"], + }); return res.json(emoji); }); -router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as EmojiCreateSchema; - - const id = Snowflake.generate(); - const emoji_count = await Emoji.count({ where: { guild_id: guild_id } }); - const { maxEmojis } = Config.get().limits.guild; - - if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); - if (body.require_colons == null) body.require_colons = true; - - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - body.image = (await handleFile(`/emojis/${id}`, body.image)) as string; - - const emoji = await Emoji.create({ - id: id, - guild_id: guild_id, - ...body, - require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not - user: user, - managed: false, - animated: false, // TODO: Add support animated emojis - available: true, - roles: [] - }).save(); - - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { +router.post( + "/", + route({ + body: "EmojiCreateSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as EmojiCreateSchema; + + const id = Snowflake.generate(); + const emoji_count = await Emoji.count({ + where: { guild_id: guild_id }, + }); + const { maxEmojis } = Config.get().limits.guild; + + if (emoji_count >= maxEmojis) + throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams( + maxEmojis, + ); + if (body.require_colons == null) body.require_colons = true; + + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + body.image = (await handleFile(`/emojis/${id}`, body.image)) as string; + + const emoji = await Emoji.create({ + id: id, guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }) - } - } as GuildEmojisUpdateEvent); + ...body, + require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not + user: user, + managed: false, + animated: false, // TODO: Add support animated emojis + available: true, + roles: [], + }).save(); - return res.status(201).json(emoji); -}); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); + + return res.status(201).json(emoji); + }, +); router.patch( "/:emoji_id", - route({ body: "EmojiModifySchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), + route({ + body: "EmojiModifySchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + }), async (req: Request, res: Response) => { const { emoji_id, guild_id } = req.params; const body = req.body as EmojiModifySchema; - const emoji = await Emoji.create({ ...body, id: emoji_id, guild_id: guild_id }).save(); + const emoji = await Emoji.create({ + ...body, + id: emoji_id, + guild_id: guild_id, + }).save(); await emitEvent({ event: "GUILD_EMOJIS_UPDATE", guild_id: guild_id, data: { guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }) - } + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, } as GuildEmojisUpdateEvent); return res.json(emoji); - } + }, ); -router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => { - const { emoji_id, guild_id } = req.params; +router.delete( + "/:emoji_id", + route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), + async (req: Request, res: Response) => { + const { emoji_id, guild_id } = req.params; - await Emoji.delete({ - id: emoji_id, - guild_id: guild_id - }); + await Emoji.delete({ + id: emoji_id, + guild_id: guild_id, + }); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }) - } - } as GuildEmojisUpdateEvent); + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - res.sendStatus(204); -}); + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts index afeb0938..715a3835 100644 --- a/src/api/routes/guilds/#guild_id/index.ts +++ b/src/api/routes/guilds/#guild_id/index.ts @@ -1,5 +1,15 @@ import { Request, Response, Router } from "express"; -import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member, GuildCreateSchema } from "@fosscord/util"; +import { + DiscordApiErrors, + emitEvent, + getPermission, + getRights, + Guild, + GuildUpdateEvent, + handleFile, + Member, + GuildCreateSchema, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; @@ -26,9 +36,13 @@ router.get("/", route({}), async (req: Request, res: Response) => { const [guild, member] = await Promise.all([ Guild.findOneOrFail({ where: { id: guild_id } }), - Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }) + Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }), ]); - if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + if (!member) + throw new HTTPError( + "You are not a member of the guild you are trying to access", + 401, + ); // @ts-ignore guild.joined_at = member?.joined_at; @@ -36,39 +50,57 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.send(guild); }); -router.patch("/", route({ body: "GuildUpdateSchema" }), async (req: Request, res: Response) => { - const body = req.body as GuildUpdateSchema; - const { guild_id } = req.params; - - - const rights = await getRights(req.user_id); - const permission = await getPermission(req.user_id, guild_id); - - if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD")) - throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD"); - - // TODO: guild update check image - - if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); - if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); - if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - - var guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - relations: ["emojis", "roles", "stickers"] - }); - // TODO: check if body ids are valid - guild.assign(body); - - const data = guild.toJSON(); - // TODO: guild hashes - // TODO: fix vanity_url_code, template_id - delete data.vanity_url_code; - delete data.template_id; - - await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data, guild_id } as GuildUpdateEvent)]); - - return res.json(data); -}); +router.patch( + "/", + route({ body: "GuildUpdateSchema" }), + async (req: Request, res: Response) => { + const body = req.body as GuildUpdateSchema; + const { guild_id } = req.params; + + const rights = await getRights(req.user_id); + const permission = await getPermission(req.user_id, guild_id); + + if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD")) + throw DiscordApiErrors.MISSING_PERMISSIONS.withParams( + "MANAGE_GUILD", + ); + + // TODO: guild update check image + + if (body.icon) + body.icon = await handleFile(`/icons/${guild_id}`, body.icon); + if (body.banner) + body.banner = await handleFile(`/banners/${guild_id}`, body.banner); + if (body.splash) + body.splash = await handleFile( + `/splashes/${guild_id}`, + body.splash, + ); + + var guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + relations: ["emojis", "roles", "stickers"], + }); + // TODO: check if body ids are valid + guild.assign(body); + + const data = guild.toJSON(); + // TODO: guild hashes + // TODO: fix vanity_url_code, template_id + delete data.vanity_url_code; + delete data.template_id; + + await Promise.all([ + guild.save(), + emitEvent({ + event: "GUILD_UPDATE", + data, + guild_id, + } as GuildUpdateEvent), + ]); + + return res.json(data); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts index b7534e31..4d033e9c 100644 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts @@ -4,12 +4,19 @@ import { Request, Response, Router } from "express"; const router = Router(); -router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; +router.get( + "/", + route({ permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); + const invites = await Invite.find({ + where: { guild_id }, + relations: PublicInviteRelation, + }); - return res.json(invites); -}); + return res.json(invites); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts index 265a1b35..c2f946b2 100644 --- a/src/api/routes/guilds/#guild_id/member-verification.ts +++ b/src/api/routes/guilds/#guild_id/member-verification.ts @@ -2,12 +2,12 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; const router = Router(); -router.get("/",route({}), async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { // TODO: member verification res.status(404).json({ message: "Unknown Guild Member Verification Form", - code: 10068 + code: 10068, }); }); diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts index 407619d3..2d867920 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,5 +1,16 @@ import { Request, Response, Router } from "express"; -import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Guild, MemberChangeSchema } from "@fosscord/util"; +import { + Member, + getPermission, + getRights, + Role, + GuildMemberUpdateEvent, + emitEvent, + Sticker, + Emoji, + Guild, + MemberChangeSchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; const router = Router(); @@ -8,48 +19,63 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - const member = await Member.findOneOrFail({ where: { id: member_id, guild_id } }); + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + }); return res.json(member); }); -router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { - let { guild_id, member_id } = req.params; - if (member_id === "@me") member_id = req.user_id; - const body = req.body as MemberChangeSchema; - - const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); - const permission = await getPermission(req.user_id, guild_id); - const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } }); - - if (body.roles) { - permission.hasThrow("MANAGE_ROLES"); - - if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id); - member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist - } - - if ('nick' in body) { - permission.hasThrow(req.user_id == member.user.id ? "CHANGE_NICKNAME" : "MANAGE_NICKNAMES"); - member.nick = body.nick?.trim() || undefined; - } - - await member.save(); - - member.roles = member.roles.filter((x) => x.id !== everyone.id); - - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) } - } as GuildMemberUpdateEvent); - - res.json(member); -}); +router.patch( + "/", + route({ body: "MemberChangeSchema" }), + async (req: Request, res: Response) => { + let { guild_id, member_id } = req.params; + if (member_id === "@me") member_id = req.user_id; + const body = req.body as MemberChangeSchema; + + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + }); + const permission = await getPermission(req.user_id, guild_id); + const everyone = await Role.findOneOrFail({ + where: { guild_id: guild_id, name: "@everyone", position: 0 }, + }); + + if (body.roles) { + permission.hasThrow("MANAGE_ROLES"); + + if (body.roles.indexOf(everyone.id) === -1) + body.roles.push(everyone.id); + member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist + } + + if ("nick" in body) { + permission.hasThrow( + req.user_id == member.user.id + ? "CHANGE_NICKNAME" + : "MANAGE_NICKNAMES", + ); + member.nick = body.nick?.trim() || undefined; + } + + await member.save(); + + member.roles = member.roles.filter((x) => x.id !== everyone.id); + + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); + + res.json(member); + }, +); router.put("/", route({}), async (req: Request, res: Response) => { - // TODO: Lurker mode const rights = await getRights(req.user_id); @@ -59,23 +85,23 @@ router.put("/", route({}), async (req: Request, res: Response) => { member_id = req.user_id; rights.hasThrow("JOIN_GUILDS"); } else { - // TODO: join others by controller + // TODO: join others by controller } var guild = await Guild.findOneOrFail({ - where: { id: guild_id } + where: { id: guild_id }, }); var emoji = await Emoji.find({ - where: { guild_id: guild_id } + where: { guild_id: guild_id }, }); var roles = await Role.find({ - where: { guild_id: guild_id } + where: { guild_id: guild_id }, }); var stickers = await Sticker.find({ - where: { guild_id: guild_id } + where: { guild_id: guild_id }, }); await Member.addToGuild(member_id, guild_id); diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts index edd47605..20443821 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -4,19 +4,23 @@ import { Request, Response, Router } from "express"; const router = Router(); -router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { - var { guild_id, member_id } = req.params; - var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; - if (member_id === "@me") { - member_id = req.user_id; - permissionString = "CHANGE_NICKNAME"; - } +router.patch( + "/", + route({ body: "MemberNickChangeSchema" }), + async (req: Request, res: Response) => { + var { guild_id, member_id } = req.params; + var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; + if (member_id === "@me") { + member_id = req.user_id; + permissionString = "CHANGE_NICKNAME"; + } - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow(permissionString); + const perms = await getPermission(req.user_id, guild_id); + perms.hasThrow(permissionString); - await Member.changeNickname(member_id, guild_id, req.body.nick); - res.status(200).send(); -}); + await Member.changeNickname(member_id, guild_id, req.body.nick); + res.status(200).send(); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index 8f5ca7ba..c0383912 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -4,18 +4,26 @@ import { Request, Response, Router } from "express"; const router = Router(); -router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; +router.delete( + "/", + route({ permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.removeRole(member_id, guild_id, role_id); - res.sendStatus(204); -}); + await Member.removeRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, +); -router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; +router.put( + "/", + route({ permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.addRole(member_id, guild_id, role_id); - res.sendStatus(204); -}); + await Member.addRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts index b730a4e7..b516b9e9 100644 --- a/src/api/routes/guilds/#guild_id/members/index.ts +++ b/src/api/routes/guilds/#guild_id/members/index.ts @@ -12,7 +12,8 @@ const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const limit = Number(req.query.limit) || 1; - if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); + if (limit > 1000 || limit < 1) + throw new HTTPError("Limit must be between 1 and 1000"); const after = `${req.query.after}`; const query = after ? { id: MoreThan(after) } : {}; @@ -22,7 +23,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { where: { guild_id, ...query }, select: PublicMemberProjection, take: limit, - order: { id: "ASC" } + order: { id: "ASC" }, }); return res.json(members); diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts index a7516ebd..f2d8087e 100644 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ b/src/api/routes/guilds/#guild_id/messages/search.ts @@ -10,36 +10,62 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { channel_id, content, - include_nsfw, // TODO + include_nsfw, // TODO offset, sort_order, - sort_by, // TODO: Handle 'relevance' + sort_by, // TODO: Handle 'relevance' limit, author_id, } = req.query; const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + if (parsedLimit < 1 || parsedLimit > 100) + throw new HTTPError("limit must be between 1 and 100", 422); if (sort_order) { - if (typeof sort_order != "string" - || ["desc", "asc"].indexOf(sort_order) == -1) - throw FieldErrors({ sort_order: { message: "Value must be one of ('desc', 'asc').", code: "BASE_TYPE_CHOICES" } }); // todo this is wrong + if ( + typeof sort_order != "string" || + ["desc", "asc"].indexOf(sort_order) == -1 + ) + throw FieldErrors({ + sort_order: { + message: "Value must be one of ('desc', 'asc').", + code: "BASE_TYPE_CHOICES", + }, + }); // todo this is wrong } - const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string); + const permissions = await getPermission( + req.user_id, + req.params.guild_id, + channel_id as string, + ); permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); + if (!permissions.has("READ_MESSAGE_HISTORY")) + return res.json({ messages: [], total_results: 0 }); var query: FindManyOptions<Message> = { - order: { timestamp: sort_order ? sort_order.toUpperCase() as "ASC" | "DESC" : "DESC" }, + order: { + timestamp: sort_order + ? (sort_order.toUpperCase() as "ASC" | "DESC") + : "DESC", + }, take: parsedLimit || 0, where: { guild: { id: req.params.guild_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", + ], skip: offset ? Number(offset) : 0, }; //@ts-ignore @@ -51,32 +77,34 @@ router.get("/", route({}), async (req: Request, res: Response) => { const messages: Message[] = await Message.find(query); - const messagesDto = messages.map(x => [{ - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, + const messagesDto = messages.map((x) => [ + { + id: x.id, + type: x.type, + content: x.content, + channel_id: x.channel_id, + author: { + id: x.author?.id, + username: x.author?.username, + avatar: x.author?.avatar, + avatar_decoration: null, + discriminator: x.author?.discriminator, + public_flags: x.author?.public_flags, + }, + attachments: x.attachments, + embeds: x.embeds, + mentions: x.mentions, + mention_roles: x.mention_roles, + pinned: x.pinned, + mention_everyone: x.mention_everyone, + tts: x.tts, + timestamp: x.timestamp, + edited_timestamp: x.edited_timestamp, + flags: x.flags, + components: x.components, + hit: true, }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - hit: true, - }]); + ]); return res.json({ messages: messagesDto, @@ -84,4 +112,4 @@ router.get("/", route({}), async (req: Request, res: Response) => { }); }); -export default router; \ No newline at end of file +export default router; diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts index 2e674349..d11244b1 100644 --- a/src/api/routes/guilds/#guild_id/prune.ts +++ b/src/api/routes/guilds/#guild_id/prune.ts @@ -5,7 +5,12 @@ import { route } from "@fosscord/api"; const router = Router(); //Returns all inactive members, respecting role hierarchy -export const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => { +export const inactiveMembers = async ( + guild_id: string, + user_id: string, + days: number, + roles: string[] = [], +) => { var date = new Date(); date.setDate(date.getDate() - days); //Snowflake should have `generateFromTime` method? Or similar? @@ -19,21 +24,27 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n where: [ { guild_id, - last_message_id: LessThan(minId.toString()) + last_message_id: LessThan(minId.toString()), }, { - last_message_id: IsNull() - } + last_message_id: IsNull(), + }, ], - relations: ["roles"] + relations: ["roles"], }); console.log(members); if (!members.length) return []; //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. - if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); - - const me = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["roles"] }); + if (roles.length && members.length) + members = members.filter((user) => + user.roles?.some((role) => roles.includes(role.id)), + ); + + const me = await Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["roles"], + }); const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); @@ -44,8 +55,8 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n member.roles?.some( (role) => role.position < myHighestRole || //roles higher than me can't be kicked - me.id === guild.owner_id //owner can kick anyone - ) + me.id === guild.owner_id, //owner can kick anyone + ), ); return members; @@ -57,23 +68,39 @@ router.get("/", route({}), async (req: Request, res: Response) => { var roles = req.query.include_roles; if (typeof roles === "string") roles = [roles]; //express will return array otherwise - const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); + const members = await inactiveMembers( + req.params.guild_id, + req.user_id, + days, + roles as string[], + ); res.send({ pruned: members.length }); }); -router.post("/", route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), async (req: Request, res: Response) => { - const days = parseInt(req.body.days); - - var roles = req.query.include_roles; - if (typeof roles === "string") roles = [roles]; - - const { guild_id } = req.params; - const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]); - - await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id))); - - res.send({ purged: members.length }); -}); +router.post( + "/", + route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), + async (req: Request, res: Response) => { + const days = parseInt(req.body.days); + + var roles = req.query.include_roles; + if (typeof roles === "string") roles = [roles]; + + const { guild_id } = req.params; + const members = await inactiveMembers( + guild_id, + req.user_id, + days, + roles as string[], + ); + + await Promise.all( + members.map((x) => Member.removeFromGuild(x.id, guild_id)), + ); + + res.send({ purged: members.length }); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts index 308d5ee5..0b275ea4 100644 --- a/src/api/routes/guilds/#guild_id/regions.ts +++ b/src/api/routes/guilds/#guild_id/regions.ts @@ -9,7 +9,12 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); //TODO we should use an enum for guild's features and not hardcoded strings - return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS"))); + return res.json( + await getVoiceRegions( + getIpAdress(req), + guild.features.includes("VIP_REGIONS"), + ), + ); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts index 87cf5261..e274e3d0 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -1,5 +1,13 @@ import { Router, Request, Response } from "express"; -import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile, RoleModifySchema } from "@fosscord/util"; +import { + Role, + Member, + GuildRoleUpdateEvent, + GuildRoleDeleteEvent, + emitEvent, + handleFile, + RoleModifySchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; @@ -12,57 +20,72 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.json(role); }); -router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; - if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); +router.delete( + "/", + route({ permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + if (role_id === guild_id) + throw new HTTPError("You can't delete the @everyone role"); - await Promise.all([ - Role.delete({ - id: role_id, - guild_id: guild_id - }), - emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id, + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", guild_id, - role_id - } - } as GuildRoleDeleteEvent) - ]); + data: { + guild_id, + role_id, + }, + } as GuildRoleDeleteEvent), + ]); - res.sendStatus(204); -}); + res.sendStatus(204); + }, +); // TODO: check role hierarchy -router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const { role_id, guild_id } = req.params; - const body = req.body as RoleModifySchema; +router.patch( + "/", + route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const { role_id, guild_id } = req.params; + const body = req.body as RoleModifySchema; - if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); - else body.icon = undefined; + if (body.icon && body.icon.length) + body.icon = await handleFile( + `/role-icons/${role_id}`, + body.icon as string, + ); + else body.icon = undefined; - const role = Role.create({ - ...body, - id: role_id, - guild_id, - permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")) - }); - - await Promise.all([ - role.save(), - emitEvent({ - event: "GUILD_ROLE_UPDATE", + const role = Role.create({ + ...body, + id: role_id, guild_id, - data: { + permissions: String( + req.permission!.bitfield & BigInt(body.permissions || "0"), + ), + }); + + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", guild_id, - role - } - } as GuildRoleUpdateEvent) - ]); + data: { + guild_id, + role, + }, + } as GuildRoleUpdateEvent), + ]); - res.json(role); -}); + res.json(role); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts index c5a86400..e3c7373e 100644 --- a/src/api/routes/guilds/#guild_id/roles/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/index.ts @@ -29,70 +29,87 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.json(roles); }); -router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as RoleModifySchema; - - const role_count = await Role.count({ where: { guild_id } }); - const { maxRoles } = Config.get().limits.guild; - - if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - - const role = Role.create({ - // values before ...body are default and can be overriden - position: 0, - hoist: false, - color: 0, - mentionable: false, - ...body, - guild_id: guild_id, - managed: false, - permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")), - tags: undefined, - icon: undefined, - unicode_emoji: undefined - }); - - await Promise.all([ - role.save(), - emitEvent({ - event: "GUILD_ROLE_CREATE", - guild_id, - data: { - guild_id, - role: role - } - } as GuildRoleCreateEvent) - ]); - - res.json(role); -}); - -router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as RolePositionUpdateSchema; - - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow("MANAGE_ROLES"); - - await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - - const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) }); - - await Promise.all( - roles.map((x) => +router.post( + "/", + route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as RoleModifySchema; + + const role_count = await Role.count({ where: { guild_id } }); + const { maxRoles } = Config.get().limits.guild; + + if (role_count > maxRoles) + throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); + + const role = Role.create({ + // values before ...body are default and can be overriden + position: 0, + hoist: false, + color: 0, + mentionable: false, + ...body, + guild_id: guild_id, + managed: false, + permissions: String( + req.permission!.bitfield & BigInt(body.permissions || "0"), + ), + tags: undefined, + icon: undefined, + unicode_emoji: undefined, + }); + + await Promise.all([ + role.save(), emitEvent({ - event: "GUILD_ROLE_UPDATE", + event: "GUILD_ROLE_CREATE", guild_id, data: { guild_id, - role: x - } - } as GuildRoleUpdateEvent) - ) - ); - - res.json(roles); -}); + role: role, + }, + } as GuildRoleCreateEvent), + ]); + + res.json(role); + }, +); + +router.patch( + "/", + route({ body: "RolePositionUpdateSchema" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as RolePositionUpdateSchema; + + const perms = await getPermission(req.user_id, guild_id); + perms.hasThrow("MANAGE_ROLES"); + + await Promise.all( + body.map(async (x) => + Role.update({ guild_id, id: x.id }, { position: x.position }), + ), + ); + + const roles = await Role.find({ + where: body.map((x) => ({ id: x.id, guild_id })), + }); + + await Promise.all( + roles.map((x) => + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role: x, + }, + } as GuildRoleUpdateEvent), + ), + ); + + res.json(roles); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts index fc0f49ab..3b1f5f8e 100644 --- a/src/api/routes/guilds/#guild_id/stickers.ts +++ b/src/api/routes/guilds/#guild_id/stickers.ts @@ -26,15 +26,18 @@ const bodyParser = multer({ limits: { fileSize: 1024 * 1024 * 100, fields: 10, - files: 1 + files: 1, }, - storage: multer.memoryStorage() + storage: multer.memoryStorage(), }).single("file"); router.post( "/", bodyParser, - route({ permission: "MANAGE_EMOJIS_AND_STICKERS", body: "ModifyGuildStickerSchema" }), + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + body: "ModifyGuildStickerSchema", + }), async (req: Request, res: Response) => { if (!req.file) throw new HTTPError("missing file"); @@ -49,15 +52,15 @@ router.post( id, type: StickerType.GUILD, format_type: getStickerFormat(req.file.mimetype), - available: true + available: true, }).save(), - uploadFile(`/stickers/${id}`, req.file) + uploadFile(`/stickers/${id}`, req.file), ]); await sendStickerUpdateEvent(guild_id); res.json(sticker); - } + }, ); export function getStickerFormat(mime_type: string) { @@ -71,7 +74,9 @@ export function getStickerFormat(mime_type: string) { case "image/gif": return StickerFormatType.GIF; default: - throw new HTTPError("invalid sticker format: must be png, apng or lottie"); + throw new HTTPError( + "invalid sticker format: must be png, apng or lottie", + ); } } @@ -79,21 +84,30 @@ router.get("/:sticker_id", route({}), async (req: Request, res: Response) => { const { guild_id, sticker_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } })); + res.json( + await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }), + ); }); router.patch( "/:sticker_id", - route({ body: "ModifyGuildStickerSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), + route({ + body: "ModifyGuildStickerSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + }), async (req: Request, res: Response) => { const { guild_id, sticker_id } = req.params; const body = req.body as ModifyGuildStickerSchema; - const sticker = await Sticker.create({ ...body, guild_id, id: sticker_id }).save(); + const sticker = await Sticker.create({ + ...body, + guild_id, + id: sticker_id, + }).save(); await sendStickerUpdateEvent(guild_id); return res.json(sticker); - } + }, ); async function sendStickerUpdateEvent(guild_id: string) { @@ -102,18 +116,22 @@ async function sendStickerUpdateEvent(guild_id: string) { guild_id: guild_id, data: { guild_id: guild_id, - stickers: await Sticker.find({ where: { guild_id: guild_id } }) - } + stickers: await Sticker.find({ where: { guild_id: guild_id } }), + }, } as GuildStickersUpdateEvent); } -router.delete("/:sticker_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; +router.delete( + "/:sticker_id", + route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; - await Sticker.delete({ guild_id, id: sticker_id }); - await sendStickerUpdateEvent(guild_id); + await Sticker.delete({ guild_id, id: sticker_id }); + await sendStickerUpdateEvent(guild_id); - return res.sendStatus(204); -}); + return res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts index 628321f5..3b5eddaa 100644 --- a/src/api/routes/guilds/#guild_id/templates.ts +++ b/src/api/routes/guilds/#guild_id/templates.ts @@ -20,63 +20,97 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "afk_channel_id", "system_channel_id", "system_channel_flags", - "icon" + "icon", ]; router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - var templates = await Template.find({ where: { source_guild_id: guild_id } }); - - return res.json(templates); -}); - -router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => { }); - if (exists) throw new HTTPError("Template already exists", 400); - - const template = await Template.create({ - ...req.body, - code: generateCode(), - creator_id: req.user_id, - created_at: new Date(), - updated_at: new Date(), - source_guild_id: guild_id, - serialized_source_guild: guild - }).save(); - - res.json(template); -}); - -router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - - const template = await Template.delete({ - code, - source_guild_id: guild_id + var templates = await Template.find({ + where: { source_guild_id: guild_id }, }); - res.json(template); -}); - -router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - - const template = await Template.create({ code, serialized_source_guild: guild }).save(); - - res.json(template); + return res.json(templates); }); -router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const { name, description } = req.body; - - const template = await Template.create({ code, name: name, description: description, source_guild_id: guild_id }).save(); - - res.json(template); -}); +router.post( + "/", + route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + }); + const exists = await Template.findOneOrFail({ + where: { id: guild_id }, + }).catch((e) => {}); + if (exists) throw new HTTPError("Template already exists", 400); + + const template = await Template.create({ + ...req.body, + code: generateCode(), + creator_id: req.user_id, + created_at: new Date(), + updated_at: new Date(), + source_guild_id: guild_id, + serialized_source_guild: guild, + }).save(); + + res.json(template); + }, +); + +router.delete( + "/:code", + route({ permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + + const template = await Template.delete({ + code, + source_guild_id: guild_id, + }); + + res.json(template); + }, +); + +router.put( + "/:code", + route({ permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + }); + + const template = await Template.create({ + code, + serialized_source_guild: guild, + }).save(); + + res.json(template); + }, +); + +router.patch( + "/:code", + route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const { name, description } = req.body; + + const template = await Template.create({ + code, + name: name, + description: description, + source_guild_id: guild_id, + }).save(); + + res.json(template); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts index d1fe4726..9a96b066 100644 --- a/src/api/routes/guilds/#guild_id/vanity-url.ts +++ b/src/api/routes/guilds/#guild_id/vanity-url.ts @@ -1,4 +1,10 @@ -import { Channel, ChannelType, Guild, Invite, VanityUrlSchema } from "@fosscord/util"; +import { + Channel, + ChannelType, + Guild, + Invite, + VanityUrlSchema, +} from "@fosscord/util"; import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; import { HTTPError } from "lambert-server"; @@ -7,52 +13,70 @@ const router = Router(); const InviteRegex = /\W/g; -router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - - if (!guild.features.includes("ALIASABLE_NAMES")) { - const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); - if (!invite) return res.json({ code: null }); - - return res.json({ code: invite.code, uses: invite.uses }); - } else { - const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } }); - if (!invite || invite.length == 0) return res.json({ code: null }); - - return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); - } -}); - -router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as VanityUrlSchema; - const code = body.code?.replace(InviteRegex, ""); - - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); - - if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); - - const invite = await Invite.findOne({ where: { code } }); - if (invite) throw new HTTPError("Invite already exists"); - - const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } }); - - await Invite.create({ - vanity_url: true, - code: code, - temporary: false, - uses: 0, - max_uses: 0, - max_age: 0, - created_at: new Date(), - expires_at: new Date(), - guild_id: guild_id, - channel_id: id - }).save(); - - return res.json({ code: code }); -}); +router.get( + "/", + route({ permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + + if (!guild.features.includes("ALIASABLE_NAMES")) { + const invite = await Invite.findOne({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite) return res.json({ code: null }); + + return res.json({ code: invite.code, uses: invite.uses }); + } else { + const invite = await Invite.find({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite || invite.length == 0) return res.json({ code: null }); + + return res.json( + invite.map((x) => ({ code: x.code, uses: x.uses })), + ); + } + }, +); + +router.patch( + "/", + route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); + + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + if (!guild.features.includes("VANITY_URL")) + throw new HTTPError("Your guild doesn't support vanity urls"); + + if (!code || code.length === 0) + throw new HTTPError("Code cannot be null or empty"); + + const invite = await Invite.findOne({ where: { code } }); + if (invite) throw new HTTPError("Invite already exists"); + + const { id } = await Channel.findOneOrFail({ + where: { guild_id, type: ChannelType.GUILD_TEXT }, + }); + + await Invite.create({ + vanity_url: true, + code: code, + temporary: false, + uses: 0, + max_uses: 0, + max_age: 0, + created_at: new Date(), + expires_at: new Date(), + guild_id: guild_id, + channel_id: id, + }).save(); + + return res.json({ code: code }); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 006e997f..af03a07e 100644 --- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -1,52 +1,71 @@ -import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent, VoiceStateUpdateSchema } from "@fosscord/util"; +import { + Channel, + ChannelType, + DiscordApiErrors, + emitEvent, + getPermission, + VoiceState, + VoiceStateUpdateEvent, + VoiceStateUpdateSchema, +} from "@fosscord/util"; import { route } from "@fosscord/api"; import { Request, Response, Router } from "express"; const router = Router(); //TODO need more testing when community guild and voice stage channel are working -router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - var { guild_id, user_id } = req.params; - if (user_id === "@me") user_id = req.user_id; +router.patch( + "/", + route({ body: "VoiceStateUpdateSchema" }), + async (req: Request, res: Response) => { + const body = req.body as VoiceStateUpdateSchema; + var { guild_id, user_id } = req.params; + if (user_id === "@me") user_id = req.user_id; - const perms = await getPermission(req.user_id, guild_id, body.channel_id); + const perms = await getPermission( + req.user_id, + guild_id, + body.channel_id, + ); - /* + /* From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. */ - if (body.suppress && user_id !== req.user_id) { - perms.hasThrow("MUTE_MEMBERS"); - } - if (!body.suppress) body.request_to_speak_timestamp = new Date(); - if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); - - const voice_state = await VoiceState.findOne({ - where: { - guild_id, - channel_id: body.channel_id, - user_id + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + + const voice_state = await VoiceState.findOne({ + where: { + guild_id, + channel_id: body.channel_id, + user_id, + }, + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ + where: { guild_id, id: body.channel_id }, + }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; } - }); - if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - - voice_state.assign(body); - const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } }); - if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { - throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; - } - - await Promise.all([ - voice_state.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voice_state, - guild_id - } as VoiceStateUpdateEvent) - ]); - return res.sendStatus(204); -}); + + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state, + guild_id, + } as VoiceStateUpdateEvent), + ]); + return res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts index 57da062d..80ab138b 100644 --- a/src/api/routes/guilds/#guild_id/welcome-screen.ts +++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts @@ -14,20 +14,30 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json(guild.welcome_screen); }); -router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as GuildUpdateWelcomeScreenSchema; - - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - - if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); - if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid - if (body.description) guild.welcome_screen.description = body.description; - if (body.enabled != null) guild.welcome_screen.enabled = body.enabled; - - await guild.save(); - - res.sendStatus(204); -}); +router.patch( + "/", + route({ + body: "GuildUpdateWelcomeScreenSchema", + permission: "MANAGE_GUILD", + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as GuildUpdateWelcomeScreenSchema; + + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + + if (!guild.welcome_screen.enabled) + throw new HTTPError("Welcome screen disabled", 400); + if (body.welcome_channels) + guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid + if (body.description) + guild.welcome_screen.description = body.description; + if (body.enabled != null) guild.welcome_screen.enabled = body.enabled; + + await guild.save(); + + res.sendStatus(204); + }, +); export default router; diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts index be5bf23f..2c3124a2 100644 --- a/src/api/routes/guilds/#guild_id/widget.json.ts +++ b/src/api/routes/guilds/#guild_id/widget.json.ts @@ -1,5 +1,12 @@ import { Request, Response, Router } from "express"; -import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; +import { + Config, + Permissions, + Guild, + Invite, + Channel, + Member, +} from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { random, route } from "@fosscord/api"; @@ -21,7 +28,9 @@ router.get("/", route({}), async (req: Request, res: Response) => { if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); // Fetch existing widget invite for widget channel - var invite = await Invite.findOne({ where: { channel_id: guild.widget_channel_id } }); + var invite = await Invite.findOne({ + where: { channel_id: guild.widget_channel_id }, + }); if (guild.widget_channel_id && !invite) { // Create invite for channel if none exists @@ -45,16 +54,24 @@ router.get("/", route({}), async (req: Request, res: Response) => { // Fetch voice channels, and the @everyone permissions object const channels = [] as any[]; - (await Channel.find({ where: { guild_id: guild_id, type: 2 }, order: { position: "ASC" } })).filter((doc) => { + ( + await Channel.find({ + where: { guild_id: guild_id, type: 2 }, + order: { position: "ASC" }, + }) + ).filter((doc) => { // Only return channels where @everyone has the CONNECT permission if ( doc.permission_overwrites === undefined || - Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT + Permissions.channelPermission( + doc.permission_overwrites, + Permissions.FLAGS.CONNECT, + ) === Permissions.FLAGS.CONNECT ) { channels.push({ id: doc.id, name: doc.name, - position: doc.position + position: doc.position, }); } }); @@ -70,7 +87,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { instant_invite: invite?.code, channels: channels, members: members, - presence_count: guild.presence_count + presence_count: guild.presence_count, }; res.set("Cache-Control", "public, max-age=300"); diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts index c17d511e..eaec8f07 100644 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts @@ -24,8 +24,13 @@ router.get("/", route({}), async (req: Request, res: Response) => { // Fetch parameter const style = req.query.style?.toString() || "shield"; - if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) { - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + if ( + !["shield", "banner1", "banner2", "banner3", "banner4"].includes(style) + ) { + throw new HTTPError( + "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", + 400, + ); } // Setup canvas @@ -34,7 +39,17 @@ router.get("/", route({}), async (req: Request, res: Response) => { const sizeOf = require("image-size"); // TODO: Widget style templates need Fosscord branding - const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); + const source = path.join( + __dirname, + "..", + "..", + "..", + "..", + "..", + "assets", + "widget", + `${style}.png`, + ); if (!fs.existsSync(source)) { throw new HTTPError("Widget template does not exist.", 400); } @@ -50,30 +65,68 @@ router.get("/", route({}), async (req: Request, res: Response) => { switch (style) { case "shield": ctx.textAlign = "center"; - await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence); + await drawText( + ctx, + 73, + 13, + "#FFFFFF", + "thin 10px Verdana", + presence, + ); break; case "banner1": if (icon) await drawIcon(ctx, 20, 27, 50, icon); await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22); - await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence); + await drawText( + ctx, + 83, + 66, + "#C9D2F0FF", + "thin 11px Verdana", + presence, + ); break; case "banner2": if (icon) await drawIcon(ctx, 13, 19, 36, icon); await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15); - await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence); + await drawText( + ctx, + 62, + 49, + "#C9D2F0FF", + "thin 11px Verdana", + presence, + ); break; case "banner3": if (icon) await drawIcon(ctx, 20, 20, 50, icon); await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27); - await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence); + await drawText( + ctx, + 83, + 58, + "#C9D2F0FF", + "thin 11px Verdana", + presence, + ); break; case "banner4": if (icon) await drawIcon(ctx, 21, 136, 50, icon); await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27); - await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence); + await drawText( + ctx, + 84, + 171, + "#C9D2F0FF", + "thin 12px Verdana", + presence, + ); break; default: - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + throw new HTTPError( + "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", + 400, + ); } // Return final image @@ -83,7 +136,13 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.send(buffer); }); -async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) { +async function drawIcon( + canvas: any, + x: number, + y: number, + scale: number, + icon: string, +) { // @ts-ignore const img = new require("canvas").Image(); img.src = icon; @@ -101,10 +160,19 @@ async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: canvas.restore(); } -async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) { +async function drawText( + canvas: any, + x: number, + y: number, + color: string, + font: string, + text: string, + maxcharacters?: number, +) { canvas.fillStyle = color; canvas.font = font; - if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "..."; + if (text.length > (maxcharacters || 0) && maxcharacters) + text = text.slice(0, maxcharacters) + "..."; canvas.fillText(text, x, y); } diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts index dbb4cc0c..108339e1 100644 --- a/src/api/routes/guilds/#guild_id/widget.ts +++ b/src/api/routes/guilds/#guild_id/widget.ts @@ -10,18 +10,31 @@ router.get("/", route({}), async (req: Request, res: Response) => { const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); + return res.json({ + enabled: guild.widget_enabled || false, + channel_id: guild.widget_channel_id || null, + }); }); // https://discord.com/developers/docs/resources/guild#modify-guild-widget -router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { - const body = req.body as WidgetModifySchema; - const { guild_id } = req.params; - - await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }); - // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request - - return res.json(body); -}); +router.patch( + "/", + route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), + async (req: Request, res: Response) => { + const body = req.body as WidgetModifySchema; + const { guild_id } = req.params; + + await Guild.update( + { id: guild_id }, + { + widget_enabled: body.enabled, + widget_channel_id: body.channel_id, + }, + ); + // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request + + return res.json(body); + }, +); export default router; |