This commit is contained in:
Puyodead1 2023-03-24 21:21:21 -04:00
parent 10e1eb95ae
commit c2ce88dee7
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
52 changed files with 1564 additions and 629 deletions

Binary file not shown.

Binary file not shown.

View File

@ -37,7 +37,17 @@ const router: Router = Router();
router.get(
"/",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
200: {
body: "GuildBansResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -73,7 +83,20 @@ router.get(
router.get(
"/:user",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
200: {
body: "BanModeratorSchema",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const user_id = req.params.ban;
@ -97,7 +120,21 @@ router.get(
router.put(
"/:user_id",
route({ requestBody: "BanCreateSchema", permission: "BAN_MEMBERS" }),
route({
requestBody: "BanCreateSchema",
permission: "BAN_MEMBERS",
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const banned_user_id = req.params.user_id;
@ -143,7 +180,20 @@ router.put(
router.put(
"/@me",
route({ requestBody: "BanCreateSchema" }),
route({
requestBody: "BanCreateSchema",
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -182,7 +232,18 @@ router.put(
router.delete(
"/:user_id",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, user_id } = req.params;

View File

@ -28,18 +28,39 @@ import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
201: {
body: "GuildChannelsResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const channels = await Channel.find({ where: { guild_id } });
res.json(channels);
});
},
);
router.post(
"/",
route({
requestBody: "ChannelModifySchema",
permission: "MANAGE_CHANNELS",
responses: {
201: {
body: "Channel",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
@ -60,6 +81,15 @@ router.patch(
route({
requestBody: "ChannelReorderSchema",
permission: "MANAGE_CHANNELS",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// changes guild channel position

View File

@ -16,16 +16,29 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { emitEvent, GuildDeleteEvent, Guild } from "@spacebar/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { Guild, GuildDeleteEvent, emitEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
// discord prefixes this route with /delete instead of using the delete method
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -47,6 +60,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
]);
return res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,12 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildDiscoveryRequirements",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
// TODO:
// Load from database
@ -50,6 +59,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
},
minimum_size: 0,
});
});
},
);
export default router;

View File

@ -34,7 +34,19 @@ import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildEmojisResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -45,9 +57,25 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(emojis);
});
},
);
router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
router.get(
"/:emoji_id",
route({
responses: {
200: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, emoji_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -58,13 +86,25 @@ router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
});
return res.json(emoji);
});
},
);
router.post(
"/",
route({
requestBody: "EmojiCreateSchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
201: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -115,6 +155,14 @@ router.patch(
route({
requestBody: "EmojiModifySchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
200: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;
@ -141,7 +189,15 @@ router.patch(
router.delete(
"/:emoji_id",
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;

View File

@ -34,7 +34,22 @@ import { HTTPError } from "lambert-server";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
"200": {
body: "GuildResponse",
},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const [guild, member] = await Promise.all([
@ -51,11 +66,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
...guild,
joined_at: member?.joined_at,
});
});
},
);
router.patch(
"/",
route({ requestBody: "GuildUpdateSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "GuildUpdateSchema",
permission: "MANAGE_GUILD",
responses: {
"200": {
body: "GuildUpdateSchema",
},
401: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as GuildUpdateSchema;
const { guild_id } = req.params;

View File

@ -16,15 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Invite, PublicInviteRelation } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Invite, PublicInviteRelation } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get(
"/",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: {
body: "GuildInvitesResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;

View File

@ -16,17 +16,27 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: member verification
res.status(404).json({
message: "Unknown Guild Member Verification Form",
code: 10068,
});
});
},
);
export default router;

View File

@ -34,7 +34,22 @@ import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Member",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -43,11 +58,28 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(member);
});
},
);
router.patch(
"/",
route({ requestBody: "MemberChangeSchema" }),
route({
requestBody: "MemberChangeSchema",
responses: {
200: {
body: "Member",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const member_id =
@ -119,7 +151,22 @@ router.patch(
},
);
router.put("/", route({}), async (req: Request, res: Response) => {
router.put(
"/",
route({
responses: {
200: {
body: "MemberJoinGuildResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: Lurker mode
const rights = await getRights(req.user_id);
@ -151,9 +198,20 @@ router.put("/", route({}), async (req: Request, res: Response) => {
await Member.addToGuild(member_id, guild_id);
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
});
},
);
router.delete("/", route({}), async (req: Request, res: Response) => {
router.delete(
"/",
route({
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
const permission = await getPermission(req.user_id, guild_id);
const rights = await getRights(req.user_id);
@ -167,6 +225,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
await Member.removeFromGuild(member_id, guild_id);
res.sendStatus(204);
});
},
);
export default router;

View File

@ -24,7 +24,18 @@ const router = Router();
router.patch(
"/",
route({ requestBody: "MemberNickChangeSchema" }),
route({
requestBody: "MemberNickChangeSchema",
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";

View File

@ -16,15 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Member } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Member } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.delete(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id, member_id } = req.params;
@ -35,7 +43,13 @@ router.delete(
router.put(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id, member_id } = req.params;

View File

@ -16,18 +16,40 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Member, PublicMemberProjection } from "@spacebar/util";
import { route } from "@spacebar/api";
import { MoreThan } from "typeorm";
import { Member, PublicMemberProjection } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { MoreThan } from "typeorm";
const router = Router();
// TODO: send over websocket
// TODO: check for GUILD_MEMBERS intent
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
limit: {
type: "number",
description:
"max number of members to return (1-1000). default 1",
},
after: {
type: "string",
},
},
responses: {
200: {
body: "GuildMembersResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const limit = Number(req.query.limit) || 1;
if (limit > 1000 || limit < 1)
@ -45,6 +67,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(members);
});
},
);
export default router;

View File

@ -18,15 +18,30 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { getPermission, FieldErrors, Message, Channel } from "@spacebar/util";
import { Channel, FieldErrors, Message, getPermission } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildMessagesSearchResponse",
},
403: {
body: "APIErrorResponse",
},
422: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const {
channel_id,
content,
@ -104,7 +119,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
req.params.guild_id,
channel.id,
);
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
if (
!perm.has("VIEW_CHANNEL") ||
!perm.has("READ_MESSAGE_HISTORY")
)
continue;
ids.push(channel.id);
}
@ -152,6 +170,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
messages: messagesDto,
total_results: messages.length,
});
});
},
);
export default router;

View File

@ -31,7 +31,20 @@ const router = Router();
router.patch(
"/:member_id",
route({ requestBody: "MemberChangeProfileSchema" }),
route({
requestBody: "MemberChangeProfileSchema",
responses: {
200: {
body: "Member",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
// const member_id =

View File

@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { Guild, Member, Snowflake } from "@spacebar/util";
import { LessThan, IsNull } from "typeorm";
import { route } from "@spacebar/api";
import { Guild, Member, Snowflake } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { IsNull, LessThan } from "typeorm";
const router = Router();
//Returns all inactive members, respecting role hierarchy
@ -80,7 +80,16 @@ export const inactiveMembers = async (
return members;
};
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
"200": {
body: "GuildPruneResponse",
},
},
}),
async (req: Request, res: Response) => {
const days = parseInt(req.query.days as string);
let roles = req.query.include_roles;
@ -94,11 +103,23 @@ router.get("/", route({}), async (req: Request, res: Response) => {
);
res.send({ pruned: members.length });
});
},
);
router.post(
"/",
route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
route({
permission: "KICK_MEMBERS",
right: "KICK_BAN_MEMBERS",
responses: {
200: {
body: "GuildPurgeResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const days = parseInt(req.body.days);

View File

@ -16,13 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
import { Guild } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { getVoiceRegions, route, getIpAdress } from "@spacebar/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildVoiceRegionsResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
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
@ -32,6 +44,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
guild.features.includes("VIP_REGIONS"),
),
);
});
},
);
export default router;

View File

@ -31,16 +31,48 @@ import { HTTPError } from "lambert-server";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Role",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
const role = await Role.findOneOrFail({ where: { guild_id, id: role_id } });
return res.json(role);
const role = await Role.findOneOrFail({
where: { guild_id, id: role_id },
});
return res.json(role);
},
);
router.delete(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id } = req.params;
if (role_id === guild_id)
@ -69,7 +101,24 @@ router.delete(
router.patch(
"/",
route({ requestBody: "RoleModifySchema", permission: "MANAGE_ROLES" }),
route({
requestBody: "RoleModifySchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "Role",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { role_id, guild_id } = req.params;
const body = req.body as RoleModifySchema;

View File

@ -21,7 +21,6 @@ import {
Config,
DiscordApiErrors,
emitEvent,
getPermission,
GuildRoleCreateEvent,
GuildRoleUpdateEvent,
Member,
@ -47,7 +46,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({ requestBody: "RoleModifySchema", permission: "MANAGE_ROLES" }),
route({
requestBody: "RoleModifySchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "Role",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
const body = req.body as RoleModifySchema;
@ -104,14 +117,25 @@ router.post(
router.patch(
"/",
route({ requestBody: "RolePositionUpdateSchema" }),
route({
requestBody: "RolePositionUpdateSchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "GuildRolesResponse",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
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 }),

View File

@ -33,12 +33,25 @@ import { HTTPError } from "lambert-server";
import multer from "multer";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildStickersResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(await Sticker.find({ where: { guild_id } }));
});
},
);
const bodyParser = multer({
limits: {
@ -55,6 +68,17 @@ router.post(
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
requestBody: "ModifyGuildStickerSchema",
responses: {
200: {
body: "Sticker",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
if (!req.file) throw new HTTPError("missing file");
@ -98,20 +122,46 @@ export function getStickerFormat(mime_type: string) {
}
}
router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
router.get(
"/:sticker_id",
route({
responses: {
200: {
body: "Sticker",
},
403: {
body: "APIErrorResponse",
},
},
}),
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 } }),
await Sticker.findOneOrFail({
where: { guild_id, id: sticker_id },
}),
);
},
);
});
router.patch(
"/:sticker_id",
route({
requestBody: "ModifyGuildStickerSchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
200: {
body: "Sticker",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
@ -141,7 +191,15 @@ async function sendStickerUpdateEvent(guild_id: string) {
router.delete(
"/:sticker_id",
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;

View File

@ -40,7 +40,16 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"icon",
];
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildTemplatesResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const templates = await Template.find({
@ -48,11 +57,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(templates);
});
},
);
router.post(
"/",
route({ requestBody: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "TemplateCreateSchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "Template",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -80,7 +107,13 @@ router.post(
router.delete(
"/:code",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
@ -95,7 +128,13 @@ router.delete(
router.put(
"/:code",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -114,7 +153,14 @@ router.put(
router.patch(
"/:code",
route({ requestBody: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "TemplateModifySchema",
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
const { name, description } = req.body;

View File

@ -33,7 +33,20 @@ const InviteRegex = /\W/g;
router.get(
"/",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: {
body: "GuildVanityUrlResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -60,7 +73,21 @@ router.get(
router.patch(
"/",
route({ requestBody: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "VanityUrlSchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "GuildVanityUrlCreateResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const body = req.body as VanityUrlSchema;

View File

@ -34,7 +34,21 @@ const router = Router();
router.patch(
"/",
route({ requestBody: "VoiceStateUpdateSchema" }),
route({
requestBody: "VoiceStateUpdateSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema;
const { guild_id } = req.params;

View File

@ -23,20 +23,42 @@ import { HTTPError } from "lambert-server";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWelcomeScreen",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(guild.welcome_screen);
});
},
);
router.patch(
"/",
route({
requestBody: "GuildUpdateWelcomeScreenSchema",
permission: "MANAGE_GUILD",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;

View File

@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Permissions, Guild, Invite, Channel, Member } from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { random, route } from "@spacebar/api";
import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
@ -32,7 +32,19 @@ const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget
// TODO: Cache the response for a guild for 5 minutes regardless of response
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWidgetJsonResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -103,6 +115,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.set("Cache-Control", "public, max-age=300");
return res.json(data);
});
},
);
export default router;

View File

@ -18,11 +18,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response, Router } from "express";
import { Guild } from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { Guild } from "@spacebar/util";
import { Request, Response, Router } from "express";
import fs from "fs";
import { HTTPError } from "lambert-server";
import path from "path";
const router: Router = Router();
@ -31,7 +31,20 @@ const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
// TODO: Cache the response
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -45,7 +58,9 @@ 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)
!["shield", "banner1", "banner2", "banner3", "banner4"].includes(
style,
)
) {
throw new HTTPError(
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
@ -96,7 +111,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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,
51,
"#FFFFFF",
"12px Verdana",
name,
22,
);
await drawText(
ctx,
83,
@ -108,7 +131,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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,
34,
"#FFFFFF",
"12px Verdana",
name,
15,
);
await drawText(
ctx,
62,
@ -120,7 +151,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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,
44,
"#FFFFFF",
"12px Verdana",
name,
27,
);
await drawText(
ctx,
83,
@ -132,7 +171,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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,
156,
"#FFFFFF",
"13px Verdana",
name,
27,
);
await drawText(
ctx,
84,
@ -154,7 +201,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.set("Content-Type", "image/png");
res.set("Cache-Control", "public, max-age=3600");
return res.send(buffer);
});
},
);
async function drawIcon(
canvas: any,

View File

@ -23,7 +23,19 @@ import { Request, Response, Router } from "express";
const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWidgetSettingsResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -32,12 +44,27 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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({ requestBody: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "WidgetModifySchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "WidgetModifySchema",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as WidgetModifySchema;
const { guild_id } = req.params;

View File

@ -33,7 +33,21 @@ const router: Router = Router();
router.post(
"/",
route({ requestBody: "GuildCreateSchema", right: "CREATE_GUILDS" }),
route({
requestBody: "GuildCreateSchema",
right: "CREATE_GUILDS",
responses: {
201: {
body: "GuildCreateResponse",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as GuildCreateSchema;

View File

@ -31,13 +31,29 @@ import { Request, Response, Router } from "express";
import fetch from "node-fetch";
const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
router.get(
"/:code",
route({
responses: {
200: {
body: "GuildTemplate",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { allowDiscordTemplates, allowRaws, enabled } =
Config.get().templates;
if (!enabled)
res.json({
code: 403,
message: "Template creation & usage is disabled on this instance.",
message:
"Template creation & usage is disabled on this instance.",
}).sendStatus(403);
const { code } = req.params;
@ -75,9 +91,12 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
return res.json(code.split("external:", 2)[1]);
}
const template = await Template.findOneOrFail({ where: { code: code } });
res.json(template);
const template = await Template.findOneOrFail({
where: { code: code },
});
res.json(template);
},
);
router.post(
"/:code",

View File

@ -24,7 +24,7 @@ import {
OneToMany,
RelationId,
} from "typeorm";
import { Config, handleFile, Snowflake } from "..";
import { Config, GuildWelcomeScreen, handleFile, Snowflake } from "..";
import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
@ -270,16 +270,7 @@ export class Guild extends BaseClass {
verification_level?: number;
@Column({ type: "simple-json" })
welcome_screen: {
enabled: boolean;
description: string;
welcome_channels: {
description: string;
emoji_id?: string;
emoji_name?: string;
channel_id: string;
}[];
};
welcome_screen: GuildWelcomeScreen;
@Column({ nullable: true })
@RelationId((guild: Guild) => guild.widget_channel)

View File

@ -0,0 +1,10 @@
export interface GuildWelcomeScreen {
enabled: boolean;
description: string;
welcome_channels: {
description: string;
emoji_id?: string;
emoji_name?: string;
channel_id: string;
}[];
}

View File

@ -19,6 +19,7 @@
export * from "./Activity";
export * from "./ConnectedAccount";
export * from "./Event";
export * from "./GuildWelcomeScreen";
export * from "./Interaction";
export * from "./Presence";
export * from "./Status";

View File

@ -0,0 +1,10 @@
export interface GuildBansResponse {
reason: string;
user: {
username: string;
discriminator: string;
id: string;
avatar: string | null;
public_flags: number;
};
}

View File

@ -0,0 +1,3 @@
import { Channel } from "../../entities";
export type GuildChannelsResponse = Channel[];

View File

@ -0,0 +1,3 @@
export interface GuildCreateResponse {
id: string;
}

View File

@ -0,0 +1,23 @@
export interface GuildDiscoveryRequirements {
uild_id: string;
safe_environment: boolean;
healthy: boolean;
health_score_pending: boolean;
size: boolean;
nsfw_properties: unknown;
protected: boolean;
sufficient: boolean;
sufficient_without_grace_period: boolean;
valid_rules_channel: boolean;
retention_healthy: boolean;
engagement_healthy: boolean;
age: boolean;
minimum_age: number;
health_score: {
avg_nonnew_participators: number;
avg_nonnew_communicators: number;
num_intentful_joiners: number;
perc_ret_w1_intentful: number;
};
minimum_size: number;
}

View File

@ -0,0 +1,3 @@
import { Emoji } from "../../entities";
export type GuildEmojisResponse = Emoji[];

View File

@ -0,0 +1,3 @@
import { Invite } from "../../entities";
export type GuildInvitesResponse = Invite[];

View File

@ -0,0 +1,3 @@
import { Member } from "../../entities";
export type GuildMembersResponse = Member[];

View File

@ -0,0 +1,32 @@
import {
Attachment,
Embed,
MessageType,
PublicUser,
Role,
} from "../../entities";
export interface GuildMessagesSearchMessage {
id: string;
type: MessageType;
content?: string;
channel_id: string;
author: PublicUser;
attachments: Attachment[];
embeds: Embed[];
mentions: PublicUser[];
mention_roles: Role[];
pinned: boolean;
mention_everyone?: boolean;
tts: boolean;
timestamp: string;
edited_timestamp: string | null;
flags: number;
components: unknown[];
hit: true;
}
export interface GuildMessagesSearchResponse {
messages: GuildMessagesSearchMessage[];
total_results: number;
}

View File

@ -0,0 +1,7 @@
export interface GuildPruneResponse {
pruned: number;
}
export interface GuildPurgeResponse {
purged: number;
}

View File

@ -0,0 +1,3 @@
import { Guild } from "../../entities";
export type GuildResponse = Guild & { joined_at: string };

View File

@ -0,0 +1,3 @@
import { Role } from "../../entities";
export type GuildRolesResponse = Role[];

View File

@ -0,0 +1,3 @@
import { Sticker } from "../../entities";
export type GuildStickersResponse = Sticker[];

View File

@ -0,0 +1,3 @@
import { Template } from "../../entities";
export type GuildTemplatesResponse = Template[];

View File

@ -0,0 +1,17 @@
export interface GuildVanityUrl {
code: string;
uses: number;
}
export interface GuildVanityUrlNoInvite {
code: null;
}
export type GuildVanityUrlResponse =
| GuildVanityUrl
| GuildVanityUrl[]
| GuildVanityUrlNoInvite;
export interface GuildVanityUrlCreateResponse {
code: string;
}

View File

@ -0,0 +1,9 @@
export interface GuildVoiceRegion {
id: string;
name: string;
custom: boolean;
deprecated: boolean;
optimal: boolean;
}
export type GuildVoiceRegionsResponse = GuildVoiceRegion[];

View File

@ -0,0 +1,21 @@
import { ClientStatus } from "../../interfaces";
export interface GuildWidgetJsonResponse {
id: string;
name: string;
instant_invite: string;
channels: {
id: string;
name: string;
position: number;
}[];
members: {
id: string;
username: string;
discriminator: string;
avatar: string | null;
status: ClientStatus;
avatar_url: string;
}[];
presence_count: number;
}

View File

@ -0,0 +1,6 @@
import { Snowflake } from "../../util";
export interface GuildWidgetSettingsResponse {
enabled: boolean;
channel_id: Snowflake | null;
}

View File

@ -0,0 +1,8 @@
import { Emoji, Guild, Role, Sticker } from "../../entities";
export interface MemberJoinGuildResponse {
guild: Guild;
emojis: Emoji[];
roles: Role[];
stickers: Sticker[];
}

View File

@ -12,7 +12,25 @@ export * from "./ChannelWebhooksResponse";
export * from "./GatewayBotResponse";
export * from "./GatewayResponse";
export * from "./GenerateRegistrationTokensResponse";
export * from "./GuildBansResponse";
export * from "./GuildChannelsResponse";
export * from "./GuildCreateResponse";
export * from "./GuildDiscoveryRequirements";
export * from "./GuildEmojisResponse";
export * from "./GuildInvitesResponse";
export * from "./GuildMembersResponse";
export * from "./GuildMessagesSearchResponse";
export * from "./GuildPruneResponse";
export * from "./GuildResponse";
export * from "./GuildRolesResponse";
export * from "./GuildStickersResponse";
export * from "./GuildTemplatesResponse";
export * from "./GuildVanityUrl";
export * from "./GuildVoiceRegionsResponse";
export * from "./GuildWidgetJsonResponse";
export * from "./GuildWidgetSettingsResponse";
export * from "./LocationMetadataResponse";
export * from "./MemberJoinGuildResponse";
export * from "./Tenor";
export * from "./TokenResponse";
export * from "./UserProfileResponse";