mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-10 04:32:35 +01:00
guilds
This commit is contained in:
parent
10e1eb95ae
commit
c2ce88dee7
3071
assets/openapi.json
3071
assets/openapi.json
File diff suppressed because it is too large
Load Diff
93495
assets/schemas.json
93495
assets/schemas.json
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
|
@ -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) => {
|
||||
const { guild_id } = req.params;
|
||||
const channels = await Channel.find({ where: { guild_id } });
|
||||
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);
|
||||
});
|
||||
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
|
||||
|
@ -16,37 +16,51 @@
|
||||
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) => {
|
||||
const { 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);
|
||||
|
||||
await Promise.all([
|
||||
Guild.delete({ id: guild_id }), // this will also delete all guild related data
|
||||
emitEvent({
|
||||
event: "GUILD_DELETE",
|
||||
data: {
|
||||
id: guild_id,
|
||||
router.post(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
204: {},
|
||||
401: {
|
||||
body: "APIErrorResponse",
|
||||
},
|
||||
guild_id: guild_id,
|
||||
} as GuildDeleteEvent),
|
||||
]);
|
||||
404: {
|
||||
body: "APIErrorResponse",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
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,
|
||||
},
|
||||
guild_id: guild_id,
|
||||
} as GuildDeleteEvent),
|
||||
]);
|
||||
|
||||
return res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -16,40 +16,50 @@
|
||||
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) => {
|
||||
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,
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: {
|
||||
body: "GuildDiscoveryRequirements",
|
||||
},
|
||||
},
|
||||
minimum_size: 0,
|
||||
});
|
||||
});
|
||||
}),
|
||||
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
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -34,37 +34,77 @@ import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
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);
|
||||
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);
|
||||
});
|
||||
return res.json(emojis);
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id, emoji_id } = req.params;
|
||||
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);
|
||||
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);
|
||||
});
|
||||
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;
|
||||
|
||||
|
@ -34,28 +34,61 @@ import { HTTPError } from "lambert-server";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
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([
|
||||
Guild.findOneOrFail({ where: { id: guild_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,
|
||||
);
|
||||
const [guild, member] = await Promise.all([
|
||||
Guild.findOneOrFail({ where: { id: guild_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,
|
||||
);
|
||||
|
||||
return res.send({
|
||||
...guild,
|
||||
joined_at: member?.joined_at,
|
||||
});
|
||||
});
|
||||
return res.send({
|
||||
...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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) => {
|
||||
// TODO: member verification
|
||||
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,
|
||||
});
|
||||
});
|
||||
res.status(404).json({
|
||||
message: "Unknown Guild Member Verification Form",
|
||||
code: 10068,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -34,20 +34,52 @@ import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id, member_id } = req.params;
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
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,54 +151,81 @@ router.patch(
|
||||
},
|
||||
);
|
||||
|
||||
router.put("/", route({}), async (req: Request, res: Response) => {
|
||||
// TODO: Lurker mode
|
||||
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);
|
||||
const rights = await getRights(req.user_id);
|
||||
|
||||
const { guild_id } = req.params;
|
||||
let { member_id } = req.params;
|
||||
if (member_id === "@me") {
|
||||
member_id = req.user_id;
|
||||
rights.hasThrow("JOIN_GUILDS");
|
||||
} else {
|
||||
// TODO: join others by controller
|
||||
}
|
||||
const { guild_id } = req.params;
|
||||
let { member_id } = req.params;
|
||||
if (member_id === "@me") {
|
||||
member_id = req.user_id;
|
||||
rights.hasThrow("JOIN_GUILDS");
|
||||
} else {
|
||||
// TODO: join others by controller
|
||||
}
|
||||
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: { id: guild_id },
|
||||
});
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: { id: guild_id },
|
||||
});
|
||||
|
||||
const emoji = await Emoji.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
const emoji = await Emoji.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
|
||||
const roles = await Role.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
const roles = await Role.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
|
||||
const stickers = await Sticker.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
const stickers = await Sticker.find({
|
||||
where: { guild_id: guild_id },
|
||||
});
|
||||
|
||||
await Member.addToGuild(member_id, guild_id);
|
||||
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
|
||||
});
|
||||
await Member.addToGuild(member_id, guild_id);
|
||||
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
|
||||
},
|
||||
);
|
||||
|
||||
router.delete("/", route({}), 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);
|
||||
if (member_id === "@me" || member_id === req.user_id) {
|
||||
// TODO: unless force-joined
|
||||
rights.hasThrow("SELF_LEAVE_GROUPS");
|
||||
} else {
|
||||
rights.hasThrow("KICK_BAN_MEMBERS");
|
||||
permission.hasThrow("KICK_MEMBERS");
|
||||
}
|
||||
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);
|
||||
if (member_id === "@me" || member_id === req.user_id) {
|
||||
// TODO: unless force-joined
|
||||
rights.hasThrow("SELF_LEAVE_GROUPS");
|
||||
} else {
|
||||
rights.hasThrow("KICK_BAN_MEMBERS");
|
||||
permission.hasThrow("KICK_MEMBERS");
|
||||
}
|
||||
|
||||
await Member.removeFromGuild(member_id, guild_id);
|
||||
res.sendStatus(204);
|
||||
});
|
||||
await Member.removeFromGuild(member_id, guild_id);
|
||||
res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
||||
|
@ -16,35 +16,58 @@
|
||||
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) => {
|
||||
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");
|
||||
const after = `${req.query.after}`;
|
||||
const query = after ? { id: MoreThan(after) } : {};
|
||||
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)
|
||||
throw new HTTPError("Limit must be between 1 and 1000");
|
||||
const after = `${req.query.after}`;
|
||||
const query = after ? { id: MoreThan(after) } : {};
|
||||
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
|
||||
const members = await Member.find({
|
||||
where: { guild_id, ...query },
|
||||
select: PublicMemberProjection,
|
||||
take: limit,
|
||||
order: { id: "ASC" },
|
||||
});
|
||||
const members = await Member.find({
|
||||
where: { guild_id, ...query },
|
||||
select: PublicMemberProjection,
|
||||
take: limit,
|
||||
order: { id: "ASC" },
|
||||
});
|
||||
|
||||
return res.json(members);
|
||||
});
|
||||
return res.json(members);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -18,140 +18,159 @@
|
||||
|
||||
/* 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) => {
|
||||
const {
|
||||
channel_id,
|
||||
content,
|
||||
// include_nsfw, // TODO
|
||||
offset,
|
||||
sort_order,
|
||||
// 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 (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
|
||||
}
|
||||
|
||||
const permissions = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel_id as string | undefined,
|
||||
);
|
||||
permissions.hasThrow("VIEW_CHANNEL");
|
||||
if (!permissions.has("READ_MESSAGE_HISTORY"))
|
||||
return res.json({ messages: [], total_results: 0 });
|
||||
|
||||
const query: FindManyOptions<Message> = {
|
||||
order: {
|
||||
timestamp: sort_order
|
||||
? (sort_order.toUpperCase() as "ASC" | "DESC")
|
||||
: "DESC",
|
||||
},
|
||||
take: parsedLimit || 0,
|
||||
where: {
|
||||
guild: {
|
||||
id: req.params.guild_id,
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: {
|
||||
body: "GuildMessagesSearchResponse",
|
||||
},
|
||||
403: {
|
||||
body: "APIErrorResponse",
|
||||
},
|
||||
422: {
|
||||
body: "APIErrorResponse",
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
"author",
|
||||
"webhook",
|
||||
"application",
|
||||
"mentions",
|
||||
"mention_roles",
|
||||
"mention_channels",
|
||||
"sticker_items",
|
||||
"attachments",
|
||||
],
|
||||
skip: offset ? Number(offset) : 0,
|
||||
};
|
||||
//@ts-ignore
|
||||
if (channel_id) query.where.channel = { id: channel_id };
|
||||
else {
|
||||
// get all channel IDs that this user can access
|
||||
const channels = await Channel.find({
|
||||
where: { guild_id: req.params.guild_id },
|
||||
select: ["id"],
|
||||
});
|
||||
const ids = [];
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const {
|
||||
channel_id,
|
||||
content,
|
||||
// include_nsfw, // TODO
|
||||
offset,
|
||||
sort_order,
|
||||
// sort_by, // TODO: Handle 'relevance'
|
||||
limit,
|
||||
author_id,
|
||||
} = req.query;
|
||||
|
||||
for (const channel of channels) {
|
||||
const perm = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel.id,
|
||||
);
|
||||
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
|
||||
continue;
|
||||
ids.push(channel.id);
|
||||
const parsedLimit = Number(limit) || 50;
|
||||
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
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
query.where.channel = { id: In(ids) };
|
||||
}
|
||||
//@ts-ignore
|
||||
if (author_id) query.where.author = { id: author_id };
|
||||
//@ts-ignore
|
||||
if (content) query.where.content = Like(`%${content}%`);
|
||||
const permissions = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel_id as string | undefined,
|
||||
);
|
||||
permissions.hasThrow("VIEW_CHANNEL");
|
||||
if (!permissions.has("READ_MESSAGE_HISTORY"))
|
||||
return res.json({ messages: [], total_results: 0 });
|
||||
|
||||
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 query: FindManyOptions<Message> = {
|
||||
order: {
|
||||
timestamp: sort_order
|
||||
? (sort_order.toUpperCase() as "ASC" | "DESC")
|
||||
: "DESC",
|
||||
},
|
||||
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,
|
||||
},
|
||||
]);
|
||||
take: parsedLimit || 0,
|
||||
where: {
|
||||
guild: {
|
||||
id: req.params.guild_id,
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
"author",
|
||||
"webhook",
|
||||
"application",
|
||||
"mentions",
|
||||
"mention_roles",
|
||||
"mention_channels",
|
||||
"sticker_items",
|
||||
"attachments",
|
||||
],
|
||||
skip: offset ? Number(offset) : 0,
|
||||
};
|
||||
//@ts-ignore
|
||||
if (channel_id) query.where.channel = { id: channel_id };
|
||||
else {
|
||||
// get all channel IDs that this user can access
|
||||
const channels = await Channel.find({
|
||||
where: { guild_id: req.params.guild_id },
|
||||
select: ["id"],
|
||||
});
|
||||
const ids = [];
|
||||
|
||||
return res.json({
|
||||
messages: messagesDto,
|
||||
total_results: messages.length,
|
||||
});
|
||||
});
|
||||
for (const channel of channels) {
|
||||
const perm = await getPermission(
|
||||
req.user_id,
|
||||
req.params.guild_id,
|
||||
channel.id,
|
||||
);
|
||||
if (
|
||||
!perm.has("VIEW_CHANNEL") ||
|
||||
!perm.has("READ_MESSAGE_HISTORY")
|
||||
)
|
||||
continue;
|
||||
ids.push(channel.id);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
query.where.channel = { id: In(ids) };
|
||||
}
|
||||
//@ts-ignore
|
||||
if (author_id) query.where.author = { id: author_id };
|
||||
//@ts-ignore
|
||||
if (content) query.where.content = Like(`%${content}%`);
|
||||
|
||||
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,
|
||||
},
|
||||
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,
|
||||
total_results: messages.length,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -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 =
|
||||
|
@ -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,25 +80,46 @@ export const inactiveMembers = async (
|
||||
return members;
|
||||
};
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const days = parseInt(req.query.days as string);
|
||||
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;
|
||||
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
|
||||
let 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 });
|
||||
});
|
||||
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);
|
||||
|
||||
|
@ -16,22 +16,35 @@
|
||||
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) => {
|
||||
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"),
|
||||
),
|
||||
);
|
||||
});
|
||||
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
|
||||
return res.json(
|
||||
await getVoiceRegions(
|
||||
getIpAdress(req),
|
||||
guild.features.includes("VIP_REGIONS"),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -31,16 +31,48 @@ import { HTTPError } from "lambert-server";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", route({}), 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);
|
||||
});
|
||||
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);
|
||||
},
|
||||
);
|
||||
|
||||
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;
|
||||
|
@ -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 }),
|
||||
|
@ -33,12 +33,25 @@ import { HTTPError } from "lambert-server";
|
||||
import multer from "multer";
|
||||
const router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
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 } }));
|
||||
});
|
||||
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) => {
|
||||
const { guild_id, sticker_id } = req.params;
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
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 } }),
|
||||
);
|
||||
});
|
||||
res.json(
|
||||
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;
|
||||
|
||||
|
@ -40,19 +40,46 @@ const TemplateGuildProjection: (keyof Guild)[] = [
|
||||
"icon",
|
||||
];
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
responses: {
|
||||
200: {
|
||||
body: "GuildTemplatesResponse",
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
|
||||
const templates = await Template.find({
|
||||
where: { source_guild_id: guild_id },
|
||||
});
|
||||
const templates = await Template.find({
|
||||
where: { source_guild_id: guild_id },
|
||||
});
|
||||
|
||||
return res.json(templates);
|
||||
});
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -23,20 +23,42 @@ import { HTTPError } from "lambert-server";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||
const guild_id = req.params.guild_id;
|
||||
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);
|
||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||
|
||||
res.json(guild.welcome_screen);
|
||||
});
|
||||
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;
|
||||
|
@ -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,77 +32,90 @@ 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) => {
|
||||
const { guild_id } = req.params;
|
||||
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 } });
|
||||
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
|
||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
|
||||
|
||||
// Fetch existing widget invite for widget channel
|
||||
let invite = await Invite.findOne({
|
||||
where: { channel_id: guild.widget_channel_id },
|
||||
});
|
||||
// Fetch existing widget invite for widget channel
|
||||
let invite = await Invite.findOne({
|
||||
where: { channel_id: guild.widget_channel_id },
|
||||
});
|
||||
|
||||
if (guild.widget_channel_id && !invite) {
|
||||
// Create invite for channel if none exists
|
||||
// TODO: Refactor invite create code to a shared function
|
||||
const max_age = 86400; // 24 hours
|
||||
const expires_at = new Date(max_age * 1000 + Date.now());
|
||||
if (guild.widget_channel_id && !invite) {
|
||||
// Create invite for channel if none exists
|
||||
// TODO: Refactor invite create code to a shared function
|
||||
const max_age = 86400; // 24 hours
|
||||
const expires_at = new Date(max_age * 1000 + Date.now());
|
||||
|
||||
invite = await Invite.create({
|
||||
code: random(),
|
||||
temporary: false,
|
||||
uses: 0,
|
||||
max_uses: 0,
|
||||
max_age: max_age,
|
||||
expires_at,
|
||||
created_at: new Date(),
|
||||
guild_id,
|
||||
channel_id: guild.widget_channel_id,
|
||||
}).save();
|
||||
}
|
||||
|
||||
// Fetch voice channels, and the @everyone permissions object
|
||||
const channels: { id: string; name: string; position: number }[] = [];
|
||||
|
||||
(
|
||||
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
|
||||
) {
|
||||
channels.push({
|
||||
id: doc.id,
|
||||
name: doc.name ?? "Unknown channel",
|
||||
position: doc.position ?? 0,
|
||||
});
|
||||
invite = await Invite.create({
|
||||
code: random(),
|
||||
temporary: false,
|
||||
uses: 0,
|
||||
max_uses: 0,
|
||||
max_age: max_age,
|
||||
expires_at,
|
||||
created_at: new Date(),
|
||||
guild_id,
|
||||
channel_id: guild.widget_channel_id,
|
||||
}).save();
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch members
|
||||
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
|
||||
const members = await Member.find({ where: { guild_id: guild_id } });
|
||||
// Fetch voice channels, and the @everyone permissions object
|
||||
const channels: { id: string; name: string; position: number }[] = [];
|
||||
|
||||
// Construct object to respond with
|
||||
const data = {
|
||||
id: guild_id,
|
||||
name: guild.name,
|
||||
instant_invite: invite?.code,
|
||||
channels: channels,
|
||||
members: members,
|
||||
presence_count: guild.presence_count,
|
||||
};
|
||||
(
|
||||
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
|
||||
) {
|
||||
channels.push({
|
||||
id: doc.id,
|
||||
name: doc.name ?? "Unknown channel",
|
||||
position: doc.position ?? 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
res.set("Cache-Control", "public, max-age=300");
|
||||
return res.json(data);
|
||||
});
|
||||
// Fetch members
|
||||
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
|
||||
const members = await Member.find({ where: { guild_id: guild_id } });
|
||||
|
||||
// Construct object to respond with
|
||||
const data = {
|
||||
id: guild_id,
|
||||
name: guild.name,
|
||||
instant_invite: invite?.code,
|
||||
channels: channels,
|
||||
members: members,
|
||||
presence_count: guild.presence_count,
|
||||
};
|
||||
|
||||
res.set("Cache-Control", "public, max-age=300");
|
||||
return res.json(data);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -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,130 +31,178 @@ 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) => {
|
||||
const { guild_id } = req.params;
|
||||
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 } });
|
||||
if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
|
||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||
if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
|
||||
|
||||
// Fetch guild information
|
||||
const icon = guild.icon;
|
||||
const name = guild.name;
|
||||
const presence = guild.presence_count + " ONLINE";
|
||||
// Fetch guild information
|
||||
const icon = guild.icon;
|
||||
const name = guild.name;
|
||||
const presence = guild.presence_count + " ONLINE";
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
// Setup canvas
|
||||
const { createCanvas } = require("canvas");
|
||||
const { loadImage } = require("canvas");
|
||||
const sizeOf = require("image-size");
|
||||
|
||||
// TODO: Widget style templates need Spacebar branding
|
||||
const source = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"widget",
|
||||
`${style}.png`,
|
||||
);
|
||||
if (!fs.existsSync(source)) {
|
||||
throw new HTTPError("Widget template does not exist.", 400);
|
||||
}
|
||||
|
||||
// Create base template image for parameter
|
||||
const { width, height } = await sizeOf(source);
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const template = await loadImage(source);
|
||||
ctx.drawImage(template, 0, 0);
|
||||
|
||||
// Add the guild specific information to the template asset image
|
||||
switch (style) {
|
||||
case "shield":
|
||||
ctx.textAlign = "center";
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Return final image
|
||||
const buffer = canvas.toBuffer("image/png");
|
||||
res.set("Content-Type", "image/png");
|
||||
res.set("Cache-Control", "public, max-age=3600");
|
||||
return res.send(buffer);
|
||||
});
|
||||
// Setup canvas
|
||||
const { createCanvas } = require("canvas");
|
||||
const { loadImage } = require("canvas");
|
||||
const sizeOf = require("image-size");
|
||||
|
||||
// TODO: Widget style templates need Spacebar branding
|
||||
const source = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"assets",
|
||||
"widget",
|
||||
`${style}.png`,
|
||||
);
|
||||
if (!fs.existsSync(source)) {
|
||||
throw new HTTPError("Widget template does not exist.", 400);
|
||||
}
|
||||
|
||||
// Create base template image for parameter
|
||||
const { width, height } = await sizeOf(source);
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const template = await loadImage(source);
|
||||
ctx.drawImage(template, 0, 0);
|
||||
|
||||
// Add the guild specific information to the template asset image
|
||||
switch (style) {
|
||||
case "shield":
|
||||
ctx.textAlign = "center";
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new HTTPError(
|
||||
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
// Return final image
|
||||
const buffer = canvas.toBuffer("image/png");
|
||||
res.set("Content-Type", "image/png");
|
||||
res.set("Cache-Control", "public, max-age=3600");
|
||||
return res.send(buffer);
|
||||
},
|
||||
);
|
||||
|
||||
async function drawIcon(
|
||||
canvas: any,
|
||||
|
@ -23,21 +23,48 @@ 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) => {
|
||||
const { guild_id } = req.params;
|
||||
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 } });
|
||||
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({ 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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -31,53 +31,72 @@ import { Request, Response, Router } from "express";
|
||||
import fetch from "node-fetch";
|
||||
const router: Router = Router();
|
||||
|
||||
router.get("/:code", route({}), 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.",
|
||||
}).sendStatus(403);
|
||||
|
||||
const { code } = req.params;
|
||||
|
||||
if (code.startsWith("discord:")) {
|
||||
if (!allowDiscordTemplates)
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message:
|
||||
"Discord templates cannot be used on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
const discordTemplateID = code.split("discord:", 2)[1];
|
||||
|
||||
const discordTemplateData = await fetch(
|
||||
`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
|
||||
{
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
router.get(
|
||||
"/:code",
|
||||
route({
|
||||
responses: {
|
||||
200: {
|
||||
body: "GuildTemplate",
|
||||
},
|
||||
);
|
||||
return res.json(await discordTemplateData.json());
|
||||
}
|
||||
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.",
|
||||
}).sendStatus(403);
|
||||
|
||||
if (code.startsWith("external:")) {
|
||||
if (!allowRaws)
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message: "Importing raws is disabled on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
const { code } = req.params;
|
||||
|
||||
return res.json(code.split("external:", 2)[1]);
|
||||
}
|
||||
if (code.startsWith("discord:")) {
|
||||
if (!allowDiscordTemplates)
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message:
|
||||
"Discord templates cannot be used on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
const discordTemplateID = code.split("discord:", 2)[1];
|
||||
|
||||
const template = await Template.findOneOrFail({ where: { code: code } });
|
||||
res.json(template);
|
||||
});
|
||||
const discordTemplateData = await fetch(
|
||||
`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
|
||||
{
|
||||
method: "get",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
return res.json(await discordTemplateData.json());
|
||||
}
|
||||
|
||||
if (code.startsWith("external:")) {
|
||||
if (!allowRaws)
|
||||
return res
|
||||
.json({
|
||||
code: 403,
|
||||
message: "Importing raws is disabled on this instance.",
|
||||
})
|
||||
.sendStatus(403);
|
||||
|
||||
return res.json(code.split("external:", 2)[1]);
|
||||
}
|
||||
|
||||
const template = await Template.findOneOrFail({
|
||||
where: { code: code },
|
||||
});
|
||||
res.json(template);
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:code",
|
||||
|
@ -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)
|
||||
|
10
src/util/interfaces/GuildWelcomeScreen.ts
Normal file
10
src/util/interfaces/GuildWelcomeScreen.ts
Normal 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;
|
||||
}[];
|
||||
}
|
@ -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";
|
||||
|
10
src/util/schemas/responses/GuildBansResponse.ts
Normal file
10
src/util/schemas/responses/GuildBansResponse.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface GuildBansResponse {
|
||||
reason: string;
|
||||
user: {
|
||||
username: string;
|
||||
discriminator: string;
|
||||
id: string;
|
||||
avatar: string | null;
|
||||
public_flags: number;
|
||||
};
|
||||
}
|
3
src/util/schemas/responses/GuildChannelsResponse.ts
Normal file
3
src/util/schemas/responses/GuildChannelsResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Channel } from "../../entities";
|
||||
|
||||
export type GuildChannelsResponse = Channel[];
|
3
src/util/schemas/responses/GuildCreateResponse.ts
Normal file
3
src/util/schemas/responses/GuildCreateResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface GuildCreateResponse {
|
||||
id: string;
|
||||
}
|
23
src/util/schemas/responses/GuildDiscoveryRequirements.ts
Normal file
23
src/util/schemas/responses/GuildDiscoveryRequirements.ts
Normal 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;
|
||||
}
|
3
src/util/schemas/responses/GuildEmojisResponse.ts
Normal file
3
src/util/schemas/responses/GuildEmojisResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Emoji } from "../../entities";
|
||||
|
||||
export type GuildEmojisResponse = Emoji[];
|
3
src/util/schemas/responses/GuildInvitesResponse.ts
Normal file
3
src/util/schemas/responses/GuildInvitesResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Invite } from "../../entities";
|
||||
|
||||
export type GuildInvitesResponse = Invite[];
|
3
src/util/schemas/responses/GuildMembersResponse.ts
Normal file
3
src/util/schemas/responses/GuildMembersResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Member } from "../../entities";
|
||||
|
||||
export type GuildMembersResponse = Member[];
|
32
src/util/schemas/responses/GuildMessagesSearchResponse.ts
Normal file
32
src/util/schemas/responses/GuildMessagesSearchResponse.ts
Normal 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;
|
||||
}
|
7
src/util/schemas/responses/GuildPruneResponse.ts
Normal file
7
src/util/schemas/responses/GuildPruneResponse.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface GuildPruneResponse {
|
||||
pruned: number;
|
||||
}
|
||||
|
||||
export interface GuildPurgeResponse {
|
||||
purged: number;
|
||||
}
|
3
src/util/schemas/responses/GuildResponse.ts
Normal file
3
src/util/schemas/responses/GuildResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Guild } from "../../entities";
|
||||
|
||||
export type GuildResponse = Guild & { joined_at: string };
|
3
src/util/schemas/responses/GuildRolesResponse.ts
Normal file
3
src/util/schemas/responses/GuildRolesResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Role } from "../../entities";
|
||||
|
||||
export type GuildRolesResponse = Role[];
|
3
src/util/schemas/responses/GuildStickersResponse.ts
Normal file
3
src/util/schemas/responses/GuildStickersResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Sticker } from "../../entities";
|
||||
|
||||
export type GuildStickersResponse = Sticker[];
|
3
src/util/schemas/responses/GuildTemplatesResponse.ts
Normal file
3
src/util/schemas/responses/GuildTemplatesResponse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Template } from "../../entities";
|
||||
|
||||
export type GuildTemplatesResponse = Template[];
|
17
src/util/schemas/responses/GuildVanityUrl.ts
Normal file
17
src/util/schemas/responses/GuildVanityUrl.ts
Normal 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;
|
||||
}
|
9
src/util/schemas/responses/GuildVoiceRegionsResponse.ts
Normal file
9
src/util/schemas/responses/GuildVoiceRegionsResponse.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface GuildVoiceRegion {
|
||||
id: string;
|
||||
name: string;
|
||||
custom: boolean;
|
||||
deprecated: boolean;
|
||||
optimal: boolean;
|
||||
}
|
||||
|
||||
export type GuildVoiceRegionsResponse = GuildVoiceRegion[];
|
21
src/util/schemas/responses/GuildWidgetJsonResponse.ts
Normal file
21
src/util/schemas/responses/GuildWidgetJsonResponse.ts
Normal 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;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { Snowflake } from "../../util";
|
||||
|
||||
export interface GuildWidgetSettingsResponse {
|
||||
enabled: boolean;
|
||||
channel_id: Snowflake | null;
|
||||
}
|
8
src/util/schemas/responses/MemberJoinGuildResponse.ts
Normal file
8
src/util/schemas/responses/MemberJoinGuildResponse.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Emoji, Guild, Role, Sticker } from "../../entities";
|
||||
|
||||
export interface MemberJoinGuildResponse {
|
||||
guild: Guild;
|
||||
emojis: Emoji[];
|
||||
roles: Role[];
|
||||
stickers: Sticker[];
|
||||
}
|
@ -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";
|
||||
|
Loading…
Reference in New Issue
Block a user