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 94174 additions and 4585 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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 } });
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 } });
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";