diff --git a/src/api/Server.ts b/src/api/Server.ts
index 0969e5e8..f81898e8 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -31,149 +31,149 @@ const PUBLIC_ASSETS_FOLDER = path.join(ASSETS_FOLDER, "public");
export type SpacebarServerOptions = ServerOptions;
declare global {
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- server: SpacebarServer;
- }
- }
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ server: SpacebarServer;
+ }
+ }
}
export class SpacebarServer extends Server {
- declare public options: SpacebarServerOptions;
+ declare public options: SpacebarServerOptions;
- constructor(opts?: Partial<SpacebarServerOptions>) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- super({ ...opts, errorHandler: false, jsonBody: false });
- }
+ constructor(opts?: Partial<SpacebarServerOptions>) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ super({ ...opts, errorHandler: false, jsonBody: false });
+ }
- async start() {
- await initDatabase();
- await Config.init();
- await initEvent();
- await Email.init();
- await ConnectionConfig.init();
- await initInstance();
- WebAuthn.init();
+ async start() {
+ await initDatabase();
+ await Config.init();
+ await initEvent();
+ await Email.init();
+ await ConnectionConfig.init();
+ await initInstance();
+ WebAuthn.init();
- const logRequests = process.env["LOG_REQUESTS"] != undefined;
- if (logRequests) {
- this.app.use(
- morgan("combined", {
- skip: (req, res) => {
- let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
- if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
- return skip;
- },
- }),
- );
- }
+ const logRequests = process.env["LOG_REQUESTS"] != undefined;
+ if (logRequests) {
+ this.app.use(
+ morgan("combined", {
+ skip: (req, res) => {
+ let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
+ if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
+ return skip;
+ },
+ }),
+ );
+ }
- this.app.set("json replacer", JSONReplacer);
- this.app.disable("x-powered-by");
+ this.app.set("json replacer", JSONReplacer);
+ this.app.disable("x-powered-by");
- const trustedProxies = Config.get().security.trustedProxies;
- if (trustedProxies) this.app.set("trust proxy", trustedProxies);
+ const trustedProxies = Config.get().security.trustedProxies;
+ if (trustedProxies) this.app.set("trust proxy", trustedProxies);
- this.app.use(CORS);
- this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
+ this.app.use(CORS);
+ this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
- const app = this.app;
- const api = Router({ mergeParams: true });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- this.app = api;
+ const app = this.app;
+ const api = Router({ mergeParams: true });
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.app = api;
- api.use(Authentication);
- await initRateLimits(api);
- await initTranslation(api);
+ api.use(Authentication);
+ await initRateLimits(api);
+ await initTranslation(api);
- this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r);
+ this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r);
- // 404 is not an error in express, so this should not be an error middleware
- // this is a fine place to put the 404 handler because its after we register the routes
- // and since its not an error middleware, our error handler below still works.
- // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now
- api.use("*_", (req: Request, res: Response) => {
- res.status(404).json({
- message: "404 endpoint not found",
- code: 0,
- });
- });
+ // 404 is not an error in express, so this should not be an error middleware
+ // this is a fine place to put the 404 handler because its after we register the routes
+ // and since its not an error middleware, our error handler below still works.
+ // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now
+ api.use("*_", (req: Request, res: Response) => {
+ res.status(404).json({
+ message: "404 endpoint not found",
+ code: 0,
+ });
+ });
- this.app = app;
+ this.app = app;
- //app.use("/__development", )
- //app.use("/__internals", )
- app.use("/api/v6", api);
- app.use("/api/v7", api);
- app.use("/api/v8", api);
- app.use("/api/v9", api);
- app.use("/api", api); // allow unversioned requests
+ //app.use("/__development", )
+ //app.use("/__internals", )
+ app.use("/api/v6", api);
+ app.use("/api/v7", api);
+ app.use("/api/v8", api);
+ app.use("/api/v9", api);
+ app.use("/api", api); // allow unversioned requests
- app.use("/imageproxy/:hash/:size/:url", ImageProxy);
+ app.use("/imageproxy/:hash/:size/:url", ImageProxy);
- app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
+ app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html")));
- app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html")));
+ app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html")));
- app.get("/_spacebar/api/schemas.json", (req, res) => {
- res.sendFile(path.join(ASSETS_FOLDER, "schemas.json"));
- });
+ app.get("/_spacebar/api/schemas.json", (req, res) => {
+ res.sendFile(path.join(ASSETS_FOLDER, "schemas.json"));
+ });
- app.get("/_spacebar/api/openapi.json", (req, res) => {
- res.sendFile(path.join(ASSETS_FOLDER, "openapi.json"));
- });
+ app.get("/_spacebar/api/openapi.json", (req, res) => {
+ res.sendFile(path.join(ASSETS_FOLDER, "openapi.json"));
+ });
- // current well-known location
- app.get("/.well-known/spacebar", (req, res) => {
- res.json({
- api: Config.get().api.endpointPublic,
- });
- });
+ // current well-known location
+ app.get("/.well-known/spacebar", (req, res) => {
+ res.json({
+ api: Config.get().api.endpointPublic,
+ });
+ });
- // new well-known location
- app.get("/.well-known/spacebar/client", (req, res) => {
- let erlpackSupported = false;
- try {
- require("@yukikaze-bot/erlpack");
- erlpackSupported = true;
- } catch (e) {
- // empty
- }
+ // new well-known location
+ app.get("/.well-known/spacebar/client", (req, res) => {
+ let erlpackSupported = false;
+ try {
+ require("@yukikaze-bot/erlpack");
+ erlpackSupported = true;
+ } catch (e) {
+ // empty
+ }
- res.json({
- api: {
- baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9
- apiVersions: {
- default: Config.get().api.defaultVersion,
- active: Config.get().api.activeVersions,
- },
- },
- cdn: {
- baseUrl: Config.get().cdn.endpointPublic,
- },
- gateway: {
- baseUrl: Config.get().gateway.endpointPublic,
- encoding: [...(erlpackSupported ? ["etf"] : []), "json"],
- compression: ["zstd-stream", "zlib-stream", null],
- },
- admin:
- Config.get().admin.endpointPublic === null
- ? undefined
- : {
- baseUrl: Config.get().admin.endpointPublic,
- },
- });
- });
+ res.json({
+ api: {
+ baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9
+ apiVersions: {
+ default: Config.get().api.defaultVersion,
+ active: Config.get().api.activeVersions,
+ },
+ },
+ cdn: {
+ baseUrl: Config.get().cdn.endpointPublic,
+ },
+ gateway: {
+ baseUrl: Config.get().gateway.endpointPublic,
+ encoding: [...(erlpackSupported ? ["etf"] : []), "json"],
+ compression: ["zstd-stream", "zlib-stream", null],
+ },
+ admin:
+ Config.get().admin.endpointPublic === null
+ ? undefined
+ : {
+ baseUrl: Config.get().admin.endpointPublic,
+ },
+ });
+ });
- this.app.use(ErrorHandler);
+ this.app.use(ErrorHandler);
- ConnectionLoader.loadConnections();
+ ConnectionLoader.loadConnections();
- if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`));
+ if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`));
- return super.start();
- }
+ return super.start();
+ }
}
diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 47f6ab37..078a566b 100644
--- a/src/api/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -21,112 +21,112 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";
export const NO_AUTHORIZATION_ROUTES = [
- // Authentication routes
- "POST /auth/login",
- "POST /auth/register",
- "GET /auth/location-metadata",
- "POST /auth/mfa/",
- "POST /auth/verify",
- "POST /auth/forgot",
- "POST /auth/reset",
- "POST /auth/fingerprint",
- "GET /invites/",
- // Routes with a seperate auth system
- /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
- /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/,
- // Public information endpoints
- "GET /ping",
- "GET /gateway",
- "GET /experiments",
- "GET /updates",
- "GET /download",
- "GET /scheduled-maintenances/upcoming.json",
- // Public kubernetes integration
- "GET /-/readyz",
- "GET /-/healthz",
- // Client analytics
- "POST /science",
- "POST /track",
- // Public policy pages
- "GET /policies/instance/",
- // Oauth callback
- "/oauth2/callback",
- // Asset delivery
- /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
- // Connections
- /^(POST|HEAD) \/connections\/\w+\/callback/,
- // Image proxy
- /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/,
+ // Authentication routes
+ "POST /auth/login",
+ "POST /auth/register",
+ "GET /auth/location-metadata",
+ "POST /auth/mfa/",
+ "POST /auth/verify",
+ "POST /auth/forgot",
+ "POST /auth/reset",
+ "POST /auth/fingerprint",
+ "GET /invites/",
+ // Routes with a seperate auth system
+ /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
+ /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/,
+ // Public information endpoints
+ "GET /ping",
+ "GET /gateway",
+ "GET /experiments",
+ "GET /updates",
+ "GET /download",
+ "GET /scheduled-maintenances/upcoming.json",
+ // Public kubernetes integration
+ "GET /-/readyz",
+ "GET /-/healthz",
+ // Client analytics
+ "POST /science",
+ "POST /track",
+ // Public policy pages
+ "GET /policies/instance/",
+ // Oauth callback
+ "/oauth2/callback",
+ // Asset delivery
+ /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/,
+ // Connections
+ /^(POST|HEAD) \/connections\/\w+\/callback/,
+ // Image proxy
+ /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/,
];
export const API_PREFIX = /^\/api(\/v\d+)?/;
export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//;
declare global {
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- user_id: string;
- user_bot: boolean;
- token: { id: string; iat: number; ver?: number; did?: string };
- rights: Rights;
- fingerprint?: string;
- }
- }
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ user_id: string;
+ user_bot: boolean;
+ token: { id: string; iat: number; ver?: number; did?: string };
+ rights: Rights;
+ fingerprint?: string;
+ }
+ }
}
export async function Authentication(req: Request, res: Response, next: NextFunction) {
- if (req.method === "OPTIONS") return res.sendStatus(204);
- const url = req.url.replace(API_PREFIX, "");
+ if (req.method === "OPTIONS") return res.sendStatus(204);
+ const url = req.url.replace(API_PREFIX, "");
- if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid=")))
- req.fingerprint = req.headers.cookie
- .split("; ")
- .find((x) => x.startsWith("__sb_sessid="))!
- .split("=")[1];
- // for some reason we need to require here, else the openapi generator fails with "route is not a function"
- else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`);
+ if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid=")))
+ req.fingerprint = req.headers.cookie
+ .split("; ")
+ .find((x) => x.startsWith("__sb_sessid="))!
+ .split("=")[1];
+ // for some reason we need to require here, else the openapi generator fails with "route is not a function"
+ else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`);
- if (
- NO_AUTHORIZATION_ROUTES.some((x) => {
- if (typeof x !== "string") {
- return x.test(req.method + " " + url);
- }
+ if (
+ NO_AUTHORIZATION_ROUTES.some((x) => {
+ if (typeof x !== "string") {
+ return x.test(req.method + " " + url);
+ }
- const fullRoute = req.method + " " + url;
+ const fullRoute = req.method + " " + url;
- if (req.method === "HEAD") {
- const urlPart = x.split(" ").slice(1).join(" ");
- if (urlPart.endsWith("/")) {
- return url.startsWith(urlPart);
- } else {
- return url === urlPart;
- }
- }
+ if (req.method === "HEAD") {
+ const urlPart = x.split(" ").slice(1).join(" ");
+ if (urlPart.endsWith("/")) {
+ return url.startsWith(urlPart);
+ } else {
+ return url === urlPart;
+ }
+ }
- if (x.endsWith("/")) {
- return fullRoute.startsWith(x);
- } else {
- return fullRoute === x;
- }
- })
- )
- return next();
+ if (x.endsWith("/")) {
+ return fullRoute.startsWith(x);
+ } else {
+ return fullRoute === x;
+ }
+ })
+ )
+ return next();
- if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
+ if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
- try {
- const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, {
- ipAddress: req.ip,
- fingerprint: req.fingerprint,
- });
+ try {
+ const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, {
+ ipAddress: req.ip,
+ fingerprint: req.fingerprint,
+ });
- req.token = decoded;
- req.user_id = decoded.id;
- req.user_bot = user.bot;
- req.rights = new Rights(Number(user.rights));
- return next();
- } catch (error) {
- return next(new HTTPError(error!.toString(), 400));
- }
+ req.token = decoded;
+ req.user_id = decoded.id;
+ req.user_bot = user.bot;
+ req.rights = new Rights(Number(user.rights));
+ return next();
+ } catch (error) {
+ return next(new HTTPError(error!.toString(), 400));
+ }
}
diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index e08aad49..a579abb3 100644
--- a/src/api/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -21,31 +21,31 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";
const errorMessages: { [key: string]: [string, number] } = {
- "entity.too.large": ["Request body too large", 413],
- "entity.parse.failed": ["Invalid JSON body", 400],
- "entity.verify.failed": ["Entity verification failed", 403],
- "request.aborted": ["Request aborted", 400],
- "request.size.invalid": ["Request size did not match content length", 400],
- "stream.encoding.set": ["Stream encoding should not be set", 500],
- "stream.not.readable": ["Stream is not readable", 500],
- "parameters.too.many": ["Too many parameters", 413],
- "charset.unsupported": ["Unsupported charset", 415],
- "encoding.unsupported": ["Unsupported content encoding", 415],
+ "entity.too.large": ["Request body too large", 413],
+ "entity.parse.failed": ["Invalid JSON body", 400],
+ "entity.verify.failed": ["Entity verification failed", 403],
+ "request.aborted": ["Request aborted", 400],
+ "request.size.invalid": ["Request size did not match content length", 400],
+ "stream.encoding.set": ["Stream encoding should not be set", 500],
+ "stream.not.readable": ["Stream is not readable", 500],
+ "parameters.too.many": ["Too many parameters", 413],
+ "charset.unsupported": ["Unsupported charset", 415],
+ "encoding.unsupported": ["Unsupported content encoding", 415],
};
export function BodyParser(opts?: OptionsJson) {
- const jsonParser = bodyParser.json(opts);
+ const jsonParser = bodyParser.json(opts);
- return (req: Request, res: Response, next: NextFunction) => {
- if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
+ return (req: Request, res: Response, next: NextFunction) => {
+ if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
- jsonParser(req, res, (err) => {
- if (err) {
- const [message, status] = errorMessages[err.type] || ["Invalid Body", 400];
- const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message;
- return next(new HTTPError(errorMessage, status));
- }
- next();
- });
- };
+ jsonParser(req, res, (err) => {
+ if (err) {
+ const [message, status] = errorMessages[err.type] || ["Invalid Body", 400];
+ const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message;
+ return next(new HTTPError(errorMessage, status));
+ }
+ next();
+ });
+ };
}
diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 34532d3f..23186250 100644
--- a/src/api/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
@@ -21,20 +21,20 @@ import { NextFunction, Request, Response } from "express";
// TODO: config settings
export function CORS(req: Request, res: Response, next: NextFunction) {
- res.set("Access-Control-Allow-Credentials", "true");
- res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
- res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*");
- res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*");
- res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically
- // TODO: use better CSP
- res.set(
- "Content-security-policy",
- "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
- );
+ res.set("Access-Control-Allow-Credentials", "true");
+ res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
+ res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*");
+ res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*");
+ res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically
+ // TODO: use better CSP
+ res.set(
+ "Content-security-policy",
+ "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
+ );
- if (req.method === "OPTIONS") {
- res.status(204).end();
- return;
- }
- next();
+ if (req.method === "OPTIONS") {
+ res.status(204).end();
+ return;
+ }
+ next();
}
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index cd306c9c..1a9a18a8 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -22,48 +22,48 @@ import { ApiError, FieldError } from "@spacebar/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) {
- if (!error) return next();
+ if (!error) return next();
- try {
- let code = 400;
- let httpcode = code;
- let message = error?.toString();
- let errors = undefined;
- let _ajvErrors = undefined;
+ try {
+ let code = 400;
+ let httpcode = code;
+ let message = error?.toString();
+ let errors = undefined;
+ let _ajvErrors = undefined;
- if (error instanceof HTTPError && error.code) code = httpcode = error.code;
- else if (error instanceof ApiError) {
- code = error.code;
- message = error.message;
- httpcode = error.httpStatus;
- } else if (error.name === "EntityNotFoundError") {
- message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
- code = httpcode = 404;
- } else if (error instanceof FieldError) {
- code = Number(error.code);
- message = error.message;
- errors = error.errors;
- _ajvErrors = error._ajvErrors;
- } else if (error?.type == "entity.parse.failed") {
- // body-parser failed
- httpcode = 400;
- code = 50109;
- message = "The request body contains invalid JSON.";
- } else {
- console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
+ if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+ else if (error instanceof ApiError) {
+ code = error.code;
+ message = error.message;
+ httpcode = error.httpStatus;
+ } else if (error.name === "EntityNotFoundError") {
+ message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
+ code = httpcode = 404;
+ } else if (error instanceof FieldError) {
+ code = Number(error.code);
+ message = error.message;
+ errors = error.errors;
+ _ajvErrors = error._ajvErrors;
+ } else if (error?.type == "entity.parse.failed") {
+ // body-parser failed
+ httpcode = 400;
+ code = 50109;
+ message = "The request body contains invalid JSON.";
+ } else {
+ console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
- if (req.server?.options?.production) {
- // don't expose internal errors to the user, instead human errors should be thrown as HTTPError
- message = "Internal Server Error";
- }
- code = httpcode = 500;
- }
+ if (req.server?.options?.production) {
+ // don't expose internal errors to the user, instead human errors should be thrown as HTTPError
+ message = "Internal Server Error";
+ }
+ code = httpcode = 500;
+ }
- if (httpcode > 511) httpcode = 400;
+ if (httpcode > 511) httpcode = 400;
- res.status(httpcode).json({ code: code, message, errors, _ajvErrors });
- } catch (error) {
- console.error(`[Internal Server Error] 500`, error);
- return res.status(500).json({ code: 500, message: "Internal Server Error" });
- }
+ res.status(httpcode).json({ code: code, message, errors, _ajvErrors });
+ } catch (error) {
+ console.error(`[Internal Server Error] 500`, error);
+ return res.status(500).json({ code: 500, message: "Internal Server Error" });
+ }
}
diff --git a/src/api/middlewares/ImageProxy.ts b/src/api/middlewares/ImageProxy.ts
index 78690129..e950ea85 100644
--- a/src/api/middlewares/ImageProxy.ts
+++ b/src/api/middlewares/ImageProxy.ts
@@ -25,9 +25,9 @@ let sharp: undefined | false | { default: typeof import("sharp") } = undefined;
let Jimp: JimpType | undefined = undefined;
try {
- Jimp = require("jimp") as JimpType;
+ Jimp = require("jimp") as JimpType;
} catch {
- // empty
+ // empty
}
let sentImageProxyWarning = false;
@@ -37,101 +37,101 @@ const jimpSupported = new Set(["image/jpeg", "image/png", "image/bmp", "image/ti
const resizeSupported = new Set([...sharpSupported, ...jimpSupported]);
export async function ImageProxy(req: Request, res: Response) {
- const path = req.originalUrl.split("/").slice(2);
+ const path = req.originalUrl.split("/").slice(2);
- // src/api/util/utility/EmbedHandlers.ts getProxyUrl
- const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
+ // src/api/util/utility/EmbedHandlers.ts getProxyUrl
+ const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
- try {
- if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature");
- } catch {
- console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]);
- res.status(403).send("Invalid signature");
- return;
- }
+ try {
+ if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature");
+ } catch {
+ console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]);
+ res.status(403).send("Invalid signature");
+ return;
+ }
- const abort = new AbortController();
- setTimeout(() => abort.abort(), 5000);
+ const abort = new AbortController();
+ setTimeout(() => abort.abort(), 5000);
- const request = await fetch("https://" + path.slice(2).join("/"), {
- headers: {
- "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)",
- },
- signal: abort.signal,
- }).catch((e) => {
- if (e.name === "AbortError") res.status(504).send("Request timed out");
- else res.status(500).send("Unable to proxy origin: " + e.message);
- });
- if (!request) return;
+ const request = await fetch("https://" + path.slice(2).join("/"), {
+ headers: {
+ "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)",
+ },
+ signal: abort.signal,
+ }).catch((e) => {
+ if (e.name === "AbortError") res.status(504).send("Request timed out");
+ else res.status(500).send("Unable to proxy origin: " + e.message);
+ });
+ if (!request) return;
- if (request.status !== 200) {
- res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText);
- return;
- }
+ if (request.status !== 200) {
+ res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText);
+ return;
+ }
- if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) {
- res.status(500).send("Origin did not provide a Content-Type or Content-Length header");
- return;
- }
+ if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) {
+ res.status(500).send("Origin did not provide a Content-Type or Content-Length header");
+ return;
+ }
- // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
- if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) {
- res.status(500).send("Origin provided a Content-Length header that is too large");
- return;
- }
+ // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
+ if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) {
+ res.status(500).send("Origin provided a Content-Length header that is too large");
+ return;
+ }
- // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
- let contentType: string = request.headers.get("Content-Type");
+ // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above)
+ let contentType: string = request.headers.get("Content-Type");
- const arrayBuffer = await request.arrayBuffer();
- let resultBuffer = Buffer.from(arrayBuffer);
+ const arrayBuffer = await request.arrayBuffer();
+ let resultBuffer = Buffer.from(arrayBuffer);
- if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) {
- if (sharp !== false) {
- try {
- sharp = await import("sharp");
- } catch {
- sharp = false;
- }
- }
+ if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) {
+ if (sharp !== false) {
+ try {
+ sharp = await import("sharp");
+ } catch {
+ sharp = false;
+ }
+ }
- if (sharp === false && !Jimp) {
- try {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore Typings don't fit
- Jimp = await import("jimp");
- } catch {
- sentImageProxyWarning = true;
- console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`);
- }
- }
+ if (sharp === false && !Jimp) {
+ try {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore Typings don't fit
+ Jimp = await import("jimp");
+ } catch {
+ sentImageProxyWarning = true;
+ console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`);
+ }
+ }
- const [width, height] = path[1].split("x").map((x) => parseInt(x));
+ const [width, height] = path[1].split("x").map((x) => parseInt(x));
- const buffer = Buffer.from(arrayBuffer);
- if (sharp && sharpSupported.has(contentType)) {
- resultBuffer = Buffer.from(
- await sharp
- .default(buffer)
- // Sharp doesn't support "scaleToFit"
- .resize(width)
- .toBuffer(),
- );
- } else if (Jimp && jimpSupported.has(contentType)) {
- resultBuffer = await Jimp.read(buffer).then((image) => {
- contentType = image.getMIME();
- return (
- image
- .scaleToFit(width, height)
- // @ts-expect-error Jimp is defined at this point
- .getBufferAsync(Jimp.AUTO)
- );
- });
- }
- }
+ const buffer = Buffer.from(arrayBuffer);
+ if (sharp && sharpSupported.has(contentType)) {
+ resultBuffer = Buffer.from(
+ await sharp
+ .default(buffer)
+ // Sharp doesn't support "scaleToFit"
+ .resize(width)
+ .toBuffer(),
+ );
+ } else if (Jimp && jimpSupported.has(contentType)) {
+ resultBuffer = await Jimp.read(buffer).then((image) => {
+ contentType = image.getMIME();
+ return (
+ image
+ .scaleToFit(width, height)
+ // @ts-expect-error Jimp is defined at this point
+ .getBufferAsync(Jimp.AUTO)
+ );
+ });
+ }
+ }
- res.header("Content-Type", contentType);
- res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds);
+ res.header("Content-Type", contentType);
+ res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds);
- res.send(resultBuffer);
+ res.send(resultBuffer);
}
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 1a41ad4f..c749df6a 100644
--- a/src/api/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -34,188 +34,188 @@ TODO: different for methods (GET/POST)
*/
type RateLimit = {
- id: "global" | "error" | string;
- executor_id: string;
- hits: number;
- blocked: boolean;
- expires_at: Date;
+ id: "global" | "error" | string;
+ executor_id: string;
+ hits: number;
+ blocked: boolean;
+ expires_at: Date;
};
const Cache = new Map<string, RateLimit>();
const EventRateLimit = "RATELIMIT";
export default function rateLimit(opts: {
- bucket?: string;
- window: number;
- count: number;
- bot?: number;
- webhook?: number;
- oauth?: number;
- GET?: number;
- MODIFY?: number;
- error?: boolean;
- success?: boolean;
- onlyIp?: boolean;
+ bucket?: string;
+ window: number;
+ count: number;
+ bot?: number;
+ webhook?: number;
+ oauth?: number;
+ GET?: number;
+ MODIFY?: number;
+ error?: boolean;
+ success?: boolean;
+ onlyIp?: boolean;
}) {
- return async (req: Request, res: Response, next: NextFunction) => {
- // exempt user? if so, immediately short circuit
- if (req.user_id) {
- const rights = await getRights(req.user_id);
- if (rights.has("BYPASS_RATE_LIMITS")) return next();
- }
+ return async (req: Request, res: Response, next: NextFunction) => {
+ // exempt user? if so, immediately short circuit
+ if (req.user_id) {
+ const rights = await getRights(req.user_id);
+ if (rights.has("BYPASS_RATE_LIMITS")) return next();
+ }
- const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
- let executor_id = req.ip || "127.0.0.1";
- if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
+ const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
+ let executor_id = req.ip || "127.0.0.1";
+ if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
- let max_hits = opts.count;
- if (opts.bot && req.user_bot) max_hits = opts.bot;
- if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
- else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
+ let max_hits = opts.count;
+ if (opts.bot && req.user_bot) max_hits = opts.bot;
+ if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
+ else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
- const offender = Cache.get(executor_id + bucket_id);
+ const offender = Cache.get(executor_id + bucket_id);
- res.set("X-RateLimit-Limit", `${max_hits}`)
- .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`)
- .set("X-RateLimit-Bucket", `${bucket_id}`)
- // assuming we aren't blocked, a new window will start after this request
- .set("X-RateLimit-Reset", `${Date.now() + opts.window}`)
- .set("X-RateLimit-Reset-After", `${opts.window}`);
+ res.set("X-RateLimit-Limit", `${max_hits}`)
+ .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`)
+ .set("X-RateLimit-Bucket", `${bucket_id}`)
+ // assuming we aren't blocked, a new window will start after this request
+ .set("X-RateLimit-Reset", `${Date.now() + opts.window}`)
+ .set("X-RateLimit-Reset-After", `${opts.window}`);
- if (offender) {
- let reset = offender.expires_at.getTime();
- let resetAfterMs = reset - Date.now();
- let resetAfterSec = Math.ceil(resetAfterMs / 1000);
+ if (offender) {
+ let reset = offender.expires_at.getTime();
+ let resetAfterMs = reset - Date.now();
+ let resetAfterSec = Math.ceil(resetAfterMs / 1000);
- if (resetAfterMs <= 0) {
- offender.hits = 0;
- offender.expires_at = new Date(Date.now() + opts.window * 1000);
- offender.blocked = false;
+ if (resetAfterMs <= 0) {
+ offender.hits = 0;
+ offender.expires_at = new Date(Date.now() + opts.window * 1000);
+ offender.blocked = false;
- Cache.delete(executor_id + bucket_id);
- }
+ Cache.delete(executor_id + bucket_id);
+ }
- res.set("X-RateLimit-Reset", `${reset}`);
- res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
+ res.set("X-RateLimit-Reset", `${reset}`);
+ res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
- if (offender.blocked) {
- const global = bucket_id === "global";
- // each block violation pushes the expiry one full window further
- reset += opts.window * 1000;
- offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
- resetAfterMs = reset - Date.now();
- resetAfterSec = Math.ceil(resetAfterMs / 1000);
+ if (offender.blocked) {
+ const global = bucket_id === "global";
+ // each block violation pushes the expiry one full window further
+ reset += opts.window * 1000;
+ offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
+ resetAfterMs = reset - Date.now();
+ resetAfterSec = Math.ceil(resetAfterMs / 1000);
- console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
- resetAfterMs,
- });
+ console.log(`blocked bucket: ${bucket_id} ${executor_id}`, {
+ resetAfterMs,
+ });
- if (global) res.set("X-RateLimit-Global", "true");
+ if (global) res.set("X-RateLimit-Global", "true");
- return (
- res
- .status(429)
- .set("X-RateLimit-Remaining", "0")
- .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
- // TODO: error rate limit message translation
- .send({
- message: "You are being rate limited.",
- retry_after: resetAfterSec,
- global,
- })
- );
- }
- }
+ return (
+ res
+ .status(429)
+ .set("X-RateLimit-Remaining", "0")
+ .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
+ // TODO: error rate limit message translation
+ .send({
+ message: "You are being rate limited.",
+ retry_after: resetAfterSec,
+ global,
+ })
+ );
+ }
+ }
- next();
- const hitRouteOpts = {
- bucket_id,
- executor_id,
- max_hits,
- window: opts.window,
- };
+ next();
+ const hitRouteOpts = {
+ bucket_id,
+ executor_id,
+ max_hits,
+ window: opts.window,
+ };
- if (opts.error || opts.success) {
- res.once("finish", () => {
- // check if error and increment error rate limit
- if (res.statusCode >= 400 && opts.error) {
- return hitRoute(hitRouteOpts);
- } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
- return hitRoute(hitRouteOpts);
- }
- });
- } else {
- return hitRoute(hitRouteOpts);
- }
- };
+ if (opts.error || opts.success) {
+ res.once("finish", () => {
+ // check if error and increment error rate limit
+ if (res.statusCode >= 400 && opts.error) {
+ return hitRoute(hitRouteOpts);
+ } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
+ return hitRoute(hitRouteOpts);
+ }
+ });
+ } else {
+ return hitRoute(hitRouteOpts);
+ }
+ };
}
export async function initRateLimits(app: Router) {
- const { routes, global, ip, error, enabled } = Config.get().limits.rate;
- if (!enabled) return;
- console.log("Enabling rate limits...");
- await listenEvent(EventRateLimit, (event) => {
- Cache.set(event.channel_id as string, event.data);
- event.acknowledge?.();
- });
- // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
- // const limits = await RateLimit.find({ blocked: true });
- // limits.forEach((limit) => {
- // Cache.set(limit.executor_id, limit);
- // });
+ const { routes, global, ip, error, enabled } = Config.get().limits.rate;
+ if (!enabled) return;
+ console.log("Enabling rate limits...");
+ await listenEvent(EventRateLimit, (event) => {
+ Cache.set(event.channel_id as string, event.data);
+ event.acknowledge?.();
+ });
+ // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date
+ // const limits = await RateLimit.find({ blocked: true });
+ // limits.forEach((limit) => {
+ // Cache.set(limit.executor_id, limit);
+ // });
- setInterval(() => {
- Cache.forEach((x, key) => {
- if (new Date() > x.expires_at) {
- Cache.delete(key);
- // RateLimit.delete({ executor_id: key });
- }
- });
- }, 1000 * 60);
+ setInterval(() => {
+ Cache.forEach((x, key) => {
+ if (new Date() > x.expires_at) {
+ Cache.delete(key);
+ // RateLimit.delete({ executor_id: key });
+ }
+ });
+ }, 1000 * 60);
- app.use(
- rateLimit({
- bucket: "global",
- onlyIp: true,
- ...ip,
- }),
- );
- app.use(rateLimit({ bucket: "global", ...global }));
- app.use(
- rateLimit({
- bucket: "error",
- error: true,
- onlyIp: true,
- ...error,
- }),
- );
- app.use("/guilds/:guild_id", rateLimit(routes.guild));
- app.use("/webhooks/:webhook_id", rateLimit(routes.webhook));
- app.use("/channels/:channel_id", rateLimit(routes.channel));
- app.use("/auth/login", rateLimit(routes.auth.login));
- app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
+ app.use(
+ rateLimit({
+ bucket: "global",
+ onlyIp: true,
+ ...ip,
+ }),
+ );
+ app.use(rateLimit({ bucket: "global", ...global }));
+ app.use(
+ rateLimit({
+ bucket: "error",
+ error: true,
+ onlyIp: true,
+ ...error,
+ }),
+ );
+ app.use("/guilds/:guild_id", rateLimit(routes.guild));
+ app.use("/webhooks/:webhook_id", rateLimit(routes.webhook));
+ app.use("/channels/:channel_id", rateLimit(routes.channel));
+ app.use("/auth/login", rateLimit(routes.auth.login));
+ app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
}
async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
- const id = opts.executor_id + opts.bucket_id;
- let limit = Cache.get(id);
- if (!limit) {
- limit = {
- id: opts.bucket_id,
- executor_id: opts.executor_id,
- expires_at: new Date(Date.now() + opts.window * 1000),
- hits: 0,
- blocked: false,
- };
- Cache.set(id, limit);
- }
+ const id = opts.executor_id + opts.bucket_id;
+ let limit = Cache.get(id);
+ if (!limit) {
+ limit = {
+ id: opts.bucket_id,
+ executor_id: opts.executor_id,
+ expires_at: new Date(Date.now() + opts.window * 1000),
+ hits: 0,
+ blocked: false,
+ };
+ Cache.set(id, limit);
+ }
- limit.hits++;
- if (limit.hits >= opts.max_hits) {
- limit.blocked = true;
- }
+ limit.hits++;
+ if (limit.hits >= opts.max_hits) {
+ limit.blocked = true;
+ }
- /*
+ /*
let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } });
if (!ratelimit) {
ratelimit = new RateLimit({
diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index e089407e..3ca204b6 100644
--- a/src/api/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -26,23 +26,23 @@ import { Router } from "express";
const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets");
export async function initTranslation(router: Router) {
- const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
- const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
- const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
+ const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales"));
+ const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en"));
+ const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5));
- await i18next
- .use(i18nextBackend)
- .use(i18nextMiddleware.LanguageDetector)
- .init({
- preload: languages,
- // debug: true,
- fallbackLng: "en",
- ns,
- backend: {
- loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
- },
- load: "all",
- });
+ await i18next
+ .use(i18nextBackend)
+ .use(i18nextMiddleware.LanguageDetector)
+ .init({
+ preload: languages,
+ // debug: true,
+ fallbackLng: "en",
+ ns,
+ backend: {
+ loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
+ },
+ load: "all",
+ });
- router.use(i18nextMiddleware.handle(i18next, {}));
+ router.use(i18nextMiddleware.handle(i18next, {}));
}
diff --git a/src/api/routes/-/healthz.ts b/src/api/routes/-/healthz.ts
index 5acd8c8c..886473cf 100644
--- a/src/api/routes/-/healthz.ts
+++ b/src/api/routes/-/healthz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- if (!getDatabase()) return res.sendStatus(503);
+ if (!getDatabase()) return res.sendStatus(503);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/-/readyz.ts b/src/api/routes/-/readyz.ts
index 5acd8c8c..886473cf 100644
--- a/src/api/routes/-/readyz.ts
+++ b/src/api/routes/-/readyz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- if (!getDatabase()) return res.sendStatus(503);
+ if (!getDatabase()) return res.sendStatus(503);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/bot/index.ts b/src/api/routes/applications/#application_id/bot/index.ts
index ad491840..83b4fc61 100644
--- a/src/api/routes/applications/#application_id/bot/index.ts
+++ b/src/api/routes/applications/#application_id/bot/index.ts
@@ -26,98 +26,98 @@ import { BotModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner"],
- });
+ "/",
+ route({
+ responses: {
+ 204: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner"],
+ });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- const user = await createAppBotUser(app, req);
+ const user = await createAppBotUser(app, req);
- res.send({
- token: await generateToken(user.id),
- });
- },
+ res.send({
+ token: await generateToken(user.id),
+ });
+ },
);
router.post(
- "/reset",
- route({
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const bot = await User.findOneOrFail({ where: { id: req.params.application_id } });
- const owner = await User.findOneOrFail({ where: { id: req.user_id } });
+ "/reset",
+ route({
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const bot = await User.findOneOrFail({ where: { id: req.params.application_id } });
+ const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- bot.data = { hash: undefined, valid_tokens_since: new Date() };
+ bot.data = { hash: undefined, valid_tokens_since: new Date() };
- await bot.save();
+ await bot.save();
- const token = await generateToken(bot.id);
+ const token = await generateToken(bot.id);
- res.json({ token }).status(200);
- },
+ res.json({ token }).status(200);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "BotModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as BotModifySchema;
- if (!body.avatar?.trim()) delete body.avatar;
+ "/",
+ route({
+ requestBody: "BotModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as BotModifySchema;
+ if (!body.avatar?.trim()) delete body.avatar;
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["bot", "owner"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["bot", "owner"],
+ });
- if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
+ if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string);
- app.bot.assign(body);
+ app.bot.assign(body);
- app.bot.save();
+ app.bot.save();
- await app.save();
- res.json(app).status(200);
- },
+ await app.save();
+ res.json(app).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
index 7907c1ba..8e4c3ce9 100644
--- a/src/api/routes/applications/#application_id/commands/#command_id/index.ts
+++ b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
@@ -24,99 +24,99 @@ import { Application, ApplicationCommand, FieldErrors, Snowflake } from "@spaceb
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!command) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!command) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- res.send(command);
+ res.send(command);
});
router.patch(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- await ApplicationCommand.update({ name: body.name.trim() }, commandForDb);
- res.send(commandForDb);
- },
+ await ApplicationCommand.update({ name: body.name.trim() }, commandForDb);
+ res.send(commandForDb);
+ },
);
router.delete("/", async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id });
- res.sendStatus(204);
+ await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id });
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/commands/index.ts b/src/api/routes/applications/#application_id/commands/index.ts
index 97753f00..91824be8 100644
--- a/src/api/routes/applications/#application_id/commands/index.ts
+++ b/src/api/routes/applications/#application_id/commands/index.ts
@@ -25,147 +25,147 @@ import { IsNull } from "typeorm";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } });
- res.send(command);
+ const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } });
+ res.send(command);
});
router.post(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
router.put(
- "/",
- route({
- requestBody: "BulkApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "BulkApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema[];
+ const body = req.body as ApplicationCommandCreateSchema[];
- // Remove commands not present in array
- const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } });
+ // Remove commands not present in array
+ const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } });
- const commandNamesInArray = body.map((c) => c.name);
- const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
+ const commandNamesInArray = body.map((c) => c.name);
+ const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
- for (const command of commandsNotInArray) {
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id });
- }
+ for (const command of commandsNotInArray) {
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id });
+ }
- for (const command of body) {
- if (!command.type) {
- command.type = 1;
- }
+ for (const command of body) {
+ if (!command.type) {
+ command.type = 1;
+ }
- if (command.name.trim().length < 1 || command.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (command.name.trim().length < 1 || command.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: command.name.trim(),
- name_localizations: command.name_localizations,
- description: command.description?.trim() || "",
- description_localizations: command.description_localizations,
- default_member_permissions: command.default_member_permissions || null,
- contexts: command.contexts,
- dm_permission: command.dm_permission || true,
- global_popularity_rank: 1,
- handler: command.handler,
- integration_types: command.integration_types,
- nsfw: command.nsfw,
- options: command.options,
- type: command.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: command.name.trim(),
+ name_localizations: command.name_localizations,
+ description: command.description?.trim() || "",
+ description_localizations: command.description_localizations,
+ default_member_permissions: command.default_member_permissions || null,
+ contexts: command.contexts,
+ dm_permission: command.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: command.handler,
+ integration_types: command.integration_types,
+ nsfw: command.nsfw,
+ options: command.options,
+ type: command.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/entitlements.ts b/src/api/routes/applications/#application_id/entitlements.ts
index 63a7e7b9..4cd13b93 100644
--- a/src/api/routes/applications/#application_id/entitlements.ts
+++ b/src/api/routes/applications/#application_id/entitlements.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationEntitlementsResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- //const { exclude_consumed } = req.query;
- res.status(200).send([]);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationEntitlementsResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ //const { exclude_consumed } = req.query;
+ res.status(200).send([]);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
index 2570358d..be2d62e7 100644
--- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
+++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
@@ -24,140 +24,140 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } });
+ const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } });
- if (!command) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!command) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- res.send(command);
+ res.send(command);
});
router.patch(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({
- where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
- });
+ const commandExists = await ApplicationCommand.exists({
+ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
+ });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.update(
- { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
- commandForDb,
- );
- res.send(commandForDb);
- },
+ await ApplicationCommand.update(
+ { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() },
+ commandForDb,
+ );
+ res.send(commandForDb);
+ },
);
router.delete("/", async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } });
- if (!commandExists) {
- res.status(404).send({ code: 404, message: "Unknown application command" });
- return;
- }
+ if (!commandExists) {
+ res.status(404).send({ code: 404, message: "Unknown application command" });
+ return;
+ }
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id });
- res.sendStatus(204);
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id });
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
index 1639e7be..607c846c 100644
--- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
+++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
@@ -24,185 +24,185 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
- res.send(command);
+ const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
+ res.send(command);
});
router.post(
- "/",
- route({
- requestBody: "ApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema;
+ const body = req.body as ApplicationCommandCreateSchema;
- if (!body.type) {
- body.type = 1;
- }
+ if (!body.type) {
+ body.type = 1;
+ }
- if (body.name.trim().length < 1 || body.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (body.name.trim().length < 1 || body.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- guild_id: req.params.guild_id,
- name: body.name.trim(),
- name_localizations: body.name_localizations,
- description: body.description?.trim() || "",
- description_localizations: body.description_localizations,
- default_member_permissions: body.default_member_permissions || null,
- contexts: body.contexts,
- dm_permission: body.dm_permission || true,
- global_popularity_rank: 1,
- handler: body.handler,
- integration_types: body.integration_types,
- nsfw: body.nsfw,
- options: body.options,
- type: body.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ guild_id: req.params.guild_id,
+ name: body.name.trim(),
+ name_localizations: body.name_localizations,
+ description: body.description?.trim() || "",
+ description_localizations: body.description_localizations,
+ default_member_permissions: body.default_member_permissions || null,
+ contexts: body.contexts,
+ dm_permission: body.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: body.handler,
+ integration_types: body.integration_types,
+ nsfw: body.nsfw,
+ options: body.options,
+ type: body.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
router.put(
- "/",
- route({
- requestBody: "BulkApplicationCommandCreateSchema",
- }),
- async (req: Request, res: Response) => {
- const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
+ "/",
+ route({
+ requestBody: "BulkApplicationCommandCreateSchema",
+ }),
+ async (req: Request, res: Response) => {
+ const applicationExists = await Application.exists({ where: { id: req.params.application_id } });
- if (!applicationExists) {
- res.status(404).send({ code: 404, message: "Unknown application" });
- return;
- }
+ if (!applicationExists) {
+ res.status(404).send({ code: 404, message: "Unknown application" });
+ return;
+ }
- const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
+ const guildExists = await Guild.exists({ where: { id: req.params.guild_id } });
- if (!guildExists) {
- res.status(404).send({ code: 404, message: "Unknown Server" });
- return;
- }
+ if (!guildExists) {
+ res.status(404).send({ code: 404, message: "Unknown Server" });
+ return;
+ }
- if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
- res.status(401).send({ code: 401, message: "Missing Access" });
- return;
- }
+ if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) {
+ res.status(401).send({ code: 401, message: "Missing Access" });
+ return;
+ }
- const body = req.body as ApplicationCommandCreateSchema[];
+ const body = req.body as ApplicationCommandCreateSchema[];
- // Remove commands not present in array
- const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
+ // Remove commands not present in array
+ const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } });
- const commandNamesInArray = body.map((c) => c.name);
- const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
+ const commandNamesInArray = body.map((c) => c.name);
+ const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name));
- for (const command of commandsNotInArray) {
- await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id });
- }
+ for (const command of commandsNotInArray) {
+ await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id });
+ }
- for (const command of body) {
- if (!command.type) {
- command.type = 1;
- }
+ for (const command of body) {
+ if (!command.type) {
+ command.type = 1;
+ }
- if (command.name.trim().length < 1 || command.name.trim().length > 32) {
- // TODO: configurable?
- throw FieldErrors({
- name: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 32 in length.`,
- },
- });
- }
+ if (command.name.trim().length < 1 || command.name.trim().length > 32) {
+ // TODO: configurable?
+ throw FieldErrors({
+ name: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 32 in length.`,
+ },
+ });
+ }
- const commandForDb: ApplicationCommandSchema = {
- application_id: req.params.application_id,
- guild_id: req.params.guild_id,
- name: command.name.trim(),
- name_localizations: command.name_localizations,
- description: command.description?.trim() || "",
- description_localizations: command.description_localizations,
- default_member_permissions: command.default_member_permissions || null,
- contexts: command.contexts,
- dm_permission: command.dm_permission || true,
- global_popularity_rank: 1,
- handler: command.handler,
- integration_types: command.integration_types,
- nsfw: command.nsfw,
- options: command.options,
- type: command.type,
- version: Snowflake.generate(),
- };
+ const commandForDb: ApplicationCommandSchema = {
+ application_id: req.params.application_id,
+ guild_id: req.params.guild_id,
+ name: command.name.trim(),
+ name_localizations: command.name_localizations,
+ description: command.description?.trim() || "",
+ description_localizations: command.description_localizations,
+ default_member_permissions: command.default_member_permissions || null,
+ contexts: command.contexts,
+ dm_permission: command.dm_permission || true,
+ global_popularity_rank: 1,
+ handler: command.handler,
+ integration_types: command.integration_types,
+ nsfw: command.nsfw,
+ options: command.options,
+ type: command.type,
+ version: Snowflake.generate(),
+ };
- const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } });
+ const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } });
- if (commandExists) {
- await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb);
- } else {
- commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
- await ApplicationCommand.save(commandForDb);
- }
- }
+ if (commandExists) {
+ await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb);
+ } else {
+ commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change
+ await ApplicationCommand.save(commandForDb);
+ }
+ }
- res.send(body);
- },
+ res.send(body);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/index.ts b/src/api/routes/applications/#application_id/index.ts
index c38895d2..bf29f404 100644
--- a/src/api/routes/applications/#application_id/index.ts
+++ b/src/api/routes/applications/#application_id/index.ts
@@ -26,106 +26,106 @@ import { ApplicationModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner", "bot"],
- });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner", "bot"],
+ });
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ApplicationModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationModifySchema;
+ "/",
+ route({
+ requestBody: "ApplicationModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationModifySchema;
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["owner", "bot"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["owner", "bot"],
+ });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (body.icon) {
- body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
- }
- if (body.cover_image) {
- body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
- }
+ if (body.icon) {
+ body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
+ }
+ if (body.cover_image) {
+ body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
+ }
- if (body.guild_id) {
- const guild = await Guild.findOneOrFail({
- where: { id: body.guild_id },
- select: ["owner_id"],
- });
- if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
- }
+ if (body.guild_id) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: body.guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
+ }
- if (app.bot) {
- app.bot.assign({ bio: body.description });
- await app.bot.save();
- }
+ if (app.bot) {
+ app.bot.assign({ bio: body.description });
+ await app.bot.save();
+ }
- app.assign(body);
+ app.assign(body);
- await app.save();
+ await app.save();
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.post(
- "/delete",
- route({
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.application_id },
- relations: ["bot", "owner"],
- });
- if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ "/delete",
+ route({
+ responses: {
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.application_id },
+ relations: ["bot", "owner"],
+ });
+ if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (app.bot) {
- await User.delete({ id: app.id });
- }
- await Application.delete({ id: app.id });
+ if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (app.bot) {
+ await User.delete({ id: app.id });
+ }
+ await Application.delete({ id: app.id });
- res.send().status(200);
- },
+ res.send().status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/#application_id/skus.ts b/src/api/routes/applications/#application_id/skus.ts
index 0877f4fb..9b9e3387 100644
--- a/src/api/routes/applications/#application_id/skus.ts
+++ b/src/api/routes/applications/#application_id/skus.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationSkusResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json([]).status(200);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationSkusResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json([]).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/applications/@me.ts b/src/api/routes/applications/@me.ts
index f4a90976..c1cf0db0 100644
--- a/src/api/routes/applications/@me.ts
+++ b/src/api/routes/applications/@me.ts
@@ -27,74 +27,74 @@ const router: Router = Router({ mergeParams: true });
// TODO: actually make this be correct - this is just a copy paste of /applications/:id/index.ts minus delete
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.user_id },
- relations: ["owner", "bot"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["owner", "bot"],
+ });
- return res.json(app);
- },
+ return res.json(app);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ApplicationModifySchema",
- responses: {
- 200: {
- body: "Application",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationModifySchema;
+ "/",
+ route({
+ requestBody: "ApplicationModifySchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationModifySchema;
- const app = await Application.findOneOrFail({
- where: { id: req.user_id },
- relations: ["owner", "bot"],
- });
+ const app = await Application.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["owner", "bot"],
+ });
- if (body.icon) {
- body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
- }
- if (body.cover_image) {
- body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
- }
+ if (body.icon) {
+ body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string);
+ }
+ if (body.cover_image) {
+ body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string);
+ }
- if (body.guild_id) {
- const guild = await Guild.findOneOrFail({
- where: { id: body.guild_id },
- select: ["owner_id"],
- });
- if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
- }
+ if (body.guild_id) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: body.guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400);
+ }
- if (app.bot) {
- app.bot.assign({ bio: body.description });
- await app.bot.save();
- }
+ if (app.bot) {
+ app.bot.assign({ bio: body.description });
+ await app.bot.save();
+ }
- app.assign(body);
+ app.assign(body);
- await app.save();
+ await app.save();
- return res.json(app);
- },
+ return res.json(app);
+ },
);
export default router;
diff --git a/src/api/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index 4de8a78d..83122e76 100644
--- a/src/api/routes/applications/detectable.ts
+++ b/src/api/routes/applications/detectable.ts
@@ -22,32 +22,32 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
const cache = {
- data: {},
- expires: 0,
+ data: {},
+ expires: 0,
};
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationDetectableResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // cache for 6 hours
- if (Date.now() > cache.expires) {
- const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
- const data = await response.json();
- cache.data = data as ApplicationDetectableResponse;
- cache.expires = Date.now() + 6 * 60 * 60 * 1000;
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationDetectableResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // cache for 6 hours
+ if (Date.now() > cache.expires) {
+ const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
+ const data = await response.json();
+ cache.data = data as ApplicationDetectableResponse;
+ cache.expires = Date.now() + 6 * 60 * 60 * 1000;
+ }
- res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
- .status(200)
- .json(cache.data);
- },
+ res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
+ .status(200)
+ .json(cache.data);
+ },
);
export default router;
diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts
index e3b270c3..ff89f03d 100644
--- a/src/api/routes/applications/index.ts
+++ b/src/api/routes/applications/index.ts
@@ -24,54 +24,54 @@ import { ApplicationCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIApplicationArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const results = await Application.find({
- where: { owner: { id: req.user_id } },
- relations: ["owner", "bot"],
- });
- res.json(results).status(200);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIApplicationArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const results = await Application.find({
+ where: { owner: { id: req.user_id } },
+ relations: ["owner", "bot"],
+ });
+ res.json(results).status(200);
+ },
);
router.post(
- "/",
- route({
- requestBody: "ApplicationCreateSchema",
- responses: {
- 200: {
- body: "Application",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationCreateSchema;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ "/",
+ route({
+ requestBody: "ApplicationCreateSchema",
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationCreateSchema;
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const app = Application.create({
- name: trimSpecial(body.name),
- description: "",
- bot_public: true,
- owner: user,
- verify_key: "IMPLEMENTME",
- flags: 0,
- });
+ const app = Application.create({
+ name: trimSpecial(body.name),
+ description: "",
+ bot_public: true,
+ owner: user,
+ verify_key: "IMPLEMENTME",
+ flags: 0,
+ });
- // april 14, 2023: discord made bot users be automatically added to all new apps
- const { autoCreateBotUsers } = Config.get().general;
- if (autoCreateBotUsers) {
- await createAppBotUser(app, req);
- } else await app.save();
+ // april 14, 2023: discord made bot users be automatically added to all new apps
+ const { autoCreateBotUsers } = Config.get().general;
+ if (autoCreateBotUsers) {
+ await createAppBotUser(app, req);
+ } else await app.save();
- res.json(app);
- },
+ res.json(app);
+ },
);
export default router;
diff --git a/src/api/routes/attachments/refresh-urls.ts b/src/api/routes/attachments/refresh-urls.ts
index 92a7dd16..f1530914 100644
--- a/src/api/routes/attachments/refresh-urls.ts
+++ b/src/api/routes/attachments/refresh-urls.ts
@@ -23,37 +23,37 @@ import { RefreshUrlsRequestSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "RefreshUrlsRequestSchema",
- responses: {
- 200: {
- body: "RefreshUrlsResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { attachment_urls } = req.body as RefreshUrlsRequestSchema;
+ "/",
+ route({
+ requestBody: "RefreshUrlsRequestSchema",
+ responses: {
+ 200: {
+ body: "RefreshUrlsResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { attachment_urls } = req.body as RefreshUrlsRequestSchema;
- const refreshed_urls = attachment_urls.map((url) => {
- return getUrlSignature(
- new NewUrlSignatureData({
- url: url,
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- )
- .applyToUrl(url)
- .toString();
- });
+ const refreshed_urls = attachment_urls.map((url) => {
+ return getUrlSignature(
+ new NewUrlSignatureData({
+ url: url,
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ )
+ .applyToUrl(url)
+ .toString();
+ });
- return res.status(200).json({
- refreshed_urls,
- });
- },
+ return res.status(200).json({
+ refreshed_urls,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/fingerprint.ts b/src/api/routes/auth/fingerprint.ts
index d7ef34b4..a4a49d07 100644
--- a/src/api/routes/auth/fingerprint.ts
+++ b/src/api/routes/auth/fingerprint.ts
@@ -21,9 +21,9 @@ import { Snowflake } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post("/", route({ responses: { 200: { body: "CreateFingerprintResponse" } } }), (req: Request, res: Response) => {
- const snowflake = Snowflake.generate();
- return res.json({
- fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`,
- });
+ const snowflake = Snowflake.generate();
+ return res.json({
+ fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`,
+ });
});
export default router;
diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts
index 6e613820..db16c3be 100644
--- a/src/api/routes/auth/forgot.ts
+++ b/src/api/routes/auth/forgot.ts
@@ -23,55 +23,55 @@ import { ForgotPasswordSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "ForgotPasswordSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { login, captcha_key } = req.body as ForgotPasswordSchema;
+ "/",
+ route({
+ requestBody: "ForgotPasswordSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { login, captcha_key } = req.body as ForgotPasswordSchema;
- const config = Config.get();
+ const config = Config.get();
- if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- res.sendStatus(204);
+ res.sendStatus(204);
- const user = await User.findOne({
- where: [{ phone: login }, { email: login }],
- select: ["username", "id", "email"],
- }).catch(() => {});
+ const user = await User.findOne({
+ where: [{ phone: login }, { email: login }],
+ select: ["username", "id", "email"],
+ }).catch(() => {});
- if (user && user.email) {
- Email.sendResetPassword(user, user.email).catch((e) => {
- console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`);
- });
- }
- },
+ if (user && user.email) {
+ Email.sendResetPassword(user, user.email).catch((e) => {
+ console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`);
+ });
+ }
+ },
);
export default router;
diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts
index be932953..a6b70c65 100644
--- a/src/api/routes/auth/generate-registration-tokens.ts
+++ b/src/api/routes/auth/generate-registration-tokens.ts
@@ -24,46 +24,46 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.get(
- "/",
- route({
- query: {
- count: {
- type: "number",
- description: "The number of registration tokens to generate. Defaults to 1.",
- },
- length: {
- type: "number",
- description: "The length of each registration token. Defaults to 255.",
- },
- },
- right: "CREATE_REGISTRATION_TOKENS",
- responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
- }),
- async (req: Request, res: Response) => {
- const count = req.query.count ? parseInt(req.query.count as string) : 1;
- const length = req.query.length ? parseInt(req.query.length as string) : 255;
+ "/",
+ route({
+ query: {
+ count: {
+ type: "number",
+ description: "The number of registration tokens to generate. Defaults to 1.",
+ },
+ length: {
+ type: "number",
+ description: "The length of each registration token. Defaults to 255.",
+ },
+ },
+ right: "CREATE_REGISTRATION_TOKENS",
+ responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
+ }),
+ async (req: Request, res: Response) => {
+ const count = req.query.count ? parseInt(req.query.count as string) : 1;
+ const length = req.query.length ? parseInt(req.query.length as string) : 255;
- const tokens: ValidRegistrationToken[] = [];
+ const tokens: ValidRegistrationToken[] = [];
- for (let i = 0; i < count; i++) {
- const token = ValidRegistrationToken.create({
- token: randomString(length),
- expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration),
- });
- tokens.push(token);
- }
+ for (let i = 0; i < count; i++) {
+ const token = ValidRegistrationToken.create({
+ token: randomString(length),
+ expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration),
+ });
+ tokens.push(token);
+ }
- // Why are these options used, exactly?
- await ValidRegistrationToken.save(tokens, {
- chunk: 1000,
- reload: false,
- transaction: false,
- });
+ // Why are these options used, exactly?
+ await ValidRegistrationToken.save(tokens, {
+ chunk: 1000,
+ reload: false,
+ transaction: false,
+ });
- const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token);
+ const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token);
- if (req.query.plain) return res.send(ret.join("\n"));
+ if (req.query.plain) return res.send(ret.join("\n"));
- return res.json({ tokens: ret });
- },
+ return res.json({ tokens: ret });
+ },
);
diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index 92f47909..6f2f26af 100644
--- a/src/api/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "LocationMetadataResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- //TODO
- //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
- const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code;
- res.json({
- consent_required: false,
- country_code: country_code,
- promotional_email_opt_in: { required: true, pre_checked: false },
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "LocationMetadataResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ //TODO
+ //Note: It's most likely related to legal. At the moment Discord hasn't finished this too
+ const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code;
+ res.json({
+ consent_required: false,
+ country_code: country_code,
+ promotional_email_opt_in: { required: true, pre_checked: false },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts
index c8ef6273..911ad4a9 100644
--- a/src/api/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -27,157 +27,157 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.post(
- "/",
- route({
- requestBody: "LoginSchema",
- responses: {
- 200: {
- body: "LoginResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { login, password, captcha_key, undelete } = req.body as LoginSchema;
+ "/",
+ route({
+ requestBody: "LoginSchema",
+ responses: {
+ 200: {
+ body: "LoginResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { login, password, captcha_key, undelete } = req.body as LoginSchema;
- const config = Config.get();
+ const config = Config.get();
- if (config.login.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (config.login.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- const user = await User.findOneOrFail({
- where: [{ phone: login }, { email: login }],
- select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"],
- relations: ["security_keys", "settings"],
- }).catch(() => {
- throw FieldErrors({
- login: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- password: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- });
- });
+ const user = await User.findOneOrFail({
+ where: [{ phone: login }, { email: login }],
+ select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"],
+ relations: ["security_keys", "settings"],
+ }).catch(() => {
+ throw FieldErrors({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ password: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ });
- // the salt is saved in the password refer to bcrypt docs
- const same_password = await bcrypt.compare(password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({
- login: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- password: {
- message: req.t("auth:login.INVALID_LOGIN"),
- code: "INVALID_LOGIN",
- },
- });
- }
+ // the salt is saved in the password refer to bcrypt docs
+ const same_password = await bcrypt.compare(password, user.data.hash || "");
+ if (!same_password) {
+ throw FieldErrors({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ password: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ }
- // return an error for unverified accounts if verification is required
- if (config.login.requireVerification && !user.verified) {
- throw FieldErrors({
- login: {
- code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
- message: "Email verification is required, please check your email.",
- },
- });
- }
+ // return an error for unverified accounts if verification is required
+ if (config.login.requireVerification && !user.verified) {
+ throw FieldErrors({
+ login: {
+ code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
+ message: "Email verification is required, please check your email.",
+ },
+ });
+ }
- if (user.mfa_enabled && !user.webauthn_enabled) {
- // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
- const ticket = crypto.randomBytes(40).toString("hex");
+ if (user.mfa_enabled && !user.webauthn_enabled) {
+ // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy
+ const ticket = crypto.randomBytes(40).toString("hex");
- await User.update({ id: user.id }, { totp_last_ticket: ticket });
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- });
- }
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
+ });
+ }
- if (user.mfa_enabled && user.webauthn_enabled) {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ if (user.mfa_enabled && user.webauthn_enabled) {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const options = await WebAuthn.fido2.assertionOptions();
- const challenge = JSON.stringify({
- publicKey: {
- ...options,
- challenge: Buffer.from(options.challenge).toString("base64"),
- allowCredentials: user.security_keys.map((x) => ({
- id: x.key_id,
- type: "public-key",
- })),
- transports: ["usb", "ble", "nfc"],
- timeout: 60000,
- },
- });
+ const options = await WebAuthn.fido2.assertionOptions();
+ const challenge = JSON.stringify({
+ publicKey: {
+ ...options,
+ challenge: Buffer.from(options.challenge).toString("base64"),
+ allowCredentials: user.security_keys.map((x) => ({
+ id: x.key_id,
+ type: "public-key",
+ })),
+ transports: ["usb", "ble", "nfc"],
+ timeout: 60000,
+ },
+ });
- const ticket = await generateWebAuthnTicket(challenge);
- await User.update({ id: user.id }, { totp_last_ticket: ticket });
+ const ticket = await generateWebAuthnTicket(challenge);
+ await User.update({ id: user.id }, { totp_last_ticket: ticket });
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- webauthn: challenge,
- });
- }
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
+ webauthn: challenge,
+ });
+ }
- if (undelete) {
- // undelete refers to un'disable' here
- if (user.disabled) await User.update({ id: user.id }, { disabled: false });
- if (user.deleted) await User.update({ id: user.id }, { deleted: false });
- } else {
- if (user.deleted)
- return res.status(400).json({
- message: "This account is scheduled for deletion.",
- code: 20011,
- });
- if (user.disabled)
- return res.status(400).json({
- message: req.t("auth:login.ACCOUNT_DISABLED"),
- code: 20013,
- });
- }
+ if (undelete) {
+ // undelete refers to un'disable' here
+ if (user.disabled) await User.update({ id: user.id }, { disabled: false });
+ if (user.deleted) await User.update({ id: user.id }, { deleted: false });
+ } else {
+ if (user.deleted)
+ return res.status(400).json({
+ message: "This account is scheduled for deletion.",
+ code: 20011,
+ });
+ if (user.disabled)
+ return res.status(400).json({
+ message: req.t("auth:login.ACCOUNT_DISABLED"),
+ code: 20013,
+ });
+ }
- const token = await generateToken(user.id);
+ const token = await generateToken(user.id);
- // Notice this will have a different token structure, than discord
- // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
- // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
+ // Notice this will have a different token structure, than discord
+ // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
+ // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
- res.json({ token, settings: { ...user.settings, index: undefined } });
- },
+ res.json({ token, settings: { ...user.settings, index: undefined } });
+ },
);
/**
diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index f20a1994..b460e31a 100644
--- a/src/api/routes/auth/logout.ts
+++ b/src/api/routes/auth/logout.ts
@@ -24,25 +24,25 @@ const router: Router = Router({ mergeParams: true });
export default router;
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- if (req.body.provider != null || req.body.voip_provider != null) {
- console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
- } else {
- delete req.body.provider;
- delete req.body.voip_provider;
- if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
- }
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (req.body.provider != null || req.body.voip_provider != null) {
+ console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
+ } else {
+ delete req.body.provider;
+ delete req.body.voip_provider;
+ if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
+ }
- if (req.token.did) {
- await Session.delete({ user_id: req.user_id, session_id: req.token.did });
- }
+ if (req.token.did) {
+ await Session.delete({ user_id: req.user_id, session_id: req.token.did });
+ }
- res.status(204).send();
- },
+ res.status(204).send();
+ },
);
diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index edf307cc..b9f2fa85 100644
--- a/src/api/routes/auth/mfa/totp.ts
+++ b/src/api/routes/auth/mfa/totp.ts
@@ -25,54 +25,54 @@ import { TotpSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpSchema",
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { code, ticket, gift_code_sku_id, login_source } =
- const { code, ticket } = req.body as TotpSchema;
+ "/",
+ route({
+ requestBody: "TotpSchema",
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { code, ticket, gift_code_sku_id, login_source } =
+ const { code, ticket } = req.body as TotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: ["id", "totp_secret"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id", "totp_secret"],
+ relations: ["settings"],
+ });
- const backup = await BackupCode.findOne({
- where: {
- code: code,
- expired: false,
- consumed: false,
- user: { id: user.id },
- },
- });
+ const backup = await BackupCode.findOne({
+ where: {
+ code: code,
+ expired: false,
+ consumed: false,
+ user: { id: user.id },
+ },
+ });
- if (!backup) {
- const ret = verifyToken(user.totp_secret || "", code);
- if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- } else {
- backup.consumed = true;
- await backup.save();
- }
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret || "", code);
+ if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ } else {
+ backup.consumed = true;
+ await backup.save();
+ }
- await User.update({ id: user.id }, { totp_last_ticket: "" });
+ await User.update({ id: user.id }, { totp_last_ticket: "" });
- return res.json({
- token: await generateToken(user.id),
- settings: { ...user.settings, index: undefined },
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ settings: { ...user.settings, index: undefined },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts
index 63b1335d..da476ab4 100644
--- a/src/api/routes/auth/mfa/webauthn.ts
+++ b/src/api/routes/auth/mfa/webauthn.ts
@@ -25,77 +25,77 @@ import { WebAuthnTotpSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
function toArrayBuffer(buf: Buffer) {
- const ab = new ArrayBuffer(buf.length);
- const view = new Uint8Array(ab);
- for (let i = 0; i < buf.length; ++i) {
- view[i] = buf[i];
- }
- return ab;
+ const ab = new ArrayBuffer(buf.length);
+ const view = new Uint8Array(ab);
+ for (let i = 0; i < buf.length; ++i) {
+ view[i] = buf[i];
+ }
+ return ab;
}
router.post(
- "/",
- route({
- requestBody: "WebAuthnTotpSchema",
- responses: {
- 200: { body: "TokenResponse" },
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ "/",
+ route({
+ requestBody: "WebAuthnTotpSchema",
+ responses: {
+ 200: { body: "TokenResponse" },
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const { code, ticket } = req.body as WebAuthnTotpSchema;
+ const { code, ticket } = req.body as WebAuthnTotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: ["id"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id"],
+ relations: ["settings"],
+ });
- const ret = await verifyWebAuthnToken(ticket);
- if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ const ret = await verifyWebAuthnToken(ticket);
+ if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- await User.update({ id: user.id }, { totp_last_ticket: "" });
+ await User.update({ id: user.id }, { totp_last_ticket: "" });
- const clientAttestationResponse = JSON.parse(code);
+ const clientAttestationResponse = JSON.parse(code);
- if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
+ if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
- clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url"));
+ clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url"));
- const securityKey = await SecurityKey.findOneOrFail({
- where: {
- key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
- },
- });
+ const securityKey = await SecurityKey.findOneOrFail({
+ where: {
+ key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"),
+ },
+ });
- const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
+ const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
- const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, {
- ...assertionExpectations,
- factor: "second",
- publicKey: securityKey.public_key,
- prevCounter: securityKey.counter,
- userHandle: securityKey.key_id,
- });
+ const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, {
+ ...assertionExpectations,
+ factor: "second",
+ publicKey: securityKey.public_key,
+ prevCounter: securityKey.counter,
+ userHandle: securityKey.key_id,
+ });
- const counter = authnResult.authnrData.get("counter");
+ const counter = authnResult.authnrData.get("counter");
- securityKey.counter = counter;
+ securityKey.counter = counter;
- await securityKey.save();
+ await securityKey.save();
- return res.json({
- token: await generateToken(user.id),
- user_settings: user.settings,
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ user_settings: user.settings,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index d4b061be..6aa52bff 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -27,294 +27,294 @@ import { RegisterSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "RegisterSchema",
- responses: {
- 200: { body: "TokenOnlyResponse" },
- 400: { body: "APIErrorOrCaptchaResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as RegisterSchema;
- const { register, security, limits } = Config.get();
- const ip = req.ip!;
+ "/",
+ route({
+ requestBody: "RegisterSchema",
+ responses: {
+ 200: { body: "TokenOnlyResponse" },
+ 400: { body: "APIErrorOrCaptchaResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as RegisterSchema;
+ const { register, security, limits } = Config.get();
+ const ip = req.ip!;
- // Reg tokens
- // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
- let regTokenUsed = false;
- if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
- // eg theyre on https://staging.spacebar.chat/register?token=whatever
- const token = req.get("Referrer")?.split("token=")[1].split("&")[0];
- if (token) {
- const regToken = await ValidRegistrationToken.findOneOrFail({
- where: { token, expires_at: MoreThan(new Date()) },
- });
- await regToken.remove();
- regTokenUsed = true;
- console.log(`[REGISTER] Registration token ${token} used for registration!`);
- } else {
- console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
- }
- }
+ // Reg tokens
+ // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
+ let regTokenUsed = false;
+ if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
+ // eg theyre on https://staging.spacebar.chat/register?token=whatever
+ const token = req.get("Referrer")?.split("token=")[1].split("&")[0];
+ if (token) {
+ const regToken = await ValidRegistrationToken.findOneOrFail({
+ where: { token, expires_at: MoreThan(new Date()) },
+ });
+ await regToken.remove();
+ regTokenUsed = true;
+ console.log(`[REGISTER] Registration token ${token} used for registration!`);
+ } else {
+ console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`);
+ }
+ }
- // check if registration is allowed
- if (!regTokenUsed && !register.allowNewRegistration) {
- throw FieldErrors({
- email: {
- code: "REGISTRATION_DISABLED",
- message: req.t("auth:register.REGISTRATION_DISABLED"),
- },
- });
- }
+ // check if registration is allowed
+ if (!regTokenUsed && !register.allowNewRegistration) {
+ throw FieldErrors({
+ email: {
+ code: "REGISTRATION_DISABLED",
+ message: req.t("auth:register.REGISTRATION_DISABLED"),
+ },
+ });
+ }
- // check if the user agreed to the Terms of Service
- if (!body.consent) {
- throw FieldErrors({
- consent: {
- code: "CONSENT_REQUIRED",
- message: req.t("auth:register.CONSENT_REQUIRED"),
- },
- });
- }
+ // check if the user agreed to the Terms of Service
+ if (!body.consent) {
+ throw FieldErrors({
+ consent: {
+ code: "CONSENT_REQUIRED",
+ message: req.t("auth:register.CONSENT_REQUIRED"),
+ },
+ });
+ }
- if (!regTokenUsed && register.disabled) {
- throw FieldErrors({
- email: {
- code: "DISABLED",
- message: "registration is disabled on this instance",
- },
- });
- }
+ if (!regTokenUsed && register.disabled) {
+ throw FieldErrors({
+ email: {
+ code: "DISABLED",
+ message: "registration is disabled on this instance",
+ },
+ });
+ }
- if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
- const { sitekey, service } = security.captcha;
- if (!body.captcha_key) {
- return res?.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) {
+ const { sitekey, service } = security.captcha;
+ if (!body.captcha_key) {
+ return res?.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const verify = await verifyCaptcha(body.captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const verify = await verifyCaptcha(body.captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- if (!regTokenUsed && !register.allowMultipleAccounts) {
- // TODO: check if fingerprint was eligible generated
- const exists = await User.findOne({
- where: { fingerprints: body.fingerprint },
- select: ["id"],
- });
+ if (!regTokenUsed && !register.allowMultipleAccounts) {
+ // TODO: check if fingerprint was eligible generated
+ const exists = await User.findOne({
+ where: { fingerprints: body.fingerprint },
+ select: ["id"],
+ });
- if (exists) {
- throw FieldErrors({
- email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
- },
- });
- }
- }
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
+ },
+ });
+ }
+ }
- if (!regTokenUsed && register.checkIp) {
- const blacklist = await AbuseIpDbClient.getBlacklist();
- if (blacklist) {
- const entry = blacklist.data.find((e) => e.ipAddress === ip);
+ if (!regTokenUsed && register.checkIp) {
+ const blacklist = await AbuseIpDbClient.getBlacklist();
+ if (blacklist) {
+ const entry = blacklist.data.find((e) => e.ipAddress === ip);
- if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
+ if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
+ }
- const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
- if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
- console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ const checkIp = await AbuseIpDbClient.checkIpAddress(ip);
+ if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) {
+ console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- const ipData = await IpDataClient.getIpInfo(ip);
- if (ipData) {
- if (!ipData.threat) {
- console.log("Invalid IPData.co response, missing threat field", ipData);
- }
- const categories = Object.entries(ipData.threat)
- .filter(([key, value]) => key.startsWith("is_") && value === true)
- .map(([key]) => key.replace("is_", ""));
- const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
- if (blockedCategories.size > 0) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ const ipData = await IpDataClient.getIpInfo(ip);
+ if (ipData) {
+ if (!ipData.threat) {
+ console.log("Invalid IPData.co response, missing threat field", ipData);
+ }
+ const categories = Object.entries(ipData.threat)
+ .filter(([key, value]) => key.startsWith("is_") && value === true)
+ .map(([key]) => key.replace("is_", ""));
+ const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes));
+ if (blockedCategories.size > 0) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockAsnTypes.includes(ipData.asn.type)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ if (register.blockAsnTypes.includes(ipData.asn.type)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockAsns.includes(ipData.asn.asn)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`);
- throw new HTTPError("Your IP is blocked from registration");
- }
+ if (register.blockAsns.includes(ipData.asn.asn)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
- if (register.blockProxies && IpDataClient.isProxy(ipData)) {
- console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`);
- throw new HTTPError("Your IP is blocked from registration");
- }
- }
- }
+ if (register.blockProxies && IpDataClient.isProxy(ipData)) {
+ console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
+ }
+ }
- // TODO: gift_code_sku_id?
- // TODO: check password strength
+ // TODO: gift_code_sku_id?
+ // TODO: check password strength
- const email = body.email;
- if (email) {
- // replace all dots and chars after +, if its a gmail.com email
- if (!email) {
- throw FieldErrors({
- email: {
- code: "INVALID_EMAIL",
- message: req?.t("auth:register.INVALID_EMAIL"),
- },
- });
- }
+ const email = body.email;
+ if (email) {
+ // replace all dots and chars after +, if its a gmail.com email
+ if (!email) {
+ throw FieldErrors({
+ email: {
+ code: "INVALID_EMAIL",
+ message: req?.t("auth:register.INVALID_EMAIL"),
+ },
+ });
+ }
- // check if there is already an account with this email
- const exists = await User.findOne({ where: { email: email } });
+ // check if there is already an account with this email
+ const exists = await User.findOne({ where: { email: email } });
- if (exists) {
- throw FieldErrors({
- email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
- },
- });
- }
- } else if (register.email.required) {
- throw FieldErrors({
- email: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (exists) {
+ throw FieldErrors({
+ email: {
+ code: "EMAIL_ALREADY_REGISTERED",
+ message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"),
+ },
+ });
+ }
+ } else if (register.email.required) {
+ throw FieldErrors({
+ email: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- if (register.dateOfBirth.required && !body.date_of_birth) {
- throw FieldErrors({
- date_of_birth: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
- const minimum = new Date();
- minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
+ if (register.dateOfBirth.required && !body.date_of_birth) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) {
+ const minimum = new Date();
+ minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum);
- let parsedDob;
- try {
- parsedDob = new Date(body.date_of_birth as Date);
- if (isNaN(parsedDob.getTime())) {
- throw new Error("Invalid date");
- }
- } catch (e) {
- throw FieldErrors({
- date_of_birth: {
- code: "DATE_OF_BIRTH_INVALID",
- message: req.t("auth:register.DATE_OF_BIRTH_INVALID"),
- },
- });
- }
+ let parsedDob;
+ try {
+ parsedDob = new Date(body.date_of_birth as Date);
+ if (isNaN(parsedDob.getTime())) {
+ throw new Error("Invalid date");
+ }
+ } catch (e) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "DATE_OF_BIRTH_INVALID",
+ message: req.t("auth:register.DATE_OF_BIRTH_INVALID"),
+ },
+ });
+ }
- // higher is younger
- if (parsedDob > minimum) {
- throw FieldErrors({
- date_of_birth: {
- code: "DATE_OF_BIRTH_UNDERAGE",
- message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", {
- years: register.dateOfBirth.minimum,
- }),
- },
- });
- }
- }
+ // higher is younger
+ if (parsedDob > minimum) {
+ throw FieldErrors({
+ date_of_birth: {
+ code: "DATE_OF_BIRTH_UNDERAGE",
+ message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", {
+ years: register.dateOfBirth.minimum,
+ }),
+ },
+ });
+ }
+ }
- if (body.password) {
- const min = register.password.minLength ?? 8;
+ if (body.password) {
+ const min = register.password.minLength ?? 8;
- if (body.password.length < min) {
- throw FieldErrors({
- password: {
- code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
- message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
- },
- });
- }
- // the salt is saved in the password refer to bcrypt docs
- body.password = await bcrypt.hash(body.password, 12);
- } else if (register.password.required) {
- throw FieldErrors({
- password: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (body.password.length < min) {
+ throw FieldErrors({
+ password: {
+ code: "PASSWORD_REQUIREMENTS_MIN_LENGTH",
+ message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }),
+ },
+ });
+ }
+ // the salt is saved in the password refer to bcrypt docs
+ body.password = await bcrypt.hash(body.password, 12);
+ } else if (register.password.required) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) {
- // require invite to register -> e.g. for organizations to send invites to their employees
- throw FieldErrors({
- email: {
- code: "INVITE_ONLY",
- message: req.t("auth:register.INVITE_ONLY"),
- },
- });
- }
+ if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) {
+ // require invite to register -> e.g. for organizations to send invites to their employees
+ throw FieldErrors({
+ email: {
+ code: "INVITE_ONLY",
+ message: req.t("auth:register.INVITE_ONLY"),
+ },
+ });
+ }
- if (
- !regTokenUsed &&
- limits.absoluteRate.register.enabled &&
- (await User.count({
- where: {
- created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
- },
- })) >= limits.absoluteRate.register.limit
- ) {
- console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
- throw FieldErrors({
- email: {
- code: "TOO_MANY_REGISTRATIONS",
- message: req.t("auth:register.TOO_MANY_REGISTRATIONS"),
- },
- });
- }
+ if (
+ !regTokenUsed &&
+ limits.absoluteRate.register.enabled &&
+ (await User.count({
+ where: {
+ created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)),
+ },
+ })) >= limits.absoluteRate.register.limit
+ ) {
+ console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
+ throw FieldErrors({
+ email: {
+ code: "TOO_MANY_REGISTRATIONS",
+ message: req.t("auth:register.TOO_MANY_REGISTRATIONS"),
+ },
+ });
+ }
- const { maxUsername } = Config.get().limits.user;
- if (body.username.length > maxUsername) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 2 and ${maxUsername} in length.`,
- },
- });
- }
+ const { maxUsername } = Config.get().limits.user;
+ if (body.username.length > maxUsername) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 2 and ${maxUsername} in length.`,
+ },
+ });
+ }
- const user = await User.register({ ...body, req });
+ const user = await User.register({ ...body, req });
- if (body.invite) {
- // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible)
- await Invite.joinGuild(user.id, body.invite);
- }
+ if (body.invite) {
+ // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible)
+ await Invite.joinGuild(user.id, body.invite);
+ }
- return res.json({ token: await generateToken(user.id) });
- },
+ return res.json({ token: await generateToken(user.id) });
+ },
);
export default router;
diff --git a/src/api/routes/auth/reset.ts b/src/api/routes/auth/reset.ts
index 69f97d28..77e93e84 100644
--- a/src/api/routes/auth/reset.ts
+++ b/src/api/routes/auth/reset.ts
@@ -26,54 +26,54 @@ const router = Router({ mergeParams: true });
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
- "/",
- route({
- requestBody: "PasswordResetSchema",
- responses: {
- 200: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { password, token } = req.body as PasswordResetSchema;
+ "/",
+ route({
+ requestBody: "PasswordResetSchema",
+ responses: {
+ 200: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password, token } = req.body as PasswordResetSchema;
- let user;
- try {
- const userTokenData = await checkToken(token, {
- select: ["email"],
- fingerprint: req.fingerprint,
- ipAddress: req.ip,
- });
- user = userTokenData.user;
- } catch {
- throw FieldErrors({
- password: {
- message: req.t("auth:password_reset.INVALID_TOKEN"),
- code: "INVALID_TOKEN",
- },
- });
- }
+ let user;
+ try {
+ const userTokenData = await checkToken(token, {
+ select: ["email"],
+ fingerprint: req.fingerprint,
+ ipAddress: req.ip,
+ });
+ user = userTokenData.user;
+ } catch {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:password_reset.INVALID_TOKEN"),
+ code: "INVALID_TOKEN",
+ },
+ });
+ }
- // the salt is saved in the password refer to bcrypt docs
- const hash = await bcrypt.hash(password, 12);
+ // the salt is saved in the password refer to bcrypt docs
+ const hash = await bcrypt.hash(password, 12);
- const data = {
- data: {
- hash,
- valid_tokens_since: new Date(),
- },
- };
- await User.update({ id: user.id }, data);
+ const data = {
+ data: {
+ hash,
+ valid_tokens_since: new Date(),
+ },
+ };
+ await User.update({ id: user.id }, data);
- // come on, the user has to have an email to reset their password in the first place
- await Email.sendPasswordChanged(user, user.email!);
+ // come on, the user has to have an email to reset their password in the first place
+ await Email.sendPasswordChanged(user, user.email!);
- res.json({ token: await generateToken(user.id) });
- },
+ res.json({ token: await generateToken(user.id) });
+ },
);
export default router;
diff --git a/src/api/routes/auth/sessions.ts b/src/api/routes/auth/sessions.ts
index 6c721f2e..3a9095f1 100644
--- a/src/api/routes/auth/sessions.ts
+++ b/src/api/routes/auth/sessions.ts
@@ -23,54 +23,54 @@ import { SessionsLogoutSchema } from "../../../schemas/api/users/SessionsSchemas
import { In } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GetSessionsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { extended = false } = req.query;
- const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[];
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GetSessionsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { extended = false } = req.query;
+ const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[];
- res.json({
- user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())),
- });
- },
+ res.json({
+ user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())),
+ });
+ },
);
router.post(
- "/logout",
- route({
- requestBody: "SessionsLogoutSchema",
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as SessionsLogoutSchema;
+ "/logout",
+ route({
+ requestBody: "SessionsLogoutSchema",
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as SessionsLogoutSchema;
- let sessions: Session[] = [];
- if ("session_ids" in body) {
- sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[];
- }
+ let sessions: Session[] = [];
+ if ("session_ids" in body) {
+ sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[];
+ }
- if ("session_id_hashes" in body) {
- const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[];
- const hashSet = new Set(body.session_id_hashes);
- const matchingSessions = allSessions.filter((session) => {
- const hash = createHash("sha256").update(session.session_id).digest("hex");
- return hashSet.has(hash);
- });
- sessions.push(...matchingSessions);
- }
+ if ("session_id_hashes" in body) {
+ const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[];
+ const hashSet = new Set(body.session_id_hashes);
+ const matchingSessions = allSessions.filter((session) => {
+ const hash = createHash("sha256").update(session.session_id).digest("hex");
+ return hashSet.has(hash);
+ });
+ sessions.push(...matchingSessions);
+ }
- for (const session of sessions) {
- await session.remove();
- }
- res.status(204).send();
- },
+ for (const session of sessions) {
+ await session.remove();
+ }
+ res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts
index 43fb23a0..7c8ca187 100644
--- a/src/api/routes/auth/verify/index.ts
+++ b/src/api/routes/auth/verify/index.ts
@@ -22,79 +22,79 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
async function getToken(user: User) {
- const token = await generateToken(user.id);
+ const token = await generateToken(user.id);
- // Notice this will have a different token structure, than discord
- // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
- // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
+ // Notice this will have a different token structure, than discord
+ // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package
+ // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png
- return { token };
+ return { token };
}
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
- "/",
- route({
- requestBody: "VerifyEmailSchema",
- responses: {
- 200: {
- body: "TokenResponse",
- },
- 400: {
- body: "APIErrorOrCaptchaResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { captcha_key, token } = req.body;
+ "/",
+ route({
+ requestBody: "VerifyEmailSchema",
+ responses: {
+ 200: {
+ body: "TokenResponse",
+ },
+ 400: {
+ body: "APIErrorOrCaptchaResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { captcha_key, token } = req.body;
- const config = Config.get();
+ const config = Config.get();
- if (config.register.requireCaptcha && config.security.captcha.enabled) {
- const { sitekey, service } = config.security.captcha;
+ if (config.register.requireCaptcha && config.security.captcha.enabled) {
+ const { sitekey, service } = config.security.captcha;
- if (!captcha_key) {
- return res.status(400).json({
- captcha_key: ["captcha-required"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
+ if (!captcha_key) {
+ return res.status(400).json({
+ captcha_key: ["captcha-required"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
- const ip = req.ip;
- const verify = await verifyCaptcha(captcha_key, ip);
- if (!verify.success) {
- return res.status(400).json({
- captcha_key: verify["error-codes"],
- captcha_sitekey: sitekey,
- captcha_service: service,
- });
- }
- }
+ const ip = req.ip;
+ const verify = await verifyCaptcha(captcha_key, ip);
+ if (!verify.success) {
+ return res.status(400).json({
+ captcha_key: verify["error-codes"],
+ captcha_sitekey: sitekey,
+ captcha_service: service,
+ });
+ }
+ }
- let user;
+ let user;
- try {
- const userTokenData = await checkToken(token, {
- fingerprint: req.fingerprint,
- ipAddress: req.ip,
- });
- user = userTokenData.user;
- } catch {
- throw FieldErrors({
- token: {
- message: req.t("auth:password_reset.INVALID_TOKEN"),
- code: "INVALID_TOKEN",
- },
- });
- }
+ try {
+ const userTokenData = await checkToken(token, {
+ fingerprint: req.fingerprint,
+ ipAddress: req.ip,
+ });
+ user = userTokenData.user;
+ } catch {
+ throw FieldErrors({
+ token: {
+ message: req.t("auth:password_reset.INVALID_TOKEN"),
+ code: "INVALID_TOKEN",
+ },
+ });
+ }
- if (user.verified) return res.json(await getToken(user));
+ if (user.verified) return res.json(await getToken(user));
- await User.update({ id: user.id }, { verified: true });
+ await User.update({ id: user.id }, { verified: true });
- return res.json(await getToken(user));
- },
+ return res.json(await getToken(user));
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts
index 4ef1d518..ce6672a7 100644
--- a/src/api/routes/auth/verify/resend.ts
+++ b/src/api/routes/auth/verify/resend.ts
@@ -23,43 +23,43 @@ import { HTTPError } from "lambert-server";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "RESEND_VERIFICATION_EMAIL",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 500: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["username", "email", "verified"],
- });
+ "/",
+ route({
+ right: "RESEND_VERIFICATION_EMAIL",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 500: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["username", "email", "verified"],
+ });
- if (!user.email) {
- // TODO: whats the proper error response for this?
- throw new HTTPError("User does not have an email address", 400);
- }
+ if (!user.email) {
+ // TODO: whats the proper error response for this?
+ throw new HTTPError("User does not have an email address", 400);
+ }
- if (user.verified) {
- throw new HTTPError("Email is already verified", 400);
- }
+ if (user.verified) {
+ throw new HTTPError("Email is already verified", 400);
+ }
- await Email.sendVerifyEmail(user, user.email)
- .then(() => {
- return res.sendStatus(204);
- })
- .catch((e) => {
- console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`);
- throw new HTTPError("Failed to send verification email", 500);
- });
- },
+ await Email.sendVerifyEmail(user, user.email)
+ .then(() => {
+ return res.sendStatus(204);
+ })
+ .catch((e) => {
+ console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`);
+ throw new HTTPError("Failed to send verification email", 500);
+ });
+ },
);
export default router;
diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
index 7c15795c..d3d40d3e 100644
--- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts
+++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -24,36 +24,36 @@ import { BackupCodesChallengeSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "BackupCodesChallengeSchema",
- responses: {
- 200: { body: "BackupCodesChallengeResponse" },
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { password } = req.body as BackupCodesChallengeSchema;
+ "/",
+ route({
+ requestBody: "BackupCodesChallengeSchema",
+ responses: {
+ 200: { body: "BackupCodesChallengeResponse" },
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password } = req.body as BackupCodesChallengeSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- if (!(await bcrypt.compare(password, user.data.hash || ""))) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- return res.json({
- nonce: "NoncePlaceholder",
- regenerate_nonce: "RegenNoncePlaceholder",
- });
- },
+ return res.json({
+ nonce: "NoncePlaceholder",
+ regenerate_nonce: "RegenNoncePlaceholder",
+ });
+ },
);
export default router;
diff --git a/src/api/routes/beaker.ts b/src/api/routes/beaker.ts
index 62df41e1..4eb2941a 100644
--- a/src/api/routes/beaker.ts
+++ b/src/api/routes/beaker.ts
@@ -23,16 +23,16 @@ const router = Router({ mergeParams: true });
// screw off, telemetry requests
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
- },
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/attachments.ts b/src/api/routes/channels/#channel_id/attachments.ts
index 039bfa21..30fd2c63 100644
--- a/src/api/routes/channels/#channel_id/attachments.ts
+++ b/src/api/routes/channels/#channel_id/attachments.ts
@@ -25,109 +25,109 @@ import { UploadAttachmentRequestSchema, UploadAttachmentResponseSchema } from "@
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "UploadAttachmentRequestSchema",
- responses: {
- 200: {
- body: "UploadAttachmentResponseSchema",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as UploadAttachmentRequestSchema;
- const { channel_id } = req.params;
+ "/",
+ route({
+ requestBody: "UploadAttachmentRequestSchema",
+ responses: {
+ 200: {
+ body: "UploadAttachmentResponseSchema",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as UploadAttachmentRequestSchema;
+ const { channel_id } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) {
- return res.status(403).json({
- code: 403,
- message: "Missing Permissions: ATTACH_FILES",
- });
- }
+ if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) {
+ return res.status(403).json({
+ code: 403,
+ message: "Missing Permissions: ATTACH_FILES",
+ });
+ }
- const cdnUrl = Config.get().cdn.endpointPublic;
- const batchId = `CLOUD_${user.id}_${randomString(128)}`;
+ const cdnUrl = Config.get().cdn.endpointPublic;
+ const batchId = `CLOUD_${user.id}_${randomString(128)}`;
- // validate IDs
- const seenIds: (string | undefined)[] = [];
- for (const file of payload.files) {
- if (seenIds.includes(file.id)) {
- return res.status(400).json({
- code: 400,
- message: `Duplicate attachment ID: ${file.id}`,
- });
- }
- seenIds.push(file.id);
- }
+ // validate IDs
+ const seenIds: (string | undefined)[] = [];
+ for (const file of payload.files) {
+ if (seenIds.includes(file.id)) {
+ return res.status(400).json({
+ code: 400,
+ message: `Duplicate attachment ID: ${file.id}`,
+ });
+ }
+ seenIds.push(file.id);
+ }
- const attachments = await Promise.all(
- payload.files.map(async (attachment) => {
- attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, "");
- const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`;
- const newAttachment = CloudAttachment.create({
- user: user,
- channel: channel,
- uploadFilename: uploadFilename,
- userAttachmentId: attachment.id ?? "0",
- userFilename: attachment.filename,
- userFileSize: attachment.file_size,
- userIsClip: attachment.is_clip,
- userOriginalContentType: attachment.original_content_type,
- });
- await newAttachment.save();
- return newAttachment;
- }),
- );
+ const attachments = await Promise.all(
+ payload.files.map(async (attachment) => {
+ attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, "");
+ const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`;
+ const newAttachment = CloudAttachment.create({
+ user: user,
+ channel: channel,
+ uploadFilename: uploadFilename,
+ userAttachmentId: attachment.id ?? "0",
+ userFilename: attachment.filename,
+ userFileSize: attachment.file_size,
+ userIsClip: attachment.is_clip,
+ userOriginalContentType: attachment.original_content_type,
+ });
+ await newAttachment.save();
+ return newAttachment;
+ }),
+ );
- res.send({
- attachments: attachments.map((a) => {
- return {
- id: a.userAttachmentId,
- upload_filename: a.uploadFilename,
- upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`,
- original_content_type: a.userOriginalContentType,
- };
- }),
- } as UploadAttachmentResponseSchema);
- },
+ res.send({
+ attachments: attachments.map((a) => {
+ return {
+ id: a.userAttachmentId,
+ upload_filename: a.uploadFilename,
+ upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`,
+ original_content_type: a.userOriginalContentType,
+ };
+ }),
+ } as UploadAttachmentResponseSchema);
+ },
);
router.delete("/:cloud_attachment_url", async (req: Request, res: Response) => {
- const { channel_id, cloud_attachment_url } = req.params;
+ const { channel_id, cloud_attachment_url } = req.params;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } });
- if (att.userId !== user.id) {
- return res.status(403).json({
- code: 403,
- message: "You do not own this attachment.",
- });
- }
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } });
+ if (att.userId !== user.id) {
+ return res.status(403).json({
+ code: 403,
+ message: "You do not own this attachment.",
+ });
+ }
- if (att.channelId !== channel.id) {
- return res.status(400).json({
- code: 400,
- message: "Attachment does not belong to this channel.",
- });
- }
+ if (att.channelId !== channel.id) {
+ return res.status(400).json({
+ code: 400,
+ message: "Attachment does not belong to this channel.",
+ });
+ }
- const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, {
- headers: {
- signature: Config.get().security.requestSignature,
- },
- method: "DELETE",
- });
+ const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, {
+ headers: {
+ signature: Config.get().security.requestSignature,
+ },
+ method: "DELETE",
+ });
- await att.remove();
- return res.status(response.status).send(response.body);
+ await att.remove();
+ return res.status(response.status).send(response.body);
});
export default router;
diff --git a/src/api/routes/channels/#channel_id/directory-entries.ts b/src/api/routes/channels/#channel_id/directory-entries.ts
index c07b1f75..da950502 100644
--- a/src/api/routes/channels/#channel_id/directory-entries.ts
+++ b/src/api/routes/channels/#channel_id/directory-entries.ts
@@ -22,20 +22,20 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "HubDirectoryEntriesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json([] as HubDirectoryEntriesResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "HubDirectoryEntriesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json([] as HubDirectoryEntriesResponse);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/greet.ts b/src/api/routes/channels/#channel_id/greet.ts
index b6ce1ee5..f78f26ab 100644
--- a/src/api/routes/channels/#channel_id/greet.ts
+++ b/src/api/routes/channels/#channel_id/greet.ts
@@ -25,83 +25,83 @@ import { GreetRequestSchema, MessageType } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "GreetRequestSchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Message",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as GreetRequestSchema;
- const { channel_id } = req.params;
+ "/",
+ route({
+ requestBody: "GreetRequestSchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as GreetRequestSchema;
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- const targetMessage = await Message.findOneOrFail({
- where: {
- id: payload.message_reference?.message_id,
- channel_id: payload.message_reference?.channel_id,
- guild_id: payload.message_reference?.guild_id,
- },
- });
+ const targetMessage = await Message.findOneOrFail({
+ where: {
+ id: payload.message_reference?.message_id,
+ channel_id: payload.message_reference?.channel_id,
+ guild_id: payload.message_reference?.guild_id,
+ },
+ });
- if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN)
- return res.status(400).json({
- code: 400, // TODO: what's the actual error code?
- message: "Cannot send greet message referencing this message.",
- });
+ if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN)
+ return res.status(400).json({
+ code: 400, // TODO: what's the actual error code?
+ message: "Cannot send greet message referencing this message.",
+ });
- if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) {
- return res.status(403).json({
- code: 403,
- message: "Missing Permissions: SEND_MESSAGES",
- });
- }
+ if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) {
+ return res.status(403).json({
+ code: 403,
+ message: "Missing Permissions: SEND_MESSAGES",
+ });
+ }
- const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick
- if (specCompliant && payload.sticker_ids.length != 1)
- return res.status(400).json({
- code: 400,
- message: "Must include exactly one sticker.",
- });
+ const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick
+ if (specCompliant && payload.sticker_ids.length != 1)
+ return res.status(400).json({
+ code: 400,
+ message: "Must include exactly one sticker.",
+ });
- const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } });
+ const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } });
- const randomSticker = stickers[Math.floor(Math.random() * stickers.length)];
+ const randomSticker = stickers[Math.floor(Math.random() * stickers.length)];
- const message = Message.create({
- channel_id: channel_id,
- author_id: req.user_id,
- type: MessageType.REPLY,
- message_reference: { ...payload.message_reference, type: 0 },
- referenced_message: targetMessage,
- sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [],
- });
+ const message = Message.create({
+ channel_id: channel_id,
+ author_id: req.user_id,
+ type: MessageType.REPLY,
+ message_reference: { ...payload.message_reference, type: 0 },
+ referenced_message: targetMessage,
+ sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [],
+ });
- channel.last_message_id = message.id;
+ channel.last_message_id = message.id;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- data: message,
- channel_id,
- } as MessageCreateEvent),
- channel.save(),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ data: message,
+ channel_id,
+ } as MessageCreateEvent),
+ channel.save(),
+ ]);
- res.send(channel);
- },
+ res.send(channel);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 97857da6..37909a94 100644
--- a/src/api/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -26,132 +26,132 @@ const router: Router = Router({ mergeParams: true });
// TODO: Get channel
router.get(
- "/",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) return res.send(channel);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) return res.send(channel);
- channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
- return res.send(channel);
- },
+ channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
+ return res.send(channel);
+ },
);
router.delete(
- "/",
- route({
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
- if (channel.type === ChannelType.DM) {
- const recipient = await Recipient.findOneOrFail({
- where: { channel_id: channel_id, user_id: req.user_id },
- });
- recipient.closed = true;
- await Promise.all([
- recipient.save(),
- emitEvent({
- event: "CHANNEL_DELETE",
- data: channel,
- user_id: req.user_id,
- } as ChannelDeleteEvent),
- ]);
- } else if (channel.type === ChannelType.GROUP_DM) {
- await Channel.removeRecipientFromChannel(channel, req.user_id);
- } else {
- if (channel.type == ChannelType.GUILD_CATEGORY) {
- const channels = await Channel.find({
- where: { parent_id: channel_id },
- });
- for await (const c of channels) {
- c.parent_id = null;
+ if (channel.type === ChannelType.DM) {
+ const recipient = await Recipient.findOneOrFail({
+ where: { channel_id: channel_id, user_id: req.user_id },
+ });
+ recipient.closed = true;
+ await Promise.all([
+ recipient.save(),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ user_id: req.user_id,
+ } as ChannelDeleteEvent),
+ ]);
+ } else if (channel.type === ChannelType.GROUP_DM) {
+ await Channel.removeRecipientFromChannel(channel, req.user_id);
+ } else {
+ if (channel.type == ChannelType.GUILD_CATEGORY) {
+ const channels = await Channel.find({
+ where: { parent_id: channel_id },
+ });
+ for await (const c of channels) {
+ c.parent_id = null;
- await Promise.all([
- c.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- data: c,
- channel_id: c.id,
- } as ChannelUpdateEvent),
- ]);
- }
- }
+ await Promise.all([
+ c.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: c,
+ channel_id: c.id,
+ } as ChannelUpdateEvent),
+ ]);
+ }
+ }
- await Promise.all([
- Channel.deleteChannel(channel),
- emitEvent({
- event: "CHANNEL_DELETE",
- data: channel,
- channel_id,
- } as ChannelDeleteEvent),
- ]);
- }
+ await Promise.all([
+ Channel.deleteChannel(channel),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ channel_id,
+ } as ChannelDeleteEvent),
+ ]);
+ }
- res.send(channel);
- },
+ res.send(channel);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "ChannelModifySchema",
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "Channel",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const payload = req.body as ChannelModifySchema;
- const { channel_id } = req.params;
- if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
+ "/",
+ route({
+ requestBody: "ChannelModifySchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Channel",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as ChannelModifySchema;
+ const { channel_id } = req.params;
+ if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- channel.assign(payload);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ channel.assign(payload);
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id,
+ } as ChannelUpdateEvent),
+ ]);
- res.send(channel);
- },
+ res.send(channel);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 298f42ed..808f0f8c 100644
--- a/src/api/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -25,96 +25,96 @@ import { InviteCreateSchema, isTextChannel } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "InviteCreateSchema",
- permission: "CREATE_INSTANT_INVITE",
- right: "CREATE_INVITES",
- responses: {
- 201: {
- body: "Invite",
- },
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req;
- const body = req.body as InviteCreateSchema;
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- select: ["id", "name", "type", "guild_id"],
- });
- isTextChannel(channel.type);
+ "/",
+ route({
+ requestBody: "InviteCreateSchema",
+ permission: "CREATE_INSTANT_INVITE",
+ right: "CREATE_INVITES",
+ responses: {
+ 201: {
+ body: "Invite",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req;
+ const body = req.body as InviteCreateSchema;
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ select: ["id", "name", "type", "guild_id"],
+ });
+ isTextChannel(channel.type);
- if (!channel.guild_id) {
- throw new HTTPError("This channel doesn't exist", 404);
- }
- const { guild_id } = channel;
+ if (!channel.guild_id) {
+ throw new HTTPError("This channel doesn't exist", 404);
+ }
+ const { guild_id } = channel;
- const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
+ const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now());
- const invite = await Invite.create({
- code: randomString(),
- temporary: body.temporary || true,
- uses: 0,
- max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0,
- max_age: body.max_age ? Math.max(0, body.max_age) : 0,
- expires_at,
- created_at: new Date(),
- guild_id,
- channel_id: channel_id,
- inviter_id: user_id,
- flags: body.flags ?? 0,
- }).save();
+ const invite = await Invite.create({
+ code: randomString(),
+ temporary: body.temporary || true,
+ uses: 0,
+ max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0,
+ max_age: body.max_age ? Math.max(0, body.max_age) : 0,
+ expires_at,
+ created_at: new Date(),
+ guild_id,
+ channel_id: channel_id,
+ inviter_id: user_id,
+ flags: body.flags ?? 0,
+ }).save();
- const data = invite.toJSON();
- data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser();
- data.guild = await Guild.findOne({ where: { id: guild_id } });
- data.channel = channel;
+ const data = invite.toJSON();
+ data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser();
+ data.guild = await Guild.findOne({ where: { id: guild_id } });
+ data.channel = channel;
- await emitEvent({
- event: "INVITE_CREATE",
- data,
- guild_id,
- } as InviteCreateEvent);
+ await emitEvent({
+ event: "INVITE_CREATE",
+ data,
+ guild_id,
+ } as InviteCreateEvent);
- res.status(201).send(data);
- },
+ res.status(201).send(data);
+ },
);
router.get(
- "/",
- route({
- permission: "MANAGE_CHANNELS",
- responses: {
- 200: {
- body: "APIInviteArray",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "APIInviteArray",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) {
- throw new HTTPError("This channel doesn't exist", 404);
- }
- const { guild_id } = channel;
+ if (!channel.guild_id) {
+ throw new HTTPError("This channel doesn't exist", 404);
+ }
+ const { guild_id } = channel;
- const invites = await Invite.find({
- where: { guild_id, channel_id },
- relations: PublicInviteRelation,
- });
+ const invites = await Invite.find({
+ where: { guild_id, channel_id },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invites);
- },
+ res.status(200).send(invites);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index c7c96fea..7b9f0397 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -27,42 +27,42 @@ const router = Router({ mergeParams: true });
// TODO: advance-only notification cursor
router.post(
- "/",
- route({
- requestBody: "MessageAcknowledgeSchema",
- responses: {
- 200: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/",
+ route({
+ requestBody: "MessageAcknowledgeSchema",
+ responses: {
+ 200: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const permission = await getPermission(req.user_id, undefined, channel_id);
- permission.hasThrow("VIEW_CHANNEL");
+ const permission = await getPermission(req.user_id, undefined, channel_id);
+ permission.hasThrow("VIEW_CHANNEL");
- let read_state = await ReadState.findOne({
- where: { user_id: req.user_id, channel_id },
- });
- if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
- read_state.last_message_id = message_id;
- //It's a little more complicated but this'll do :P
- read_state.mention_count = 0;
+ let read_state = await ReadState.findOne({
+ where: { user_id: req.user_id, channel_id },
+ });
+ if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
+ read_state.last_message_id = message_id;
+ //It's a little more complicated but this'll do :P
+ read_state.mention_count = 0;
- await read_state.save();
+ await read_state.save();
- await emitEvent({
- event: "MESSAGE_ACK",
- user_id: req.user_id,
- data: {
- channel_id,
- message_id,
- version: 3763,
- },
- } as MessageAckEvent);
+ await emitEvent({
+ event: "MESSAGE_ACK",
+ user_id: req.user_id,
+ data: {
+ channel_id,
+ message_id,
+ version: 3763,
+ },
+ } as MessageAckEvent);
- res.json({ token: null });
- },
+ res.json({ token: null });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index 6d89c504..2aad157d 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -22,43 +22,43 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.json({
- id: "",
- type: 0,
- content: "",
- channel_id: "",
- author: {
- id: "",
- username: "",
- avatar: "",
- discriminator: "",
- public_flags: 64,
- },
- attachments: [],
- embeds: [],
- mentions: [],
- mention_roles: [],
- pinned: false,
- mention_everyone: false,
- tts: false,
- timestamp: "",
- edited_timestamp: null,
- flags: 1,
- components: [],
- poll: {},
- }).status(200);
- },
+ "/",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.json({
+ id: "",
+ type: 0,
+ content: "",
+ channel_id: "",
+ author: {
+ id: "",
+ username: "",
+ avatar: "",
+ discriminator: "",
+ public_flags: 64,
+ },
+ attachments: [],
+ embeds: [],
+ mentions: [],
+ mention_roles: [],
+ pinned: false,
+ mention_everyone: false,
+ tts: false,
+ timestamp: "",
+ edited_timestamp: null,
+ flags: 1,
+ components: [],
+ poll: {},
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index cdba9b16..64f29c93 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -17,19 +17,19 @@
*/
import {
- Attachment,
- Channel,
- Message,
- MessageCreateEvent,
- MessageDeleteEvent,
- MessageUpdateEvent,
- Snowflake,
- SpacebarApiErrors,
- emitEvent,
- getPermission,
- getRights,
- uploadFile,
- NewUrlUserSignatureData,
+ Attachment,
+ Channel,
+ Message,
+ MessageCreateEvent,
+ MessageDeleteEvent,
+ MessageUpdateEvent,
+ Snowflake,
+ SpacebarApiErrors,
+ emitEvent,
+ getPermission,
+ getRights,
+ uploadFile,
+ NewUrlUserSignatureData,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -41,285 +41,285 @@ const router = Router({ mergeParams: true });
// TODO: message content/embed string length limit
const messageUpload = multer({
- limits: {
- fileSize: 1024 * 1024 * 100,
- fields: 10,
- files: 1,
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: 1024 * 1024 * 100,
+ fields: 10,
+ files: 1,
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
router.patch(
- "/",
- route({
- requestBody: "MessageEditSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- let body = req.body as MessageEditSchema;
+ "/",
+ route({
+ requestBody: "MessageEditSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ let body = req.body as MessageEditSchema;
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- relations: ["attachments"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- if (req.user_id !== message.author_id) {
- if (!rights.has("MANAGE_MESSAGES")) {
- permissions.hasThrow("MANAGE_MESSAGES");
- body = { flags: body.flags };
- // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
- }
- } else rights.hasThrow("SELF_EDIT_MESSAGES");
+ if (req.user_id !== message.author_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ permissions.hasThrow("MANAGE_MESSAGES");
+ body = { flags: body.flags };
+ // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
+ }
+ } else rights.hasThrow("SELF_EDIT_MESSAGES");
- // no longer necessary, somehow resolved by updating the type of `attachments`...?
- // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema
- const new_message = await handleMessage({
- ...message,
- // TODO: should message_reference be overridable?
- message_reference: message.message_reference,
- ...body,
- author_id: message.author_id,
- channel_id,
- id: message_id,
- edited_timestamp: new Date(),
- });
+ // no longer necessary, somehow resolved by updating the type of `attachments`...?
+ // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema
+ const new_message = await handleMessage({
+ ...message,
+ // TODO: should message_reference be overridable?
+ message_reference: message.message_reference,
+ ...body,
+ author_id: message.author_id,
+ channel_id,
+ id: message_id,
+ edited_timestamp: new Date(),
+ });
- await Promise.all([
- new_message.save(),
- await emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: {
- ...new_message.toJSON(),
- nonce: undefined,
- member: new_message.member?.toPublicMember(),
- },
- } as MessageUpdateEvent),
- ]);
+ await Promise.all([
+ new_message.save(),
+ await emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: {
+ ...new_message.toJSON(),
+ nonce: undefined,
+ member: new_message.member?.toPublicMember(),
+ },
+ } as MessageUpdateEvent),
+ ]);
- postHandleMessage(new_message);
+ postHandleMessage(new_message);
- // TODO: a DTO?
- return res.json({
- ...new_message.toJSON(),
- id: new_message.id,
- type: new_message.type,
- channel_id: new_message.channel_id,
- member: new_message.member?.toPublicMember(),
- author: new_message.author?.toPublicUser(),
- attachments: new_message.attachments,
- embeds: new_message.embeds,
- mentions: new_message.embeds,
- mention_roles: new_message.mention_roles,
- mention_everyone: new_message.mention_everyone,
- pinned: new_message.pinned,
- timestamp: new_message.timestamp,
- edited_timestamp: new_message.edited_timestamp,
+ // TODO: a DTO?
+ return res.json({
+ ...new_message.toJSON(),
+ id: new_message.id,
+ type: new_message.type,
+ channel_id: new_message.channel_id,
+ member: new_message.member?.toPublicMember(),
+ author: new_message.author?.toPublicUser(),
+ attachments: new_message.attachments,
+ embeds: new_message.embeds,
+ mentions: new_message.embeds,
+ mention_roles: new_message.mention_roles,
+ mention_everyone: new_message.mention_everyone,
+ pinned: new_message.pinned,
+ timestamp: new_message.timestamp,
+ edited_timestamp: new_message.edited_timestamp,
- // these are not in the Discord.com response
- mention_channels: new_message.mention_channels,
- });
- },
+ // these are not in the Discord.com response
+ mention_channels: new_message.mention_channels,
+ });
+ },
);
// Backfill message with specific timestamp
router.put(
- "/",
- messageUpload.single("file"),
- async (req, res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.single("file"),
+ async (req, res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "MessageCreateSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_BACKDATED_EVENTS",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
- const body = req.body as MessageCreateSchema;
- const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
+ next();
+ },
+ route({
+ requestBody: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_BACKDATED_EVENTS",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+ const body = req.body as MessageCreateSchema;
+ const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
- const rights = await getRights(req.user_id);
- rights.hasThrow("SEND_MESSAGES");
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SEND_MESSAGES");
- // regex to check if message contains anything other than numerals ( also no decimals )
- if (!message_id.match(/^\+?\d+$/)) {
- throw new HTTPError("Message IDs must be positive integers", 400);
- }
+ // regex to check if message contains anything other than numerals ( also no decimals )
+ if (!message_id.match(/^\+?\d+$/)) {
+ throw new HTTPError("Message IDs must be positive integers", 400);
+ }
- const snowflake = Snowflake.deconstruct(message_id);
- if (Date.now() < snowflake.timestamp) {
- // message is in the future
- throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
- }
+ const snowflake = Snowflake.deconstruct(message_id);
+ if (Date.now() < snowflake.timestamp) {
+ // message is in the future
+ throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
+ }
- const exists = await Message.findOne({
- where: { id: message_id, channel_id: channel_id },
- });
- if (exists) {
- throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL;
- }
+ const exists = await Message.findOne({
+ where: { id: message_id, channel_id: channel_id },
+ });
+ if (exists) {
+ throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL;
+ }
- if (req.file) {
- try {
- const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- return res.status(400).json(error);
- }
- }
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients", "recipients.user"],
- });
+ if (req.file) {
+ try {
+ const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ return res.status(400).json(error);
+ }
+ }
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
- const embeds = body.embeds || [];
- if (body.embed) embeds.push(body.embed);
- const message = await handleMessage({
- ...body,
- type: 0,
- pinned: false,
- author_id: req.user_id,
- id: message_id,
- embeds,
- channel_id,
- attachments,
- edited_timestamp: undefined,
- timestamp: new Date(snowflake.timestamp),
- });
+ const embeds = body.embeds || [];
+ if (body.embed) embeds.push(body.embed);
+ const message = await handleMessage({
+ ...body,
+ type: 0,
+ pinned: false,
+ author_id: req.user_id,
+ id: message_id,
+ embeds,
+ channel_id,
+ attachments,
+ edited_timestamp: undefined,
+ timestamp: new Date(snowflake.timestamp),
+ });
- //Fix for the client bug
- delete message.member;
+ //Fix for the client bug
+ delete message.member;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: channel_id,
- data: message,
- } as MessageCreateEvent),
- channel.save(),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ channel.save(),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return res.json(
- message.withSignedAttachments(
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- );
- },
+ return res.json(
+ message.withSignedAttachments(
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ );
+ },
);
router.get(
- "/",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- relations: ["attachments"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ relations: ["attachments"],
+ });
- const permissions = await getPermission(req.user_id, undefined, channel_id);
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
- if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
+ if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
- return res.json(message);
- },
+ return res.json(message);
+ },
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
- const rights = await getRights(req.user_id);
+ const rights = await getRights(req.user_id);
- if (message.author_id !== req.user_id) {
- if (!rights.has("MANAGE_MESSAGES")) {
- const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
- permission.hasThrow("MANAGE_MESSAGES");
- }
- } else rights.hasThrow("SELF_DELETE_MESSAGES");
+ if (message.author_id !== req.user_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permission.hasThrow("MANAGE_MESSAGES");
+ }
+ } else rights.hasThrow("SELF_DELETE_MESSAGES");
- await Message.delete({ id: message_id });
+ await Message.delete({ id: message_id });
- await emitEvent({
- event: "MESSAGE_DELETE",
- channel_id,
- data: {
- id: message_id,
- channel_id,
- guild_id: channel.guild_id,
- },
- } as MessageDeleteEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE",
+ channel_id,
+ data: {
+ id: message_id,
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index 63c41cd7..b21dbd34 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -18,18 +18,18 @@
import { route } from "@spacebar/api";
import {
- Channel,
- emitEvent,
- Emoji,
- getPermission,
- Member,
- Message,
- MessageReactionAddEvent,
- MessageReactionRemoveAllEvent,
- MessageReactionRemoveEmojiEvent,
- MessageReactionRemoveEvent,
- User,
- arrayRemove,
+ Channel,
+ emitEvent,
+ Emoji,
+ getPermission,
+ Member,
+ Message,
+ MessageReactionAddEvent,
+ MessageReactionRemoveAllEvent,
+ MessageReactionRemoveEmojiEvent,
+ MessageReactionRemoveEvent,
+ User,
+ arrayRemove,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -40,326 +40,326 @@ const router = Router({ mergeParams: true });
// TODO: check if emoji is really an unicode emoji or a properly encoded external emoji
function getEmoji(emoji: string): PartialEmoji {
- emoji = decodeURIComponent(emoji);
- const parts = emoji.includes(":") && emoji.split(":");
- if (parts)
- return {
- name: parts[0],
- id: parts[1],
- };
+ emoji = decodeURIComponent(emoji);
+ const parts = emoji.includes(":") && emoji.split(":");
+ if (parts)
+ return {
+ name: parts[0],
+ id: parts[1],
+ };
- return {
- id: undefined,
- name: emoji,
- };
+ return {
+ id: undefined,
+ name: emoji,
+ };
}
router.delete(
- "/",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- await Message.update({ id: message_id, channel_id }, { reactions: [] });
+ await Message.update({ id: message_id, channel_id }, { reactions: [] });
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE_ALL",
- channel_id,
- data: {
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- },
- } as MessageReactionRemoveAllEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_ALL",
+ channel_id,
+ data: {
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageReactionRemoveAllEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji",
- route({
- permission: "MANAGE_MESSAGES",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ "/:emoji",
+ route({
+ permission: "MANAGE_MESSAGES",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added) throw new HTTPError("Reaction not found", 404);
- arrayRemove(message.reactions, already_added);
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ if (!already_added) throw new HTTPError("Reaction not found", 404);
+ arrayRemove(message.reactions, already_added);
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_REACTION_REMOVE_EMOJI",
- channel_id,
- data: {
- channel_id,
- message_id,
- guild_id: message.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEmojiEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_EMOJI",
+ channel_id,
+ data: {
+ channel_id,
+ message_id,
+ guild_id: message.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEmojiEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/:emoji",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 200: {
- body: "PublicUser",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ "/:emoji",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 200: {
+ body: "PublicUser",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ const emoji = getEmoji(req.params.emoji);
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
- const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!reaction) throw new HTTPError("Reaction not found", 404);
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ if (!reaction) throw new HTTPError("Reaction not found", 404);
- const users = (
- await User.find({
- where: {
- id: In(reaction.user_ids),
- },
- select: PublicUserProjection,
- })
- ).map((user) => user.toPublicUser());
+ const users = (
+ await User.find({
+ where: {
+ id: In(reaction.user_ids),
+ },
+ select: PublicUserProjection,
+ })
+ ).map((user) => user.toPublicUser());
- res.json(users);
- },
+ res.json(users);
+ },
);
router.put(
- "/:emoji/:user_id",
- route({
- permission: "READ_MESSAGE_HISTORY",
- right: "SELF_ADD_REACTIONS",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { message_id, channel_id, user_id } = req.params;
- if (user_id !== "@me") throw new HTTPError("Invalid user");
- const emoji = getEmoji(req.params.emoji);
+ "/:emoji/:user_id",
+ route({
+ permission: "READ_MESSAGE_HISTORY",
+ right: "SELF_ADD_REACTIONS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id, user_id } = req.params;
+ if (user_id !== "@me") throw new HTTPError("Invalid user");
+ const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
+ if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
- if (emoji.id) {
- const external_emoji = await Emoji.findOneOrFail({
- where: { id: emoji.id },
- });
- if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS");
- emoji.animated = external_emoji.animated;
- emoji.name = external_emoji.name;
- }
+ if (emoji.id) {
+ const external_emoji = await Emoji.findOneOrFail({
+ where: { id: emoji.id },
+ });
+ if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS");
+ emoji.animated = external_emoji.animated;
+ emoji.name = external_emoji.name;
+ }
- if (already_added) {
- if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
- already_added.count++;
- already_added.user_ids.push(req.user_id);
- } else
- message.reactions.push({
- count: 1,
- emoji,
- user_ids: [req.user_id],
- });
+ if (already_added) {
+ if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error
+ already_added.count++;
+ already_added.user_ids.push(req.user_id);
+ } else
+ message.reactions.push({
+ count: 1,
+ emoji,
+ user_ids: [req.user_id],
+ });
- await message.save();
+ await message.save();
- const member =
- channel.guild_id &&
- (
- await Member.findOneOrFail({
- where: { id: req.user_id },
- select: PublicMemberProjection,
- })
- ).toPublicMember();
+ const member =
+ channel.guild_id &&
+ (
+ await Member.findOneOrFail({
+ where: { id: req.user_id },
+ select: PublicMemberProjection,
+ })
+ ).toPublicMember();
- await emitEvent({
- event: "MESSAGE_REACTION_ADD",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- member,
- },
- } as MessageReactionAddEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_ADD",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ member,
+ },
+ } as MessageReactionAddEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- let { user_id } = req.params;
- const { message_id, channel_id } = req.params;
+ "/:emoji/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ let { user_id } = req.params;
+ const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- if (user_id === "@me") user_id = req.user_id;
- else {
- const permissions = await getPermission(req.user_id, undefined, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- }
+ if (user_id === "@me") user_id = req.user_id;
+ else {
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
+ permissions.hasThrow("MANAGE_MESSAGES");
+ }
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
- already_added.count--;
+ already_added.count--;
- if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
- else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
+ if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
+ else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
- await message.save();
+ await message.save();
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:emoji/:burst/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- let { user_id } = req.params;
- const { message_id, channel_id } = req.params;
+ "/:emoji/:burst/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ let { user_id } = req.params;
+ const { message_id, channel_id } = req.params;
- const emoji = getEmoji(req.params.emoji);
+ const emoji = getEmoji(req.params.emoji);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const message = await Message.findOneOrFail({
- where: { id: message_id, channel_id },
- });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id, channel_id },
+ });
- if (user_id === "@me") user_id = req.user_id;
- else {
- const permissions = await getPermission(req.user_id, undefined, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- }
+ if (user_id === "@me") user_id = req.user_id;
+ else {
+ const permissions = await getPermission(req.user_id, undefined, channel_id);
+ permissions.hasThrow("MANAGE_MESSAGES");
+ }
- const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
- if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
+ const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name);
+ if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404);
- already_added.count--;
+ already_added.count--;
- if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
- else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
+ if (already_added.count <= 0) arrayRemove(message.reactions, already_added);
+ else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1);
- await message.save();
+ await message.save();
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
- channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji,
- },
- } as MessageReactionRemoveEvent);
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
+ channel_id,
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 54801a48..f4645abd 100644
--- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -29,48 +29,48 @@ export default router;
// should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post(
- "/",
- route({
- requestBody: "BulkDeleteSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
+ "/",
+ route({
+ requestBody: "BulkDeleteSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400);
- const rights = await getRights(req.user_id);
- rights.hasThrow("SELF_DELETE_MESSAGES");
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("SELF_DELETE_MESSAGES");
- const superuser = rights.has("MANAGE_MESSAGES");
- const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
+ const superuser = rights.has("MANAGE_MESSAGES");
+ const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
- const { maxBulkDelete } = Config.get().limits.message;
+ const { maxBulkDelete } = Config.get().limits.message;
- const { messages } = req.body as { messages: string[] };
- if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
- if (!superuser) {
- permission.hasThrow("MANAGE_MESSAGES");
- if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
- }
+ const { messages } = req.body as { messages: string[] };
+ if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete");
+ if (!superuser) {
+ permission.hasThrow("MANAGE_MESSAGES");
+ if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`);
+ }
- await Message.delete(messages);
+ await Message.delete(messages);
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: { ids: messages, channel_id, guild_id: channel.guild_id },
- } as MessageDeleteBulkEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: { ids: messages, channel_id, guild_id: channel.guild_id },
+ } as MessageDeleteBulkEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 2ce37748..272fa8ac 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -18,29 +18,29 @@
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import {
- Attachment,
- AutomodRule,
- AutomodTriggerTypes,
- Channel,
- Config,
- DiscordApiErrors,
- DmChannelDTO,
- emitEvent,
- FieldErrors,
- getPermission,
- getUrlSignature,
- Member,
- Message,
- MessageCreateEvent,
- NewUrlSignatureData,
- NewUrlUserSignatureData,
- ReadState,
- Relationship,
- Rights,
- Snowflake,
- stringGlobToRegexp,
- uploadFile,
- User,
+ Attachment,
+ AutomodRule,
+ AutomodTriggerTypes,
+ Channel,
+ Config,
+ DiscordApiErrors,
+ DmChannelDTO,
+ emitEvent,
+ FieldErrors,
+ getPermission,
+ getUrlSignature,
+ Member,
+ Message,
+ MessageCreateEvent,
+ NewUrlSignatureData,
+ NewUrlUserSignatureData,
+ ReadState,
+ Relationship,
+ Rights,
+ Snowflake,
+ stringGlobToRegexp,
+ uploadFile,
+ User,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
@@ -48,17 +48,17 @@ import multer from "multer";
import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm";
import { URL } from "url";
import {
- AcknowledgeDeleteSchema,
- AutomodCustomWordsRule,
- AutomodRuleActionType,
- AutomodRuleEventType,
- isTextChannel,
- MessageCreateAttachment,
- MessageCreateCloudAttachment,
- MessageCreateSchema,
- Reaction,
- ReadStateType,
- RelationshipType,
+ AcknowledgeDeleteSchema,
+ AutomodCustomWordsRule,
+ AutomodRuleActionType,
+ AutomodRuleEventType,
+ isTextChannel,
+ MessageCreateAttachment,
+ MessageCreateCloudAttachment,
+ MessageCreateSchema,
+ Reaction,
+ ReadStateType,
+ RelationshipType,
} from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
@@ -66,221 +66,221 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/channel#create-message
// get messages
router.get(
- "/",
- route({
- query: {
- around: {
- type: "string",
- },
- before: {
- type: "string",
- },
- after: {
- type: "string",
- },
- limit: {
- type: "number",
- description: "max number of messages to return (1-100). defaults to 50",
- },
- },
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel) throw new HTTPError("Channel not found", 404);
+ "/",
+ route({
+ query: {
+ around: {
+ type: "string",
+ },
+ before: {
+ type: "string",
+ },
+ after: {
+ type: "string",
+ },
+ limit: {
+ type: "number",
+ description: "max number of messages to return (1-100). defaults to 50",
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const channel_id = req.params.channel_id;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel) throw new HTTPError("Channel not found", 404);
- isTextChannel(channel.type);
- const around = req.query.around ? `${req.query.around}` : undefined;
- const before = req.query.before ? `${req.query.before}` : undefined;
- const after = req.query.after ? `${req.query.after}` : undefined;
- const limit = Number(req.query.limit) || 50;
- if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ isTextChannel(channel.type);
+ const around = req.query.around ? `${req.query.around}` : undefined;
+ const before = req.query.before ? `${req.query.before}` : undefined;
+ const after = req.query.after ? `${req.query.after}` : undefined;
+ const limit = Number(req.query.limit) || 50;
+ if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
+ const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
- const query: FindManyOptions<Message> & {
- where: { id?: FindOperator<string> | FindOperator<string>[] };
- } = {
- order: { timestamp: "DESC" },
- take: limit,
- where: { channel_id },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- "referenced_message",
- "referenced_message.author",
- "referenced_message.webhook",
- "referenced_message.application",
- "referenced_message.mentions",
- "referenced_message.mention_roles",
- "referenced_message.mention_channels",
- "referenced_message.sticker_items",
- "referenced_message.attachments",
- ],
- };
+ const query: FindManyOptions<Message> & {
+ where: { id?: FindOperator<string> | FindOperator<string>[] };
+ } = {
+ order: { timestamp: "DESC" },
+ take: limit,
+ where: { channel_id },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ "referenced_message",
+ "referenced_message.author",
+ "referenced_message.webhook",
+ "referenced_message.application",
+ "referenced_message.mentions",
+ "referenced_message.mention_roles",
+ "referenced_message.mention_channels",
+ "referenced_message.sticker_items",
+ "referenced_message.attachments",
+ ],
+ };
- let messages: Message[];
+ let messages: Message[];
- if (around) {
- query.take = Math.floor(limit / 2);
- if (query.take != 0) {
- const [right, left] = await Promise.all([
- Message.find({
- ...query,
- where: { channel_id, id: LessThan(around) },
- }),
- Message.find({
- ...query,
- where: { channel_id, id: MoreThanOrEqual(around) },
- order: { timestamp: "ASC" },
- }),
- ]);
- left.push(...right);
- messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
- } else {
- query.take = 1;
- const message = await Message.findOne({
- ...query,
- where: { channel_id, id: around },
- });
- messages = message ? [message] : [];
- }
- } else {
- if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422);
+ if (around) {
+ query.take = Math.floor(limit / 2);
+ if (query.take != 0) {
+ const [right, left] = await Promise.all([
+ Message.find({
+ ...query,
+ where: { channel_id, id: LessThan(around) },
+ }),
+ Message.find({
+ ...query,
+ where: { channel_id, id: MoreThanOrEqual(around) },
+ order: { timestamp: "ASC" },
+ }),
+ ]);
+ left.push(...right);
+ messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
+ } else {
+ query.take = 1;
+ const message = await Message.findOne({
+ ...query,
+ where: { channel_id, id: around },
+ });
+ messages = message ? [message] : [];
+ }
+ } else {
+ if (after) {
+ if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422);
- query.where.id = MoreThan(after);
- query.order = { timestamp: "ASC" };
- } else if (before) {
- if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422);
+ query.where.id = MoreThan(after);
+ query.order = { timestamp: "ASC" };
+ } else if (before) {
+ if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422);
- query.where.id = LessThan(before);
- }
+ query.where.id = LessThan(before);
+ }
- messages = await Message.find(query);
- }
+ messages = await Message.find(query);
+ }
- const endpoint = Config.get().cdn.endpointPublic;
+ const endpoint = Config.get().cdn.endpointPublic;
- const ret = messages.map((x: Message) => {
- x = x.toJSON();
+ const ret = messages.map((x: Message) => {
+ x = x.toJSON();
- (x.reactions || []).forEach((y: Partial<Reaction>) => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- if ((y.user_ids || []).includes(req.user_id)) y.me = true;
- delete y.user_ids;
- });
- if (!x.author)
- x.author = User.create({
- id: "4",
- discriminator: "0000",
- username: "Spacebar Ghost",
- public_flags: 0,
- });
- x.attachments?.forEach((y: Attachment) => {
- // dynamically set attachment proxy_url in case the endpoint changed
- const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
+ (x.reactions || []).forEach((y: Partial<Reaction>) => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ if ((y.user_ids || []).includes(req.user_id)) y.me = true;
+ delete y.user_ids;
+ });
+ if (!x.author)
+ x.author = User.create({
+ id: "4",
+ discriminator: "0000",
+ username: "Spacebar Ghost",
+ public_flags: 0,
+ });
+ x.attachments?.forEach((y: Attachment) => {
+ // dynamically set attachment proxy_url in case the endpoint changed
+ const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
- const url = new URL(uri);
- if (endpoint) {
- const newBase = new URL(endpoint);
- url.protocol = newBase.protocol;
- url.hostname = newBase.hostname;
- url.port = newBase.port;
- }
+ const url = new URL(uri);
+ if (endpoint) {
+ const newBase = new URL(endpoint);
+ url.protocol = newBase.protocol;
+ url.hostname = newBase.hostname;
+ url.port = newBase.port;
+ }
- y.proxy_url = url.toString();
+ y.proxy_url = url.toString();
- y.proxy_url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.proxy_url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.proxy_url)
- .toString();
+ y.proxy_url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: y.proxy_url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(y.proxy_url)
+ .toString();
- y.url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.url)
- .toString();
- });
+ y.url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: y.url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(y.url)
+ .toString();
+ });
- /**
+ /**
Some clients ( discord.js ) only check if a property exists within the response,
which causes errors when, say, the `application` property is `null`.
**/
- // for (var curr in x) {
- // if (x[curr] === null)
- // delete x[curr];
- // }
+ // for (var curr in x) {
+ // if (x[curr] === null)
+ // delete x[curr];
+ // }
- return x;
- });
+ return x;
+ });
- await Promise.all(
- ret
- .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
- .map(async (x: MessageCreateSchema) => {
- x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
- }),
- );
+ await Promise.all(
+ ret
+ .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user)
+ .map(async (x: MessageCreateSchema) => {
+ x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } });
+ }),
+ );
- // polyfill message references for old messages
- await Promise.all(
- ret
- .filter((msg) => msg.message_reference && !msg.referenced_message?.id)
- .map(async (msg) => {
- const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
- id: msg.message_reference!.message_id,
- };
- if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
- if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
+ // polyfill message references for old messages
+ await Promise.all(
+ ret
+ .filter((msg) => msg.message_reference && !msg.referenced_message?.id)
+ .map(async (msg) => {
+ const whereOptions: { id: string; guild_id?: string; channel_id?: string } = {
+ id: msg.message_reference!.message_id,
+ };
+ if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id;
+ if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id;
- msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
- }),
- );
+ msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
+ }),
+ );
- return res.json(ret);
- },
+ return res.json(ret);
+ },
);
// TODO: config max upload size
const messageUpload = multer({
- limits: {
- fileSize: Config.get().limits.message.maxAttachmentSize,
- fields: 10,
- // files: 1
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: Config.get().limits.message.maxAttachmentSize,
+ fields: 10,
+ // files: 1
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
/**
TODO: dynamically change limit of MessageCreateSchema with config
@@ -292,213 +292,213 @@ const messageUpload = multer({
**/
// Send message
router.post(
- "/",
- messageUpload.any(),
- (req, res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.any(),
+ (req, res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "MessageCreateSchema",
- permission: "SEND_MESSAGES",
- right: "SEND_MESSAGES",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const body = req.body as MessageCreateSchema;
- const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
+ next();
+ },
+ route({
+ requestBody: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const body = req.body as MessageCreateSchema;
+ const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients", "recipients.user"],
- });
- if (!channel.isWritable()) {
- throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
- }
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
+ if (!channel.isWritable()) {
+ throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400);
+ }
- // handle blocked users in dms
- if (channel.recipients?.length == 2) {
- const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user;
- if (otherUser) {
- const relationship = await Relationship.findOne({
- where: [
- { from_id: req.user_id, to_id: otherUser.id },
- { from_id: otherUser.id, to_id: req.user_id },
- ],
- });
+ // handle blocked users in dms
+ if (channel.recipients?.length == 2) {
+ const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user;
+ if (otherUser) {
+ const relationship = await Relationship.findOne({
+ where: [
+ { from_id: req.user_id, to_id: otherUser.id },
+ { from_id: otherUser.id, to_id: req.user_id },
+ ],
+ });
- if (relationship?.type === RelationshipType.blocked) {
- throw DiscordApiErrors.CANNOT_MESSAGE_USER;
- }
- }
- }
+ if (relationship?.type === RelationshipType.blocked) {
+ throw DiscordApiErrors.CANNOT_MESSAGE_USER;
+ }
+ }
+ }
- if (body.nonce) {
- const existing = await Message.findOne({
- where: {
- nonce: body.nonce,
- channel_id: channel.id,
- author_id: req.user_id,
- },
- });
- if (existing) {
- return res.json(existing);
- }
- }
+ if (body.nonce) {
+ const existing = await Message.findOne({
+ where: {
+ nonce: body.nonce,
+ channel_id: channel.id,
+ author_id: req.user_id,
+ },
+ });
+ if (existing) {
+ return res.json(existing);
+ }
+ }
- if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
- const limits = Config.get().limits;
- if (limits.absoluteRate.sendMessage.enabled) {
- const count = await Message.count({
- where: {
- channel_id,
- timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
- },
- });
+ if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
+ const limits = Config.get().limits;
+ if (limits.absoluteRate.sendMessage.enabled) {
+ const count = await Message.count({
+ where: {
+ channel_id,
+ timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
+ },
+ });
- if (count >= limits.absoluteRate.sendMessage.limit)
- throw FieldErrors({
- channel_id: {
- code: "TOO_MANY_MESSAGES",
- message: req.t("common:toomany.MESSAGE"),
- },
- });
- }
- }
+ if (count >= limits.absoluteRate.sendMessage.limit)
+ throw FieldErrors({
+ channel_id: {
+ code: "TOO_MANY_MESSAGES",
+ message: req.t("common:toomany.MESSAGE"),
+ },
+ });
+ }
+ }
- const files = (req.files as Express.Multer.File[]) ?? [];
- for (const currFile of files) {
- try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- return res.status(400).json({ message: error?.toString() });
- }
- }
+ const files = (req.files as Express.Multer.File[]) ?? [];
+ for (const currFile of files) {
+ try {
+ const file = await uploadFile(`/attachments/${channel.id}`, currFile);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ return res.status(400).json({ message: error?.toString() });
+ }
+ }
- const embeds = body.embeds || [];
- if (body.embed) embeds.push(body.embed);
- const message = await handleMessage({
- ...body,
- type: 0,
- pinned: false,
- author_id: req.user_id,
- embeds,
- channel_id,
- attachments,
- timestamp: new Date(),
- });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore dont care2
- message.edited_timestamp = null;
+ const embeds = body.embeds || [];
+ if (body.embed) embeds.push(body.embed);
+ const message = await handleMessage({
+ ...body,
+ type: 0,
+ pinned: false,
+ author_id: req.user_id,
+ embeds,
+ channel_id,
+ attachments,
+ timestamp: new Date(),
+ });
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore dont care2
+ message.edited_timestamp = null;
- channel.last_message_id = message.id;
+ channel.last_message_id = message.id;
- if (channel.isDm()) {
- const channel_dto = await DmChannelDTO.from(channel);
+ if (channel.isDm()) {
+ const channel_dto = await DmChannelDTO.from(channel);
- // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
- await Promise.all(
- channel.recipients?.map((recipient) => {
- if (recipient.closed) {
- recipient.closed = false;
- return Promise.all([
- recipient.save(),
- emitEvent({
- event: "CHANNEL_CREATE",
- data: channel_dto.excludedRecipients([recipient.user_id]),
- user_id: recipient.user_id,
- }),
- ]);
- }
- }) || [],
- );
- }
+ // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
+ await Promise.all(
+ channel.recipients?.map((recipient) => {
+ if (recipient.closed) {
+ recipient.closed = false;
+ return Promise.all([
+ recipient.save(),
+ emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id,
+ }),
+ ]);
+ }
+ }) || [],
+ );
+ }
- if (message.guild_id) {
- // handleMessage will fetch the Member, but only if they are not guild owner.
- // have to fetch ourselves otherwise.
- if (!message.member) {
- message.member = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: message.guild_id },
- relations: ["roles"],
- });
- }
+ if (message.guild_id) {
+ // handleMessage will fetch the Member, but only if they are not guild owner.
+ // have to fetch ourselves otherwise.
+ if (!message.member) {
+ message.member = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: message.guild_id },
+ relations: ["roles"],
+ });
+ }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
- }
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id);
+ }
- let read_state = await ReadState.findOne({
- where: { user_id: req.user_id, channel_id },
- });
- if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
- read_state.last_message_id = message.id;
- //It's a little more complicated than this but this'll do
- read_state.mention_count = 0;
+ let read_state = await ReadState.findOne({
+ where: { user_id: req.user_id, channel_id },
+ });
+ if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id });
+ read_state.last_message_id = message.id;
+ //It's a little more complicated than this but this'll do
+ read_state.mention_count = 0;
- await Promise.all([
- read_state.save(),
- message.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: channel_id,
- data: message,
- } as MessageCreateEvent),
- message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
- channel.save(),
- ]);
+ await Promise.all([
+ read_state.save(),
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null,
+ channel.save(),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return res.json(
- message.withSignedAttachments(
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- );
- },
+ return res.json(
+ message.withSignedAttachments(
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ );
+ },
);
router.delete(
- "/ack",
- route({
- requestBody: "AcknowledgeDeleteSchema",
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL
- const body = req.body as AcknowledgeDeleteSchema;
- if (body.version != 2) return res.status(204).send();
- // TODO: handle other read state types
- if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send();
+ "/ack",
+ route({
+ requestBody: "AcknowledgeDeleteSchema",
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL
+ const body = req.body as AcknowledgeDeleteSchema;
+ if (body.version != 2) return res.status(204).send();
+ // TODO: handle other read state types
+ if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send();
- const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } });
- if (readState) {
- await readState.remove();
- }
+ const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } });
+ if (readState) {
+ await readState.remove();
+ }
- res.status(204).send();
- },
+ res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/pins/index.ts b/src/api/routes/channels/#channel_id/messages/pins/index.ts
index 1d770bd2..2355c8b9 100644
--- a/src/api/routes/channels/#channel_id/messages/pins/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/pins/index.ts
@@ -24,169 +24,169 @@ import { IsNull, Not } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.put(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- // * in dm channels anyone can pin messages -> only check for guilds
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ // * in dm channels anyone can pin messages -> only check for guilds
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- const pinned_count = await Message.count({
- where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
- });
+ const pinned_count = await Message.count({
+ where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
+ });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
- message.pinned_at = new Date();
+ message.pinned_at = new Date();
- const author = await User.getPublicUser(req.user_id);
+ const author = await User.getPublicUser(req.user_id);
- const systemPinMessage = Message.create({
- timestamp: new Date(),
- type: 6,
- guild_id: message.guild_id,
- channel_id: message.channel_id,
- author,
- message_reference: {
- message_id: message.id,
- channel_id: message.channel_id,
- guild_id: message.guild_id,
- },
- reactions: [],
- attachments: [],
- embeds: [],
- sticker_items: [],
- edited_timestamp: undefined,
- mentions: [],
- mention_channels: [],
- mention_roles: [],
- mention_everyone: false,
- });
+ const systemPinMessage = Message.create({
+ timestamp: new Date(),
+ type: 6,
+ guild_id: message.guild_id,
+ channel_id: message.channel_id,
+ author,
+ message_reference: {
+ message_id: message.id,
+ channel_id: message.channel_id,
+ guild_id: message.guild_id,
+ },
+ reactions: [],
+ attachments: [],
+ embeds: [],
+ sticker_items: [],
+ edited_timestamp: undefined,
+ mentions: [],
+ mention_channels: [],
+ mention_roles: [],
+ mention_everyone: false,
+ });
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- systemPinMessage.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: message.channel_id,
- data: systemPinMessage,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ systemPinMessage.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: systemPinMessage,
+ } as MessageCreateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- message.pinned_at = null;
+ message.pinned_at = null;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/",
- route({
- permission: ["READ_MESSAGE_HISTORY"],
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: ["READ_MESSAGE_HISTORY"],
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const pins = await Message.find({
- where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
- relations: ["author"],
- order: { pinned_at: "DESC" },
- });
+ const pins = await Message.find({
+ where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
+ relations: ["author"],
+ order: { pinned_at: "DESC" },
+ });
- const items = pins.map((message: Message) => ({
- message,
- pinned_at: message.pinned_at,
- }));
+ const items = pins.map((message: Message) => ({
+ message,
+ pinned_at: message.pinned_at,
+ }));
- res.send({
- items,
- has_more: false,
- });
- },
+ res.send({
+ items,
+ has_more: false,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/search.ts b/src/api/routes/channels/#channel_id/messages/search.ts
index 7331945d..bec9a44c 100644
--- a/src/api/routes/channels/#channel_id/messages/search.ts
+++ b/src/api/routes/channels/#channel_id/messages/search.ts
@@ -27,116 +27,116 @@ import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildMessagesSearchResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 422: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { guild_id: req.params.guild_id },
- select: ["id"],
- });
- const {
- content,
- // include_nsfw, // TODO
- offset,
- sort_order,
- // sort_by, // TODO: Handle 'relevance'
- limit,
- author_id,
- } = req.query;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildMessagesSearchResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 422: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id: req.params.guild_id },
+ select: ["id"],
+ });
+ const {
+ 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);
+ 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
- }
+ 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, channel.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 permissions = await getPermission(req.user_id, channel.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: channel.guild_id,
- },
- channel: {
- id: channel_id,
- },
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- skip: offset ? Number(offset) : 0,
- };
- //@ts-ignore
- query.where.channel = { id: channel_id };
+ const query: FindManyOptions<Message> = {
+ order: {
+ timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC",
+ },
+ take: parsedLimit || 0,
+ where: {
+ guild: {
+ id: channel.guild_id,
+ },
+ channel: {
+ id: channel_id,
+ },
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ skip: offset ? Number(offset) : 0,
+ };
+ //@ts-ignore
+ query.where.channel = { id: channel_id };
- //@ts-ignore
- if (author_id) query.where.author = { id: author_id };
- //@ts-ignore
- if (content) query.where.content = Like(`%${content}%`);
+ //@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);
- delete query.take;
- const total_results = await Message.count(query);
+ const messages: Message[] = await Message.find(query);
+ delete query.take;
+ const total_results = await Message.count(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,
- poll: x.poll,
- hit: true,
- },
- ]);
+ 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,
+ poll: x.poll,
+ hit: true,
+ },
+ ]);
- return res.json({
- messages: messagesDto,
- total_results,
- });
- },
+ return res.json({
+ messages: messagesDto,
+ total_results,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index b45ff144..c03ca03f 100644
--- a/src/api/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -27,80 +27,80 @@ const router: Router = Router({ mergeParams: true });
// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel)
router.put(
- "/:overwrite_id",
- route({
- requestBody: "ChannelPermissionOverwriteSchema",
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 404: {},
- 501: {},
- 400: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
- const body = req.body as ChannelPermissionOverwriteSchema;
+ "/:overwrite_id",
+ route({
+ requestBody: "ChannelPermissionOverwriteSchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 404: {},
+ 501: {},
+ 400: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, overwrite_id } = req.params;
+ const body = req.body as ChannelPermissionOverwriteSchema;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
- channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+ channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild);
- if (body.type === ChannelPermissionOverwriteType.role) {
- if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
- } else if (body.type === ChannelPermissionOverwriteType.member) {
- if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
- } else throw new HTTPError("type not supported", 501);
+ if (body.type === ChannelPermissionOverwriteType.role) {
+ if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
+ } else if (body.type === ChannelPermissionOverwriteType.member) {
+ if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
+ } else throw new HTTPError("type not supported", 501);
- let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id);
- if (!overwrite) {
- overwrite = {
- id: overwrite_id,
- type: body.type,
- allow: "0",
- deny: "0",
- };
- channel.permission_overwrites?.push(overwrite);
- }
- overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0")));
- overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0")));
+ let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id);
+ if (!overwrite) {
+ overwrite = {
+ id: overwrite_id,
+ type: body.type,
+ allow: "0",
+ deny: "0",
+ };
+ channel.permission_overwrites?.push(overwrite);
+ }
+ overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0")));
+ overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0")));
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel,
+ } as ChannelUpdateEvent),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
// TODO: check permission hierarchy
router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }), async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
+ const { channel_id, overwrite_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
- channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id);
+ channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id);
- await Promise.all([
- channel.save(),
- emitEvent({
- event: "CHANNEL_UPDATE",
- channel_id,
- data: channel,
- } as ChannelUpdateEvent),
- ]);
+ await Promise.all([
+ channel.save(),
+ emitEvent({
+ event: "CHANNEL_UPDATE",
+ channel_id,
+ data: channel,
+ } as ChannelUpdateEvent),
+ ]);
- return res.sendStatus(204);
+ return res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index 4b62115f..8e56a54d 100644
--- a/src/api/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -25,161 +25,161 @@ const router: Router = Router({ mergeParams: true });
// This is the old endpoint
router.put(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- // * in dm channels anyone can pin messages -> only check for guilds
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ // * in dm channels anyone can pin messages -> only check for guilds
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- const pinned_count = await Message.count({
- where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
- });
+ const pinned_count = await Message.count({
+ where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) },
+ });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
- message.pinned_at = new Date();
+ message.pinned_at = new Date();
- const author = await User.getPublicUser(req.user_id);
+ const author = await User.getPublicUser(req.user_id);
- const systemPinMessage = Message.create({
- timestamp: new Date(),
- type: 6,
- guild_id: message.guild_id,
- channel_id: message.channel_id,
- author,
- message_reference: {
- message_id: message.id,
- channel_id: message.channel_id,
- guild_id: message.guild_id,
- },
- reactions: [],
- attachments: [],
- embeds: [],
- sticker_items: [],
- edited_timestamp: undefined,
- mentions: [],
- mention_channels: [],
- mention_roles: [],
- mention_everyone: false,
- });
+ const systemPinMessage = Message.create({
+ timestamp: new Date(),
+ type: 6,
+ guild_id: message.guild_id,
+ channel_id: message.channel_id,
+ author,
+ message_reference: {
+ message_id: message.id,
+ channel_id: message.channel_id,
+ guild_id: message.guild_id,
+ },
+ reactions: [],
+ attachments: [],
+ embeds: [],
+ sticker_items: [],
+ edited_timestamp: undefined,
+ mentions: [],
+ mention_channels: [],
+ mention_roles: [],
+ mention_everyone: false,
+ });
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- systemPinMessage.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: message.channel_id,
- data: systemPinMessage,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ systemPinMessage.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: systemPinMessage,
+ } as MessageCreateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.delete(
- "/:message_id",
- route({
- permission: "VIEW_CHANNEL",
- responses: {
- 204: {},
- 403: {},
- 404: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+ "/:message_id",
+ route({
+ permission: "VIEW_CHANNEL",
+ responses: {
+ 204: {},
+ 403: {},
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
- const message = await Message.findOneOrFail({
- where: { id: message_id },
- relations: ["author"],
- });
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ relations: ["author"],
+ });
- if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
+ if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
- message.pinned_at = null;
+ message.pinned_at = null;
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message,
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined,
- },
- } as ChannelPinsUpdateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.get(
- "/",
- route({
- permission: ["READ_MESSAGE_HISTORY"],
- responses: {
- 200: {
- body: "APIMessageArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+ "/",
+ route({
+ permission: ["READ_MESSAGE_HISTORY"],
+ responses: {
+ 200: {
+ body: "APIMessageArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const pins = await Message.find({
- where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
- relations: ["author"],
- order: { pinned_at: "DESC" },
- });
+ const pins = await Message.find({
+ where: { channel_id: channel_id, pinned_at: Not(IsNull()) },
+ relations: ["author"],
+ order: { pinned_at: "DESC" },
+ });
- res.send(pins);
- },
+ res.send(pins);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts
index 9686bc20..3a46beb7 100644
--- a/src/api/routes/channels/#channel_id/purge.ts
+++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -31,71 +31,71 @@ export default router;
TODO: apply the delete bit by bit to prevent client and database stress
**/
router.post(
- "/",
- route({
- /*body: "PurgeSchema",*/
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ /*body: "PurgeSchema",*/
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
- isTextChannel(channel.type);
+ if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400);
+ isTextChannel(channel.type);
- const rights = await getRights(req.user_id);
- if (!rights.has("MANAGE_MESSAGES")) {
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
- permissions.hasThrow("MANAGE_MESSAGES");
- permissions.hasThrow("MANAGE_CHANNELS");
- }
+ const rights = await getRights(req.user_id);
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ permissions.hasThrow("MANAGE_MESSAGES");
+ permissions.hasThrow("MANAGE_CHANNELS");
+ }
- const { before, after } = req.body as PurgeSchema;
+ const { before, after } = req.body as PurgeSchema;
- // TODO: send the deletion event bite-by-bite to prevent client stress
+ // TODO: send the deletion event bite-by-bite to prevent client stress
- const query: FindManyOptions<Message> & {
- where: { id?: FindOperator<string> };
- } = {
- order: { id: "ASC" },
- // take: limit,
- where: {
- channel_id,
- id: Between(after, before), // the right way around
- author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id),
- // if you lack the right of self-deletion, you can't delete your own messages, even in purges
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- };
+ const query: FindManyOptions<Message> & {
+ where: { id?: FindOperator<string> };
+ } = {
+ order: { id: "ASC" },
+ // take: limit,
+ where: {
+ channel_id,
+ id: Between(after, before), // the right way around
+ author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id),
+ // if you lack the right of self-deletion, you can't delete your own messages, even in purges
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ };
- const messages = await Message.find(query);
+ const messages = await Message.find(query);
- if (messages.length == 0) {
- res.sendStatus(304);
- return;
- }
+ if (messages.length == 0) {
+ res.sendStatus(304);
+ return;
+ }
- await Message.delete(messages.map((x) => x.id));
+ await Message.delete(messages.map((x) => x.id));
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: {
- ids: messages.map((x) => x.id),
- channel_id,
- guild_id: channel.guild_id,
- },
- } as MessageDeleteBulkEvent);
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: {
+ ids: messages.map((x) => x.id),
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteBulkEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index 6388cfe2..d0af3b4a 100644
--- a/src/api/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -24,79 +24,79 @@ import { ChannelType, PublicUserProjection } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.put(
- "/:user_id",
- route({
- responses: {
- 201: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
+ "/:user_id",
+ route({
+ responses: {
+ 201: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
- if (channel.type !== ChannelType.GROUP_DM) {
- const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])];
+ if (channel.type !== ChannelType.GROUP_DM) {
+ const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])];
- const new_channel = await Channel.createDMChannel(recipients, req.user_id);
- return res.status(201).json(new_channel);
- } else {
- if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
- throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
- }
+ const new_channel = await Channel.createDMChannel(recipients, req.user_id);
+ return res.status(201).json(new_channel);
+ } else {
+ if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
- await channel.save();
+ channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
+ await channel.save();
- await emitEvent({
- event: "CHANNEL_CREATE",
- data: await DmChannelDTO.from(channel, [user_id]),
- user_id: user_id,
- });
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id,
+ });
- await emitEvent({
- event: "CHANNEL_RECIPIENT_ADD",
- data: {
- channel_id: channel_id,
- user: await User.findOneOrFail({
- where: { id: user_id },
- select: PublicUserProjection,
- }),
- },
- channel_id: channel_id,
- } as ChannelRecipientAddEvent);
- return res.sendStatus(204);
- }
- },
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_ADD",
+ data: {
+ channel_id: channel_id,
+ user: await User.findOneOrFail({
+ where: { id: user_id },
+ select: PublicUserProjection,
+ }),
+ },
+ channel_id: channel_id,
+ } as ChannelRecipientAddEvent);
+ return res.sendStatus(204);
+ }
+ },
);
router.delete(
- "/:user_id",
- route({
- responses: {
- 204: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
- if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS;
+ "/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients"],
+ });
+ if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS;
- if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
- throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
- }
+ if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- await Channel.removeRecipientFromChannel(channel, user_id);
+ await Channel.removeRecipientFromChannel(channel, user_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 8c36add1..a41ab7ff 100644
--- a/src/api/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -23,47 +23,47 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- permission: "SEND_MESSAGES",
- responses: {
- 204: {},
- 404: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const user_id = req.user_id;
- const timestamp = Math.floor(Date.now() / 1000);
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- const member = await Member.findOne({
- where: { id: user_id, guild_id: channel.guild_id },
- relations: ["roles", "user"],
- });
- await emitEvent({
- event: "TYPING_START",
- channel_id: channel_id,
- data: {
- ...(member
- ? {
- member: {
- ...member.toPublicMember(),
- roles: member?.roles?.map((x) => x.id),
- },
- }
- : null),
- channel_id,
- timestamp,
- user_id,
- guild_id: channel.guild_id,
- },
- } as TypingStartEvent);
+ "/",
+ route({
+ permission: "SEND_MESSAGES",
+ responses: {
+ 204: {},
+ 404: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const user_id = req.user_id;
+ const timestamp = Math.floor(Date.now() / 1000);
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ const member = await Member.findOne({
+ where: { id: user_id, guild_id: channel.guild_id },
+ relations: ["roles", "user"],
+ });
+ await emitEvent({
+ event: "TYPING_START",
+ channel_id: channel_id,
+ data: {
+ ...(member
+ ? {
+ member: {
+ ...member.toPublicMember(),
+ roles: member?.roles?.map((x) => x.id),
+ },
+ }
+ : null),
+ channel_id,
+ timestamp,
+ user_id,
+ guild_id: channel.guild_id,
+ },
+ } as TypingStartEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 9d226b19..1c3c980a 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -26,88 +26,88 @@ import { isTextChannel, WebhookCreateSchema, WebhookType } from "@spacebar/schem
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "APIWebhookArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const webhooks = await Webhook.find({
- where: { channel_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "APIWebhookArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const webhooks = await Webhook.find({
+ where: { channel_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- return res.json(
- webhooks.map((webhook) => ({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- })),
- );
- },
+ return res.json(
+ webhooks.map((webhook) => ({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ })),
+ );
+ },
);
// TODO: use Image Data Type for avatar instead of String
router.post(
- "/",
- route({
- requestBody: "WebhookCreateSchema",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "WebhookCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
+ "/",
+ route({
+ requestBody: "WebhookCreateSchema",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "WebhookCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const channel_id = req.params.channel_id;
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- isTextChannel(channel.type);
- if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
+ isTextChannel(channel.type);
+ if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400);
- const webhook_count = await Webhook.count({ where: { channel_id } });
- const { maxWebhooks } = Config.get().limits.channel;
- if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
+ const webhook_count = await Webhook.count({ where: { channel_id } });
+ const { maxWebhooks } = Config.get().limits.channel;
+ if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
- let { avatar, name } = req.body as WebhookCreateSchema;
- name = trimSpecial(name);
+ let { avatar, name } = req.body as WebhookCreateSchema;
+ name = trimSpecial(name);
- // TODO: move this
- if (name) {
- ValidateName(name);
- }
+ // TODO: move this
+ if (name) {
+ ValidateName(name);
+ }
- if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
+ if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
- const hook = await Webhook.create({
- type: WebhookType.Incoming,
- name,
- avatar,
- guild_id: channel.guild_id,
- channel_id: channel.id,
- user_id: req.user_id,
- token: crypto.randomBytes(24).toString("base64url"),
- }).save();
+ const hook = await Webhook.create({
+ type: WebhookType.Incoming,
+ name,
+ avatar,
+ guild_id: channel.guild_id,
+ channel_id: channel.id,
+ user_id: req.user_id,
+ token: crypto.randomBytes(24).toString("base64url"),
+ }).save();
- const user = await User.getPublicUser(req.user_id);
+ const user = await User.getPublicUser(req.user_id);
- return res.json({
- ...hook,
- user: user,
- });
- },
+ return res.json({
+ ...hook,
+ user: user,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/channels/preload-messages.ts b/src/api/routes/channels/preload-messages.ts
index cdfc58f0..6a190524 100644
--- a/src/api/routes/channels/preload-messages.ts
+++ b/src/api/routes/channels/preload-messages.ts
@@ -23,49 +23,49 @@ import { PreloadMessagesRequestSchema, PreloadMessagesResponseSchema } from "@sp
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "PreloadMessagesRequestSchema",
- responses: {
- 200: {
- body: "PreloadMessagesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as PreloadMessagesRequestSchema;
- if (body.channels.length > Config.get().limits.message.maxPreloadCount)
- return res.status(400).send({
- code: 400,
- message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`,
- });
+ "/",
+ route({
+ requestBody: "PreloadMessagesRequestSchema",
+ responses: {
+ 200: {
+ body: "PreloadMessagesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as PreloadMessagesRequestSchema;
+ if (body.channels.length > Config.get().limits.message.maxPreloadCount)
+ return res.status(400).send({
+ code: 400,
+ message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`,
+ });
- const messages = (
- await Promise.all(
- body.channels.map(
- async (channelId) =>
- await Message.findOne({
- where: { channel_id: channelId },
- order: { timestamp: "DESC" },
- }),
- ),
- )
- ).filter((x) => x !== null) as Message[];
+ const messages = (
+ await Promise.all(
+ body.channels.map(
+ async (channelId) =>
+ await Message.findOne({
+ where: { channel_id: channelId },
+ order: { timestamp: "DESC" },
+ }),
+ ),
+ )
+ ).filter((x) => x !== null) as Message[];
- const filteredMessages = messages.map((message) => {
- const x = message.toJSON();
- // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- x.reactions = undefined;
- return x;
- }) as PreloadMessagesResponseSchema;
+ const filteredMessages = messages.map((message) => {
+ const x = message.toJSON();
+ // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-expect-error
+ x.reactions = undefined;
+ return x;
+ }) as PreloadMessagesResponseSchema;
- return res.status(200).send(filteredMessages);
- },
+ return res.status(200).send(filteredMessages);
+ },
);
export default router;
diff --git a/src/api/routes/collectibles-categories.ts b/src/api/routes/collectibles-categories.ts
index d2cac396..ffeff099 100644
--- a/src/api/routes/collectibles-categories.ts
+++ b/src/api/routes/collectibles-categories.ts
@@ -23,18 +23,18 @@ import { CollectiblesCategoriesResponse } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesCategoriesResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- res.send([] as CollectiblesCategoriesResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesCategoriesResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ res.send([] as CollectiblesCategoriesResponse);
+ },
);
export default router;
diff --git a/src/api/routes/collectibles-shop.ts b/src/api/routes/collectibles-shop.ts
index b4c82f57..756c80c5 100644
--- a/src/api/routes/collectibles-shop.ts
+++ b/src/api/routes/collectibles-shop.ts
@@ -23,21 +23,21 @@ import { CollectiblesShopResponse } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesShopResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- res.send({
- shop_blocks: [],
- categories: [],
- } as CollectiblesShopResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesShopResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ res.send({
+ shop_blocks: [],
+ categories: [],
+ } as CollectiblesShopResponse);
+ },
);
export default router;
diff --git a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
index 46f20aee..4a1a1cb4 100644
--- a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
+++ b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
@@ -21,9 +21,9 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- // TODO:
- // const { connection_name, connection_id } = req.params;
- res.sendStatus(204);
+ // TODO:
+ // const { connection_name, connection_id } = req.params;
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/connections/#connection_name/authorize.ts b/src/api/routes/connections/#connection_name/authorize.ts
index b8e50db6..1efb6564 100644
--- a/src/api/routes/connections/#connection_name/authorize.ts
+++ b/src/api/routes/connections/#connection_name/authorize.ts
@@ -23,28 +23,28 @@ import { ConnectionStore, FieldErrors } from "../../../../util";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { connection_name } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
- if (!connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: Array.from(ConnectionStore.connections.keys()).join(", "),
- }),
- },
- });
+ const { connection_name } = req.params;
+ const connection = ConnectionStore.connections.get(connection_name);
+ if (!connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: Array.from(ConnectionStore.connections.keys()).join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- res.json({
- url: await connection.getAuthorizationUrl(req.user_id),
- });
+ res.json({
+ url: await connection.getAuthorizationUrl(req.user_id),
+ });
});
export default router;
diff --git a/src/api/routes/connections/#connection_name/callback.ts b/src/api/routes/connections/#connection_name/callback.ts
index 5732a8e6..a83882ca 100644
--- a/src/api/routes/connections/#connection_name/callback.ts
+++ b/src/api/routes/connections/#connection_name/callback.ts
@@ -24,38 +24,38 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => {
- const { connection_name } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
- if (!connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: Array.from(ConnectionStore.connections.keys()).join(", "),
- }),
- },
- });
+ const { connection_name } = req.params;
+ const connection = ConnectionStore.connections.get(connection_name);
+ if (!connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: Array.from(ConnectionStore.connections.keys()).join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- const body = req.body as ConnectionCallbackSchema;
- const userId = connection.getUserId(body.state);
- const connectedAccnt = await connection.handleCallback(body);
+ const body = req.body as ConnectionCallbackSchema;
+ const userId = connection.getUserId(body.state);
+ const connectedAccnt = await connection.handleCallback(body);
- // whether we should emit a connections update event, only used when a connection doesnt already exist
- if (connectedAccnt)
- emitEvent({
- event: "USER_CONNECTIONS_UPDATE",
- data: { ...connectedAccnt, token_data: undefined },
- user_id: userId,
- });
+ // whether we should emit a connections update event, only used when a connection doesnt already exist
+ if (connectedAccnt)
+ emitEvent({
+ event: "USER_CONNECTIONS_UPDATE",
+ data: { ...connectedAccnt, token_data: undefined },
+ user_id: userId,
+ });
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/connections/index.ts b/src/api/routes/connections/index.ts
index b30d4027..017ebbbb 100644
--- a/src/api/routes/connections/index.ts
+++ b/src/api/routes/connections/index.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIConnectionsConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const config = ConnectionConfig.get();
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIConnectionsConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const config = ConnectionConfig.get();
- Object.keys(config).forEach((key) => {
- delete config[key].clientId;
- delete config[key].clientSecret;
- });
+ Object.keys(config).forEach((key) => {
+ delete config[key].clientId;
+ delete config[key].clientSecret;
+ });
- res.json(config);
- },
+ res.json(config);
+ },
);
export default router;
diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index ce34aa04..dcecec38 100644
--- a/src/api/routes/discoverable-guilds.ts
+++ b/src/api/routes/discoverable-guilds.ts
@@ -25,52 +25,52 @@ import { Like } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "DiscoverableGuildsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { offset, limit, categories } = req.query;
- const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
- const configLimit = Config.get().guild.discovery.limit;
- let guilds;
- if (categories == undefined) {
- guilds = showAllGuilds
- ? await Guild.find({
- take: Math.abs(Number(limit || configLimit)),
- })
- : await Guild.find({
- where: { features: Like(`%DISCOVERABLE%`) },
- take: Math.abs(Number(limit || configLimit)),
- });
- } else {
- guilds = showAllGuilds
- ? await Guild.find({
- where: { primary_category_id: categories.toString() },
- take: Math.abs(Number(limit || configLimit)),
- })
- : await Guild.find({
- where: {
- primary_category_id: categories.toString(),
- features: Like("%DISCOVERABLE%"),
- },
- take: Math.abs(Number(limit || configLimit)),
- });
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "DiscoverableGuildsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { offset, limit, categories } = req.query;
+ const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+ const configLimit = Config.get().guild.discovery.limit;
+ let guilds;
+ if (categories == undefined) {
+ guilds = showAllGuilds
+ ? await Guild.find({
+ take: Math.abs(Number(limit || configLimit)),
+ })
+ : await Guild.find({
+ where: { features: Like(`%DISCOVERABLE%`) },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ } else {
+ guilds = showAllGuilds
+ ? await Guild.find({
+ where: { primary_category_id: categories.toString() },
+ take: Math.abs(Number(limit || configLimit)),
+ })
+ : await Guild.find({
+ where: {
+ primary_category_id: categories.toString(),
+ features: Like("%DISCOVERABLE%"),
+ },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ }
- const total = guilds ? guilds.length : undefined;
+ const total = guilds ? guilds.length : undefined;
- res.send({
- total: total,
- guilds: guilds,
- offset: Number(offset || Config.get().guild.discovery.offset),
- limit: Number(limit || configLimit),
- });
- },
+ res.send({
+ total: total,
+ guilds: guilds,
+ offset: Number(offset || Config.get().guild.discovery.offset),
+ limit: Number(limit || configLimit),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts
index cb1e3a9e..d865dfe8 100644
--- a/src/api/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -23,25 +23,25 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/categories",
- route({
- responses: {
- 200: {
- body: "APIDiscoveryCategoryArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO:
- // Get locale instead
+ "/categories",
+ route({
+ responses: {
+ 200: {
+ body: "APIDiscoveryCategoryArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO:
+ // Get locale instead
- // const { locale, primary_only } = req.query;
- const { primary_only } = req.query;
+ // const { locale, primary_only } = req.query;
+ const { primary_only } = req.query;
- const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find();
+ const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find();
- res.send(out);
- },
+ res.send(out);
+ },
);
export default router;
diff --git a/src/api/routes/download.ts b/src/api/routes/download.ts
index 06588326..f54a3bb2 100644
--- a/src/api/routes/download.ts
+++ b/src/api/routes/download.ts
@@ -23,36 +23,36 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 302: {},
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { platform } = req.query;
+ "/",
+ route({
+ responses: {
+ 302: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { platform } = req.query;
- if (!platform)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
- const release = await ClientRelease.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
+ const release = await ClientRelease.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
+ },
+ order: { pub_date: "DESC" },
+ });
- res.redirect(release.url);
- },
+ res.redirect(release.url);
+ },
);
export default router;
diff --git a/src/api/routes/emojis/#emoji_id/source.ts b/src/api/routes/emojis/#emoji_id/source.ts
index 7a5e1c7a..f5c4a830 100644
--- a/src/api/routes/emojis/#emoji_id/source.ts
+++ b/src/api/routes/emojis/#emoji_id/source.ts
@@ -24,63 +24,63 @@ import { APIErrorResponse, EmojiGuild, EmojiSourceResponse } from "@spacebar/sch
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "EmojiSourceResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { emoji_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "EmojiSourceResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { emoji_id } = req.params;
- const emoji = await Emoji.findOne({ where: { id: emoji_id } });
- if (!emoji) {
- res.status(404).json({
- code: DiscordApiErrors.UNKNOWN_EMOJI.code,
- message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`,
- errors: {},
- } as APIErrorResponse);
- return;
- }
+ const emoji = await Emoji.findOne({ where: { id: emoji_id } });
+ if (!emoji) {
+ res.status(404).json({
+ code: DiscordApiErrors.UNKNOWN_EMOJI.code,
+ message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`,
+ errors: {},
+ } as APIErrorResponse);
+ return;
+ }
- // TODO: emojis can be owned by applications these days, account for this when we get there?
- res.json({
- type: "GUILD",
- guild: {
- ...(await Guild.findOne({
- where: {
- id: emoji.guild_id,
- },
- select: {
- id: true,
- name: true,
- icon: true,
- description: true,
- features: true,
- emojis: true,
- premium_tier: true,
- premium_subscription_count: true,
- },
- })),
- approximate_member_count: await Member.countBy({
- guild_id: emoji.guild_id,
- }),
- approximate_presence_count: await Member.countBy({
- guild_id: emoji.guild_id,
- user: {
- sessions: {
- status: "online",
- },
- },
- }),
- } as EmojiGuild,
- } as EmojiSourceResponse);
- },
+ // TODO: emojis can be owned by applications these days, account for this when we get there?
+ res.json({
+ type: "GUILD",
+ guild: {
+ ...(await Guild.findOne({
+ where: {
+ id: emoji.guild_id,
+ },
+ select: {
+ id: true,
+ name: true,
+ icon: true,
+ description: true,
+ features: true,
+ emojis: true,
+ premium_tier: true,
+ premium_subscription_count: true,
+ },
+ })),
+ approximate_member_count: await Member.countBy({
+ guild_id: emoji.guild_id,
+ }),
+ approximate_presence_count: await Member.countBy({
+ guild_id: emoji.guild_id,
+ user: {
+ sessions: {
+ status: "online",
+ },
+ },
+ }),
+ } as EmojiGuild,
+ } as EmojiSourceResponse);
+ },
);
export default router;
diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts
index bf120639..9dd54cc0 100644
--- a/src/api/routes/experiments.ts
+++ b/src/api/routes/experiments.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
+ // TODO:
+ res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
});
export default router;
diff --git a/src/api/routes/games/detectable.ts b/src/api/routes/games/detectable.ts
index 9e10bd58..e1415ea3 100644
--- a/src/api/routes/games/detectable.ts
+++ b/src/api/routes/games/detectable.ts
@@ -22,33 +22,33 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
const cache = {
- data: {},
- expires: 0,
+ data: {},
+ expires: 0,
};
// modern dclients call this, is /applications/detectable deprecated?
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ApplicationDetectableResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // cache for 6 hours
- if (Date.now() > cache.expires) {
- const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
- const data = await response.json();
- cache.data = data as ApplicationDetectableResponse;
- cache.expires = Date.now() + 6 * 60 * 60 * 1000;
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationDetectableResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // cache for 6 hours
+ if (Date.now() > cache.expires) {
+ const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways
+ const data = await response.json();
+ cache.data = data as ApplicationDetectableResponse;
+ cache.expires = Date.now() + 6 * 60 * 60 * 1000;
+ }
- res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
- .status(200)
- .json(cache.data);
- },
+ res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`)
+ .status(200)
+ .json(cache.data);
+ },
);
export default router;
diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index 80fa9512..34a648cd 100644
--- a/src/api/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
@@ -23,27 +23,27 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GatewayBotResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic,
- shards: 1,
- session_start_limit: {
- total: 1000,
- remaining: 999,
- reset_after: 14400000,
- max_concurrency: 1,
- },
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayBotResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic,
+ shards: 1,
+ session_start_limit: {
+ total: 1000,
+ remaining: 999,
+ reset_after: 14400000,
+ max_concurrency: 1,
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 1fafbf5b..b85fcd9f 100644
--- a/src/api/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
@@ -23,20 +23,20 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GatewayResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic,
- });
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index e81c2c3a..ca07956e 100644
--- a/src/api/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -25,45 +25,45 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- q: {
- type: "string",
- required: true,
- description: "Search query",
- },
- media_format: {
- type: "string",
- description: "Media format",
- values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
- },
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorGifsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- const { q, media_format, locale } = req.query;
+ "/",
+ route({
+ query: {
+ q: {
+ type: "string",
+ required: true,
+ description: "Search query",
+ },
+ media_format: {
+ type: "string",
+ description: "Media format",
+ values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
+ },
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorGifsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ const { q, media_format, locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- });
+ const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- const { results } = (await response.json()) as { results: TenorGif[] };
+ const { results } = (await response.json()) as { results: TenorGif[] };
- res.json(results.map(parseGifResult)).status(200);
- },
+ res.json(results.map(parseGifResult)).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index d5907b4a..99aa528b 100644
--- a/src/api/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -24,40 +24,40 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- media_format: {
- type: "string",
- description: "Media format",
- values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
- },
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorGifsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- const { media_format, locale } = req.query;
+ "/",
+ route({
+ query: {
+ media_format: {
+ type: "string",
+ description: "Media format",
+ values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))),
+ },
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorGifsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ const { media_format, locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- });
+ const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- const { results } = (await response.json()) as { results: TenorGif[] };
+ const { results } = (await response.json()) as { results: TenorGif[] };
- res.json(results.map(parseGifResult)).status(200);
- },
+ res.json(results.map(parseGifResult)).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index d86c06b5..4a7a3f3c 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -24,50 +24,50 @@ import { TenorCategoriesResults, TenorTrendingResults } from "@spacebar/schemas"
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- locale: {
- type: "string",
- description: "Locale",
- },
- },
- responses: {
- 200: {
- body: "TenorTrendingResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Custom providers
- // TODO: return gifs as mp4
- // const { media_format, locale } = req.query;
- const { locale } = req.query;
+ "/",
+ route({
+ query: {
+ locale: {
+ type: "string",
+ description: "Locale",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TenorTrendingResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ // TODO: return gifs as mp4
+ // const { media_format, locale } = req.query;
+ const { locale } = req.query;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const [responseSource, trendGifSource] = await Promise.all([
- fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
- fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
- ]);
+ const [responseSource, trendGifSource] = await Promise.all([
+ fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ }),
+ fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ }),
+ ]);
- const { tags } = (await responseSource.json()) as TenorCategoriesResults;
- const { results } = (await trendGifSource.json()) as TenorTrendingResults;
+ const { tags } = (await responseSource.json()) as TenorCategoriesResults;
+ const { results } = (await trendGifSource.json()) as TenorTrendingResults;
- res.json({
- categories: tags.map((x) => ({
- name: x.searchterm,
- src: x.image,
- })),
- gifs: [parseGifResult(results[0])],
- }).status(200);
- },
+ res.json({
+ categories: tags.map((x) => ({
+ name: x.searchterm,
+ src: x.image,
+ })),
+ gifs: [parseGifResult(results[0])],
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index fa642ea4..38ba1025 100644
--- a/src/api/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -25,32 +25,32 @@ import { Like } from "typeorm";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildRecommendationsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { limit, personalization_disabled } = req.query;
- const { limit } = req.query;
- const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildRecommendationsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { limit, personalization_disabled } = req.query;
+ const { limit } = req.query;
+ const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
- const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
+ const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join("");
- const guilds = showAllGuilds
- ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
- : await Guild.find({
- where: { features: Like("%DISCOVERABLE%") },
- take: Math.abs(Number(limit || 24)),
- });
- res.send({
- recommended_guilds: guilds,
- load_id: `server_recs/${genLoadId(32)}`,
- }).status(200);
- },
+ const guilds = showAllGuilds
+ ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
+ : await Guild.find({
+ where: { features: Like("%DISCOVERABLE%") },
+ take: Math.abs(Number(limit || 24)),
+ });
+ res.send({
+ recommended_guilds: guilds,
+ load_id: `server_recs/${genLoadId(32)}`,
+ }).status(200);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/application-command-index.ts b/src/api/routes/guilds/#guild_id/application-command-index.ts
index bb1d8000..244029d5 100644
--- a/src/api/routes/guilds/#guild_id/application-command-index.ts
+++ b/src/api/routes/guilds/#guild_id/application-command-index.ts
@@ -25,66 +25,66 @@ import { ApplicationCommandSchema, ApplicationCommandType } from "@spacebar/sche
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } });
- const applications: Application[] = [];
+ const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } });
+ const applications: Application[] = [];
- for (const member of members) {
- const app = await Application.findOne({ where: { id: member.id } });
- if (app) applications.push(app);
- }
+ for (const member of members) {
+ const app = await Application.findOne({ where: { id: member.id } });
+ if (app) applications.push(app);
+ }
- const applicationsSendable = [];
+ const applicationsSendable = [];
- for (const application of applications) {
- applicationsSendable.push({
- bot_id: application.bot?.id,
- description: application.description,
- flags: application.flags,
- icon: application.icon,
- id: application.id,
- name: application.name,
- });
- }
+ for (const application of applications) {
+ applicationsSendable.push({
+ bot_id: application.bot?.id,
+ description: application.description,
+ flags: application.flags,
+ icon: application.icon,
+ id: application.id,
+ name: application.name,
+ });
+ }
- const applicationCommands: ApplicationCommand[][] = [];
+ const applicationCommands: ApplicationCommand[][] = [];
- for (const application of applications) {
- applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } }));
- applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } }));
- }
+ for (const application of applications) {
+ applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } }));
+ applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } }));
+ }
- const applicationCommandsSendable: ApplicationCommandSchema[] = [];
+ const applicationCommandsSendable: ApplicationCommandSchema[] = [];
- for (const command of applicationCommands.flat()) {
- applicationCommandsSendable.push({
- id: command.id,
- type: command.type,
- application_id: command.application_id,
- guild_id: command.guild_id,
- name: command.name,
- name_localizations: command.name_localizations,
- // name_localized: // TODO: make this work
- description: command.description,
- description_localizations: command.description_localizations,
- // description_localized: // TODO: make this work
- options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined,
- default_member_permissions: command.default_member_permissions,
- dm_permission: command.dm_permission,
- permissions: command.permissions,
- nsfw: command.nsfw,
- integration_types: command.integration_types,
- global_popularity_rank: command.global_popularity_rank,
- contexts: command.contexts,
- version: command.version,
- handler: command.handler,
- });
- }
+ for (const command of applicationCommands.flat()) {
+ applicationCommandsSendable.push({
+ id: command.id,
+ type: command.type,
+ application_id: command.application_id,
+ guild_id: command.guild_id,
+ name: command.name,
+ name_localizations: command.name_localizations,
+ // name_localized: // TODO: make this work
+ description: command.description,
+ description_localizations: command.description_localizations,
+ // description_localized: // TODO: make this work
+ options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined,
+ default_member_permissions: command.default_member_permissions,
+ dm_permission: command.dm_permission,
+ permissions: command.permissions,
+ nsfw: command.nsfw,
+ integration_types: command.integration_types,
+ global_popularity_rank: command.global_popularity_rank,
+ contexts: command.contexts,
+ version: command.version,
+ handler: command.handler,
+ });
+ }
- res.send({
- applications: applicationsSendable,
- application_commands: applicationCommandsSendable,
- version: Snowflake.generate(),
- });
+ res.send({
+ applications: applicationsSendable,
+ application_commands: applicationCommandsSendable,
+ version: Snowflake.generate(),
+ });
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index 620f8822..4acc8e50 100644
--- a/src/api/routes/guilds/#guild_id/audit-logs.ts
+++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -22,14 +22,14 @@ const router = Router({ mergeParams: true });
//TODO: implement audit logs
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json({
- audit_log_entries: [],
- users: [],
- integrations: [],
- webhooks: [],
- guild_scheduled_events: [],
- threads: [],
- application_commands: [],
- });
+ res.json({
+ audit_log_entries: [],
+ users: [],
+ integrations: [],
+ webhooks: [],
+ guild_scheduled_events: [],
+ threads: [],
+ application_commands: [],
+ });
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
index e2b37324..8c24651d 100644
--- a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
+++ b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
@@ -25,116 +25,116 @@ import { AutomodRuleSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId[]",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const rules = await AutomodRule.find({ where: { guild_id } });
- return res.json(rules);
- },
+ "/",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId[]",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const rules = await AutomodRule.find({ where: { guild_id } });
+ return res.json(rules);
+ },
);
router.post(
- "/",
- route({
- // requestBody: "AutomodRuleSchema",
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403);
+ "/",
+ route({
+ // requestBody: "AutomodRuleSchema",
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403);
- if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403);
+ if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403);
- if (req.body.id) {
- throw new HTTPError("You can't specify an ID for a new rule", 400);
- }
+ if (req.body.id) {
+ throw new HTTPError("You can't specify an ID for a new rule", 400);
+ }
- const data = req.body as AutomodRuleSchema;
+ const data = req.body as AutomodRuleSchema;
- const created = AutomodRule.create({
- creator: await User.findOneOrFail({
- where: { id: data.creator_id },
- }),
- ...data,
- });
+ const created = AutomodRule.create({
+ creator: await User.findOneOrFail({
+ where: { id: data.creator_id },
+ }),
+ ...data,
+ });
- const savedRule = await AutomodRule.save(created);
- return res.json(savedRule);
- },
+ const savedRule = await AutomodRule.save(created);
+ return res.json(savedRule);
+ },
);
router.patch(
- "/:rule_id",
- route({
- // requestBody: "AutomodRuleSchema
- permission: ["MANAGE_GUILD"],
- responses: {
- 200: {
- body: "AutomodRuleSchemaWithId",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { rule_id } = req.params;
- const rule = await AutomodRule.findOneOrFail({
- where: { id: rule_id },
- });
+ "/:rule_id",
+ route({
+ // requestBody: "AutomodRuleSchema
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ const rule = await AutomodRule.findOneOrFail({
+ where: { id: rule_id },
+ });
- const data = req.body as AutomodRuleSchema;
+ const data = req.body as AutomodRuleSchema;
- AutomodRule.merge(rule, data);
- const savedRule = await AutomodRule.save(rule);
- return res.json(savedRule);
- },
+ AutomodRule.merge(rule, data);
+ const savedRule = await AutomodRule.save(rule);
+ return res.json(savedRule);
+ },
);
router.delete(
- "/:rule_id",
- route({
- permission: ["MANAGE_GUILD"],
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { rule_id } = req.params;
- await AutomodRule.delete({ id: rule_id });
- return res.status(204).send();
- },
+ "/:rule_id",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ await AutomodRule.delete({ id: rule_id });
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 03b4edea..cf043b7e 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -27,267 +27,267 @@ const router: Router = Router({ mergeParams: true });
/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
router.get(
- "/",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 200: {
- body: "APIBansArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 200: {
+ body: "APIBansArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- let bans = await Ban.find({ where: { guild_id: guild_id } });
- const promisesToAwait: Promise<User>[] = [];
- const bansObj: APIBansArray = [];
+ let bans = await Ban.find({ where: { guild_id: guild_id } });
+ const promisesToAwait: Promise<User>[] = [];
+ const bansObj: APIBansArray = [];
- bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- bans.forEach((ban) => {
- promisesToAwait.push(User.getPublicUser(ban.user_id));
- });
+ bans.forEach((ban) => {
+ promisesToAwait.push(User.getPublicUser(ban.user_id));
+ });
- const bannedUsers = await Promise.all(promisesToAwait);
+ const bannedUsers = await Promise.all(promisesToAwait);
- bans.forEach((ban, index) => {
- const user = bannedUsers[index];
- bansObj.push({
- reason: ban.reason ?? null,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- });
- });
+ bans.forEach((ban, index) => {
+ const user = bannedUsers[index];
+ bansObj.push({
+ reason: ban.reason ?? null,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ });
+ });
- return res.json(bansObj);
- },
+ return res.json(bansObj);
+ },
);
router.get(
- "/search",
- route({
- permission: "BAN_MEMBERS",
- query: {
- query: {
- type: "string",
- description: "Query to match username(s) and display name(s) against (1-32 characters)",
- required: true,
- },
- limit: {
- type: "number",
- description: "Max number of members to return (1-10, default 10)",
- required: false,
- },
- },
- responses: {
- 200: {
- body: "APIBansArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/search",
+ route({
+ permission: "BAN_MEMBERS",
+ query: {
+ query: {
+ type: "string",
+ description: "Query to match username(s) and display name(s) against (1-32 characters)",
+ required: true,
+ },
+ limit: {
+ type: "number",
+ description: "Max number of members to return (1-10, default 10)",
+ required: false,
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIBansArray",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const limit = Number(req.query.limit) || 10;
- if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10");
+ const limit = Number(req.query.limit) || 10;
+ if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10");
- const query = String(req.query.query);
- if (!query || query.trim().length === 0 || query.length > 32) {
- throw new HTTPError("The query must be between 1 and 32 characters in length");
- }
+ const query = String(req.query.query);
+ if (!query || query.trim().length === 0 || query.length > 32) {
+ throw new HTTPError("The query must be between 1 and 32 characters in length");
+ }
- let bans = await Ban.createQueryBuilder("ban")
- .leftJoinAndSelect("ban.user", "user")
- .where("ban.guild_id = :guildId", { guildId: guild_id })
- .andWhere("user.username LIKE :userName", {
- userName: `%${query}%`,
- })
- .limit(limit)
- .getMany();
+ let bans = await Ban.createQueryBuilder("ban")
+ .leftJoinAndSelect("ban.user", "user")
+ .where("ban.guild_id = :guildId", { guildId: guild_id })
+ .andWhere("user.username LIKE :userName", {
+ userName: `%${query}%`,
+ })
+ .limit(limit)
+ .getMany();
- bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- const bansObj: APIBansArray = bans.map((ban) => {
- const user = ban.user;
- return {
- reason: ban.reason ?? null,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- };
- });
+ const bansObj: APIBansArray = bans.map((ban) => {
+ const user = ban.user;
+ return {
+ reason: ban.reason ?? null,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ };
+ });
- return res.json(bansObj);
- },
+ return res.json(bansObj);
+ },
);
router.get(
- "/:user_id",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 200: {
- body: "GuildBansResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ "/:user_id",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 200: {
+ body: "GuildBansResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- const ban = (await Ban.findOneOrFail({
- where: { guild_id: guild_id, user_id: user_id },
- })) as BanRegistrySchema;
+ const ban = (await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ })) as BanRegistrySchema;
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // pretend self-bans don't exist to prevent victim chasing
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // pretend self-bans don't exist to prevent victim chasing
- const user = await User.getPublicUser(ban.user_id);
+ const user = await User.getPublicUser(ban.user_id);
- const banInfo: GuildBansResponse = {
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar ?? null,
- public_flags: user.public_flags,
- },
- reason: ban.reason ?? null,
- };
+ const banInfo: GuildBansResponse = {
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar ?? null,
+ public_flags: user.public_flags,
+ },
+ reason: ban.reason ?? null,
+ };
- return res.json(banInfo);
- },
+ return res.json(banInfo);
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "BanCreateSchema",
- permission: "BAN_MEMBERS",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const banned_user_id = req.params.user_id;
- const opts = req.body as BanCreateSchema;
+ "/:user_id",
+ route({
+ requestBody: "BanCreateSchema",
+ permission: "BAN_MEMBERS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const banned_user_id = req.params.user_id;
+ const opts = req.body as BanCreateSchema;
- let deleteMessagesMs = opts.delete_message_days
- ? (opts.delete_message_days as number) * 86400000
- : opts.delete_message_seconds
- ? (opts.delete_message_seconds as number) * 1000
- : 0;
+ let deleteMessagesMs = opts.delete_message_days
+ ? (opts.delete_message_days as number) * 86400000
+ : opts.delete_message_seconds
+ ? (opts.delete_message_seconds as number) * 1000
+ : 0;
- if (deleteMessagesMs < 0) deleteMessagesMs = 0;
+ if (deleteMessagesMs < 0) deleteMessagesMs = 0;
- if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
+ if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
+ throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
- if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
+ if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
- const existingBan = await Ban.findOne({
- where: { guild_id: guild_id, user_id: banned_user_id },
- });
+ const existingBan = await Ban.findOne({
+ where: { guild_id: guild_id, user_id: banned_user_id },
+ });
- // Bans on already banned users are silently ignored
- if (existingBan) return res.status(204).send();
+ // Bans on already banned users are silently ignored
+ if (existingBan) return res.status(204).send();
- const banned_user = await User.getPublicUser(banned_user_id);
+ const banned_user = await User.getPublicUser(banned_user_id);
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- executor_id: req.user_id,
- reason: req.body.reason, // || otherwise empty
- });
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user.toPublicUser(),
- delete_message_secs: Math.floor(deleteMessagesMs / 1000),
- },
- guild_id: guild_id,
- } as GuildBanAddEvent),
- ]);
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user.toPublicUser(),
+ delete_message_secs: Math.floor(deleteMessagesMs / 1000),
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
- return res.status(204).send();
- },
+ return res.status(204).send();
+ },
);
router.delete(
- "/:user_id",
- route({
- permission: "BAN_MEMBERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ "/:user_id",
+ route({
+ permission: "BAN_MEMBERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- await Ban.findOneOrFail({
- where: { guild_id: guild_id, user_id: user_id },
- });
+ await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ });
- const banned_user = await User.getPublicUser(user_id);
+ const banned_user = await User.getPublicUser(user_id);
- await Promise.all([
- Ban.delete({
- user_id: user_id,
- guild_id,
- }),
+ await Promise.all([
+ Ban.delete({
+ user_id: user_id,
+ guild_id,
+ }),
- emitEvent({
- event: "GUILD_BAN_REMOVE",
- data: {
- guild_id,
- user: banned_user,
- },
- guild_id,
- } as GuildBanRemoveEvent),
- ]);
+ emitEvent({
+ event: "GUILD_BAN_REMOVE",
+ data: {
+ guild_id,
+ user: banned_user,
+ },
+ guild_id,
+ } as GuildBanRemoveEvent),
+ ]);
- return res.status(204).send();
- },
+ return res.status(204).send();
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bulk-ban.ts b/src/api/routes/guilds/#guild_id/bulk-ban.ts
index a564e2d4..004c2ab2 100644
--- a/src/api/routes/guilds/#guild_id/bulk-ban.ts
+++ b/src/api/routes/guilds/#guild_id/bulk-ban.ts
@@ -25,93 +25,93 @@ import { Config } from "@spacebar/util";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "BulkBanSchema",
- permission: ["BAN_MEMBERS", "MANAGE_GUILD"],
- responses: {
- 200: {
- body: "Ban",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ requestBody: "BulkBanSchema",
+ permission: ["BAN_MEMBERS", "MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "Ban",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const userIds: Array<string> = req.body.user_ids;
- if (!userIds) throw new HTTPError("The user_ids array is missing", 400);
+ const userIds: Array<string> = req.body.user_ids;
+ if (!userIds) throw new HTTPError("The user_ids array is missing", 400);
- if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400);
+ if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400);
- const banned_users = [];
- const failed_users = [];
- for await (const banned_user_id of userIds) {
- if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) {
- failed_users.push(banned_user_id);
- continue;
- }
+ const banned_users = [];
+ const failed_users = [];
+ for await (const banned_user_id of userIds) {
+ if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- if (req.permission?.cache.guild?.owner_id === banned_user_id) {
- failed_users.push(banned_user_id);
- continue;
- }
+ if (req.permission?.cache.guild?.owner_id === banned_user_id) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- const existingBan = await Ban.findOne({
- where: { guild_id: guild_id, user_id: banned_user_id },
- });
- if (existingBan) {
- failed_users.push(banned_user_id);
- continue;
- }
+ const existingBan = await Ban.findOne({
+ where: { guild_id: guild_id, user_id: banned_user_id },
+ });
+ if (existingBan) {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- let banned_user;
- try {
- banned_user = await User.getPublicUser(banned_user_id);
- } catch {
- failed_users.push(banned_user_id);
- continue;
- }
+ let banned_user;
+ try {
+ banned_user = await User.getPublicUser(banned_user_id);
+ } catch {
+ failed_users.push(banned_user_id);
+ continue;
+ }
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- ip: req.ip,
- executor_id: req.user_id,
- reason: req.body.reason, // || otherwise empty
- });
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ ip: req.ip,
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- try {
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user.toPublicUser(),
- },
- guild_id: guild_id,
- } as GuildBanAddEvent),
- ]);
- banned_users.push(banned_user_id);
- } catch {
- failed_users.push(banned_user_id);
- continue;
- }
- }
+ try {
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user.toPublicUser(),
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
+ banned_users.push(banned_user_id);
+ } catch {
+ failed_users.push(banned_user_id);
+ continue;
+ }
+ }
- if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED;
- return res.json({
- banned_users: banned_users,
- failed_users: failed_users,
- });
- },
+ if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED;
+ return res.json({
+ banned_users: banned_users,
+ failed_users: failed_users,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index ecb25bee..fc374acb 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -23,148 +23,148 @@ import { ChannelModifySchema, ChannelReorderSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 201: {
- body: "APIChannelArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const channels = await Channel.find({ where: { guild_id } });
+ "/",
+ route({
+ responses: {
+ 201: {
+ body: "APIChannelArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const channels = await Channel.find({ where: { guild_id } });
- for await (const channel of channels) {
- channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
- }
- channels.sort((a, b) => a.position - b.position);
+ for await (const channel of channels) {
+ channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
+ }
+ channels.sort((a, b) => a.position - b.position);
- 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
- const { guild_id } = req.params;
- const body = req.body as ChannelModifySchema;
+ "/",
+ 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
+ const { guild_id } = req.params;
+ const body = req.body as ChannelModifySchema;
- const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
- channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
+ const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
+ channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild);
- res.status(201).json(channel);
- },
+ res.status(201).json(channel);
+ },
);
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
- const { guild_id } = req.params;
- let body = req.body as ChannelReorderSchema;
+ "/",
+ route({
+ requestBody: "ChannelReorderSchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // changes guild channel position
+ const { guild_id } = req.params;
+ let body = req.body as ChannelReorderSchema;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: { channel_ordering: true },
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: { channel_ordering: true },
+ });
- body = body.sort((a, b) => {
- const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0);
- const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0);
- return apos - bpos;
- });
+ body = body.sort((a, b) => {
+ const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0);
+ const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0);
+ return apos - bpos;
+ });
- // The channels not listed for this query
- const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
+ // The channels not listed for this query
+ const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x));
- const withParents = body.filter((x) => x.parent_id !== undefined);
- const withPositions = body.filter((x) => x.position !== undefined);
- // You can't do it with Promise.all or the way this is being done is super incorrect
- for await (const opt of withPositions) {
- const channel = await Channel.findOneOrFail({
- where: { id: opt.id },
- });
+ const withParents = body.filter((x) => x.parent_id !== undefined);
+ const withPositions = body.filter((x) => x.position !== undefined);
+ // You can't do it with Promise.all or the way this is being done is super incorrect
+ for await (const opt of withPositions) {
+ const channel = await Channel.findOneOrFail({
+ where: { id: opt.id },
+ });
- notMentioned.splice(opt.position as number, 0, channel.id);
- channel.position = notMentioned.findIndex((_) => _ === channel.id);
+ notMentioned.splice(opt.position as number, 0, channel.id);
+ channel.position = notMentioned.findIndex((_) => _ === channel.id);
- await emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id: channel.id,
- guild_id,
- } as ChannelUpdateEvent);
- }
- // Due to this also being able to change the order, this needs to be done in order
- // have to do the parents after the positions
- for await (const opt of withParents) {
- const [channel, parent] = await Promise.all([
- Channel.findOneOrFail({
- where: { id: opt.id },
- }),
- opt.parent_id
- ? Channel.findOneOrFail({
- where: { id: opt.parent_id },
- select: {
- permission_overwrites: true,
- id: true,
- },
- })
- : null,
- ]);
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: channel.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }
+ // Due to this also being able to change the order, this needs to be done in order
+ // have to do the parents after the positions
+ for await (const opt of withParents) {
+ const [channel, parent] = await Promise.all([
+ Channel.findOneOrFail({
+ where: { id: opt.id },
+ }),
+ opt.parent_id
+ ? Channel.findOneOrFail({
+ where: { id: opt.parent_id },
+ select: {
+ permission_overwrites: true,
+ id: true,
+ },
+ })
+ : null,
+ ]);
- if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
- if (parent && opt.position === undefined) {
- const parentPos = notMentioned.indexOf(parent.id);
- notMentioned.splice(parentPos + 1, 0, channel.id);
- channel.position = (parentPos + 1) as number;
- }
- channel.parent = parent || undefined;
- channel.parent_id = parent?.id || null;
- await channel.save();
+ if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites });
+ if (parent && opt.position === undefined) {
+ const parentPos = notMentioned.indexOf(parent.id);
+ notMentioned.splice(parentPos + 1, 0, channel.id);
+ channel.position = (parentPos + 1) as number;
+ }
+ channel.parent = parent || undefined;
+ channel.parent_id = parent?.id || null;
+ await channel.save();
- await emitEvent({
- event: "CHANNEL_UPDATE",
- data: channel,
- channel_id: channel.id,
- guild_id,
- } as ChannelUpdateEvent);
- }
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: channel.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }
- await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
+ await Guild.update({ id: guild_id }, { channel_ordering: notMentioned });
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index 509445c4..4edd2c39 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -26,40 +26,40 @@ const router = Router({ mergeParams: true });
// 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({
- responses: {
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ 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);
+ 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),
- ]);
+ 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);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index 89727335..150c9209 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -22,44 +22,44 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildDiscoveryRequirementsResponse",
- },
- },
- }),
- 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
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildDiscoveryRequirementsResponse",
+ },
+ },
+ }),
+ 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,
- });
- },
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index c74f939f..5b07380f 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -24,186 +24,186 @@ import { EmojiCreateSchema, EmojiModifySchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIEmojiArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIEmojiArray",
+ },
+ 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({
- responses: {
- 200: {
- body: "Emoji",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, emoji_id } = req.params;
+ "/: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;
- const body = req.body as EmojiCreateSchema;
+ "/",
+ 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;
+ const body = req.body as EmojiCreateSchema;
- const id = Snowflake.generate();
- const emoji_count = await Emoji.count({
- where: { guild_id: guild_id },
- });
- const { maxEmojis } = Config.get().limits.guild;
+ const id = Snowflake.generate();
+ const emoji_count = await Emoji.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxEmojis } = Config.get().limits.guild;
- if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
- if (body.require_colons == null) body.require_colons = true;
+ if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
+ if (body.require_colons == null) body.require_colons = true;
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- await handleFile(`/emojis/${id}`, body.image);
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ await handleFile(`/emojis/${id}`, body.image);
- const mimeType = body.image.split(":")[1].split(";")[0];
- const emoji = await Emoji.create({
- id: id,
- guild_id: guild_id,
- name: body.name,
- require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
- user: user,
- managed: false,
- animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm",
- available: true,
- roles: [],
- }).save();
+ const mimeType = body.image.split(":")[1].split(";")[0];
+ const emoji = await Emoji.create({
+ id: id,
+ guild_id: guild_id,
+ name: body.name,
+ require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
+ user: user,
+ managed: false,
+ animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm",
+ available: true,
+ roles: [],
+ }).save();
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- return res.status(201).json(emoji);
- },
+ return res.status(201).json(emoji);
+ },
);
router.patch(
- "/:emoji_id",
- 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;
- const body = req.body as EmojiModifySchema;
+ "/:emoji_id",
+ 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;
+ const body = req.body as EmojiModifySchema;
- const emoji = await Emoji.create({
- ...body,
- id: emoji_id,
- guild_id: guild_id,
- }).save();
+ const emoji = await Emoji.create({
+ ...body,
+ id: emoji_id,
+ guild_id: guild_id,
+ }).save();
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- return res.json(emoji);
- },
+ return res.json(emoji);
+ },
);
router.delete(
- "/:emoji_id",
- route({
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { emoji_id, guild_id } = req.params;
+ "/:emoji_id",
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { emoji_id, guild_id } = req.params;
- await Emoji.delete({
- id: emoji_id,
- guild_id: guild_id,
- });
+ await Emoji.delete({
+ id: emoji_id,
+ guild_id: guild_id,
+ });
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } }),
- },
- } as GuildEmojisUpdateEvent);
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index d6593bd8..38cc9139 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -25,188 +25,188 @@ import { GuildUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "APIGuildWithJoinedAt",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ "200": {
+ body: "APIGuildWithJoinedAt",
+ },
+ 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",
- responses: {
- 200: {
- body: "GuildCreateResponse",
- },
- 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;
+ "/",
+ route({
+ requestBody: "GuildUpdateSchema",
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "GuildCreateResponse",
+ },
+ 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;
- const rights = await getRights(req.user_id);
- const permission = await getPermission(req.user_id, guild_id);
+ const rights = await getRights(req.user_id);
+ const permission = await getPermission(req.user_id, guild_id);
- if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
+ if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- relations: ["emojis", "roles", "stickers"],
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ relations: ["emojis", "roles", "stickers"],
+ });
- // trying to `select` this fails
- guild.channel_ordering = (
- await Guild.findOneOrFail({
- where: { id: guild_id },
- select: { channel_ordering: true },
- })
- ).channel_ordering;
+ // trying to `select` this fails
+ guild.channel_ordering = (
+ await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: { channel_ordering: true },
+ })
+ ).channel_ordering;
- // TODO: guild update check image
+ // TODO: guild update check image
- if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
+ if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
- if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
+ if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
- if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
+ if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
- if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
- body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
+ if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
+ body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
- if (body.features) {
- const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
+ if (body.features) {
+ const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
- // TODO move these
- const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
+ // TODO move these
+ const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
- for (const feature of diff) {
- if (MUTABLE_FEATURES.includes(feature)) continue;
+ for (const feature of diff) {
+ if (MUTABLE_FEATURES.includes(feature)) continue;
- throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
- }
+ throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
+ }
- // for some reason, they don't update in the assign.
- guild.features = body.features;
- }
+ // for some reason, they don't update in the assign.
+ guild.features = body.features;
+ }
- // TODO: check if body ids are valid
- guild.assign(body);
+ // TODO: check if body ids are valid
+ guild.assign(body);
- if (body.public_updates_channel_id == "1") {
- // create an updates channel for them
- const channel = await Channel.createChannel(
- {
- name: "moderator-only",
- guild_id: guild.id,
- position: 0,
- type: 0,
- permission_overwrites: [
- // remove SEND_MESSAGES from @everyone
- {
- id: guild.id,
- allow: "0",
- deny: Permissions.FLAGS.VIEW_CHANNEL.toString(),
- type: 0,
- },
- ],
- },
- undefined,
- { skipPermissionCheck: true },
- );
+ if (body.public_updates_channel_id == "1") {
+ // create an updates channel for them
+ const channel = await Channel.createChannel(
+ {
+ name: "moderator-only",
+ guild_id: guild.id,
+ position: 0,
+ type: 0,
+ permission_overwrites: [
+ // remove SEND_MESSAGES from @everyone
+ {
+ id: guild.id,
+ allow: "0",
+ deny: Permissions.FLAGS.VIEW_CHANNEL.toString(),
+ type: 0,
+ },
+ ],
+ },
+ undefined,
+ { skipPermissionCheck: true },
+ );
- await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
+ await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
- guild.public_updates_channel_id = channel.id;
- } else if (body.public_updates_channel_id != undefined) {
- // ensure channel exists in this guild
- await Channel.findOneOrFail({
- where: { guild_id, id: body.public_updates_channel_id },
- select: { id: true },
- });
- }
+ guild.public_updates_channel_id = channel.id;
+ } else if (body.public_updates_channel_id != undefined) {
+ // ensure channel exists in this guild
+ await Channel.findOneOrFail({
+ where: { guild_id, id: body.public_updates_channel_id },
+ select: { id: true },
+ });
+ }
- if (body.rules_channel_id == "1") {
- // create a rules for them
- const channel = await Channel.createChannel(
- {
- name: "rules",
- guild_id: guild.id,
- position: 0,
- type: 0,
- permission_overwrites: [
- // remove SEND_MESSAGES from @everyone
- {
- id: guild.id,
- allow: "0",
- deny: Permissions.FLAGS.SEND_MESSAGES.toString(),
- type: 0,
- },
- ],
- },
- undefined,
- { skipPermissionCheck: true },
- );
+ if (body.rules_channel_id == "1") {
+ // create a rules for them
+ const channel = await Channel.createChannel(
+ {
+ name: "rules",
+ guild_id: guild.id,
+ position: 0,
+ type: 0,
+ permission_overwrites: [
+ // remove SEND_MESSAGES from @everyone
+ {
+ id: guild.id,
+ allow: "0",
+ deny: Permissions.FLAGS.SEND_MESSAGES.toString(),
+ type: 0,
+ },
+ ],
+ },
+ undefined,
+ { skipPermissionCheck: true },
+ );
- await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
+ await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild);
- guild.rules_channel_id = channel.id;
- } else if (body.rules_channel_id != undefined) {
- // ensure channel exists in this guild
- await Channel.findOneOrFail({
- where: { guild_id, id: body.rules_channel_id },
- select: { id: true },
- });
- }
+ guild.rules_channel_id = channel.id;
+ } else if (body.rules_channel_id != undefined) {
+ // ensure channel exists in this guild
+ await Channel.findOneOrFail({
+ where: { guild_id, id: body.rules_channel_id },
+ select: { id: true },
+ });
+ }
- const data = guild.toJSON();
- // TODO: guild hashes
- // TODO: fix vanity_url_code, template_id
- // delete data.vanity_url_code;
- delete data.template_id;
+ const data = guild.toJSON();
+ // TODO: guild hashes
+ // TODO: fix vanity_url_code, template_id
+ // delete data.vanity_url_code;
+ delete data.template_id;
- await Promise.all([
- guild.save(),
- emitEvent({
- event: "GUILD_UPDATE",
- data,
- guild_id,
- } as GuildUpdateEvent),
- ]);
+ await Promise.all([
+ guild.save(),
+ emitEvent({
+ event: "GUILD_UPDATE",
+ data,
+ guild_id,
+ } as GuildUpdateEvent),
+ ]);
- return res.json(data);
- },
+ return res.json(data);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts
index e396e235..704b5a50 100644
--- a/src/api/routes/guilds/#guild_id/integrations.ts
+++ b/src/api/routes/guilds/#guild_id/integrations.ts
@@ -22,6 +22,6 @@ const router = Router({ mergeParams: true });
//TODO: implement integrations list
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json([]);
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index 9372651a..32d86a4a 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -23,33 +23,33 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: {
- body: "APIInviteArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: {
+ body: "APIInviteArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const invites = await Invite.find({
- where: { guild_id },
- relations: PublicInviteRelation,
- });
+ const invites = await Invite.find({
+ where: { guild_id },
+ relations: PublicInviteRelation,
+ });
- await Promise.all(
- invites
- .filter((i) => i.isExpired())
- .map(async (i) => {
- await Invite.delete({ code: i.code });
- }),
- );
+ await Promise.all(
+ invites
+ .filter((i) => i.isExpired())
+ .map(async (i) => {
+ await Invite.delete({ code: i.code });
+ }),
+ );
- return res.json(invites.filter((i) => !i.isExpired()));
- },
+ return res.json(invites.filter((i) => !i.isExpired()));
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 20225e2b..11b36a3c 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -21,22 +21,22 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: member verification
+ "/",
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 9696cb6b..81cda182 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -24,212 +24,212 @@ import { MemberChangeSchema, PublicMemberProjection, PublicUserProjection } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPublicMember",
- },
- 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);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPublicMember",
+ },
+ 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 },
- relations: ["roles", "user"],
- select: {
- index: true,
- // only grab public member props
- ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
- // and public user props
- user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
- roles: {
- id: true,
- },
- },
- });
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ select: {
+ index: true,
+ // only grab public member props
+ ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])),
+ // and public user props
+ user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
+ roles: {
+ id: true,
+ },
+ },
+ });
- return res.json({
- ...member.toPublicMember(),
- user: member.user.toPublicUser(),
- roles: member.roles.map((x) => x.id),
- });
- },
+ return res.json({
+ ...member.toPublicMember(),
+ user: member.user.toPublicUser(),
+ roles: member.roles.map((x) => x.id),
+ });
+ },
);
router.patch(
- "/",
- 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 = req.params.member_id === "@me" ? req.user_id : req.params.member_id;
- const body = req.body as 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 = req.params.member_id === "@me" ? req.user_id : req.params.member_id;
+ const body = req.body as MemberChangeSchema;
- const member = await Member.findOneOrFail({
- where: { id: member_id, guild_id },
- relations: ["roles", "user"],
- });
- const permission = await getPermission(req.user_id, guild_id);
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ });
+ const permission = await getPermission(req.user_id, guild_id);
- if ("nick" in body) {
- permission.hasThrow("MANAGE_NICKNAMES");
+ if ("nick" in body) {
+ permission.hasThrow("MANAGE_NICKNAMES");
- if (!body.nick) {
- delete body.nick;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore shut up
- member.nick = null; // remove the nickname
- }
- }
+ if (!body.nick) {
+ delete body.nick;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore shut up
+ member.nick = null; // remove the nickname
+ }
+ }
- if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") {
- const rights = await getRights(req.user_id);
- rights.hasThrow("MANAGE_USERS");
- }
+ if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") {
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("MANAGE_USERS");
+ }
- if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string);
- member.assign(body);
+ member.assign(body);
- // must do this after the assign because the body roles array
- // is string[] not Role[]
- if ("roles" in body) {
- permission.hasThrow("MANAGE_ROLES");
+ // must do this after the assign because the body roles array
+ // is string[] not Role[]
+ if ("roles" in body) {
+ permission.hasThrow("MANAGE_ROLES");
- body.roles = body.roles || [];
- body.roles.filter((x) => !!x);
+ body.roles = body.roles || [];
+ body.roles.filter((x) => !!x);
- if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id);
- // foreign key constraint will fail if role doesn't exist
- member.roles = body.roles.map((x) => Role.create({ id: x }));
- }
+ if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id);
+ // foreign key constraint will fail if role doesn't exist
+ member.roles = body.roles.map((x) => Role.create({ id: x }));
+ }
- if ("communication_disabled_until" in body) {
- permission.hasThrow("MODERATE_MEMBERS");
- member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until);
- }
+ if ("communication_disabled_until" in body) {
+ permission.hasThrow("MODERATE_MEMBERS");
+ member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until);
+ }
- await member.save();
+ await member.save();
- member.roles = member.roles.filter((x) => x.id !== guild_id);
+ member.roles = member.roles.filter((x) => x.id !== guild_id);
- // do not use promise.all as we have to first write to db before emitting the event to catch errors
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- guild_id,
- data: { ...member, roles: member.roles.map((x) => x.id) },
- } as GuildMemberUpdateEvent);
+ // do not use promise.all as we have to first write to db before emitting the event to catch errors
+ await emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ guild_id,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
- res.json(member);
- },
+ res.json(member);
+ },
);
router.put(
- "/",
- route({
- responses: {
- 200: {
- body: "MemberJoinGuildResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // TODO: Lurker mode
+ "/",
+ 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");
- if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
- } else {
- // TODO: check oauth2 scope
+ const { guild_id } = req.params;
+ let { member_id } = req.params;
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ rights.hasThrow("JOIN_GUILDS");
+ if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
+ } else {
+ // TODO: check oauth2 scope
- throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE;
- }
+ throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE;
+ }
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- });
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
- if (!guild.features.includes("DISCOVERABLE")) {
- throw DiscordApiErrors.UNKNOWN_GUILD;
- }
+ if (!guild.features.includes("DISCOVERABLE")) {
+ throw DiscordApiErrors.UNKNOWN_GUILD;
+ }
- 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({
- 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");
- }
+ "/",
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 2a707771..33f89152 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -23,38 +23,38 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.patch(
- "/",
- route({
- requestBody: "MemberNickChangeSchema",
- responses: {
- 200: {
- body: "APIPublicMember",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
- const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id;
+ "/",
+ route({
+ requestBody: "MemberNickChangeSchema",
+ responses: {
+ 200: {
+ body: "APIPublicMember",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
+ const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id;
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow(permissionString);
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow(permissionString);
- await Member.changeNickname(member_id, guild_id, req.body.nick);
+ await Member.changeNickname(member_id, guild_id, req.body.nick);
- const member = await Member.findOne({
- where: { id: member_id, guild_id },
- relations: ["roles"],
- });
+ const member = await Member.findOne({
+ where: { id: member_id, guild_id },
+ relations: ["roles"],
+ });
- res.send(member?.toPublicMember());
- },
+ res.send(member?.toPublicMember());
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 5a5bfdfd..d482c06e 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -23,39 +23,39 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.delete(
- "/",
- route({
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.removeRole(member_id, guild_id, role_id);
- res.sendStatus(204);
- },
+ await Member.removeRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
);
router.put(
- "/",
- route({
- permission: "MANAGE_ROLES",
- responses: {
- 204: {},
- 403: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+ "/",
+ route({
+ permission: "MANAGE_ROLES",
+ responses: {
+ 204: {},
+ 403: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.addRole(member_id, guild_id, role_id);
- res.sendStatus(204);
- },
+ await Member.addRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index a047472c..08b77076 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -29,44 +29,44 @@ const router = Router({ mergeParams: true });
// TODO: check for GUILD_MEMBERS intent
router.get(
- "/",
- route({
- query: {
- limit: {
- type: "number",
- description: "max number of members to return (1-1000). default 1",
- },
- after: {
- type: "string",
- },
- },
- responses: {
- 200: {
- body: "APIMemberArray",
- },
- 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) } : {};
+ "/",
+ route({
+ query: {
+ limit: {
+ type: "number",
+ description: "max number of members to return (1-1000). default 1",
+ },
+ after: {
+ type: "string",
+ },
+ },
+ responses: {
+ 200: {
+ body: "APIMemberArray",
+ },
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index 6532b53b..be00d5de 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -27,125 +27,125 @@ import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildMessagesSearchResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 422: {
- body: "APIErrorResponse",
- },
- },
- }),
- 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;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildMessagesSearchResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 422: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ 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);
+ 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
- }
+ 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 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,
- },
- },
- 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 = [];
+ 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,
+ },
+ },
+ 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 = [];
- 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);
- }
+ 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}%`);
+ //@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);
- delete query.take;
- const total_results = await Message.count(query);
+ const messages: Message[] = await Message.find(query);
+ delete query.take;
+ const total_results = await Message.count(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,
- poll: x.poll,
- hit: true,
- },
- ]);
+ 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,
+ poll: x.poll,
+ hit: true,
+ },
+ ]);
- return res.json({
- messages: messagesDto,
- total_results,
- });
- },
+ return res.json({
+ messages: messagesDto,
+ total_results,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts
index b72ded26..fc0d34bf 100644
--- a/src/api/routes/guilds/#guild_id/premium.ts
+++ b/src/api/routes/guilds/#guild_id/premium.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/subscriptions", route({}), async (req: Request, res: Response) => {
- // TODO:
- res.json([]);
+ // TODO:
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile.ts b/src/api/routes/guilds/#guild_id/profile.ts
index 4b263fb0..176c612c 100644
--- a/src/api/routes/guilds/#guild_id/profile.ts
+++ b/src/api/routes/guilds/#guild_id/profile.ts
@@ -24,43 +24,43 @@ import { GuildProfileResponse, GuildVisibilityLevel } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "GuildProfileResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- const profileResponse: GuildProfileResponse = {
- id: guild_id,
- name: guild.name,
- icon_hash: guild.icon ?? null,
- member_count: guild.member_count!,
- online_count: guild.member_count!,
- description: guild.description ?? "A Spacebar guild",
- brand_color_primary: "#FF00FF",
- banner_hash: null,
- game_application_ids: [], // We don't track this
- game_activity: {}, // We don't track this
- tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags
- badge: 0,
- badge_color_primary: "#FF00FF",
- badge_color_secondary: "#00FFFF",
- badge_hash: "",
- traits: [],
- features: guild.features ?? [],
- visibility: GuildVisibilityLevel.PUBLIC,
- custom_banner_hash: guild.banner ?? null,
- premium_subscription_count: guild.premium_subscription_count ?? 0,
- premium_tier: guild.premium_tier ?? 0,
- };
+ "/",
+ route({
+ responses: {
+ "200": {
+ body: "GuildProfileResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const profileResponse: GuildProfileResponse = {
+ id: guild_id,
+ name: guild.name,
+ icon_hash: guild.icon ?? null,
+ member_count: guild.member_count!,
+ online_count: guild.member_count!,
+ description: guild.description ?? "A Spacebar guild",
+ brand_color_primary: "#FF00FF",
+ banner_hash: null,
+ game_application_ids: [], // We don't track this
+ game_activity: {}, // We don't track this
+ tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags
+ badge: 0,
+ badge_color_primary: "#FF00FF",
+ badge_color_secondary: "#00FFFF",
+ badge_hash: "",
+ traits: [],
+ features: guild.features ?? [],
+ visibility: GuildVisibilityLevel.PUBLIC,
+ custom_banner_hash: guild.banner ?? null,
+ premium_subscription_count: guild.premium_subscription_count ?? 0,
+ premium_tier: guild.premium_tier ?? 0,
+ };
- res.send(profileResponse);
- },
+ res.send(profileResponse);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts
index 8f0e3857..7a944b1f 100644
--- a/src/api/routes/guilds/#guild_id/profile/index.ts
+++ b/src/api/routes/guilds/#guild_id/profile/index.ts
@@ -24,47 +24,47 @@ import { MemberChangeProfileSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.patch(
- "/:member_id",
- 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 =
- // req.params.member_id === "@me" ? req.user_id : req.params.member_id;
- const body = req.body as MemberChangeProfileSchema;
+ "/:member_id",
+ 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 =
+ // req.params.member_id === "@me" ? req.user_id : req.params.member_id;
+ const body = req.body as MemberChangeProfileSchema;
- let member = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id },
- relations: ["roles", "user"],
- });
+ let member = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id },
+ relations: ["roles", "user"],
+ });
- if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
+ if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
- member = await OrmUtils.mergeDeep(member, body);
+ member = await OrmUtils.mergeDeep(member, body);
- await member.save();
+ await member.save();
- // do not use promise.all as we have to first write to db before emitting the event to catch errors
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- guild_id,
- data: { ...member, roles: member.roles.map((x) => x.id) },
- } as GuildMemberUpdateEvent);
+ // do not use promise.all as we have to first write to db before emitting the event to catch errors
+ await emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ guild_id,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
- res.json(member);
- },
+ res.json(member);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 47984e02..c8a42b84 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -24,102 +24,102 @@ const router = Router({ mergeParams: true });
//Returns all inactive members, respecting role hierarchy
const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
- const date = new Date();
- date.setDate(date.getDate() - days);
- //Snowflake should have `generateFromTime` method? Or similar?
- const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
+ const date = new Date();
+ date.setDate(date.getDate() - days);
+ //Snowflake should have `generateFromTime` method? Or similar?
+ const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
- /**
+ /**
idea: ability to customise the cutoff variable
possible candidates: public read receipt, last presence, last VC leave
**/
- let members = await Member.find({
- where: [
- {
- guild_id,
- last_message_id: LessThan(minId.toString()),
- },
- {
- guild_id,
- last_message_id: IsNull(),
- },
- ],
- relations: ["roles"],
- });
- if (!members.length) return [];
+ let members = await Member.find({
+ where: [
+ {
+ guild_id,
+ last_message_id: LessThan(minId.toString()),
+ },
+ {
+ guild_id,
+ last_message_id: IsNull(),
+ },
+ ],
+ relations: ["roles"],
+ });
+ if (!members.length) return [];
- //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
- if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
+ //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
+ if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
- const me = await Member.findOneOrFail({
- where: { id: user_id, guild_id },
- relations: ["roles"],
- });
- const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
+ const me = await Member.findOneOrFail({
+ where: { id: user_id, guild_id },
+ relations: ["roles"],
+ });
+ const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- members = members.filter(
- (member) =>
- member.id !== guild.owner_id && //can't kick owner
- member.roles?.some(
- (role) =>
- role.position < myHighestRole || //roles higher than me can't be kicked
- me.id === guild.owner_id, //owner can kick anyone
- ),
- );
+ members = members.filter(
+ (member) =>
+ member.id !== guild.owner_id && //can't kick owner
+ member.roles?.some(
+ (role) =>
+ role.position < myHighestRole || //roles higher than me can't be kicked
+ me.id === guild.owner_id, //owner can kick anyone
+ ),
+ );
- return members;
+ return members;
};
router.get(
- "/",
- route({
- responses: {
- "200": {
- body: "GuildPruneResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const days = parseInt(req.query.days as string);
+ "/",
+ 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",
- responses: {
- 200: {
- body: "GuildPurgeResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const days = parseInt(req.body.days);
+ "/",
+ 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);
- let roles = req.query.include_roles;
- if (typeof roles === "string") roles = [roles];
+ let roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles];
- const { guild_id } = req.params;
- const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
+ const { guild_id } = req.params;
+ const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
- await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
+ await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
- res.send({ purged: members.length });
- },
+ res.send({ purged: members.length });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index a3c5a81b..b2a311d7 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -23,23 +23,23 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildVoiceRegion",
- },
- 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(req.ip!, guild.features.includes("VIP_REGIONS")));
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildVoiceRegion",
+ },
+ 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(req.ip!, guild.features.includes("VIP_REGIONS")));
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index b8349eb6..1811284a 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -25,121 +25,121 @@ import { RoleModifySchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
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);
- },
+ "/",
+ 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",
- 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) throw new HTTPError("You can't delete the @everyone role");
+ "/",
+ 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) throw new HTTPError("You can't delete the @everyone role");
- await Promise.all([
- Role.delete({
- id: role_id,
- guild_id: guild_id,
- }),
- emitEvent({
- event: "GUILD_ROLE_DELETE",
- guild_id,
- data: {
- guild_id,
- role_id,
- },
- } as GuildRoleDeleteEvent),
- ]);
+ await Promise.all([
+ Role.delete({
+ id: role_id,
+ guild_id: guild_id,
+ }),
+ emitEvent({
+ event: "GUILD_ROLE_DELETE",
+ guild_id,
+ data: {
+ guild_id,
+ role_id,
+ },
+ } as GuildRoleDeleteEvent),
+ ]);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
// TODO: check role hierarchy
router.patch(
- "/",
- 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;
+ "/",
+ 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;
- if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
- else body.icon = undefined;
+ if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
+ else body.icon = undefined;
- const role = await Role.findOneOrFail({
- where: { id: role_id, guild: { id: guild_id } },
- });
- role.assign({
- ...body,
- permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
- });
+ const role = await Role.findOneOrFail({
+ where: { id: role_id, guild: { id: guild_id } },
+ });
+ role.assign({
+ ...body,
+ permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
+ });
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
- guild_id,
- data: {
- guild_id,
- role,
- },
- } as GuildRoleUpdateEvent),
- ]);
+ await Promise.all([
+ role.save(),
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role,
+ },
+ } as GuildRoleUpdateEvent),
+ ]);
- res.json(role);
- },
+ res.json(role);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
index 6558da58..ab61325e 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
@@ -23,20 +23,20 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
+ const { guild_id, role_id } = req.params;
- // TODO: Is this route really not paginated?
- const members = await Member.find({
- select: ["id"],
- where: {
- roles: {
- id: role_id,
- },
- guild_id,
- },
- });
+ // TODO: Is this route really not paginated?
+ const members = await Member.find({
+ select: ["id"],
+ where: {
+ roles: {
+ id: role_id,
+ },
+ guild_id,
+ },
+ });
- return res.json(members.map((x) => x.id));
+ return res.json(members.map((x) => x.id));
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
index d22d1434..f89d5935 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
@@ -23,24 +23,24 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- // Payload is JSON containing a list of member_ids, the new list of members to have the role
- const { guild_id, role_id } = req.params;
- const { member_ids } = req.body;
+ // Payload is JSON containing a list of member_ids, the new list of members to have the role
+ const { guild_id, role_id } = req.params;
+ const { member_ids } = req.body;
- // don't mess with @everyone
- if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE;
+ // don't mess with @everyone
+ if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE;
- const members = await Member.find({
- where: { guild_id },
- relations: ["roles"],
- });
+ const members = await Member.find({
+ where: { guild_id },
+ relations: ["roles"],
+ });
- const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
+ const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id));
- // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
- await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]);
+ // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn
+ await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]);
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index 26584e90..8caea56f 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -25,129 +25,129 @@ import { RoleModifySchema, RolePositionUpdateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
+ const guild_id = req.params.guild_id;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const roles = await Role.find({ where: { guild_id: guild_id } });
+ const roles = await Role.find({ where: { guild_id: guild_id } });
- return res.json(roles);
+ return res.json(roles);
});
router.post(
- "/",
- 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;
+ "/",
+ 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;
- const role_count = await Role.count({ where: { guild_id } });
- const { maxRoles } = Config.get().limits.guild;
+ const role_count = await Role.count({ where: { guild_id } });
+ const { maxRoles } = Config.get().limits.guild;
- if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
+ if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
- const role = Role.create({
- // values before ...body are default and can be overridden
- position: 1,
- hoist: false,
- color: 0,
- mentionable: false,
- ...body,
- guild_id: guild_id,
- managed: false,
- permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
- tags: undefined,
- icon: undefined,
- unicode_emoji: undefined,
- id: Snowflake.generate(),
- colors: {
- primary_color: body.colors?.primary_color || body.color || 0,
- secondary_color: body.colors?.secondary_color || undefined, // gradient
- tertiary_color: body.colors?.tertiary_color || undefined, // "holographic"
- },
- });
+ const role = Role.create({
+ // values before ...body are default and can be overridden
+ position: 1,
+ hoist: false,
+ color: 0,
+ mentionable: false,
+ ...body,
+ guild_id: guild_id,
+ managed: false,
+ permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")),
+ tags: undefined,
+ icon: undefined,
+ unicode_emoji: undefined,
+ id: Snowflake.generate(),
+ colors: {
+ primary_color: body.colors?.primary_color || body.color || 0,
+ secondary_color: body.colors?.secondary_color || undefined, // gradient
+ tertiary_color: body.colors?.tertiary_color || undefined, // "holographic"
+ },
+ });
- await Promise.all([
- role.save(),
- // Move all existing roles up one position, to accommodate the new role
- Role.createQueryBuilder("roles")
- .where({
- guild: { id: guild_id },
- name: Not("@everyone"),
- id: Not(role.id),
- })
- .update({ position: () => "position + 1" })
- .execute(),
- emitEvent({
- event: "GUILD_ROLE_CREATE",
- guild_id,
- data: {
- guild_id,
- role: role,
- },
- } as GuildRoleCreateEvent),
- ]);
+ await Promise.all([
+ role.save(),
+ // Move all existing roles up one position, to accommodate the new role
+ Role.createQueryBuilder("roles")
+ .where({
+ guild: { id: guild_id },
+ name: Not("@everyone"),
+ id: Not(role.id),
+ })
+ .update({ position: () => "position + 1" })
+ .execute(),
+ emitEvent({
+ event: "GUILD_ROLE_CREATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: role,
+ },
+ } as GuildRoleCreateEvent),
+ ]);
- res.json(role);
- },
+ res.json(role);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "RolePositionUpdateSchema",
- permission: "MANAGE_ROLES",
- responses: {
- 200: {
- body: "APIRoleArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as RolePositionUpdateSchema;
+ "/",
+ route({
+ requestBody: "RolePositionUpdateSchema",
+ permission: "MANAGE_ROLES",
+ responses: {
+ 200: {
+ body: "APIRoleArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as RolePositionUpdateSchema;
- await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
+ await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
- const roles = await Role.find({
- where: body.map((x) => ({ id: x.id, guild_id })),
- });
+ const roles = await Role.find({
+ where: body.map((x) => ({ id: x.id, guild_id })),
+ });
- await Promise.all(
- roles.map((x) =>
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
- guild_id,
- data: {
- guild_id,
- role: x,
- },
- } as GuildRoleUpdateEvent),
- ),
- );
+ await Promise.all(
+ roles.map((x) =>
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: x,
+ },
+ } as GuildRoleUpdateEvent),
+ ),
+ );
- res.json(roles);
- },
+ res.json(roles);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/member-counts.ts b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
index 5574e59e..d77149d5 100644
--- a/src/api/routes/guilds/#guild_id/roles/member-counts.ts
+++ b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
@@ -23,16 +23,16 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ const { guild_id } = req.params;
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const role_ids = await Role.find({ where: { guild_id }, select: ["id"] });
- const counts: { [id: string]: number } = {};
- for (const { id } of role_ids) {
- counts[id] = await Member.count({ where: { roles: { id }, guild_id } });
- }
+ const role_ids = await Role.find({ where: { guild_id }, select: ["id"] });
+ const counts: { [id: string]: number } = {};
+ for (const { id } of role_ids) {
+ counts[id] = await Member.count({ where: { roles: { id }, guild_id } });
+ }
- return res.json(counts);
+ return res.json(counts);
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 231c3b55..f3a9f527 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -25,185 +25,185 @@ import { ModifyGuildStickerSchema, StickerFormatType, StickerType } from "@space
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIStickerArray",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIStickerArray",
+ },
+ 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: {
- fileSize: 1024 * 1024 * 100,
- fields: 10,
- files: 1,
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: 1024 * 1024 * 100,
+ fields: 10,
+ files: 1,
+ },
+ storage: multer.memoryStorage(),
}).single("file");
router.post(
- "/",
- bodyParser,
- 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");
+ "/",
+ bodyParser,
+ 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");
- const { guild_id } = req.params;
- const body = req.body as ModifyGuildStickerSchema;
- const id = Snowflake.generate();
+ const { guild_id } = req.params;
+ const body = req.body as ModifyGuildStickerSchema;
+ const id = Snowflake.generate();
- const sticker_count = await Sticker.count({
- where: { guild_id: guild_id },
- });
- const { maxStickers } = Config.get().limits.guild;
+ const sticker_count = await Sticker.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxStickers } = Config.get().limits.guild;
- if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers);
+ if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers);
- const [sticker] = await Promise.all([
- Sticker.create({
- ...body,
- guild_id,
- id,
- type: StickerType.GUILD,
- format_type: getStickerFormat(req.file.mimetype),
- available: true,
- }).save(),
- uploadFile(`/stickers/${id}`, req.file),
- ]);
+ const [sticker] = await Promise.all([
+ Sticker.create({
+ ...body,
+ guild_id,
+ id,
+ type: StickerType.GUILD,
+ format_type: getStickerFormat(req.file.mimetype),
+ available: true,
+ }).save(),
+ uploadFile(`/stickers/${id}`, req.file),
+ ]);
- await sendStickerUpdateEvent(guild_id);
+ await sendStickerUpdateEvent(guild_id);
- res.json(sticker);
- },
+ res.json(sticker);
+ },
);
function getStickerFormat(mime_type: string) {
- switch (mime_type) {
- case "image/apng":
- return StickerFormatType.APNG;
- case "application/json":
- return StickerFormatType.LOTTIE;
- case "image/png":
- return StickerFormatType.PNG;
- case "image/gif":
- return StickerFormatType.GIF;
- default:
- throw new HTTPError("invalid sticker format: must be png, apng or lottie");
- }
+ switch (mime_type) {
+ case "image/apng":
+ return StickerFormatType.APNG;
+ case "application/json":
+ return StickerFormatType.LOTTIE;
+ case "image/png":
+ return StickerFormatType.PNG;
+ case "image/gif":
+ return StickerFormatType.GIF;
+ default:
+ throw new HTTPError("invalid sticker format: must be png, apng or lottie");
+ }
}
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);
+ "/: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;
- const body = req.body as ModifyGuildStickerSchema;
+ "/: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;
+ const body = req.body as ModifyGuildStickerSchema;
- const sticker = await Sticker.create({
- ...body,
- guild_id,
- id: sticker_id,
- }).save();
- await sendStickerUpdateEvent(guild_id);
+ const sticker = await Sticker.create({
+ ...body,
+ guild_id,
+ id: sticker_id,
+ }).save();
+ await sendStickerUpdateEvent(guild_id);
- return res.json(sticker);
- },
+ return res.json(sticker);
+ },
);
async function sendStickerUpdateEvent(guild_id: string) {
- return emitEvent({
- event: "GUILD_STICKERS_UPDATE",
- guild_id: guild_id,
- data: {
- guild_id: guild_id,
- stickers: await Sticker.find({ where: { guild_id: guild_id } }),
- },
- } as GuildStickersUpdateEvent);
+ return emitEvent({
+ event: "GUILD_STICKERS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ stickers: await Sticker.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildStickersUpdateEvent);
}
router.delete(
- "/:sticker_id",
- route({
- permission: "MANAGE_EMOJIS_AND_STICKERS",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
+ "/:sticker_id",
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
- await Sticker.delete({ guild_id, id: sticker_id });
- await sendStickerUpdateEvent(guild_id);
+ await Sticker.delete({ guild_id, id: sticker_id });
+ await sendStickerUpdateEvent(guild_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index ef9d12c6..2cc9604c 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -24,160 +24,160 @@ import { HTTPError } from "lambert-server";
const router: Router = Router({ mergeParams: true });
const TemplateGuildProjection: (keyof Guild)[] = [
- "id",
- "name",
- "description",
- "region",
- "verification_level",
- "default_message_notifications",
- "explicit_content_filter",
- "preferred_locale",
- "afk_timeout",
- // "roles",
- // "channels",
- "afk_channel_id",
- "system_channel_id",
- "system_channel_flags",
- "icon",
+ "id",
+ "name",
+ "description",
+ "region",
+ "verification_level",
+ "default_message_notifications",
+ "explicit_content_filter",
+ "preferred_locale",
+ "afk_timeout",
+ // "roles",
+ // "channels",
+ "afk_channel_id",
+ "system_channel_id",
+ "system_channel_flags",
+ "icon",
];
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APITemplateArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APITemplateArray",
+ },
+ },
+ }),
+ 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",
- 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({
- where: { id: guild_id },
- select: TemplateGuildProjection,
- relations: ["roles", "channels"],
- });
- const exists = await Template.findOne({
- where: { id: guild_id },
- });
- if (exists) throw new HTTPError("Template already exists", 400);
+ "/",
+ 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({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ relations: ["roles", "channels"],
+ });
+ const exists = await Template.findOne({
+ where: { id: guild_id },
+ });
+ if (exists) throw new HTTPError("Template already exists", 400);
- const template = await Template.create({
- ...req.body,
- code: generateCode(),
- creator_id: req.user_id,
- created_at: new Date(),
- updated_at: new Date(),
- source_guild_id: guild_id,
- serialized_source_guild: guild,
- }).save();
+ const template = await Template.create({
+ ...req.body,
+ code: generateCode(),
+ creator_id: req.user_id,
+ created_at: new Date(),
+ updated_at: new Date(),
+ source_guild_id: guild_id,
+ serialized_source_guild: guild,
+ }).save();
- res.json(template);
- },
+ res.json(template);
+ },
);
router.delete(
- "/:code",
- route({
- permission: "MANAGE_GUILD",
- responses: {
- 200: { body: "Template" },
- 403: { body: "APIErrorResponse" },
- },
- }),
- async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
+ "/:code",
+ route({
+ permission: "MANAGE_GUILD",
+ responses: {
+ 200: { body: "Template" },
+ 403: { body: "APIErrorResponse" },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
- const template = await Template.delete({
- code,
- source_guild_id: guild_id,
- });
+ const template = await Template.delete({
+ code,
+ source_guild_id: guild_id,
+ });
- res.json(template);
- },
+ res.json(template);
+ },
);
router.put(
- "/:code",
- 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({
- where: { id: guild_id },
- select: TemplateGuildProjection,
- });
+ "/:code",
+ 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({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
- const template = await Template.create({
- code,
- serialized_source_guild: guild,
- }).save();
+ const template = await Template.create({
+ code,
+ serialized_source_guild: guild,
+ }).save();
- res.json(template);
- },
+ res.json(template);
+ },
);
router.patch(
- "/:code",
- 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;
+ "/:code",
+ 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;
- const template = await Template.findOneOrFail({
- where: { code, source_guild_id: guild_id },
- });
+ const template = await Template.findOneOrFail({
+ where: { code, source_guild_id: guild_id },
+ });
- template.name = name;
- template.description = description;
+ template.name = name;
+ template.description = description;
- await template.save();
+ await template.save();
- res.json(template);
- },
+ res.json(template);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 77d72ed0..e622c255 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -27,96 +27,96 @@ const router = Router({ mergeParams: true });
const InviteRegex = /\W/g;
router.get(
- "/",
- 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 } });
+ "/",
+ 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 } });
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- const invite = await Invite.findOne({
- where: { guild_id: guild_id, vanity_url: true },
- });
- if (!invite) return res.json({ code: null });
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ const invite = await Invite.findOne({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite) return res.json({ code: null });
- return res.json({ code: invite.code, uses: invite.uses });
- } else {
- const invite = await Invite.find({
- where: { guild_id: guild_id, vanity_url: true },
- });
- if (!invite || invite.length == 0) return res.json({ code: null });
+ return res.json({ code: invite.code, uses: invite.uses });
+ } else {
+ const invite = await Invite.find({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite || invite.length == 0) return res.json({ code: null });
- return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
- }
- },
+ return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
+ }
+ },
);
router.patch(
- "/",
- 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;
- const code = body.code?.replace(InviteRegex, "");
+ "/",
+ 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;
+ const code = body.code?.replace(InviteRegex, "");
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
- if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
+ if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
- const invite = await Invite.findOne({ where: { code } });
- if (invite) throw new HTTPError("Invite already exists");
+ const invite = await Invite.findOne({ where: { code } });
+ if (invite) throw new HTTPError("Invite already exists");
- const { id } = await Channel.findOneOrFail({
- where: { guild_id, type: ChannelType.GUILD_TEXT },
- });
+ const { id } = await Channel.findOneOrFail({
+ where: { guild_id, type: ChannelType.GUILD_TEXT },
+ });
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- await Invite.delete({ guild_id, vanity_url: true });
- }
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ await Invite.delete({ guild_id, vanity_url: true });
+ }
- await Invite.create({
- vanity_url: true,
- code,
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: 0,
- created_at: new Date(),
- guild_id: guild_id,
- channel_id: id,
- flags: 0,
- }).save();
+ await Invite.create({
+ vanity_url: true,
+ code,
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: 0,
+ created_at: new Date(),
+ guild_id: guild_id,
+ channel_id: id,
+ flags: 0,
+ }).save();
- return res.json({ code });
- },
+ return res.json({ code });
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 38be139c..5283ae01 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -25,67 +25,67 @@ const router = Router({ mergeParams: true });
//TODO need more testing when community guild and voice stage channel are working
router.patch(
- "/",
- 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;
- const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id;
+ "/",
+ 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;
+ const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id;
- const perms = await getPermission(req.user_id, guild_id, body.channel_id);
+ const perms = await getPermission(req.user_id, guild_id, body.channel_id);
- /*
+ /*
From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself.
You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
*/
- if (body.suppress && user_id !== req.user_id) {
- perms.hasThrow("MUTE_MEMBERS");
- }
- if (!body.suppress) body.request_to_speak_timestamp = new Date();
- if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
+ if (body.suppress && user_id !== req.user_id) {
+ perms.hasThrow("MUTE_MEMBERS");
+ }
+ if (!body.suppress) body.request_to_speak_timestamp = new Date();
+ if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
- const voice_state = await VoiceState.findOne({
- where: {
- guild_id,
- channel_id: body.channel_id,
- user_id,
- },
- });
- if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
+ const voice_state = await VoiceState.findOne({
+ where: {
+ guild_id,
+ channel_id: body.channel_id,
+ user_id,
+ },
+ });
+ if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
- voice_state.assign(body);
- const channel = await Channel.findOneOrFail({
- where: { guild_id, id: body.channel_id },
- });
- if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
- throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
- }
+ voice_state.assign(body);
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id, id: body.channel_id },
+ });
+ if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
+ throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
+ }
- await Promise.all([
- voice_state.save(),
- emitEvent({
- event: "VOICE_STATE_UPDATE",
- data: voice_state.toPublicVoiceState(),
- guild_id,
- } as VoiceStateUpdateEvent),
- ]);
- return res.sendStatus(204);
- },
+ await Promise.all([
+ voice_state.save(),
+ emitEvent({
+ event: "VOICE_STATE_UPDATE",
+ data: voice_state.toPublicVoiceState(),
+ guild_id,
+ } as VoiceStateUpdateEvent),
+ ]);
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts
index 49f34b6b..81837d34 100644
--- a/src/api/routes/guilds/#guild_id/webhooks.ts
+++ b/src/api/routes/guilds/#guild_id/webhooks.ts
@@ -22,31 +22,31 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.",
- permission: "MANAGE_WEBHOOKS",
- responses: {
- 200: {
- body: "APIWebhookArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const webhooks = await Webhook.find({
- where: { guild_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.",
+ permission: "MANAGE_WEBHOOKS",
+ responses: {
+ 200: {
+ body: "APIWebhookArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const webhooks = await Webhook.find({
+ where: { guild_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- const instanceUrl = Config.get().api.endpointPublic;
- return res.json(
- webhooks.map((webhook) => ({
- ...webhook,
- url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token,
- })),
- );
- },
+ const instanceUrl = Config.get().api.endpointPublic;
+ return res.json(
+ webhooks.map((webhook) => ({
+ ...webhook,
+ url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token,
+ })),
+ );
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index a8956eea..474d5977 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -24,69 +24,69 @@ import { GuildUpdateWelcomeScreenSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildWelcomeScreen",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
+ "/",
+ 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;
- const body = req.body as GuildUpdateWelcomeScreenSchema;
+ "/",
+ 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;
+ const body = req.body as GuildUpdateWelcomeScreenSchema;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled;
+ if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled;
- if (body.description != undefined) guild.welcome_screen.description = body.description;
+ if (body.description != undefined) guild.welcome_screen.description = body.description;
- if (body.welcome_channels != undefined) {
- // Ensure channels exist within the guild
- await Promise.all(
- body.welcome_channels?.map(({ channel_id }) =>
- Channel.findOneOrFail({
- where: { id: channel_id, guild_id },
- select: { id: true },
- }),
- ) || [],
- );
- guild.welcome_screen.welcome_channels = body.welcome_channels;
- }
+ if (body.welcome_channels != undefined) {
+ // Ensure channels exist within the guild
+ await Promise.all(
+ body.welcome_channels?.map(({ channel_id }) =>
+ Channel.findOneOrFail({
+ where: { id: channel_id, guild_id },
+ select: { id: true },
+ }),
+ ) || [],
+ );
+ guild.welcome_screen.welcome_channels = body.welcome_channels;
+ }
- await guild.save();
+ await guild.save();
- res.status(200).json(guild.welcome_screen);
- },
+ res.status(200).json(guild.welcome_screen);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index d6f958f7..8f41417a 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -32,88 +32,88 @@ const router: Router = Router({ mergeParams: true });
// 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({
- responses: {
- 200: {
- body: "GuildWidgetJsonResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ 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 },
- select: {
- channel_ordering: true,
- widget_channel_id: true,
- widget_enabled: true,
- presence_count: true,
- name: true,
- },
- });
- if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: {
+ channel_ordering: true,
+ widget_channel_id: true,
+ widget_enabled: true,
+ presence_count: true,
+ name: true,
+ },
+ });
+ if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
- // 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: randomString(),
- 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,
- flags: 0,
- }).save();
- }
+ invite = await Invite.create({
+ code: randomString(),
+ 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,
+ flags: 0,
+ }).save();
+ }
- // Fetch voice channels, and the @everyone permissions object
- const channels: { id: string; name: string; position: number }[] = [];
+ // Fetch voice channels, and the @everyone permissions object
+ const channels: { id: string; name: string; position: number }[] = [];
- (await Channel.getOrderedChannels(guild.id, guild)).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,
- });
- }
- });
+ (await Channel.getOrderedChannels(guild.id, guild)).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,
+ });
+ }
+ });
- // 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 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,
- };
+ // 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);
- },
+ res.set("Cache-Control", "public, max-age=300");
+ return res.json(data);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index a7a9b60b..b0abc95b 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -33,112 +33,112 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
// TODO: Cache the response
router.get(
- "/",
- route({
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ 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 DiscordApiErrors.EMBED_DISABLED;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED;
- // Fetch guild information
- const icon = "avatars/" + guild_id + "/" + guild.icon;
- const name = guild.name;
- const presence = guild.presence_count + " ONLINE";
+ // Fetch guild information
+ const icon = "avatars/" + guild_id + "/" + 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);
- }
+ // 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, loadImage } = require("canvas");
- const sizeOf = require("image-size");
+ // Setup canvas
+ const { createCanvas, 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);
- }
+ // 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);
+ // 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);
- }
+ // 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);
- },
+ // 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, x: number, y: number, scale: number, icon: string) {
- const { loadImage } = require("canvas");
- const img = await loadImage(await storage.get(icon));
+ const { loadImage } = require("canvas");
+ const img = await loadImage(await storage.get(icon));
- // Do some canvas clipping magic!
- canvas.save();
- canvas.beginPath();
+ // Do some canvas clipping magic!
+ canvas.save();
+ canvas.beginPath();
- const r = scale / 2; // use scale to determine radius
- canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center
+ const r = scale / 2; // use scale to determine radius
+ canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center
- canvas.clip();
- canvas.drawImage(img, x, y, scale, scale);
+ canvas.clip();
+ canvas.drawImage(img, x, y, scale, scale);
- canvas.restore();
+ canvas.restore();
}
async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) {
- canvas.fillStyle = color;
- canvas.font = font;
- if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
- canvas.fillText(text, x, y);
+ canvas.fillStyle = color;
+ canvas.font = font;
+ if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
+ canvas.fillText(text, x, y);
}
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index cad26f3d..5a2fdb96 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -25,62 +25,62 @@ const router: Router = Router({ mergeParams: true });
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "GuildWidgetSettingsResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+ "/",
+ 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",
- 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;
+ "/",
+ 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;
- await Guild.update(
- { id: guild_id },
- {
- widget_enabled: body.enabled,
- widget_channel_id: body.channel_id,
- },
- );
- // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
+ await Guild.update(
+ { id: guild_id },
+ {
+ widget_enabled: body.enabled,
+ widget_channel_id: body.channel_id,
+ },
+ );
+ // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
- return res.json(body);
- },
+ return res.json(body);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/automations/email-domain-lookup.ts b/src/api/routes/guilds/automations/email-domain-lookup.ts
index 93ae231b..605026ba 100644
--- a/src/api/routes/guilds/automations/email-domain-lookup.ts
+++ b/src/api/routes/guilds/automations/email-domain-lookup.ts
@@ -26,74 +26,74 @@ import { EmailDomainLookupResponse, EmailDomainLookupSchema, EmailDomainLookupVe
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "EmailDomainLookupSchema",
- responses: {
- 200: {
- body: "EmailDomainLookupResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { email } = req.body as EmailDomainLookupSchema;
+ "/",
+ route({
+ requestBody: "EmailDomainLookupSchema",
+ responses: {
+ 200: {
+ body: "EmailDomainLookupResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email } = req.body as EmailDomainLookupSchema;
- const [_, tld] = email.split("@");
+ const [_, tld] = email.split("@");
- if (emailProviders.includes(tld.toLowerCase())) {
- throw FieldErrors({
- name: {
- message: "That looks like a personal email address. Please use your official student email.",
- code: "EMAIL_IS_UNOFFICIAL",
- },
- });
- }
+ if (emailProviders.includes(tld.toLowerCase())) {
+ throw FieldErrors({
+ name: {
+ message: "That looks like a personal email address. Please use your official student email.",
+ code: "EMAIL_IS_UNOFFICIAL",
+ },
+ });
+ }
- return res.json({
- guilds_info: [],
- has_matching_guild: false,
- } as EmailDomainLookupResponse);
- },
+ return res.json({
+ guilds_info: [],
+ has_matching_guild: false,
+ } as EmailDomainLookupResponse);
+ },
);
router.post(
- "/verify-code",
- route({
- requestBody: "EmailDomainLookupVerifyCodeSchema",
- responses: {
- // 200: {
- // body: "EmailDomainLookupVerifyCodeResponse",
- // },
- 400: {
- body: "APIErrorResponse",
- },
- 501: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { email } = req.body as EmailDomainLookupVerifyCodeSchema;
+ "/verify-code",
+ route({
+ requestBody: "EmailDomainLookupVerifyCodeSchema",
+ responses: {
+ // 200: {
+ // body: "EmailDomainLookupVerifyCodeResponse",
+ // },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 501: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email } = req.body as EmailDomainLookupVerifyCodeSchema;
- const [_, tld] = email.split("@");
+ const [_, tld] = email.split("@");
- if (emailProviders.includes(tld.toLowerCase())) {
- throw FieldErrors({
- name: {
- message: "That looks like a personal email address. Please use your official student email.",
- code: "EMAIL_IS_UNOFFICIAL",
- },
- });
- }
+ if (emailProviders.includes(tld.toLowerCase())) {
+ throw FieldErrors({
+ name: {
+ message: "That looks like a personal email address. Please use your official student email.",
+ code: "EMAIL_IS_UNOFFICIAL",
+ },
+ });
+ }
- throw new HTTPError("Not implemented", 501);
+ throw new HTTPError("Not implemented", 501);
- // return res.json({
- // guild: null,
- // joined: false,
- // } as EmailDomainLookupVerifyCodeResponse);
- },
+ // return res.json({
+ // guild: null,
+ // joined: false,
+ // } as EmailDomainLookupVerifyCodeResponse);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 6f60fa67..78ba93a2 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -26,49 +26,49 @@ const router: Router = Router({ mergeParams: true });
//TODO: create default channel
router.post(
- "/",
- 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;
+ "/",
+ 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;
- const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: req.user_id } });
- const rights = await getRights(req.user_id);
- if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) {
- throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- }
+ const { maxGuilds } = Config.get().limits.user;
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ const rights = await getRights(req.user_id);
+ if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) {
+ throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ }
- const guild = await Guild.createGuild({
- ...body,
- owner_id: req.user_id,
- template_guild_id: null,
- });
+ const guild = await Guild.createGuild({
+ ...body,
+ owner_id: req.user_id,
+ template_guild_id: null,
+ });
- const { autoJoin } = Config.get().guild;
- if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- }
+ const { autoJoin } = Config.get().guild;
+ if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ }
- await Member.addToGuild(req.user_id, guild.id);
+ await Member.addToGuild(req.user_id, guild.id);
- res.status(201).json(guild);
- },
+ res.status(201).json(guild);
+ },
);
export default router;
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 50ba017d..e5dfd684 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -25,80 +25,80 @@ import { GuildTemplateCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:template_code",
- route({
- responses: {
- 200: {
- body: "Template",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { template_code } = req.params;
+ "/:template_code",
+ route({
+ responses: {
+ 200: {
+ body: "Template",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { template_code } = req.params;
- const template = await getTemplate(template_code);
+ const template = await getTemplate(template_code);
- res.json(template);
- },
+ res.json(template);
+ },
);
router.post("/:template_code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
- const { template_code } = req.params;
- const body = req.body as GuildTemplateCreateSchema;
+ const { template_code } = req.params;
+ const body = req.body as GuildTemplateCreateSchema;
- const { maxGuilds } = Config.get().limits.user;
+ const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ where: { id: req.user_id } });
- if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
+ const guild_count = await Member.count({ where: { id: req.user_id } });
+ if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
- const template = (await getTemplate(template_code)) as Template;
+ const template = (await getTemplate(template_code)) as Template;
- const guild = await Guild.createGuild({
- ...template.serialized_source_guild,
- // body comes after the template
- ...body,
- owner_id: req.user_id,
- template_guild_id: template.source_guild_id,
- });
+ const guild = await Guild.createGuild({
+ ...template.serialized_source_guild,
+ // body comes after the template
+ ...body,
+ owner_id: req.user_id,
+ template_guild_id: template.source_guild_id,
+ });
- await Member.addToGuild(req.user_id, guild.id);
+ await Member.addToGuild(req.user_id, guild.id);
- res.status(201).json({ id: guild.id });
+ res.status(201).json({ id: guild.id });
});
async function getTemplate(code: string) {
- const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
+ const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
- if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403);
+ if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403);
- if (code.startsWith("discord:")) {
- if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403);
+ if (code.startsWith("discord:")) {
+ if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403);
- const discordTemplateID = code.split("discord:", 2)[1];
+ 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" },
- });
+ const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
- return await discordTemplateData.json();
- }
+ return await discordTemplateData.json();
+ }
- if (code.startsWith("external:")) {
- if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403);
+ if (code.startsWith("external:")) {
+ if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403);
- return code.split("external:", 2)[1];
- }
+ return code.split("external:", 2)[1];
+ }
- return await Template.findOneOrFail({
- where: { code: code },
- });
+ return await Template.findOneOrFail({
+ where: { code: code },
+ });
}
export default router;
diff --git a/src/api/routes/hub-waitlist.ts b/src/api/routes/hub-waitlist.ts
index bdf09311..43c2662b 100644
--- a/src/api/routes/hub-waitlist.ts
+++ b/src/api/routes/hub-waitlist.ts
@@ -22,28 +22,28 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/signup",
- route({
- requestBody: "HubWaitlistSignupSchema",
- responses: {
- 200: {
- body: "HubWaitlistSignupResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { email, school } = req.body as HubWaitlistSignupSchema;
+ "/signup",
+ route({
+ requestBody: "HubWaitlistSignupSchema",
+ responses: {
+ 200: {
+ body: "HubWaitlistSignupResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { email, school } = req.body as HubWaitlistSignupSchema;
- res.json({
- email,
- email_domain: email.split("@")[1],
- school,
- user_id: req.user_id,
- } as HubWaitlistSignupResponse);
- },
+ res.json({
+ email,
+ email_domain: email.split("@")[1],
+ school,
+ user_id: req.user_id,
+ } as HubWaitlistSignupResponse);
+ },
);
export default router;
diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
index 442ce0ef..e94d455a 100644
--- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
+++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
@@ -26,79 +26,79 @@ import { sendMessage } from "../../../../util/handlers/Message";
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- const body = req.body as InteractionCallbackSchema;
+ const body = req.body as InteractionCallbackSchema;
- const errors: Record<string, { code?: string; message: string }> = {};
- const knownComponentIds: string[] = [];
+ const errors: Record<string, { code?: string; message: string }> = {};
+ const knownComponentIds: string[] = [];
- for (const row of body.data.components || []) {
- if (!row.components) {
- continue;
- }
+ for (const row of body.data.components || []) {
+ if (!row.components) {
+ continue;
+ }
- if (row.components.length < 1 || row.components.length > 5) {
- errors[`data.components[${body.data.components!.indexOf(row)}].components`] = {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 1 and 5 in length.`,
- };
- }
+ if (row.components.length < 1 || row.components.length > 5) {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components`] = {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 1 and 5 in length.`,
+ };
+ }
- for (const component of row.components) {
- if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) {
- if (component.custom_id?.trim() === "") {
- errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
- code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED",
- message: "A custom id required",
- };
- }
+ for (const component of row.components) {
+ if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) {
+ if (component.custom_id?.trim() === "") {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
+ code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED",
+ message: "A custom id required",
+ };
+ }
- if (knownComponentIds.includes(component.custom_id!)) {
- errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
- code: "COMPONENT_CUSTOM_ID_DUPLICATED",
- message: "Component custom id cannot be duplicated",
- };
- } else {
- knownComponentIds.push(component.custom_id!);
- }
- }
- }
- }
+ if (knownComponentIds.includes(component.custom_id!)) {
+ errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = {
+ code: "COMPONENT_CUSTOM_ID_DUPLICATED",
+ message: "Component custom id cannot be duplicated",
+ };
+ } else {
+ knownComponentIds.push(component.custom_id!);
+ }
+ }
+ }
+ }
- if (Object.keys(errors).length > 0) {
- throw FieldErrors(errors);
- }
+ if (Object.keys(errors).length > 0) {
+ throw FieldErrors(errors);
+ }
- const interactionId = req.params.interaction_id;
- const interaction = pendingInteractions.get(req.params.interaction_id);
+ const interactionId = req.params.interaction_id;
+ const interaction = pendingInteractions.get(req.params.interaction_id);
- if (!interaction) {
- return;
- }
+ if (!interaction) {
+ return;
+ }
- clearTimeout(interaction.timeout);
+ clearTimeout(interaction.timeout);
- emitEvent({
- event: "INTERACTION_SUCCESS",
- user_id: interaction?.userId,
- data: {
- id: interactionId,
- nonce: interaction?.nonce,
- },
- } as InteractionSuccessEvent);
+ emitEvent({
+ event: "INTERACTION_SUCCESS",
+ user_id: interaction?.userId,
+ data: {
+ id: interactionId,
+ nonce: interaction?.nonce,
+ },
+ } as InteractionSuccessEvent);
- switch (body.type) {
- case InteractionCallbackType.PONG:
- // TODO
- break;
- case InteractionCallbackType.ACKNOWLEDGE:
- // Deprected
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
- const user = await User.findOneOrFail({ where: { id: interaction.userId } });
- /*
+ switch (body.type) {
+ case InteractionCallbackType.PONG:
+ // TODO
+ break;
+ case InteractionCallbackType.ACKNOWLEDGE:
+ // Deprected
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: {
+ const user = await User.findOneOrFail({ where: { id: interaction.userId } });
+ /*
const files = (req.files as Express.Multer.File[]) ?? [];
//I don't think traditional attachments are allowed anyways
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = [];
@@ -111,71 +111,71 @@ router.post("/", route({}), async (req: Request, res: Response) => {
}
}
*/
- await sendMessage({
- type: MessageType.APPLICATION_COMMAND,
- timestamp: new Date(),
- application_id: interaction.applicationId,
- channel_id: interaction.channelId,
- author_id: interaction.applicationId,
- nonce: interaction.nonce,
- content: body.data.content,
- components: body.data.components || [],
- tts: body.data.tts,
- embeds: body.data.embeds || [],
- attachments: body.data.attachments,
- poll: body.data.poll,
- flags: body.data.flags,
- reactions: [],
- // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
- interaction: {
- id: interactionId,
- name: interaction.commandName,
- type: 2,
- user,
- },
- interaction_metadata: {
- id: interactionId,
- type: 2,
- user_id: interaction.userId,
- user,
- authorizing_integration_owners: {
- "1": interaction.userId,
- },
- name: interaction.commandName,
- command_type: interaction.commandType,
- },
- });
+ await sendMessage({
+ type: MessageType.APPLICATION_COMMAND,
+ timestamp: new Date(),
+ application_id: interaction.applicationId,
+ channel_id: interaction.channelId,
+ author_id: interaction.applicationId,
+ nonce: interaction.nonce,
+ content: body.data.content,
+ components: body.data.components || [],
+ tts: body.data.tts,
+ embeds: body.data.embeds || [],
+ attachments: body.data.attachments,
+ poll: body.data.poll,
+ flags: body.data.flags,
+ reactions: [],
+ // webhook_id: interaction.applicationId, // This one requires a webhook to be created first
+ interaction: {
+ id: interactionId,
+ name: interaction.commandName,
+ type: 2,
+ user,
+ },
+ interaction_metadata: {
+ id: interactionId,
+ type: 2,
+ user_id: interaction.userId,
+ user,
+ authorizing_integration_owners: {
+ "1": interaction.userId,
+ },
+ name: interaction.commandName,
+ command_type: interaction.commandType,
+ },
+ });
- break;
- }
- case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
- // TODO
- break;
- case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.UPDATE_MESSAGE:
- // TODO
- break;
- case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
- // TODO
- break;
- case InteractionCallbackType.MODAL:
- // TODO
- break;
- case InteractionCallbackType.PREMIUM_REQUIRED:
- // Deprecated
- break;
- case InteractionCallbackType.IFRAME_MODAL:
- // TODO
- break;
- case InteractionCallbackType.LAUNCH_ACTIVITY:
- // TODO
- break;
- }
+ break;
+ }
+ case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE:
+ // TODO
+ break;
+ case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.UPDATE_MESSAGE:
+ // TODO
+ break;
+ case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT:
+ // TODO
+ break;
+ case InteractionCallbackType.MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.PREMIUM_REQUIRED:
+ // Deprecated
+ break;
+ case InteractionCallbackType.IFRAME_MODAL:
+ // TODO
+ break;
+ case InteractionCallbackType.LAUNCH_ACTIVITY:
+ // TODO
+ break;
+ }
- pendingInteractions.delete(interactionId);
- res.sendStatus(204);
+ pendingInteractions.delete(interactionId);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts
index 9f154395..eb516ced 100644
--- a/src/api/routes/interactions/index.ts
+++ b/src/api/routes/interactions/index.ts
@@ -27,104 +27,104 @@ import { InteractionCreateSchema } from "@spacebar/schemas/api/bots/InteractionC
const router = Router({ mergeParams: true });
router.post("/", route({}), async (req: Request, res: Response) => {
- const body = req.body as InteractionSchema;
+ const body = req.body as InteractionSchema;
- const interactionId = Snowflake.generate();
- const interactionToken = randomBytes(24).toString("base64url");
+ const interactionId = Snowflake.generate();
+ const interactionToken = randomBytes(24).toString("base64url");
- emitEvent({
- event: "INTERACTION_CREATE",
- user_id: req.user_id,
- data: {
- id: interactionId,
- nonce: body.nonce,
- },
- } as InteractionCreateEvent);
+ emitEvent({
+ event: "INTERACTION_CREATE",
+ user_id: req.user_id,
+ data: {
+ id: interactionId,
+ nonce: body.nonce,
+ },
+ } as InteractionCreateEvent);
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
- const interactionData: Partial<InteractionCreateSchema> = {
- id: interactionId,
- application_id: body.application_id,
- channel_id: body.channel_id,
- type: body.type,
- token: interactionToken,
- version: 1,
- entitlements: [],
- authorizing_integration_owners: { "0": req.user_id },
- attachment_size_limit: Config.get().cdn.maxAttachmentSize,
- };
+ const interactionData: Partial<InteractionCreateSchema> = {
+ id: interactionId,
+ application_id: body.application_id,
+ channel_id: body.channel_id,
+ type: body.type,
+ token: interactionToken,
+ version: 1,
+ entitlements: [],
+ authorizing_integration_owners: { "0": req.user_id },
+ attachment_size_limit: Config.get().cdn.maxAttachmentSize,
+ };
- if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) {
- interactionData.data = body.data;
- }
+ if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) {
+ interactionData.data = body.data;
+ }
- if (body.type != InteractionType.Ping) {
- interactionData.locale = user?.settings?.locale;
- }
+ if (body.type != InteractionType.Ping) {
+ interactionData.locale = user?.settings?.locale;
+ }
- if (body.guild_id) {
- interactionData.context = 0;
- interactionData.guild_id = body.guild_id;
- interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString();
+ if (body.guild_id) {
+ interactionData.context = 0;
+ interactionData.guild_id = body.guild_id;
+ interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString();
- const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } });
- const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] });
+ const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } });
+ const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] });
- interactionData.guild = {
- id: guild.id,
- features: guild.features,
- locale: guild.preferred_locale!,
- };
+ interactionData.guild = {
+ id: guild.id,
+ features: guild.features,
+ locale: guild.preferred_locale!,
+ };
- interactionData.guild_locale = guild.preferred_locale;
- interactionData.member = member.toPublicMember();
- } else {
- interactionData.user = user.toPublicUser();
- interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString();
+ interactionData.guild_locale = guild.preferred_locale;
+ interactionData.member = member.toPublicMember();
+ } else {
+ interactionData.user = user.toPublicUser();
+ interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString();
- if (body.channel_id === body.application_id) {
- interactionData.context = 1;
- } else {
- interactionData.context = 2;
- }
- }
+ if (body.channel_id === body.application_id) {
+ interactionData.context = 1;
+ } else {
+ interactionData.context = 2;
+ }
+ }
- if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) {
- interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] });
- }
+ if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) {
+ interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] });
+ }
- emitEvent({
- event: "INTERACTION_CREATE",
- user_id: body.application_id,
- data: interactionData,
- } as InteractionCreateEvent);
+ emitEvent({
+ event: "INTERACTION_CREATE",
+ user_id: body.application_id,
+ data: interactionData,
+ } as InteractionCreateEvent);
- const interactionTimeout = setTimeout(() => {
- emitEvent({
- event: "INTERACTION_FAILURE",
- user_id: req.user_id,
- data: {
- id: interactionId,
- nonce: body.nonce,
- reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT,
- },
- } as InteractionFailureEvent);
- }, 3000);
+ const interactionTimeout = setTimeout(() => {
+ emitEvent({
+ event: "INTERACTION_FAILURE",
+ user_id: req.user_id,
+ data: {
+ id: interactionId,
+ nonce: body.nonce,
+ reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT,
+ },
+ } as InteractionFailureEvent);
+ }, 3000);
- pendingInteractions.set(interactionId, {
- timeout: interactionTimeout,
- nonce: body.nonce,
- applicationId: body.application_id,
- userId: req.user_id,
- guildId: body.guild_id,
- channelId: body.channel_id,
- type: body.type,
- commandType: body.data.type,
- commandName: body.data.name,
- });
+ pendingInteractions.set(interactionId, {
+ timeout: interactionTimeout,
+ nonce: body.nonce,
+ applicationId: body.application_id,
+ userId: req.user_id,
+ guildId: body.guild_id,
+ channelId: body.channel_id,
+ type: body.type,
+ commandType: body.data.type,
+ commandName: body.data.name,
+ });
- res.sendStatus(204);
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index 87715f35..26b0ad90 100644
--- a/src/api/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -25,134 +25,134 @@ import { UserFlags } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:invite_code",
- route({
- responses: {
- "200": {
- body: "Invite",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { invite_code } = req.params;
+ "/:invite_code",
+ route({
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { invite_code } = req.params;
- const invite = await Invite.findOneOrFail({
- where: { code: invite_code },
- relations: PublicInviteRelation,
- });
+ const invite = await Invite.findOneOrFail({
+ where: { code: invite_code },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invite);
- },
+ res.status(200).send(invite);
+ },
);
router.post(
- "/:invite_code",
- route({
- right: "USE_MASS_INVITES",
- responses: {
- "200": {
- body: "Invite",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
+ "/:invite_code",
+ route({
+ right: "USE_MASS_INVITES",
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT;
- const { invite_code } = req.params;
- const { guild_id } = await Invite.findOneOrFail({
- where: { code: invite_code },
- });
- const { features } = await Guild.findOneOrFail({
- where: { id: guild_id },
- });
- const { public_flags } = await User.findOneOrFail({
- where: { id: req.user_id },
- });
- const ban = await Ban.findOne({
- where: [
- { guild_id: guild_id, user_id: req.user_id },
- { guild_id: guild_id, ip: req.ip },
- ],
- });
+ const { invite_code } = req.params;
+ const { guild_id } = await Invite.findOneOrFail({
+ where: { code: invite_code },
+ });
+ const { features } = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
+ const { public_flags } = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
+ const ban = await Ban.findOne({
+ where: [
+ { guild_id: guild_id, user_id: req.user_id },
+ { guild_id: guild_id, ip: req.ip },
+ ],
+ });
- if (ban) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`);
- throw DiscordApiErrors.USER_BANNED;
- }
+ if (ban) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`);
+ throw DiscordApiErrors.USER_BANNED;
+ }
- if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`);
- throw DiscordApiErrors.UNKNOWN_INVITE;
- }
+ if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`);
+ throw DiscordApiErrors.UNKNOWN_INVITE;
+ }
- if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`);
- throw new HTTPError("Only intended for the staff of this instance.", 401);
- }
+ if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`);
+ throw new HTTPError("Only intended for the staff of this instance.", 401);
+ }
- if (features.includes("INVITES_DISABLED")) {
- console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`);
- throw new HTTPError("Sorry, this guild has joins closed.", 403);
- }
+ if (features.includes("INVITES_DISABLED")) {
+ console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`);
+ throw new HTTPError("Sorry, this guild has joins closed.", 403);
+ }
- const invite = await Invite.joinGuild(req.user_id, invite_code);
+ const invite = await Invite.joinGuild(req.user_id, invite_code);
- res.json(invite);
- },
+ res.json(invite);
+ },
);
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete(
- "/:invite_code",
- route({
- responses: {
- "200": {
- body: "Invite",
- },
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { invite_code } = req.params;
- const invite = await Invite.findOneOrFail({ where: { code: invite_code } });
- const { guild_id, channel_id } = invite;
+ "/:invite_code",
+ route({
+ responses: {
+ "200": {
+ body: "Invite",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { invite_code } = req.params;
+ const invite = await Invite.findOneOrFail({ where: { code: invite_code } });
+ const { guild_id, channel_id } = invite;
- const permission = await getPermission(req.user_id, guild_id, channel_id);
+ const permission = await getPermission(req.user_id, guild_id, channel_id);
- if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
+ if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401);
- await Promise.all([
- Invite.delete({ code: invite_code }),
- emitEvent({
- event: "INVITE_DELETE",
- guild_id: guild_id,
- data: {
- channel_id: channel_id,
- guild_id: guild_id,
- code: invite_code,
- },
- } as InviteDeleteEvent),
- ]);
+ await Promise.all([
+ Invite.delete({ code: invite_code }),
+ emitEvent({
+ event: "INVITE_DELETE",
+ guild_id: guild_id,
+ data: {
+ channel_id: channel_id,
+ guild_id: guild_id,
+ code: invite_code,
+ },
+ } as InviteDeleteEvent),
+ ]);
- res.json({ invite: invite });
- },
+ res.json({ invite: invite });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/applications/@me.ts b/src/api/routes/oauth2/applications/@me.ts
index 6daad797..9cdd47c0 100644
--- a/src/api/routes/oauth2/applications/@me.ts
+++ b/src/api/routes/oauth2/applications/@me.ts
@@ -24,30 +24,30 @@ import { PublicUserProjection } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Application",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.id }, // ...huh? there's no ID in the path...
- relations: ["bot", "owner"],
- select: {
- owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
- },
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Application",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const app = await Application.findOneOrFail({
+ where: { id: req.params.id }, // ...huh? there's no ID in the path...
+ relations: ["bot", "owner"],
+ select: {
+ owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])),
+ },
+ });
- if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
+ if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT;
- res.json({
- ...app,
- owner: app.owner.toPublicUser(),
- install_params: app.install_params !== null ? app.install_params : undefined,
- });
- },
+ res.json({
+ ...app,
+ owner: app.owner.toPublicUser(),
+ install_params: app.install_params !== null ? app.install_params : undefined,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts
index 4084a059..4dea75c6 100644
--- a/src/api/routes/oauth2/authorize.ts
+++ b/src/api/routes/oauth2/authorize.ts
@@ -25,198 +25,198 @@ const router = Router({ mergeParams: true });
// TODO: scopes, other oauth types
router.get(
- "/",
- route({
- query: {
- client_id: {
- type: "string",
- },
- },
- responses: {
- // TODO: I really didn't feel like typing all of it out
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { client_id, scope, response_type, redirect_url } = req.query;
- const { client_id } = req.query;
- if (!client_id) {
- throw FieldErrors({
- client_id: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ "/",
+ route({
+ query: {
+ client_id: {
+ type: "string",
+ },
+ },
+ responses: {
+ // TODO: I really didn't feel like typing all of it out
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { client_id, scope, response_type, redirect_url } = req.query;
+ const { client_id } = req.query;
+ if (!client_id) {
+ throw FieldErrors({
+ client_id: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- const app = await Application.findOne({
- where: {
- id: client_id as string,
- },
- relations: ["bot"],
- });
+ const app = await Application.findOne({
+ where: {
+ id: client_id as string,
+ },
+ relations: ["bot"],
+ });
- // TODO: use DiscordApiErrors
- // findOneOrFail throws code 404
- if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
- if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
+ // TODO: use DiscordApiErrors
+ // findOneOrFail throws code 404
+ if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
+ if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
- const bot = app.bot;
- delete app.bot;
+ const bot = app.bot;
+ delete app.bot;
- const user = await User.findOneOrFail({
- where: {
- id: req.user_id,
- bot: false,
- },
- select: ["id", "username", "avatar", "discriminator", "public_flags"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ id: req.user_id,
+ bot: false,
+ },
+ select: ["id", "username", "avatar", "discriminator", "public_flags"],
+ });
- const guilds = await Member.find({
- where: {
- id: req.user_id,
- },
- relations: ["guild", "roles", "user"],
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- // prettier-ignore
- select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"],
- });
+ const guilds = await Member.find({
+ where: {
+ id: req.user_id,
+ },
+ relations: ["guild", "roles", "user"],
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ // prettier-ignore
+ select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"],
+ });
- const guildsWithPermissions = guilds.map((x) => {
- const perms = Permissions.finalPermission({
- user: {
- id: user.id,
- roles: x.roles?.map((x) => x.id) || [],
- communication_disabled_until: x.communication_disabled_until,
- flags: x.user.flags,
- },
- guild: {
- roles: x?.roles || [],
- id: x.guild.id,
- owner_id: x.guild.owner_id!, // ownerless guilds...?
- },
- });
+ const guildsWithPermissions = guilds.map((x) => {
+ const perms = Permissions.finalPermission({
+ user: {
+ id: user.id,
+ roles: x.roles?.map((x) => x.id) || [],
+ communication_disabled_until: x.communication_disabled_until,
+ flags: x.user.flags,
+ },
+ guild: {
+ roles: x?.roles || [],
+ id: x.guild.id,
+ owner_id: x.guild.owner_id!, // ownerless guilds...?
+ },
+ });
- return {
- id: x.guild.id,
- name: x.guild.name,
- icon: x.guild.icon,
- mfa_level: x.guild.mfa_level,
- permissions: perms.bitfield.toString(),
- };
- });
+ return {
+ id: x.guild.id,
+ name: x.guild.name,
+ icon: x.guild.icon,
+ mfa_level: x.guild.mfa_level,
+ permissions: perms.bitfield.toString(),
+ };
+ });
- return res.json({
- guilds: guildsWithPermissions,
- user: {
- id: user.id,
- username: user.username,
- avatar: user.avatar,
- avatar_decoration: null, // TODO
- discriminator: user.discriminator,
- public_flags: user.public_flags,
- },
- application: {
- id: app.id,
- name: app.name,
- icon: app.icon,
- description: app.description,
- summary: app.summary,
- type: app.type,
- hook: app.hook,
- guild_id: null, // TODO support guilds
- bot_public: app.bot_public,
- bot_require_code_grant: app.bot_require_code_grant,
- verify_key: app.verify_key,
- flags: app.flags,
- },
- bot: {
- id: bot.id,
- username: bot.username,
- avatar: bot.avatar,
- avatar_decoration: null, // TODO
- discriminator: bot.discriminator,
- public_flags: bot.public_flags,
- bot: true,
- approximated_guild_count: 0, // TODO
- },
- authorized: false,
- });
- },
+ return res.json({
+ guilds: guildsWithPermissions,
+ user: {
+ id: user.id,
+ username: user.username,
+ avatar: user.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: user.discriminator,
+ public_flags: user.public_flags,
+ },
+ application: {
+ id: app.id,
+ name: app.name,
+ icon: app.icon,
+ description: app.description,
+ summary: app.summary,
+ type: app.type,
+ hook: app.hook,
+ guild_id: null, // TODO support guilds
+ bot_public: app.bot_public,
+ bot_require_code_grant: app.bot_require_code_grant,
+ verify_key: app.verify_key,
+ flags: app.flags,
+ },
+ bot: {
+ id: bot.id,
+ username: bot.username,
+ avatar: bot.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: bot.discriminator,
+ public_flags: bot.public_flags,
+ bot: true,
+ approximated_guild_count: 0, // TODO
+ },
+ authorized: false,
+ });
+ },
);
router.post(
- "/",
- route({
- requestBody: "ApplicationAuthorizeSchema",
- query: {
- client_id: {
- type: "string",
- },
- },
- responses: {
- 200: {
- body: "OAuthAuthorizeResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as ApplicationAuthorizeSchema;
- // const { client_id, scope, response_type, redirect_url } = req.query;
- const { client_id } = req.query;
+ "/",
+ route({
+ requestBody: "ApplicationAuthorizeSchema",
+ query: {
+ client_id: {
+ type: "string",
+ },
+ },
+ responses: {
+ 200: {
+ body: "OAuthAuthorizeResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as ApplicationAuthorizeSchema;
+ // const { client_id, scope, response_type, redirect_url } = req.query;
+ const { client_id } = req.query;
- if (!client_id) {
- throw FieldErrors({
- client_id: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (!client_id) {
+ throw FieldErrors({
+ client_id: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- // TODO: ensure guild_id is not an empty string
- // TODO: captcha verification
- // TODO: MFA verification
+ // TODO: ensure guild_id is not an empty string
+ // TODO: captcha verification
+ // TODO: MFA verification
- const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
- // getPermission cache won't exist if we're owner
- if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
- perms.hasThrow("MANAGE_GUILD");
+ const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
+ // getPermission cache won't exist if we're owner
+ if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
+ perms.hasThrow("MANAGE_GUILD");
- const app = await Application.findOne({
- where: {
- id: client_id as string,
- },
- relations: ["bot"],
- });
+ const app = await Application.findOne({
+ where: {
+ id: client_id as string,
+ },
+ relations: ["bot"],
+ });
- // TODO: use DiscordApiErrors
- // findOneOrFail throws code 404
- if (!app) throw new ApiError("Unknown Application", 10002, 404);
- if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
+ // TODO: use DiscordApiErrors
+ // findOneOrFail throws code 404
+ if (!app) throw new ApiError("Unknown Application", 10002, 404);
+ if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
- await Member.addToGuild(app.id, body.guild_id);
+ await Member.addToGuild(app.id, body.guild_id);
- return res.json({
- location: "/oauth2/authorized", // redirect URL
- });
- },
+ return res.json({
+ location: "/oauth2/authorized", // redirect URL
+ });
+ },
);
export default router;
diff --git a/src/api/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts
index 6b85bab5..4af10611 100644
--- a/src/api/routes/oauth2/tokens.ts
+++ b/src/api/routes/oauth2/tokens.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]);
+ //TODO
+ res.json([]);
});
export default router;
diff --git a/src/api/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts
index f1f9a292..e8e6f657 100644
--- a/src/api/routes/outbound-promotions.ts
+++ b/src/api/routes/outbound-promotions.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 30ec8b3f..9a08bc6b 100644
--- a/src/api/routes/partners/#guild_id/requirements.ts
+++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -22,34 +22,34 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
- res.send({
- guild_id: guild_id,
- safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0,
- },
- minimum_size: 0,
- });
+ 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;
diff --git a/src/api/routes/ping.ts b/src/api/routes/ping.ts
index 55e4b7d5..be737f84 100644
--- a/src/api/routes/ping.ts
+++ b/src/api/routes/ping.ts
@@ -23,32 +23,32 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstancePingResponse",
- },
- },
- }),
- (req: Request, res: Response) => {
- const { general } = Config.get();
- res.send({
- ping: "pong!",
- instance: {
- id: general.instanceId,
- name: general.instanceName,
- description: general.instanceDescription,
- image: general.image,
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstancePingResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.send({
+ ping: "pong!",
+ instance: {
+ id: general.instanceId,
+ name: general.instanceName,
+ description: general.instanceDescription,
+ image: general.image,
- correspondenceEmail: general.correspondenceEmail,
- correspondenceUserID: general.correspondenceUserID,
+ correspondenceEmail: general.correspondenceEmail,
+ correspondenceUserID: general.correspondenceUserID,
- frontPage: general.frontPage,
- tosPage: general.tosPage,
- },
- });
- },
+ frontPage: general.frontPage,
+ tosPage: general.tosPage,
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/config.ts b/src/api/routes/policies/instance/config.ts
index 97fb5093..946ac8aa 100755
--- a/src/api/routes/policies/instance/config.ts
+++ b/src/api/routes/policies/instance/config.ts
@@ -23,44 +23,44 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Object",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const general = Config.get();
- let outputtedConfig;
- if (req.user_id) {
- const rights = await getRights(req.user_id);
- if (rights.has("OPERATOR")) outputtedConfig = general;
- } else {
- outputtedConfig = {
- limits_user_maxGuilds: general.limits.user.maxGuilds,
- limits_user_maxBio: general.limits.user.maxBio,
- limits_guild_maxEmojis: general.limits.guild.maxEmojis,
- limits_guild_maxRoles: general.limits.guild.maxRoles,
- limits_message_maxCharacters: general.limits.message.maxCharacters,
- limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize,
- limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize,
- limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
- register_dateOfBirth_requiredc: general.register.dateOfBirth.required,
- register_password_required: general.register.password.required,
- register_disabled: general.register.disabled,
- register_requireInvite: general.register.requireInvite,
- register_allowNewRegistration: general.register.allowNewRegistration,
- register_allowMultipleAccounts: general.register.allowMultipleAccounts,
- guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
- guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
- register_email_required: general.register.email.required,
- can_recover_account: general.email.provider != null && general.general.frontPage != null,
- };
- }
- res.send(outputtedConfig);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Object",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const general = Config.get();
+ let outputtedConfig;
+ if (req.user_id) {
+ const rights = await getRights(req.user_id);
+ if (rights.has("OPERATOR")) outputtedConfig = general;
+ } else {
+ outputtedConfig = {
+ limits_user_maxGuilds: general.limits.user.maxGuilds,
+ limits_user_maxBio: general.limits.user.maxBio,
+ limits_guild_maxEmojis: general.limits.guild.maxEmojis,
+ limits_guild_maxRoles: general.limits.guild.maxRoles,
+ limits_message_maxCharacters: general.limits.message.maxCharacters,
+ limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize,
+ limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize,
+ limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
+ register_dateOfBirth_requiredc: general.register.dateOfBirth.required,
+ register_password_required: general.register.password.required,
+ register_disabled: general.register.disabled,
+ register_requireInvite: general.register.requireInvite,
+ register_allowNewRegistration: general.register.allowNewRegistration,
+ register_allowMultipleAccounts: general.register.allowMultipleAccounts,
+ guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
+ guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
+ register_email_required: general.register.email.required,
+ can_recover_account: general.email.provider != null && general.general.frontPage != null,
+ };
+ }
+ res.send(outputtedConfig);
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 967ac2f4..7aaaeb59 100644
--- a/src/api/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
@@ -22,26 +22,26 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstanceDomainsResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { cdn, gateway, api } = Config.get();
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstanceDomainsResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { cdn, gateway, api } = Config.get();
- res.json({
- admin: Config.get().admin.endpointPublic,
- api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client
- apiEndpoint: api.endpointPublic,
- cdn: cdn.endpointPublic,
- defaultApiVersion: api.defaultVersion,
- gateway: gateway.endpointPublic,
- });
- },
+ res.json({
+ admin: Config.get().admin.endpointPublic,
+ api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client
+ apiEndpoint: api.endpointPublic,
+ cdn: cdn.endpointPublic,
+ defaultApiVersion: api.defaultVersion,
+ gateway: gateway.endpointPublic,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index 8fc214f8..a54570f7 100644
--- a/src/api/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGeneralConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { general } = Config.get();
- res.json(general);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGeneralConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.json(general);
+ },
);
export default router;
diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 0d0ed326..7ade1519 100644
--- a/src/api/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APILimitsConfiguration",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { limits } = Config.get();
- res.json(limits);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APILimitsConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { limits } = Config.get();
+ res.json(limits);
+ },
);
export default router;
diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts
index 0828c37c..0edcfd68 100644
--- a/src/api/routes/policies/stats.ts
+++ b/src/api/routes/policies/stats.ts
@@ -22,32 +22,32 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "InstanceStatsResponse",
- },
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (!Config.get().security.statsWorldReadable) {
- const rights = await getRights(req.user_id);
- rights.hasThrow("VIEW_SERVER_STATS");
- }
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstanceStatsResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!Config.get().security.statsWorldReadable) {
+ const rights = await getRights(req.user_id);
+ rights.hasThrow("VIEW_SERVER_STATS");
+ }
- res.json({
- counts: {
- user: await User.count(),
- guild: await Guild.count(),
- message: await Message.count(),
- members: await Member.count(),
- },
- });
- },
+ res.json({
+ counts: {
+ user: await User.count(),
+ guild: await Guild.count(),
+ message: await Message.count(),
+ members: await Member.count(),
+ },
+ });
+ },
);
export default router;
diff --git a/src/api/routes/read-states/ack-bulk.ts b/src/api/routes/read-states/ack-bulk.ts
index a597e4bc..d0212c0b 100644
--- a/src/api/routes/read-states/ack-bulk.ts
+++ b/src/api/routes/read-states/ack-bulk.ts
@@ -23,48 +23,48 @@ import { AckBulkSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "AckBulkSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as AckBulkSchema;
+ "/",
+ route({
+ requestBody: "AckBulkSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as AckBulkSchema;
- // TODO: what is read_state_type ?
+ // TODO: what is read_state_type ?
- await Promise.all([
- // for every new state
- ...body.read_states.map(async (x) => {
- // find an existing one
- const ret =
- (await ReadState.findOne({
- where: {
- user_id: req.user_id,
- channel_id: x.channel_id,
- },
- })) ??
- // if it doesn't exist, create it (not a promise)
- ReadState.create({
- user_id: req.user_id,
- channel_id: x.channel_id,
- });
+ await Promise.all([
+ // for every new state
+ ...body.read_states.map(async (x) => {
+ // find an existing one
+ const ret =
+ (await ReadState.findOne({
+ where: {
+ user_id: req.user_id,
+ channel_id: x.channel_id,
+ },
+ })) ??
+ // if it doesn't exist, create it (not a promise)
+ ReadState.create({
+ user_id: req.user_id,
+ channel_id: x.channel_id,
+ });
- ret.last_message_id = x.message_id;
- //It's a little more complicated than this but this'll do
- ret.mention_count = 0;
+ ret.last_message_id = x.message_id;
+ //It's a little more complicated than this but this'll do
+ ret.mention_count = 0;
- return ret.save();
- }),
- ]);
+ return ret.save();
+ }),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/reporting/menu/message.ts b/src/api/routes/reporting/menu/message.ts
index c72bc840..de57c794 100644
--- a/src/api/routes/reporting/menu/message.ts
+++ b/src/api/routes/reporting/menu/message.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "ReportingMenuResponse",
- },
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO: implement
- //res.send([] as ReportingMenuResponseSchema);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ReportingMenuResponse",
+ },
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO: implement
+ //res.send([] as ReportingMenuResponseSchema);
+ },
);
export default router;
diff --git a/src/api/routes/safety-hub/@me/index.ts b/src/api/routes/safety-hub/@me/index.ts
index 06a1e285..7d0e7ff9 100644
--- a/src/api/routes/safety-hub/@me/index.ts
+++ b/src/api/routes/safety-hub/@me/index.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "AccountStandingResponse",
- },
- 401: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "AccountStandingResponse",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- res.send({
- classifications: [],
- guild_classifications: [],
- account_standing: {
- state: AccountStandingState.ALL_GOOD,
- },
- is_dsa_eligible: true,
- username: user.username,
- discriminator: user.discriminator,
- is_appeal_eligible: true,
- appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
- } as AccountStandingResponse);
- },
+ res.send({
+ classifications: [],
+ guild_classifications: [],
+ account_standing: {
+ state: AccountStandingState.ALL_GOOD,
+ },
+ is_dsa_eligible: true,
+ username: user.username,
+ discriminator: user.discriminator,
+ is_appeal_eligible: true,
+ appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
+ } as AccountStandingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/safety-hub/suspended/@me.ts b/src/api/routes/safety-hub/suspended/@me.ts
index 06a1e285..7d0e7ff9 100644
--- a/src/api/routes/safety-hub/suspended/@me.ts
+++ b/src/api/routes/safety-hub/suspended/@me.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "AccountStandingResponse",
- },
- 401: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "AccountStandingResponse",
+ },
+ 401: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- res.send({
- classifications: [],
- guild_classifications: [],
- account_standing: {
- state: AccountStandingState.ALL_GOOD,
- },
- is_dsa_eligible: true,
- username: user.username,
- discriminator: user.discriminator,
- is_appeal_eligible: true,
- appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
- } as AccountStandingResponse);
- },
+ res.send({
+ classifications: [],
+ guild_classifications: [],
+ account_standing: {
+ state: AccountStandingState.ALL_GOOD,
+ },
+ is_dsa_eligible: true,
+ username: user.username,
+ discriminator: user.discriminator,
+ is_appeal_eligible: true,
+ appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE],
+ } as AccountStandingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/scheduled-maintenances/upcoming.json.ts b/src/api/routes/scheduled-maintenances/upcoming.json.ts
index de4dcbf1..5958d582 100644
--- a/src/api/routes/scheduled-maintenances/upcoming.json.ts
+++ b/src/api/routes/scheduled-maintenances/upcoming.json.ts
@@ -21,10 +21,10 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json({
- page: {},
- scheduled_maintenances: {},
- });
+ res.json({
+ page: {},
+ scheduled_maintenances: {},
+ });
});
export default router;
diff --git a/src/api/routes/science.ts b/src/api/routes/science.ts
index 2d2d5195..4a6ddeab 100644
--- a/src/api/routes/science.ts
+++ b/src/api/routes/science.ts
@@ -22,16 +22,16 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
- },
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/stage-instances.ts b/src/api/routes/stage-instances.ts
index f1f9a292..e8e6f657 100644
--- a/src/api/routes/stage-instances.ts
+++ b/src/api/routes/stage-instances.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index 7d2fe178..749ce026 100644
--- a/src/api/routes/sticker-packs/index.ts
+++ b/src/api/routes/sticker-packs/index.ts
@@ -23,21 +23,21 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIStickerPackArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const sticker_packs = await StickerPack.find({
- relations: ["stickers"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIStickerPackArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const sticker_packs = await StickerPack.find({
+ relations: ["stickers"],
+ });
- res.json({ sticker_packs });
- },
+ res.json({ sticker_packs });
+ },
);
export default router;
diff --git a/src/api/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index ddbc3978..0f850efd 100644
--- a/src/api/routes/stickers/#sticker_id/index.ts
+++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "Sticker",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { sticker_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "Sticker",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { sticker_id } = req.params;
- res.json(await Sticker.find({ where: { id: sticker_id } }));
- },
+ res.json(await Sticker.find({ where: { id: sticker_id } }));
+ },
);
export default router;
diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index bf4af562..be29e0ad 100644
--- a/src/api/routes/stop.ts
+++ b/src/api/routes/stop.ts
@@ -22,21 +22,21 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "OPERATOR",
- responses: {
- 200: {},
- 403: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
- res.sendStatus(200);
- process.kill(process.pid, "SIGTERM");
- },
+ "/",
+ route({
+ right: "OPERATOR",
+ responses: {
+ 200: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
+ res.sendStatus(200);
+ process.kill(process.pid, "SIGTERM");
+ },
);
export default router;
diff --git a/src/api/routes/store/published-listings/applications/#application_id/index.ts b/src/api/routes/store/published-listings/applications/#application_id/index.ts
index eb2df64e..39fa9db2 100644
--- a/src/api/routes/store/published-listings/applications/#application_id/index.ts
+++ b/src/api/routes/store/published-listings/applications/#application_id/index.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- // const id = req.params.id;
- res.json({
- id: "",
- summary: "",
- sku: {
- id: "",
- type: 1,
- dependent_sku_id: null,
- application_id: "",
- manifest_labels: [],
- access_type: 2,
- name: "",
- features: [],
- release_date: "",
- premium: false,
- slug: "",
- flags: 4,
- genres: [],
- legal_notice: "",
- application: {
- id: "",
- name: "",
- icon: "",
- description: "",
- summary: "",
- cover_image: "",
- primary_sku_id: "",
- hook: true,
- slug: "",
- guild_id: "",
- bot_public: "",
- bot_require_code_grant: false,
- verify_key: "",
- publishers: [
- {
- id: "",
- name: "",
- },
- ],
- developers: [
- {
- id: "",
- name: "",
- },
- ],
- system_requirements: {},
- show_age_gate: false,
- price: {
- amount: 0,
- currency: "EUR",
- },
- locales: [],
- },
- tagline: "",
- description: "",
- carousel_items: [
- {
- asset_id: "",
- },
- ],
- header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
- header_logo_light_theme: {},
- box_art: {},
- thumbnail: {},
- header_background: {},
- hero_background: {},
- assets: [],
- },
- }).status(200);
+ //TODO
+ // const id = req.params.id;
+ res.json({
+ id: "",
+ summary: "",
+ sku: {
+ id: "",
+ type: 1,
+ dependent_sku_id: null,
+ application_id: "",
+ manifest_labels: [],
+ access_type: 2,
+ name: "",
+ features: [],
+ release_date: "",
+ premium: false,
+ slug: "",
+ flags: 4,
+ genres: [],
+ legal_notice: "",
+ application: {
+ id: "",
+ name: "",
+ icon: "",
+ description: "",
+ summary: "",
+ cover_image: "",
+ primary_sku_id: "",
+ hook: true,
+ slug: "",
+ guild_id: "",
+ bot_public: "",
+ bot_require_code_grant: false,
+ verify_key: "",
+ publishers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ developers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ system_requirements: {},
+ show_age_gate: false,
+ price: {
+ amount: 0,
+ currency: "EUR",
+ },
+ locales: [],
+ },
+ tagline: "",
+ description: "",
+ carousel_items: [
+ {
+ asset_id: "",
+ },
+ ],
+ header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
+ header_logo_light_theme: {},
+ box_art: {},
+ thumbnail: {},
+ header_background: {},
+ hero_background: {},
+ assets: [],
+ },
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
index b8f5963f..953e3919 100644
--- a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
@@ -22,22 +22,22 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([
- {
- id: "",
- name: "",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "",
- fallback_price: 499,
- fallback_currency: "eur",
- currency: "eur",
- price: 4199,
- price_tier: null,
- },
- ]).status(200);
+ //TODO
+ res.json([
+ {
+ id: "",
+ name: "",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "",
+ fallback_price: 499,
+ fallback_currency: "eur",
+ currency: "eur",
+ price: 4199,
+ price_tier: null,
+ },
+ ]).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index a463b4e4..03cbab46 100644
--- a/src/api/routes/store/published-listings/skus.ts
+++ b/src/api/routes/store/published-listings/skus.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/:sku_id", route({}), async (req: Request, res: Response) => {
- //TODO
- // const id = req.params.id;
- res.json({
- id: "",
- summary: "",
- sku: {
- id: "",
- type: 1,
- dependent_sku_id: null,
- application_id: "",
- manifets_labels: [],
- access_type: 2,
- name: "",
- features: [],
- release_date: "",
- premium: false,
- slug: "",
- flags: 4,
- genres: [],
- legal_notice: "",
- application: {
- id: "",
- name: "",
- icon: "",
- description: "",
- summary: "",
- cover_image: "",
- primary_sku_id: "",
- hook: true,
- slug: "",
- guild_id: "",
- bot_public: "",
- bot_require_code_grant: false,
- verify_key: "",
- publishers: [
- {
- id: "",
- name: "",
- },
- ],
- developers: [
- {
- id: "",
- name: "",
- },
- ],
- system_requirements: {},
- show_age_gate: false,
- price: {
- amount: 0,
- currency: "EUR",
- },
- locales: [],
- },
- tagline: "",
- description: "",
- carousel_items: [
- {
- asset_id: "",
- },
- ],
- header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
- header_logo_light_theme: {},
- box_art: {},
- thumbnail: {},
- header_background: {},
- hero_background: {},
- assets: [],
- },
- }).status(200);
+ //TODO
+ // const id = req.params.id;
+ res.json({
+ id: "",
+ summary: "",
+ sku: {
+ id: "",
+ type: 1,
+ dependent_sku_id: null,
+ application_id: "",
+ manifets_labels: [],
+ access_type: 2,
+ name: "",
+ features: [],
+ release_date: "",
+ premium: false,
+ slug: "",
+ flags: 4,
+ genres: [],
+ legal_notice: "",
+ application: {
+ id: "",
+ name: "",
+ icon: "",
+ description: "",
+ summary: "",
+ cover_image: "",
+ primary_sku_id: "",
+ hook: true,
+ slug: "",
+ guild_id: "",
+ bot_public: "",
+ bot_require_code_grant: false,
+ verify_key: "",
+ publishers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ developers: [
+ {
+ id: "",
+ name: "",
+ },
+ ],
+ system_requirements: {},
+ show_age_gate: false,
+ price: {
+ amount: 0,
+ currency: "EUR",
+ },
+ locales: [],
+ },
+ tagline: "",
+ description: "",
+ carousel_items: [
+ {
+ asset_id: "",
+ },
+ ],
+ header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
+ header_logo_light_theme: {},
+ box_art: {},
+ thumbnail: {},
+ header_background: {},
+ hero_background: {},
+ assets: [],
+ },
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index f2ea23cc..0aae1495 100644
--- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -22,310 +22,310 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
const skus = new Map([
- [
- "521842865731534868",
- [
- {
- id: "511651856145973248",
- name: "Individual Premium Tier 3 Monthly (Legacy)",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521842865731534868",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651860671627264",
- name: "Individiual Premium Tier 3 Yearly (Legacy)",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521842865731534868",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "521846918637420545",
- [
- {
- id: "511651871736201216",
- name: "Individual Premium Tier 2 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651876987469824",
- name: "Individual Premum Tier 2 Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "978380684370378761",
- name: "Individual Premum Tier 1",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521846918637420545",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "521847234246082599",
- [
- {
- id: "642251038925127690",
- name: "Individual Premium Tier 3 Quarterly",
- interval: 1,
- interval_count: 3,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651880837840896",
- name: "Individual Premium Tier 3 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "511651885459963904",
- name: "Individual Premium Tier 3 Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "521847234246082599",
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "590663762298667008",
- [
- {
- id: "590665532894740483",
- name: "Crowd Premium Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "590663762298667008",
- discount_price: 0,
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- {
- id: "590665538238152709",
- name: "Crowd Premium Yearly",
- interval: 2,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "590663762298667008",
- discount_price: 0,
- currency: "eur",
- price: 0,
- price_tier: null,
- },
- ],
- ],
- [
- "978380684370378762",
- [
- [
- {
- id: "978380692553465866",
- name: "Premium Tier 0 Monthly",
- interval: 1,
- interval_count: 1,
- tax_inclusive: true,
- sku_id: "978380684370378762",
- currency: "usd",
- price: 299,
- price_tier: null,
- prices: {
- "0": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "3": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "4": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- "1": {
- country_prices: {
- country_code: "US",
- prices: [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- payment_source_prices: {
- "775487223059316758": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "736345864146255982": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- "683074999590060249": [
- {
- currency: "usd",
- amount: 0,
- exponent: 2,
- },
- ],
- },
- },
- },
- },
- ],
- ],
- ],
+ [
+ "521842865731534868",
+ [
+ {
+ id: "511651856145973248",
+ name: "Individual Premium Tier 3 Monthly (Legacy)",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521842865731534868",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651860671627264",
+ name: "Individiual Premium Tier 3 Yearly (Legacy)",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521842865731534868",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "521846918637420545",
+ [
+ {
+ id: "511651871736201216",
+ name: "Individual Premium Tier 2 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651876987469824",
+ name: "Individual Premum Tier 2 Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "978380684370378761",
+ name: "Individual Premum Tier 1",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521846918637420545",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "521847234246082599",
+ [
+ {
+ id: "642251038925127690",
+ name: "Individual Premium Tier 3 Quarterly",
+ interval: 1,
+ interval_count: 3,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651880837840896",
+ name: "Individual Premium Tier 3 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "511651885459963904",
+ name: "Individual Premium Tier 3 Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "521847234246082599",
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "590663762298667008",
+ [
+ {
+ id: "590665532894740483",
+ name: "Crowd Premium Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "590663762298667008",
+ discount_price: 0,
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ {
+ id: "590665538238152709",
+ name: "Crowd Premium Yearly",
+ interval: 2,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "590663762298667008",
+ discount_price: 0,
+ currency: "eur",
+ price: 0,
+ price_tier: null,
+ },
+ ],
+ ],
+ [
+ "978380684370378762",
+ [
+ [
+ {
+ id: "978380692553465866",
+ name: "Premium Tier 0 Monthly",
+ interval: 1,
+ interval_count: 1,
+ tax_inclusive: true,
+ sku_id: "978380684370378762",
+ currency: "usd",
+ price: 299,
+ price_tier: null,
+ prices: {
+ "0": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "3": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "4": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ "1": {
+ country_prices: {
+ country_code: "US",
+ prices: [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ payment_source_prices: {
+ "775487223059316758": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "736345864146255982": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ "683074999590060249": [
+ {
+ currency: "usd",
+ amount: 0,
+ exponent: 2,
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ ],
+ ],
]);
router.get("/", route({}), async (req: Request, res: Response) => {
- // TODO: add the ability to add custom
- const { sku_id } = req.params;
+ // TODO: add the ability to add custom
+ const { sku_id } = req.params;
- if (!skus.has(sku_id)) {
- console.log(`Request for invalid SKU ${sku_id}! Please report this!`);
- res.sendStatus(404);
- } else {
- res.json(skus.get(sku_id)).status(200);
- }
+ if (!skus.has(sku_id)) {
+ console.log(`Request for invalid SKU ${sku_id}! Please report this!`);
+ res.sendStatus(404);
+ } else {
+ res.json(skus.get(sku_id)).status(200);
+ }
});
export default router;
diff --git a/src/api/routes/teams.ts b/src/api/routes/teams.ts
index 21ec3a5d..27ccabf2 100644
--- a/src/api/routes/teams.ts
+++ b/src/api/routes/teams.ts
@@ -25,67 +25,67 @@ import { TeamCreateSchema, TeamMemberRole, TeamMemberState } from "@spacebar/sch
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- query: {
- include_payout_account_status: {
- type: "boolean",
- description: "Whether to include team payout account status in the response (default false)",
- },
- },
- responses: {
- 200: {
- body: "TeamListResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const teams = await Team.find({
- where: {
- owner_user_id: req.user_id,
- },
- relations: ["members"],
- });
+ "/",
+ route({
+ query: {
+ include_payout_account_status: {
+ type: "boolean",
+ description: "Whether to include team payout account status in the response (default false)",
+ },
+ },
+ responses: {
+ 200: {
+ body: "TeamListResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const teams = await Team.find({
+ where: {
+ owner_user_id: req.user_id,
+ },
+ relations: ["members"],
+ });
- res.send(teams);
- },
+ res.send(teams);
+ },
);
router.post(
- "/",
- route({
- requestBody: "TeamCreateSchema",
- responses: {
- 200: {
- body: "Team",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: [{ id: req.user_id }],
- select: ["mfa_enabled"],
- });
- if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team");
+ "/",
+ route({
+ requestBody: "TeamCreateSchema",
+ responses: {
+ 200: {
+ body: "Team",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: [{ id: req.user_id }],
+ select: ["mfa_enabled"],
+ });
+ if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team");
- const body = req.body as TeamCreateSchema;
+ const body = req.body as TeamCreateSchema;
- const team = Team.create({
- name: body.name,
- owner_user_id: req.user_id,
- });
- await team.save();
+ const team = Team.create({
+ name: body.name,
+ owner_user_id: req.user_id,
+ });
+ await team.save();
- await TeamMember.create({
- user_id: req.user_id,
- team_id: team.id,
- membership_state: TeamMemberState.ACCEPTED,
- permissions: ["*"],
- role: TeamMemberRole.ADMIN,
- }).save();
+ await TeamMember.create({
+ user_id: req.user_id,
+ team_id: team.id,
+ membership_state: TeamMemberState.ACCEPTED,
+ permissions: ["*"],
+ role: TeamMemberRole.ADMIN,
+ }).save();
- res.json(team);
- },
+ res.json(team);
+ },
);
export default router;
diff --git a/src/api/routes/track.ts b/src/api/routes/track.ts
index 644302cd..d3206138 100644
--- a/src/api/routes/track.ts
+++ b/src/api/routes/track.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
+ // TODO:
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts
index 5262dc2d..7228a761 100644
--- a/src/api/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -23,46 +23,46 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UpdatesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const platform = req.query.platform;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UpdatesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const platform = req.query.platform;
- if (!platform)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
- const release = await ClientRelease.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
+ const release = await ClientRelease.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
+ },
+ order: { pub_date: "DESC" },
+ });
- res.json({
- name: release.name,
- pub_date: release.pub_date,
- url: release.url,
- notes: release.notes,
- });
- },
+ res.json({
+ name: release.name,
+ pub_date: release.pub_date,
+ url: release.url,
+ notes: release.notes,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/delete.ts b/src/api/routes/users/#user_id/delete.ts
index be361f18..c2286cea 100644
--- a/src/api/routes/users/#user_id/delete.ts
+++ b/src/api/routes/users/#user_id/delete.ts
@@ -18,20 +18,20 @@
import { route } from "@spacebar/api";
import {
- Channel,
- ChannelDeleteEvent,
- ChannelRecipientRemoveEvent,
- emitEvent,
- Emoji,
- Guild,
- InstanceBan,
- Member,
- Recipient,
- Sticker,
- Stopwatch,
- User,
- UserDeleteEvent,
- UserSettingsProtos,
+ Channel,
+ ChannelDeleteEvent,
+ ChannelRecipientRemoveEvent,
+ emitEvent,
+ Emoji,
+ Guild,
+ InstanceBan,
+ Member,
+ Recipient,
+ Sticker,
+ Stopwatch,
+ User,
+ UserDeleteEvent,
+ UserSettingsProtos,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas";
@@ -40,158 +40,158 @@ import { Not } from "typeorm";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- right: "MANAGE_USERS",
- requestBody: "InstanceUserDeleteSchema",
- responses: {
- 204: {},
- 403: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const sw = Stopwatch.startNew();
- const body = req.body as InstanceUserDeleteSchema | undefined;
- const user = await User.findOneOrFail({
- where: { id: req.params.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ "/",
+ route({
+ right: "MANAGE_USERS",
+ requestBody: "InstanceUserDeleteSchema",
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const sw = Stopwatch.startNew();
+ const body = req.body as InstanceUserDeleteSchema | undefined;
+ const user = await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } })))
- await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "<legacy instance ban API - no reason specified>" }).save();
+ if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } })))
+ await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "<legacy instance ban API - no reason specified>" }).save();
- // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow
- const dmChannels = await user.getDmChannels();
- for (const channel of dmChannels) {
- console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`);
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: channel.toJSON(),
- channel_id: channel.id,
- } as ChannelDeleteEvent);
- await Recipient.delete({ channel_id: channel.id });
- await Channel.deleteChannel(channel);
- }
+ // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow
+ const dmChannels = await user.getDmChannels();
+ for (const channel of dmChannels) {
+ console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`);
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel.toJSON(),
+ channel_id: channel.id,
+ } as ChannelDeleteEvent);
+ await Recipient.delete({ channel_id: channel.id });
+ await Channel.deleteChannel(channel);
+ }
- //leave all group channels
- const groupChannels = await Channel.find({
- where: { type: ChannelType.GROUP_DM },
- relations: ["recipients"],
- select: {
- id: true,
- owner_id: true,
- recipients: {
- id: true,
- user_id: true,
- },
- },
- });
+ //leave all group channels
+ const groupChannels = await Channel.find({
+ where: { type: ChannelType.GROUP_DM },
+ relations: ["recipients"],
+ select: {
+ id: true,
+ owner_id: true,
+ recipients: {
+ id: true,
+ user_id: true,
+ },
+ },
+ });
- await Promise.all(
- groupChannels.map(async (channel) => {
- const recipient = channel.recipients!.find((r) => r.user_id === user.id);
- if (recipient) {
- await Recipient.delete({ id: recipient.id });
- await emitEvent({
- event: "CHANNEL_RECIPIENT_REMOVE",
- data: {
- user: user.toPublicUser(),
- channel_id: channel.id,
- },
- channel_id: channel.id,
- } as ChannelRecipientRemoveEvent);
- console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`);
- }
+ await Promise.all(
+ groupChannels.map(async (channel) => {
+ const recipient = channel.recipients!.find((r) => r.user_id === user.id);
+ if (recipient) {
+ await Recipient.delete({ id: recipient.id });
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_REMOVE",
+ data: {
+ user: user.toPublicUser(),
+ channel_id: channel.id,
+ },
+ channel_id: channel.id,
+ } as ChannelRecipientRemoveEvent);
+ console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`);
+ }
- // if no recipients remain, delete the channel
- const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } });
- if (remainingRecipients.length === 0) {
- await emitEvent({
- event: "CHANNEL_DELETE",
- data: channel.toJSON(),
- channel_id: channel.id,
- } as ChannelDeleteEvent);
- await Channel.deleteChannel(channel);
- console.log(`[Instance ban] Deleted empty group channel ${channel.id}`);
- } else {
- // otherwise, if the banned user was the owner, reassign ownership
- if (channel.owner_id === user.id) {
- channel.owner_id = remainingRecipients[0].user_id;
- await channel.save();
- console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`);
- }
- }
- }),
- );
+ // if no recipients remain, delete the channel
+ const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } });
+ if (remainingRecipients.length === 0) {
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel.toJSON(),
+ channel_id: channel.id,
+ } as ChannelDeleteEvent);
+ await Channel.deleteChannel(channel);
+ console.log(`[Instance ban] Deleted empty group channel ${channel.id}`);
+ } else {
+ // otherwise, if the banned user was the owner, reassign ownership
+ if (channel.owner_id === user.id) {
+ channel.owner_id = remainingRecipients[0].user_id;
+ await channel.save();
+ console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`);
+ }
+ }
+ }),
+ );
- // change ownership on guilds
- const guilds = await Guild.find({ where: { owner_id: req.params.user_id } });
- await Promise.all(
- guilds.map(async (guild) => {
- const members = await Member.find({
- where: { guild_id: guild.id, id: Not(req.params.user_id) },
- relations: { roles: true },
- select: { id: true, roles: { id: true, position: true } },
- });
- const sortedMembers = members
- .filter((m) => m.id !== req.params.user_id)
- .sort((a, b) => {
- const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
- const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
- return bHighestRole.position - aHighestRole.position;
- });
- if (sortedMembers.length === 0) {
- // no members left, delete guild
- await guild.remove();
- console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`);
- } else {
- // assign new owner
- guild.owner_id = sortedMembers[0].id;
- await guild.save();
- console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`);
+ // change ownership on guilds
+ const guilds = await Guild.find({ where: { owner_id: req.params.user_id } });
+ await Promise.all(
+ guilds.map(async (guild) => {
+ const members = await Member.find({
+ where: { guild_id: guild.id, id: Not(req.params.user_id) },
+ relations: { roles: true },
+ select: { id: true, roles: { id: true, position: true } },
+ });
+ const sortedMembers = members
+ .filter((m) => m.id !== req.params.user_id)
+ .sort((a, b) => {
+ const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
+ const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
+ return bHighestRole.position - aHighestRole.position;
+ });
+ if (sortedMembers.length === 0) {
+ // no members left, delete guild
+ await guild.remove();
+ console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`);
+ } else {
+ // assign new owner
+ guild.owner_id = sortedMembers[0].id;
+ await guild.save();
+ console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`);
- // safety - reassign emojis/stickers owned by the old owner
- const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
- await Promise.all(
- stickers.map(async (sticker) => {
- sticker.user_id = guild.owner_id;
- await sticker.save();
- console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`);
- }),
- );
+ // safety - reassign emojis/stickers owned by the old owner
+ const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
+ await Promise.all(
+ stickers.map(async (sticker) => {
+ sticker.user_id = guild.owner_id;
+ await sticker.save();
+ console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`);
+ }),
+ );
- const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
- await Promise.all(
- emojis.map(async (emoji) => {
- emoji.user_id = guild.owner_id!;
- await emoji.save();
- console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`);
- }),
- );
- }
- }),
- );
+ const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
+ await Promise.all(
+ emojis.map(async (emoji) => {
+ emoji.user_id = guild.owner_id!;
+ await emoji.save();
+ console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`);
+ }),
+ );
+ }
+ }),
+ );
- const members = await Member.find({ where: { id: req.params.user_id } });
- await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
- await UserSettingsProtos.delete({ user_id: req.params.user_id });
- await User.delete({ id: req.params.user_id });
+ const members = await Member.find({ where: { id: req.params.user_id } });
+ await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
+ await UserSettingsProtos.delete({ user_id: req.params.user_id });
+ await User.delete({ id: req.params.user_id });
- // TODO: respect intents as USER_DELETE has potential to cause privacy issues
- await emitEvent({
- event: "USER_DELETE",
- user_id: req.user_id,
- data: { user_id: req.params.user_id },
- } as UserDeleteEvent);
+ // TODO: respect intents as USER_DELETE has potential to cause privacy issues
+ await emitEvent({
+ event: "USER_DELETE",
+ user_id: req.user_id,
+ data: { user_id: req.params.user_id },
+ } as UserDeleteEvent);
- console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`);
- res.sendStatus(204);
- },
+ console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`);
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/index.ts b/src/api/routes/users/#user_id/index.ts
index 417f961d..7fca1e0e 100644
--- a/src/api/routes/users/#user_id/index.ts
+++ b/src/api/routes/users/#user_id/index.ts
@@ -23,19 +23,19 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPublicUser",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPublicUser",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
- res.json(await User.getPublicUser(user_id));
- },
+ res.json(await User.getPublicUser(user_id));
+ },
);
export default router;
diff --git a/src/api/routes/users/#user_id/messages.ts b/src/api/routes/users/#user_id/messages.ts
index 717bca96..95c7cfc4 100644
--- a/src/api/routes/users/#user_id/messages.ts
+++ b/src/api/routes/users/#user_id/messages.ts
@@ -23,33 +23,33 @@ import { DmMessagesResponseSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "DmMessagesResponseSchema",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.params.user_id } });
- const channel = await user.getDmChannelWith(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "DmMessagesResponseSchema",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({ where: { id: req.params.user_id } });
+ const channel = await user.getDmChannelWith(req.user_id);
- const messages = (
- await Message.find({
- where: { channel_id: channel?.id },
- order: { timestamp: "DESC" },
- take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount),
- })
- ).filter((x) => x !== null) as Message[];
+ const messages = (
+ await Message.find({
+ where: { channel_id: channel?.id },
+ order: { timestamp: "DESC" },
+ take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount),
+ })
+ ).filter((x) => x !== null) as Message[];
- const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema;
+ const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema;
- return res.status(200).send(filteredMessages);
- },
+ return res.status(200).send(filteredMessages);
+ },
);
// TODO: POST to send a message to the user
diff --git a/src/api/routes/users/#user_id/profile.ts b/src/api/routes/users/#user_id/profile.ts
index a5ea3e9c..bcd59cd1 100644
--- a/src/api/routes/users/#user_id/profile.ts
+++ b/src/api/routes/users/#user_id/profile.ts
@@ -25,146 +25,146 @@ import { PrivateUserProjection, PublicUser, PublicUserProjection, RelationshipTy
const router: Router = Router({ mergeParams: true });
router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
- if (req.params.user_id === "@me") req.params.user_id = req.user_id;
+ if (req.params.user_id === "@me") req.params.user_id = req.user_id;
- const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query;
+ const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query;
- const user = await User.getPublicUser(req.params.user_id, {
- relations: ["connected_accounts"],
- });
+ const user = await User.getPublicUser(req.params.user_id, {
+ relations: ["connected_accounts"],
+ });
- const mutual_guilds: object[] = [];
- let premium_guild_since;
+ const mutual_guilds: object[] = [];
+ let premium_guild_since;
- if (with_mutual_guilds == "true") {
- const requested_member = await Member.find({
- where: { id: req.params.user_id },
- });
- const self_member = await Member.find({
- where: { id: req.user_id },
- });
+ if (with_mutual_guilds == "true") {
+ const requested_member = await Member.find({
+ where: { id: req.params.user_id },
+ });
+ const self_member = await Member.find({
+ where: { id: req.user_id },
+ });
- for (const rmem of requested_member) {
- if (rmem.premium_since) {
- if (premium_guild_since) {
- if (premium_guild_since > rmem.premium_since) {
- premium_guild_since = rmem.premium_since;
- }
- } else {
- premium_guild_since = rmem.premium_since;
- }
- }
- for (const smem of self_member) {
- if (smem.guild_id === rmem.guild_id) {
- mutual_guilds.push({
- id: rmem.guild_id,
- nick: rmem.nick,
- });
- }
- }
- }
- }
+ for (const rmem of requested_member) {
+ if (rmem.premium_since) {
+ if (premium_guild_since) {
+ if (premium_guild_since > rmem.premium_since) {
+ premium_guild_since = rmem.premium_since;
+ }
+ } else {
+ premium_guild_since = rmem.premium_since;
+ }
+ }
+ for (const smem of self_member) {
+ if (smem.guild_id === rmem.guild_id) {
+ mutual_guilds.push({
+ id: rmem.guild_id,
+ nick: rmem.nick,
+ });
+ }
+ }
+ }
+ }
- const guild_member =
- guild_id && typeof guild_id == "string"
- ? await Member.findOneOrFail({
- where: { id: req.params.user_id, guild_id: guild_id },
- relations: ["roles"],
- })
- : undefined;
+ const guild_member =
+ guild_id && typeof guild_id == "string"
+ ? await Member.findOneOrFail({
+ where: { id: req.params.user_id, guild_id: guild_id },
+ relations: ["roles"],
+ })
+ : undefined;
- // TODO: make proper DTO's in util?
+ // TODO: make proper DTO's in util?
- const userProfile = {
- bio: req.user_bot ? null : user.bio,
- accent_color: user.accent_color,
- banner: user.banner,
- pronouns: user.pronouns,
- theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers
- };
+ const userProfile = {
+ bio: req.user_bot ? null : user.bio,
+ accent_color: user.accent_color,
+ banner: user.banner,
+ pronouns: user.pronouns,
+ theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers
+ };
- const guildMemberProfile = {
- accent_color: null,
- banner: guild_member?.banner || null,
- bio: guild_member?.bio || "",
- guild_id,
- };
+ const guildMemberProfile = {
+ accent_color: null,
+ banner: guild_member?.banner || null,
+ bio: guild_member?.bio || "",
+ guild_id,
+ };
- const badges = await Badge.find();
+ const badges = await Badge.find();
- let mutual_friends: PublicUser[] = [];
- let mutual_friends_count = 0;
+ let mutual_friends: PublicUser[] = [];
+ let mutual_friends_count = 0;
- if (with_mutual_friends == "true" || with_mutual_friends_count == "true") {
- const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } });
- const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } });
- const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id));
- if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length;
- if (with_mutual_friends) {
- const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection });
- mutual_friends = users.map((u) => u.toPublicUser());
- }
- }
+ if (with_mutual_friends == "true" || with_mutual_friends_count == "true") {
+ const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } });
+ const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } });
+ const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id));
+ if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length;
+ if (with_mutual_friends) {
+ const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection });
+ mutual_friends = users.map((u) => u.toPublicUser());
+ }
+ }
- res.json({
- connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0),
- premium_guild_since: premium_guild_since, // TODO
- premium_since: user.premium_since, // TODO
- mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
- mutual_friends: with_mutual_friends ? mutual_friends : undefined,
- mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined,
- user: user.toPublicUser(),
- premium_type: user.premium_type,
- profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
- user_profile: userProfile,
- guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() },
- guild_member_profile: guild_id && guildMemberProfile,
- badges: badges.filter((x) => user.badge_ids?.includes(x.id)),
- });
+ res.json({
+ connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0),
+ premium_guild_since: premium_guild_since, // TODO
+ premium_since: user.premium_since, // TODO
+ mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
+ mutual_friends: with_mutual_friends ? mutual_friends : undefined,
+ mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined,
+ user: user.toPublicUser(),
+ premium_type: user.premium_type,
+ profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason?
+ user_profile: userProfile,
+ guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() },
+ guild_member_profile: guild_id && guildMemberProfile,
+ badges: badges.filter((x) => user.badge_ids?.includes(x.id)),
+ });
});
router.patch("/", route({ requestBody: "UserProfileModifySchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserProfileModifySchema;
+ const body = req.body as UserProfileModifySchema;
- if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- if (body.bio) {
- const { maxBio } = Config.get().limits.user;
- if (body.bio.length > maxBio) {
- throw FieldErrors({
- bio: {
- code: "BIO_INVALID",
- message: `Bio must be less than ${maxBio} in length`,
- },
- });
- }
- }
+ if (body.bio) {
+ const { maxBio } = Config.get().limits.user;
+ if (body.bio.length > maxBio) {
+ throw FieldErrors({
+ bio: {
+ code: "BIO_INVALID",
+ message: `Bio must be less than ${maxBio} in length`,
+ },
+ });
+ }
+ }
- user.assign(body);
- await user.save();
+ user.assign(body);
+ await user.save();
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- delete user.data;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ delete user.data;
- // TODO: send update member list event in gateway
- await emitEvent({
- event: "USER_UPDATE",
- user_id: req.user_id,
- data: user,
- } as UserUpdateEvent);
+ // TODO: send update member list event in gateway
+ await emitEvent({
+ event: "USER_UPDATE",
+ user_id: req.user_id,
+ data: user,
+ } as UserUpdateEvent);
- res.json({
- accent_color: user.accent_color,
- bio: user.bio,
- banner: user.banner,
- theme_colors: user.theme_colors,
- pronouns: user.pronouns,
- });
+ res.json({
+ accent_color: user.accent_color,
+ bio: user.bio,
+ banner: user.banner,
+ theme_colors: user.theme_colors,
+ pronouns: user.pronouns,
+ });
});
export default router;
diff --git a/src/api/routes/users/#user_id/relationships.ts b/src/api/routes/users/#user_id/relationships.ts
index 7e33291e..3272ef7b 100644
--- a/src/api/routes/users/#user_id/relationships.ts
+++ b/src/api/routes/users/#user_id/relationships.ts
@@ -24,44 +24,44 @@ import { UserRelationsResponse } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: { body: "UserRelationsResponse" },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const mutual_relations: UserRelationsResponse = [];
+ "/",
+ route({
+ responses: {
+ 200: { body: "UserRelationsResponse" },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const mutual_relations: UserRelationsResponse = [];
- const requested_relations = await User.findOneOrFail({
- where: { id: req.params.user_id },
- relations: ["relationships"],
- });
- const self_relations = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships"],
- });
+ const requested_relations = await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ relations: ["relationships"],
+ });
+ const self_relations = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships"],
+ });
- for (const rmem of requested_relations.relationships) {
- for (const smem of self_relations.relationships)
- if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
- const relation_user = await User.getPublicUser(rmem.to_id);
+ for (const rmem of requested_relations.relationships) {
+ for (const smem of self_relations.relationships)
+ if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) {
+ const relation_user = await User.getPublicUser(rmem.to_id);
- mutual_relations.push({
- id: relation_user.id,
- username: relation_user.username,
- avatar: relation_user.avatar,
- discriminator: relation_user.discriminator,
- public_flags: relation_user.public_flags,
- });
- }
- }
+ mutual_relations.push({
+ id: relation_user.id,
+ username: relation_user.username,
+ avatar: relation_user.avatar,
+ discriminator: relation_user.discriminator,
+ public_flags: relation_user.public_flags,
+ });
+ }
+ }
- res.json(mutual_relations);
- },
+ res.json(mutual_relations);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts
index a9bc5961..b0119161 100644
--- a/src/api/routes/users/@me/activities/statistics/applications.ts
+++ b/src/api/routes/users/@me/activities/statistics/applications.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts
index 30fe8879..8de9aa58 100644
--- a/src/api/routes/users/@me/affinities/guilds.ts
+++ b/src/api/routes/users/@me/affinities/guilds.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send({ guild_affinities: [] });
+ // TODO:
+ res.status(200).send({ guild_affinities: [] });
});
export default router;
diff --git a/src/api/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts
index 038f4108..2f666252 100644
--- a/src/api/routes/users/@me/affinities/users.ts
+++ b/src/api/routes/users/@me/affinities/users.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send({ user_affinities: [], inverse_user_affinities: [] });
+ // TODO:
+ res.status(200).send({ user_affinities: [], inverse_user_affinities: [] });
});
export default router;
diff --git a/src/api/routes/users/@me/applications/#application_id/entitlements.ts b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
index f1f9a292..e8e6f657 100644
--- a/src/api/routes/users/@me/applications/#application_id/entitlements.ts
+++ b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts
index 1907536a..8bddafc3 100644
--- a/src/api/routes/users/@me/billing/country-code.ts
+++ b/src/api/routes/users/@me/billing/country-code.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json({ country_code: "US" }).status(200);
+ //TODO
+ res.json({ country_code: "US" }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/location-info.ts b/src/api/routes/users/@me/billing/location-info.ts
index 3fe7f679..918ea913 100644
--- a/src/api/routes/users/@me/billing/location-info.ts
+++ b/src/api/routes/users/@me/billing/location-info.ts
@@ -22,9 +22,9 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- // TODO: subdivision_code (optional)
- res.json({ country_code: "US" }).status(200);
+ //TODO
+ // TODO: subdivision_code (optional)
+ res.json({ country_code: "US" }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts
index c2ebd3fe..1380ed52 100644
--- a/src/api/routes/users/@me/billing/payment-sources.ts
+++ b/src/api/routes/users/@me/billing/payment-sources.ts
@@ -23,59 +23,59 @@ const router = Router({ mergeParams: true });
// https://docs.discord.food/resources/billing#example-payment-source
const example = {
- id: "1422548914485198869",
- type: 1,
- invalid: false,
- flags: 2,
- deleted_at: null,
- brand: "visa",
- last_4: "4242",
- expires_month: 9,
- expires_year: 2077,
- billing_address: {
- name: "John Doe",
- line_1: "123 Main Street",
- line_2: "Apt 4B",
- city: "San Francisco",
- state: "CA",
- country: "US",
- postal_code: "94105",
- },
- country: "US",
- payment_gateway: 1,
- payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo",
- default: false,
+ id: "1422548914485198869",
+ type: 1,
+ invalid: false,
+ flags: 2,
+ deleted_at: null,
+ brand: "visa",
+ last_4: "4242",
+ expires_month: 9,
+ expires_year: 2077,
+ billing_address: {
+ name: "John Doe",
+ line_1: "123 Main Street",
+ line_2: "Apt 4B",
+ city: "San Francisco",
+ state: "CA",
+ country: "US",
+ postal_code: "94105",
+ },
+ country: "US",
+ payment_gateway: 1,
+ payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo",
+ default: false,
};
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json([example]).status(200);
+ // TODO: schema
+ res.json([example]).status(200);
});
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json([example]).status(200);
+ // TODO: schema
+ res.json([example]).status(200);
});
router.get("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json({
- ...example,
- id: req.route.payment_source_id,
- }).status(200);
+ // TODO: schema
+ res.json({
+ ...example,
+ id: req.route.payment_source_id,
+ }).status(200);
});
router.patch("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.json({
- ...example,
- id: req.route.payment_source_id,
- }).status(200);
+ // TODO: schema
+ res.json({
+ ...example,
+ id: req.route.payment_source_id,
+ }).status(200);
});
router.delete("/:payment_source_id", route({}), (req: Request, res: Response) => {
- // TODO: schema
- res.status(204);
+ // TODO: schema
+ res.status(204);
});
export default router;
diff --git a/src/api/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts
index f1f9a292..e8e6f657 100644
--- a/src/api/routes/users/@me/billing/subscriptions.ts
+++ b/src/api/routes/users/@me/billing/subscriptions.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- //TODO
- res.json([]).status(200);
+ //TODO
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 9411e72e..e13b874a 100644
--- a/src/api/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -24,37 +24,37 @@ import { DmChannelCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIDMChannelArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const recipients = await Recipient.find({
- where: { user_id: req.user_id, closed: false },
- relations: ["channel", "channel.recipients"],
- });
- res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIDMChannelArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const recipients = await Recipient.find({
+ where: { user_id: req.user_id, closed: false },
+ relations: ["channel", "channel.recipients"],
+ });
+ res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
+ },
);
router.post(
- "/",
- route({
- requestBody: "DmChannelCreateSchema",
- responses: {
- 200: {
- body: "DmChannelDTO",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as DmChannelCreateSchema;
- res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
- },
+ "/",
+ route({
+ requestBody: "DmChannelCreateSchema",
+ responses: {
+ 200: {
+ body: "DmChannelDTO",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as DmChannelCreateSchema;
+ res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/collectibles-marketing.ts b/src/api/routes/users/@me/collectibles-marketing.ts
index c9f1a1d1..a043a146 100644
--- a/src/api/routes/users/@me/collectibles-marketing.ts
+++ b/src/api/routes/users/@me/collectibles-marketing.ts
@@ -24,26 +24,26 @@ const router = Router({ mergeParams: true });
// Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "CollectiblesMarketingResponse",
- },
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.send({
- marketings: {},
- } as CollectiblesMarketingResponse);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "CollectiblesMarketingResponse",
+ },
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.send({
+ marketings: {},
+ } as CollectiblesMarketingResponse);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/collectibles-purchases.ts b/src/api/routes/users/@me/collectibles-purchases.ts
index 9491e30a..1e6a08d0 100644
--- a/src/api/routes/users/@me/collectibles-purchases.ts
+++ b/src/api/routes/users/@me/collectibles-purchases.ts
@@ -23,24 +23,24 @@ const router = Router({ mergeParams: true });
// Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home
router.get(
- "/",
- route({
- responses: {
- 200: {
- // body: "CollectiblesPurchasesResponse",
- },
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.send([]);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ // body: "CollectiblesPurchasesResponse",
+ },
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.send([]);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
index fcab4321..8846efe4 100644
--- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
+++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
@@ -28,49 +28,49 @@ const ALLOWED_CONNECTIONS = ["twitch", "youtube"];
// NOTE: this route has not been extensively tested, as the required connections are not implemented as of writing
router.get("/", route({}), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
+ const { connection_name, connection_id } = req.params;
- const connection = ConnectionStore.connections.get(connection_name);
+ const connection = ConnectionStore.connections.get(connection_name);
- if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection)
- throw FieldErrors({
- provider_id: {
- code: "BASE_TYPE_CHOICES",
- message: req.t("common:field.BASE_TYPE_CHOICES", {
- types: ALLOWED_CONNECTIONS.join(", "),
- }),
- },
- });
+ if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection)
+ throw FieldErrors({
+ provider_id: {
+ code: "BASE_TYPE_CHOICES",
+ message: req.t("common:field.BASE_TYPE_CHOICES", {
+ types: ALLOWED_CONNECTIONS.join(", "),
+ }),
+ },
+ });
- if (!connection.settings.enabled)
- throw FieldErrors({
- provider_id: {
- message: "This connection has been disabled server-side.",
- },
- });
+ if (!connection.settings.enabled)
+ throw FieldErrors({
+ provider_id: {
+ message: "This connection has been disabled server-side.",
+ },
+ });
- const connectedAccount = await ConnectedAccount.findOne({
- where: {
- type: connection_name,
- external_id: connection_id,
- user_id: req.user_id,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
- });
- if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION;
- if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED;
- if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400);
+ const connectedAccount = await ConnectedAccount.findOne({
+ where: {
+ type: connection_name,
+ external_id: connection_id,
+ user_id: req.user_id,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
+ });
+ if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION;
+ if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED;
+ if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400);
- let access_token = connectedAccount.token_data.access_token;
- const { expires_at, expires_in, fetched_at } = connectedAccount.token_data;
+ let access_token = connectedAccount.token_data.access_token;
+ const { expires_at, expires_in, fetched_at } = connectedAccount.token_data;
- if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) {
- if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400);
- const tokenData = await connection.refresh(connectedAccount);
- access_token = tokenData.access_token;
- }
+ if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) {
+ if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400);
+ const tokenData = await connection.refresh(connectedAccount);
+ access_token = tokenData.access_token;
+ }
- res.json({ access_token });
+ res.json({ access_token });
});
export default router;
diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
index 37deee38..111312ae 100644
--- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
+++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
@@ -24,65 +24,65 @@ const router = Router({ mergeParams: true });
// TODO: connection update schema
router.patch("/", route({ requestBody: "ConnectionUpdateSchema" }), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
- const body = req.body as ConnectionUpdateSchema;
+ const { connection_name, connection_id } = req.params;
+ const body = req.body as ConnectionUpdateSchema;
- const connection = await ConnectedAccount.findOne({
- where: {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"],
- });
+ const connection = await ConnectedAccount.findOne({
+ where: {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"],
+ });
- if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
- // TODO: do we need to do anything if the connection is revoked?
+ if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION;
+ // TODO: do we need to do anything if the connection is revoked?
- if (typeof body.visibility === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.visibility = body.visibility ? 1 : 0;
- if (typeof body.show_activity === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.show_activity = body.show_activity ? 1 : 0;
- if (typeof body.metadata_visibility === "boolean")
- //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
- body.metadata_visibility = body.metadata_visibility ? 1 : 0;
+ if (typeof body.visibility === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.visibility = body.visibility ? 1 : 0;
+ if (typeof body.show_activity === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.show_activity = body.show_activity ? 1 : 0;
+ if (typeof body.metadata_visibility === "boolean")
+ //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number?
+ body.metadata_visibility = body.metadata_visibility ? 1 : 0;
- connection.assign(req.body);
+ connection.assign(req.body);
- await ConnectedAccount.update(
- {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- connection,
- );
- res.json(connection.toJSON());
+ await ConnectedAccount.update(
+ {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ connection,
+ );
+ res.json(connection.toJSON());
});
router.delete("/", route({}), async (req: Request, res: Response) => {
- const { connection_name, connection_id } = req.params;
+ const { connection_name, connection_id } = req.params;
- const account = await ConnectedAccount.findOneOrFail({
- where: {
- user_id: req.user_id,
- external_id: connection_id,
- type: connection_name,
- },
- });
+ const account = await ConnectedAccount.findOneOrFail({
+ where: {
+ user_id: req.user_id,
+ external_id: connection_id,
+ type: connection_name,
+ },
+ });
- await Promise.all([
- ConnectedAccount.remove(account),
- emitEvent({
- event: "USER_CONNECTIONS_UPDATE",
- data: account,
- user_id: req.user_id,
- }),
- ]);
+ await Promise.all([
+ ConnectedAccount.remove(account),
+ emitEvent({
+ event: "USER_CONNECTIONS_UPDATE",
+ data: account,
+ user_id: req.user_id,
+ }),
+ ]);
- return res.sendStatus(200);
+ return res.sendStatus(200);
});
export default router;
diff --git a/src/api/routes/users/@me/connections/index.ts b/src/api/routes/users/@me/connections/index.ts
index 00625eea..a2d8b82a 100644
--- a/src/api/routes/users/@me/connections/index.ts
+++ b/src/api/routes/users/@me/connections/index.ts
@@ -23,14 +23,14 @@ import { ConnectedAccount, ConnectedAccountDTO } from "@spacebar/util";
const router: Router = Router({ mergeParams: true });
router.get("/", route({}), async (req: Request, res: Response) => {
- const connections = await ConnectedAccount.find({
- where: {
- user_id: req.user_id,
- },
- select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
- });
+ const connections = await ConnectedAccount.find({
+ where: {
+ user_id: req.user_id,
+ },
+ select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
+ });
- res.json(connections.map((x) => new ConnectedAccountDTO(x, true)));
+ res.json(connections.map((x) => new ConnectedAccountDTO(x, true)));
});
export default router;
diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index 43861181..668f7c19 100644
--- a/src/api/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -25,44 +25,44 @@ import { HTTPError } from "lambert-server";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- 401: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 401: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
+ let correctpass = true;
- if (user.data.hash) {
- // guest accounts can delete accounts without password
- correctpass = await bcrypt.compare(req.body.password, user.data.hash);
- if (!correctpass) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
- }
- }
+ if (user.data.hash) {
+ // guest accounts can delete accounts without password
+ correctpass = await bcrypt.compare(req.body.password, user.data.hash);
+ if (!correctpass) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
+ }
- // TODO: decrement guild member count
+ // TODO: decrement guild member count
- if (correctpass) {
- const members = await Member.find({ where: { id: req.user_id } });
- await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
+ if (correctpass) {
+ const members = await Member.find({ where: { id: req.user_id } });
+ await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
- res.sendStatus(204);
- } else {
- res.sendStatus(401);
- }
- },
+ res.sendStatus(204);
+ } else {
+ res.sendStatus(401);
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 644302cd..d3206138 100644
--- a/src/api/routes/users/@me/devices.ts
+++ b/src/api/routes/users/@me/devices.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.post("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.sendStatus(204);
+ // TODO:
+ res.sendStatus(204);
});
export default router;
diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 73de8395..95995e4b 100644
--- a/src/api/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
@@ -24,41 +24,41 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
+ let correctpass = true;
- if (user.data.hash) {
- // guest accounts can delete accounts without password
- correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
- }
+ if (user.data.hash) {
+ // guest accounts can delete accounts without password
+ correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
+ }
- if (correctpass) {
- await User.update({ id: req.user_id }, { disabled: true });
+ if (correctpass) {
+ await User.update({ id: req.user_id }, { disabled: true });
- res.sendStatus(204);
- } else {
- res.status(400).json({
- message: "Password does not match",
- code: 50018,
- });
- }
- },
+ res.sendStatus(204);
+ } else {
+ res.status(400).json({
+ message: "Password does not match",
+ code: 50018,
+ });
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index c5575009..ede73532 100644
--- a/src/api/routes/users/@me/email-settings.ts
+++ b/src/api/routes/users/@me/email-settings.ts
@@ -22,17 +22,17 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json({
- categories: {
- social: true,
- communication: true,
- tips: false,
- updates_and_announcements: false,
- recommendations_and_events: false,
- },
- initialized: false,
- }).status(200);
+ // TODO:
+ res.json({
+ categories: {
+ social: true,
+ communication: true,
+ tips: false,
+ updates_and_announcements: false,
+ recommendations_and_events: false,
+ },
+ initialized: false,
+ }).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts
index fd953eee..ac73a12c 100644
--- a/src/api/routes/users/@me/entitlements.ts
+++ b/src/api/routes/users/@me/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/gifts", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index df71f46e..c06c39c4 100644
--- a/src/api/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -24,62 +24,62 @@ import { HTTPError } from "lambert-server";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildArray",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const members = await Member.find({
- relations: ["guild"],
- where: { id: req.user_id },
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const members = await Member.find({
+ relations: ["guild"],
+ where: { id: req.user_id },
+ });
- let guild = members.map((x) => x.guild);
+ let guild = members.map((x) => x.guild);
- if ("with_counts" in req.query && req.query.with_counts == "true") {
- guild = []; // TODO: Load guilds with user role permissions number
- }
+ if ("with_counts" in req.query && req.query.with_counts == "true") {
+ guild = []; // TODO: Load guilds with user role permissions number
+ }
- res.json(guild);
- },
+ res.json(guild);
+ },
);
// user send to leave a certain guild
router.delete(
- "/:guild_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { autoJoin } = Config.get().guild;
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: ["owner_id"],
- });
+ "/:guild_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { autoJoin } = Config.get().guild;
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
- if (!guild) throw new HTTPError("Guild doesn't exist", 404);
- if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
- if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) {
- throw new HTTPError("You can't leave instance auto join guilds", 400);
- }
+ if (!guild) throw new HTTPError("Guild doesn't exist", 404);
+ if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400);
+ if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) {
+ throw new HTTPError("You can't leave instance auto join guilds", 400);
+ }
- await Member.removeFromGuild(req.user_id, guild_id);
+ await Member.removeFromGuild(req.user_id, guild_id);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
index 7fda9a90..16b64ec5 100644
--- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts
+++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -25,54 +25,54 @@ const router = Router({ mergeParams: true });
// GET doesn't exist on discord.com
router.get(
- "/",
- route({
- responses: {
- 200: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const user = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"],
- });
- return res.json(user.settings);
- },
+ "/",
+ route({
+ responses: {
+ 200: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ select: ["settings"],
+ });
+ return res.json(user.settings);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserGuildSettingsSchema",
- responses: {
- 200: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserGuildSettingsSchema;
+ "/",
+ route({
+ requestBody: "UserGuildSettingsSchema",
+ responses: {
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserGuildSettingsSchema;
- if (body.channel_overrides) {
- for (const channel in body.channel_overrides) {
- Channel.findOneOrFail({ where: { id: channel } });
- }
- }
+ if (body.channel_overrides) {
+ for (const channel in body.channel_overrides) {
+ Channel.findOneOrFail({ where: { id: channel } });
+ }
+ }
- const user = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"],
- });
- OrmUtils.mergeDeep(user.settings || {}, body);
- Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ select: ["settings"],
+ });
+ OrmUtils.mergeDeep(user.settings || {}, body);
+ Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user);
- res.json(user.settings);
- },
+ res.json(user.settings);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
index a9bc5961..b0119161 100644
--- a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
+++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.json([]).status(200);
+ // TODO:
+ res.json([]).status(200);
});
export default router;
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index aba9f928..bd1fa077 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -25,182 +25,182 @@ import { PrivateUserProjection, UserModifySchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIPrivateUser",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json(
- await User.findOne({
- select: PrivateUserProjection,
- where: { id: req.user_id },
- }),
- );
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPrivateUser",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json(
+ await User.findOne({
+ select: PrivateUserProjection,
+ where: { id: req.user_id },
+ }),
+ );
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserModifySchema",
- responses: {
- 200: {
- body: "UserUpdateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserModifySchema;
+ "/",
+ route({
+ requestBody: "UserModifySchema",
+ responses: {
+ 200: {
+ body: "UserUpdateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserModifySchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: [...PrivateUserProjection, "data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
- // Populated on password change
- let newToken: string | undefined;
+ // Populated on password change
+ let newToken: string | undefined;
- if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
- if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
+ if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
- if (body.password) {
- if (user.data?.hash) {
- const same_password = await bcrypt.compare(body.password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
- } else {
- user.data.hash = await bcrypt.hash(body.password, 12);
- }
- }
+ if (body.password) {
+ if (user.data?.hash) {
+ const same_password = await bcrypt.compare(body.password, user.data.hash || "");
+ if (!same_password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+ } else {
+ user.data.hash = await bcrypt.hash(body.password, 12);
+ }
+ }
- if (body.email) {
- if (!body.email && Config.get().register.email.required)
- throw FieldErrors({
- email: {
- message: req.t("auth:register.EMAIL_INVALID"),
- code: "EMAIL_INVALID",
- },
- });
- if (!body.password)
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (body.email) {
+ if (!body.email && Config.get().register.email.required)
+ throw FieldErrors({
+ email: {
+ message: req.t("auth:register.EMAIL_INVALID"),
+ code: "EMAIL_INVALID",
+ },
+ });
+ if (!body.password)
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- if (body.new_password) {
- if (!body.password && user.email) {
- throw FieldErrors({
- password: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
- user.data.hash = await bcrypt.hash(body.new_password, 12);
- user.data.valid_tokens_since = new Date();
- newToken = (await generateToken(user.id)) as string;
- }
+ if (body.new_password) {
+ if (!body.password && user.email) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
+ user.data.hash = await bcrypt.hash(body.new_password, 12);
+ user.data.valid_tokens_since = new Date();
+ newToken = (await generateToken(user.id)) as string;
+ }
- if (body.username) {
- const check_username = body?.username?.replace(/\s/g, "").trim();
- if (!check_username) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
- },
- });
- }
+ if (body.username) {
+ const check_username = body?.username?.replace(/\s/g, "").trim();
+ if (!check_username) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
- const { maxUsername } = Config.get().limits.user;
- if (check_username.length > maxUsername || check_username.length < 2) {
- throw FieldErrors({
- username: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: `Must be between 2 and ${maxUsername} in length.`,
- },
- });
- }
+ const { maxUsername } = Config.get().limits.user;
+ if (check_username.length > maxUsername || check_username.length < 2) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: `Must be between 2 and ${maxUsername} in length.`,
+ },
+ });
+ }
- if (!body.password) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
- }
+ if (!body.password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+ }
- if (body.discriminator) {
- if (
- await User.findOne({
- where: {
- discriminator: body.discriminator,
- username: body.username || user.username,
- },
- })
- ) {
- throw FieldErrors({
- discriminator: {
- code: "INVALID_DISCRIMINATOR",
- message: "This discriminator is already in use.",
- },
- });
- }
- }
+ if (body.discriminator) {
+ if (
+ await User.findOne({
+ where: {
+ discriminator: body.discriminator,
+ username: body.username || user.username,
+ },
+ })
+ ) {
+ throw FieldErrors({
+ discriminator: {
+ code: "INVALID_DISCRIMINATOR",
+ message: "This discriminator is already in use.",
+ },
+ });
+ }
+ }
- if (body.bio) {
- const { maxBio } = Config.get().limits.user;
- if (body.bio.length > maxBio) {
- throw FieldErrors({
- bio: {
- code: "BIO_INVALID",
- message: `Bio must be less than ${maxBio} in length`,
- },
- });
- }
- }
+ if (body.bio) {
+ const { maxBio } = Config.get().limits.user;
+ if (body.bio.length > maxBio) {
+ throw FieldErrors({
+ bio: {
+ code: "BIO_INVALID",
+ message: `Bio must be less than ${maxBio} in length`,
+ },
+ });
+ }
+ }
- user.assign(body);
- user.validate();
- await user.save();
+ user.assign(body);
+ user.validate();
+ await user.save();
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- delete user.data;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ delete user.data;
- // TODO: send update member list event in gateway
- await emitEvent({
- event: "USER_UPDATE",
- user_id: req.user_id,
- data: user,
- } as UserUpdateEvent);
+ // TODO: send update member list event in gateway
+ await emitEvent({
+ event: "USER_UPDATE",
+ user_id: req.user_id,
+ data: user,
+ } as UserUpdateEvent);
- res.json({
- ...user,
- newToken,
- });
- },
+ res.json({
+ ...user,
+ newToken,
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts
index 845cd171..8d09b38d 100644
--- a/src/api/routes/users/@me/library.ts
+++ b/src/api/routes/users/@me/library.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api";
const router = Router({ mergeParams: true });
router.get("/", route({}), (req: Request, res: Response) => {
- // TODO:
- res.status(200).send([]);
+ // TODO:
+ res.status(200).send([]);
});
export default router;
diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts
index 37a7abe1..2d631e90 100644
--- a/src/api/routes/users/@me/mentions.ts
+++ b/src/api/routes/users/@me/mentions.ts
@@ -24,139 +24,139 @@ import { In, LessThan, FindOptionsWhere } from "typeorm";
const router: Router = Router({ mergeParams: true });
router.get(
- "",
- route({
- responses: {
- 200: {
- body: "MessageListResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- // AFAICT this endpoint doesn't list DMs
- async (req: Request, res: Response) => {
- const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25;
- const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true;
- const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true;
- const before = req.query.before !== undefined ? String(req.query.before as string) : undefined;
- const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined;
+ "",
+ route({
+ responses: {
+ 200: {
+ body: "MessageListResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ // AFAICT this endpoint doesn't list DMs
+ async (req: Request, res: Response) => {
+ const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25;
+ const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true;
+ const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true;
+ const before = req.query.before !== undefined ? String(req.query.before as string) : undefined;
+ const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
- const memberships = await Member.find({
- where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) },
- select: {
- guild_id: true,
- id: true,
- communication_disabled_until: true,
- roles: {
- // We don't want to include all guild roles, as this could cause a lot more explosive behavior
- id: true,
- position: true,
- permissions: true,
- mentionable: true, // cause we can skip querying for unmentionable roles
- },
- guild: {
- id: true,
- owner_id: true,
- },
- },
- relations: ["guild", "roles"],
- });
+ const memberships = await Member.find({
+ where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) },
+ select: {
+ guild_id: true,
+ id: true,
+ communication_disabled_until: true,
+ roles: {
+ // We don't want to include all guild roles, as this could cause a lot more explosive behavior
+ id: true,
+ position: true,
+ permissions: true,
+ mentionable: true, // cause we can skip querying for unmentionable roles
+ },
+ guild: {
+ id: true,
+ owner_id: true,
+ },
+ },
+ relations: ["guild", "roles"],
+ });
- const channels = await Channel.find({
- where: {
- guild_id: In(memberships.map((m) => m.guild_id)),
- },
- select: { id: true, guild_id: true, permission_overwrites: true },
- });
+ const channels = await Channel.find({
+ where: {
+ guild_id: In(memberships.map((m) => m.guild_id)),
+ },
+ select: { id: true, guild_id: true, permission_overwrites: true },
+ });
- const visibleChannels = channels.filter((c) => {
- const member = memberships.find((m) => m.guild_id === c.guild_id)!;
- return Permissions.finalPermission({
- user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 },
- guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles },
- channel: c,
- }).has("VIEW_CHANNEL");
- });
+ const visibleChannels = channels.filter((c) => {
+ const member = memberships.find((m) => m.guild_id === c.guild_id)!;
+ return Permissions.finalPermission({
+ user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 },
+ guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles },
+ channel: c,
+ }).has("VIEW_CHANNEL");
+ });
- const visibleChannelIds = visibleChannels.map((c) => c.id);
- const ownedMentionableRoleIds = memberships.reduce((acc, m) => {
- acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id));
- return acc;
- }, [] as Snowflake[]);
+ const visibleChannelIds = visibleChannels.map((c) => c.id);
+ const ownedMentionableRoleIds = memberships.reduce((acc, m) => {
+ acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id));
+ return acc;
+ }, [] as Snowflake[]);
- const whereQuery: FindOptionsWhere<Message>[] = [
- {
- channel_id: In(visibleChannelIds),
- mentions: { id: user.id },
- id: before ? LessThan(before) : undefined,
- },
- ];
- if (everyone) {
- whereQuery.push({
- channel_id: In(visibleChannelIds),
- mention_everyone: true,
- id: before ? LessThan(before) : undefined,
- });
- }
- if (roles) {
- whereQuery.push({
- channel_id: In(visibleChannelIds),
- mention_roles: { id: In(ownedMentionableRoleIds) },
- id: before ? LessThan(before) : undefined,
- });
- }
+ const whereQuery: FindOptionsWhere<Message>[] = [
+ {
+ channel_id: In(visibleChannelIds),
+ mentions: { id: user.id },
+ id: before ? LessThan(before) : undefined,
+ },
+ ];
+ if (everyone) {
+ whereQuery.push({
+ channel_id: In(visibleChannelIds),
+ mention_everyone: true,
+ id: before ? LessThan(before) : undefined,
+ });
+ }
+ if (roles) {
+ whereQuery.push({
+ channel_id: In(visibleChannelIds),
+ mention_roles: { id: In(ownedMentionableRoleIds) },
+ id: before ? LessThan(before) : undefined,
+ });
+ }
- const sw = Stopwatch.startNew();
- const finalMessages = (
- await Message.find({
- where: whereQuery,
- order: { timestamp: "DESC" },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- "referenced_message",
- "referenced_message.author",
- "referenced_message.webhook",
- "referenced_message.application",
- "referenced_message.mentions",
- "referenced_message.mention_roles",
- "referenced_message.mention_channels",
- "referenced_message.sticker_items",
- "referenced_message.attachments",
- ],
- take: limit,
- })
- ).map((m) => {
- return {
- ...m.toJSON(),
- attachments: m.attachments?.map((attachment: Attachment) =>
- Attachment.prototype.signUrls.call(
- attachment,
- new NewUrlUserSignatureData({
- ip: req.ip,
- userAgent: req.headers["user-agent"] as string,
- }),
- ),
- ),
- };
- });
+ const sw = Stopwatch.startNew();
+ const finalMessages = (
+ await Message.find({
+ where: whereQuery,
+ order: { timestamp: "DESC" },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ "referenced_message",
+ "referenced_message.author",
+ "referenced_message.webhook",
+ "referenced_message.application",
+ "referenced_message.mentions",
+ "referenced_message.mention_roles",
+ "referenced_message.mention_channels",
+ "referenced_message.sticker_items",
+ "referenced_message.attachments",
+ ],
+ take: limit,
+ })
+ ).map((m) => {
+ return {
+ ...m.toJSON(),
+ attachments: m.attachments?.map((attachment: Attachment) =>
+ Attachment.prototype.signUrls.call(
+ attachment,
+ new NewUrlUserSignatureData({
+ ip: req.ip,
+ userAgent: req.headers["user-agent"] as string,
+ }),
+ ),
+ ),
+ };
+ });
- console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`);
+ console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`);
- return res.json(finalMessages);
- },
+ return res.json(finalMessages);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index c8995d83..4cc4c072 100644
--- a/src/api/routes/users/@me/mfa/codes-verification.ts
+++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -24,52 +24,52 @@ import { CodesVerificationSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "CodesVerificationSchema",
- responses: {
- 200: {
- body: "APIBackupCodeArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
- const { regenerate } = req.body as CodesVerificationSchema;
+ "/",
+ route({
+ requestBody: "CodesVerificationSchema",
+ responses: {
+ 200: {
+ body: "APIBackupCodeArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
+ const { regenerate } = req.body as CodesVerificationSchema;
- // TODO: We don't have email/etc etc, so can't send a verification code.
- // Once that's done, this route can verify `key`
+ // TODO: We don't have email/etc etc, so can't send a verification code.
+ // Once that's done, this route can verify `key`
- // const user = await User.findOneOrFail({ where: { id: req.user_id } });
- if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER;
+ // const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER;
- let codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
+ let codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map((x) => x.save()));
- } else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
- },
- expired: false,
- },
- });
- }
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
+ },
+ });
+ }
- return res.json({
- backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
- });
- },
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
index 579a106e..2eb1a3e3 100644
--- a/src/api/routes/users/@me/mfa/codes.ts
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -27,61 +27,61 @@ const router = Router({ mergeParams: true });
// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients
router.post(
- "/",
- route({
- requestBody: "MfaCodesSchema",
- deprecated: true,
- description: "This route is replaced with users/@me/mfa/codes-verification in newer clients",
- responses: {
- 200: {
- body: "APIBackupCodeArray",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { password, regenerate } = req.body as MfaCodesSchema;
+ "/",
+ route({
+ requestBody: "MfaCodesSchema",
+ deprecated: true,
+ description: "This route is replaced with users/@me/mfa/codes-verification in newer clients",
+ responses: {
+ 200: {
+ body: "APIBackupCodeArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { password, regenerate } = req.body as MfaCodesSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ });
- if (!(await bcrypt.compare(password, user.data.hash || ""))) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- let codes: BackupCode[];
- if (regenerate) {
- await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
+ let codes: BackupCode[];
+ if (regenerate) {
+ await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
- codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(codes.map((x) => x.save()));
- } else {
- codes = await BackupCode.find({
- where: {
- user: {
- id: req.user_id,
- },
- expired: false,
- },
- });
- }
+ codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(codes.map((x) => x.save()));
+ } else {
+ codes = await BackupCode.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ expired: false,
+ },
+ });
+ }
- return res.json({
- backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
- });
- },
+ return res.json({
+ backup_codes: codes.map((x) => ({ ...x, expired: undefined })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
index 003dcb74..11c927e4 100644
--- a/src/api/routes/users/@me/mfa/totp/disable.ts
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -26,51 +26,51 @@ import { TotpDisableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpDisableSchema",
- responses: {
- 200: {
- body: "TokenOnlyResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as TotpDisableSchema;
+ "/",
+ route({
+ requestBody: "TotpDisableSchema",
+ responses: {
+ 200: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpDisableSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["totp_secret"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["totp_secret"],
+ });
- const backup = await BackupCode.findOne({ where: { code: body.code } });
- if (!backup) {
- const ret = verifyToken(user.totp_secret || "", body.code);
- if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- }
+ const backup = await BackupCode.findOne({ where: { code: body.code } });
+ if (!backup) {
+ const ret = verifyToken(user.totp_secret || "", body.code);
+ if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ }
- await User.update(
- { id: req.user_id },
- {
- mfa_enabled: false,
- totp_secret: "",
- },
- );
+ await User.update(
+ { id: req.user_id },
+ {
+ mfa_enabled: false,
+ totp_secret: "",
+ },
+ );
- await BackupCode.update(
- { user: { id: req.user_id } },
- {
- expired: true,
- },
- );
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ {
+ expired: true,
+ },
+ );
- return res.json({
- token: await generateToken(user.id),
- });
- },
+ return res.json({
+ token: await generateToken(user.id),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
index f0563e30..a400c628 100644
--- a/src/api/routes/users/@me/mfa/totp/enable.ts
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -27,54 +27,54 @@ import { TotpEnableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.post(
- "/",
- route({
- requestBody: "TotpEnableSchema",
- responses: {
- 200: {
- body: "TokenWithBackupCodesResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as TotpEnableSchema;
+ "/",
+ route({
+ requestBody: "TotpEnableSchema",
+ responses: {
+ 200: {
+ body: "TokenWithBackupCodesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpEnableSchema;
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data", "email"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data", "email"],
+ });
- // TODO: Are guests allowed to enable 2fa?
- if (user.data.hash) {
- if (!(await bcrypt.compare(body.password, user.data.hash))) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
- }
- }
+ // TODO: Are guests allowed to enable 2fa?
+ if (user.data.hash) {
+ if (!(await bcrypt.compare(body.password, user.data.hash))) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
+ }
- if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
+ if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
- if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- const backup_codes = generateMfaBackupCodes(req.user_id);
- await Promise.all(backup_codes.map((x) => x.save()));
- await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret });
+ const backup_codes = generateMfaBackupCodes(req.user_id);
+ await Promise.all(backup_codes.map((x) => x.save()));
+ await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret });
- res.send({
- token: await generateToken(user.id),
- backup_codes: backup_codes.map((x) => ({
- ...x,
- expired: undefined,
- })),
- });
- },
+ res.send({
+ token: await generateToken(user.id),
+ backup_codes: backup_codes.map((x) => ({
+ ...x,
+ expired: undefined,
+ })),
+ });
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
index 6dc06563..77180cac 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
@@ -22,29 +22,29 @@ import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { key_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { key_id } = req.params;
- await SecurityKey.delete({
- id: key_id,
- user_id: req.user_id,
- });
+ await SecurityKey.delete({
+ id: key_id,
+ user_id: req.user_id,
+ });
- const keys = await SecurityKey.count({
- where: { user_id: req.user_id },
- });
+ const keys = await SecurityKey.count({
+ where: { user_id: req.user_id },
+ });
- // disable webauthn if there are no keys left
- if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false });
+ // disable webauthn if there are no keys left
+ if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false });
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
index 0c4b733d..7bd29a57 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
@@ -26,138 +26,138 @@ import { CreateWebAuthnCredentialSchema, GenerateWebAuthnCredentialsSchema, WebA
const router = Router({ mergeParams: true });
const isGenerateSchema = (body: WebAuthnPostSchema): body is GenerateWebAuthnCredentialsSchema => {
- return "password" in body;
+ return "password" in body;
};
const isCreateSchema = (body: WebAuthnPostSchema): body is CreateWebAuthnCredentialSchema => {
- return "credential" in body;
+ return "credential" in body;
};
function toArrayBuffer(buf: Buffer) {
- const ab = new ArrayBuffer(buf.length);
- const view = new Uint8Array(ab);
- for (let i = 0; i < buf.length; ++i) {
- view[i] = buf[i];
- }
- return ab;
+ const ab = new ArrayBuffer(buf.length);
+ const view = new Uint8Array(ab);
+ for (let i = 0; i < buf.length; ++i) {
+ view[i] = buf[i];
+ }
+ return ab;
}
router.get("/", route({}), async (req: Request, res: Response) => {
- const securityKeys = await SecurityKey.find({
- where: {
- user_id: req.user_id,
- },
- });
+ const securityKeys = await SecurityKey.find({
+ where: {
+ user_id: req.user_id,
+ },
+ });
- return res.json(
- securityKeys.map((key) => ({
- id: key.id,
- name: key.name,
- })),
- );
+ return res.json(
+ securityKeys.map((key) => ({
+ id: key.id,
+ name: key.name,
+ })),
+ );
});
router.post(
- "/",
- route({
- requestBody: "WebAuthnPostSchema",
- responses: {
- 200: {
- body: "WebAuthnCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- if (!WebAuthn.fido2) {
- // TODO: I did this for typescript and I can't use !
- throw new Error("WebAuthn not enabled");
- }
+ "/",
+ route({
+ requestBody: "WebAuthnPostSchema",
+ responses: {
+ 200: {
+ body: "WebAuthnCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ if (!WebAuthn.fido2) {
+ // TODO: I did this for typescript and I can't use !
+ throw new Error("WebAuthn not enabled");
+ }
- const user = await User.findOneOrFail({
- where: {
- id: req.user_id,
- },
- select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"],
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ id: req.user_id,
+ },
+ select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"],
+ relations: ["settings"],
+ });
- if (isGenerateSchema(req.body)) {
- const { password } = req.body;
- const same_password = await bcrypt.compare(password, user.data.hash || "");
- if (!same_password) {
- throw FieldErrors({
- password: {
- message: req.t("auth:login.INVALID_PASSWORD"),
- code: "INVALID_PASSWORD",
- },
- });
- }
+ if (isGenerateSchema(req.body)) {
+ const { password } = req.body;
+ const same_password = await bcrypt.compare(password, user.data.hash || "");
+ if (!same_password) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- const registrationOptions = await WebAuthn.fido2.attestationOptions();
- const challenge = JSON.stringify({
- publicKey: {
- ...registrationOptions,
- challenge: Buffer.from(registrationOptions.challenge).toString("base64"),
- user: {
- id: user.id,
- name: user.username,
- displayName: user.username,
- },
- },
- });
+ const registrationOptions = await WebAuthn.fido2.attestationOptions();
+ const challenge = JSON.stringify({
+ publicKey: {
+ ...registrationOptions,
+ challenge: Buffer.from(registrationOptions.challenge).toString("base64"),
+ user: {
+ id: user.id,
+ name: user.username,
+ displayName: user.username,
+ },
+ },
+ });
- const ticket = await generateWebAuthnTicket(challenge);
+ const ticket = await generateWebAuthnTicket(challenge);
- return res.json({
- ticket: ticket,
- challenge,
- });
- } else if (isCreateSchema(req.body)) {
- const { credential, name, ticket } = req.body;
+ return res.json({
+ ticket: ticket,
+ challenge,
+ });
+ } else if (isCreateSchema(req.body)) {
+ const { credential, name, ticket } = req.body;
- const verified = await verifyWebAuthnToken(ticket);
- if (!verified) throw new HTTPError("Invalid ticket", 400);
+ const verified = await verifyWebAuthnToken(ticket);
+ if (!verified) throw new HTTPError("Invalid ticket", 400);
- const clientAttestationResponse = JSON.parse(credential);
+ const clientAttestationResponse = JSON.parse(credential);
- if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
+ if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
- const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64");
- clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
+ const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64");
+ clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
- const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
+ const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
- const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, {
- ...attestationExpectations,
- factor: "second",
- });
+ const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, {
+ ...attestationExpectations,
+ factor: "second",
+ });
- const authnrData = regResult.authnrData;
- const keyId = Buffer.from(authnrData.get("credId")).toString("base64");
- const counter = authnrData.get("counter");
- const publicKey = authnrData.get("credentialPublicKeyPem");
+ const authnrData = regResult.authnrData;
+ const keyId = Buffer.from(authnrData.get("credId")).toString("base64");
+ const counter = authnrData.get("counter");
+ const publicKey = authnrData.get("credentialPublicKeyPem");
- const securityKey = SecurityKey.create({
- name,
- counter,
- public_key: publicKey,
- user_id: req.user_id,
- key_id: keyId,
- });
+ const securityKey = SecurityKey.create({
+ name,
+ counter,
+ public_key: publicKey,
+ user_id: req.user_id,
+ key_id: keyId,
+ });
- await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]);
+ await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]);
- return res.json({
- name,
- id: securityKey.id,
- });
- } else {
- throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN;
- }
- },
+ return res.json({
+ name,
+ id: securityKey.id,
+ });
+ } else {
+ throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN;
+ }
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index 4969fa75..8cd33304 100644
--- a/src/api/routes/users/@me/notes.ts
+++ b/src/api/routes/users/@me/notes.ts
@@ -23,89 +23,89 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/:user_id",
- route({
- responses: {
- 200: {
- body: "UserNoteResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
+ "/:user_id",
+ route({
+ responses: {
+ 200: {
+ body: "UserNoteResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
- const note = await Note.findOneOrFail({
- where: {
- owner: { id: req.user_id },
- target: { id: user_id },
- },
- });
+ const note = await Note.findOneOrFail({
+ where: {
+ owner: { id: req.user_id },
+ target: { id: user_id },
+ },
+ });
- return res.json({
- note: note?.content,
- note_user_id: user_id,
- user_id: req.user_id,
- });
- },
+ return res.json({
+ note: note?.content,
+ note_user_id: user_id,
+ user_id: req.user_id,
+ });
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "UserNoteUpdateSchema",
- responses: {
- 204: {},
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
- const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw
- const { note } = req.body;
+ "/:user_id",
+ route({
+ requestBody: "UserNoteUpdateSchema",
+ responses: {
+ 204: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
+ const owner = await User.findOneOrFail({ where: { id: req.user_id } });
+ const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw
+ const { note } = req.body;
- if (note && note.length) {
- // upsert a note
- if (
- await Note.findOne({
- where: {
- owner: { id: owner.id },
- target: { id: target.id },
- },
- })
- ) {
- Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note });
- } else {
- Note.insert({
- id: Snowflake.generate(),
- owner,
- target,
- content: note,
- });
- }
- } else {
- await Note.delete({
- owner: { id: owner.id },
- target: { id: target.id },
- });
- }
+ if (note && note.length) {
+ // upsert a note
+ if (
+ await Note.findOne({
+ where: {
+ owner: { id: owner.id },
+ target: { id: target.id },
+ },
+ })
+ ) {
+ Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note });
+ } else {
+ Note.insert({
+ id: Snowflake.generate(),
+ owner,
+ target,
+ content: note,
+ });
+ }
+ } else {
+ await Note.delete({
+ owner: { id: owner.id },
+ target: { id: target.id },
+ });
+ }
- await emitEvent({
- event: "USER_NOTE_UPDATE",
- data: {
- note: note,
- id: target.id,
- },
- user_id: owner.id,
- });
+ await emitEvent({
+ event: "USER_NOTE_UPDATE",
+ data: {
+ note: note,
+ id: target.id,
+ },
+ user_id: owner.id,
+ });
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index d3df9eb0..ef6c7798 100644
--- a/src/api/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -27,255 +27,255 @@ const router = Router({ mergeParams: true });
const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UserRelationshipsResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships", "relationships.to"],
- select: ["id", "relationships"],
- });
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserRelationshipsResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: ["id", "relationships"],
+ });
- const related_users = user.relationships.map((r) => r.toPublicRelationship());
- return res.json(related_users);
- },
+ const related_users = user.relationships.map((r) => r.toPublicRelationship());
+ return res.json(related_users);
+ },
);
router.put(
- "/:user_id",
- route({
- requestBody: "RelationshipPutSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({
- where: { id: req.params.user_id },
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- }),
- req.body.type ?? RelationshipType.friends,
- );
- },
+ "/:user_id",
+ route({
+ requestBody: "RelationshipPutSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ where: { id: req.params.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ }),
+ req.body.type ?? RelationshipType.friends,
+ );
+ },
);
router.post(
- "/",
- route({
- requestBody: "RelationshipPostSchema",
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- where: {
- discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes
- username: req.body.username,
- },
- }),
- req.body.type,
- );
- },
+ "/",
+ route({
+ requestBody: "RelationshipPostSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ where: {
+ discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes
+ username: req.body.username,
+ },
+ }),
+ req.body.type,
+ );
+ },
);
router.delete(
- "/:user_id",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { user_id } = req.params;
- if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
+ "/:user_id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req.params;
+ if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: userProjection,
- relations: ["relationships"],
- });
- const friend = await User.findOneOrFail({
- where: { id: user_id },
- select: userProjection,
- relations: ["relationships"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
+ const friend = await User.findOneOrFail({
+ where: { id: user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
- const relationship = user.relationships.find((x) => x.to_id === user_id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ const relationship = user.relationships.find((x) => x.to_id === user_id);
+ const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
- if (!relationship) throw new HTTPError("You are not friends with the user", 404);
+ if (!relationship) throw new HTTPError("You are not friends with the user", 404);
- if (relationship?.type === RelationshipType.blocked) {
- // unblock user
- await Promise.all([
- Relationship.delete({ id: relationship.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- user_id: req.user_id,
- data: relationship.toPublicRelationship(),
- } as RelationshipRemoveEvent),
- ]);
- return res.sendStatus(204);
- }
- if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
- await Promise.all([
- Relationship.delete({ id: friendRequest.id }),
- await emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: friendRequest.toPublicRelationship(),
- user_id: user_id,
- } as RelationshipRemoveEvent),
- ]);
- }
+ if (relationship?.type === RelationshipType.blocked) {
+ // unblock user
+ await Promise.all([
+ Relationship.delete({ id: relationship.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ user_id: req.user_id,
+ data: relationship.toPublicRelationship(),
+ } as RelationshipRemoveEvent),
+ ]);
+ return res.sendStatus(204);
+ }
+ if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+ await Promise.all([
+ Relationship.delete({ id: friendRequest.id }),
+ await emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: friendRequest.toPublicRelationship(),
+ user_id: user_id,
+ } as RelationshipRemoveEvent),
+ ]);
+ }
- await Promise.all([
- Relationship.delete({ id: relationship.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipRemoveEvent),
- ]);
+ await Promise.all([
+ Relationship.delete({ id: relationship.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: relationship.toPublicRelationship(),
+ user_id: req.user_id,
+ } as RelationshipRemoveEvent),
+ ]);
- return res.sendStatus(204);
- },
+ return res.sendStatus(204);
+ },
);
export default router;
async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) {
- const id = friend.id;
- if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
+ const id = friend.id;
+ if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships", "relationships.to"],
- select: userProjection,
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ });
- let relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
+ let relationship = user.relationships.find((x) => x.to_id === id);
+ const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
- // TODO: you can add infinitely many blocked users (should this be prevented?)
- if (type === RelationshipType.blocked) {
- if (relationship) {
- if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user");
- relationship.type = RelationshipType.blocked;
- await relationship.save();
- } else {
- relationship = await Relationship.create({
- to_id: id,
- type: RelationshipType.blocked,
- from_id: req.user_id,
- }).save();
- }
+ // TODO: you can add infinitely many blocked users (should this be prevented?)
+ if (type === RelationshipType.blocked) {
+ if (relationship) {
+ if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user");
+ relationship.type = RelationshipType.blocked;
+ await relationship.save();
+ } else {
+ relationship = await Relationship.create({
+ to_id: id,
+ type: RelationshipType.blocked,
+ from_id: req.user_id,
+ }).save();
+ }
- if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
- await Promise.all([
- Relationship.delete({ id: friendRequest.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: friendRequest.toPublicRelationship(),
- user_id: id,
- } as RelationshipRemoveEvent),
- ]);
- }
+ if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+ await Promise.all([
+ Relationship.delete({ id: friendRequest.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: friendRequest.toPublicRelationship(),
+ user_id: id,
+ } as RelationshipRemoveEvent),
+ ]);
+ }
- await emitEvent({
- event: "RELATIONSHIP_ADD",
- data: relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipAddEvent);
+ await emitEvent({
+ event: "RELATIONSHIP_ADD",
+ data: relationship.toPublicRelationship(),
+ user_id: req.user_id,
+ } as RelationshipAddEvent);
- return res.sendStatus(204);
- }
+ return res.sendStatus(204);
+ }
- const { maxFriends } = Config.get().limits.user;
- if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
+ const { maxFriends } = Config.get().limits.user;
+ if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
- let incoming_relationship = Relationship.create({
- nickname: undefined,
- type: RelationshipType.incoming,
- to: user,
- from: friend,
- });
- let outgoing_relationship = Relationship.create({
- nickname: undefined,
- type: RelationshipType.outgoing,
- to: friend,
- from: user,
- });
+ let incoming_relationship = Relationship.create({
+ nickname: undefined,
+ type: RelationshipType.incoming,
+ to: user,
+ from: friend,
+ });
+ let outgoing_relationship = Relationship.create({
+ nickname: undefined,
+ type: RelationshipType.outgoing,
+ to: friend,
+ from: user,
+ });
- if (friendRequest) {
- if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
- if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
- // accept friend request
- incoming_relationship = friendRequest;
- incoming_relationship.type = RelationshipType.friends;
- }
+ if (friendRequest) {
+ if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
+ if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
+ // accept friend request
+ incoming_relationship = friendRequest;
+ incoming_relationship.type = RelationshipType.friends;
+ }
- if (relationship) {
- if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
- if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
- if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
- outgoing_relationship = relationship;
- outgoing_relationship.type = RelationshipType.friends;
- }
+ if (relationship) {
+ if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
+ if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
+ if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
+ outgoing_relationship = relationship;
+ outgoing_relationship.type = RelationshipType.friends;
+ }
- await Promise.all([
- incoming_relationship.save(),
- outgoing_relationship.save(),
- emitEvent({
- event: "RELATIONSHIP_ADD",
- data: outgoing_relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipAddEvent),
- emitEvent({
- event: "RELATIONSHIP_ADD",
- data: {
- ...incoming_relationship.toPublicRelationship(),
- should_notify: true,
- },
- user_id: id,
- } as RelationshipAddEvent),
- ]);
+ await Promise.all([
+ incoming_relationship.save(),
+ outgoing_relationship.save(),
+ emitEvent({
+ event: "RELATIONSHIP_ADD",
+ data: outgoing_relationship.toPublicRelationship(),
+ user_id: req.user_id,
+ } as RelationshipAddEvent),
+ emitEvent({
+ event: "RELATIONSHIP_ADD",
+ data: {
+ ...incoming_relationship.toPublicRelationship(),
+ should_notify: true,
+ },
+ user_id: id,
+ } as RelationshipAddEvent),
+ ]);
- return res.sendStatus(204);
+ return res.sendStatus(204);
}
diff --git a/src/api/routes/users/@me/settings-proto/1.ts b/src/api/routes/users/@me/settings-proto/1.ts
index 6f58b422..d54bdf36 100644
--- a/src/api/routes/users/@me/settings-proto/1.ts
+++ b/src/api/routes/users/@me/settings-proto/1.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true });
//#region Protobuf
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "SettingsProtoResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: PreloadedUserSettings.toBase64(userSettings.userSettings!),
- } as SettingsProtoResponse);
- },
+ res.json({
+ settings: PreloadedUserSettings.toBase64(userSettings.userSettings!),
+ } as SettingsProtoResponse);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "SettingsProtoUpdateSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
- const { atomic } = req.query;
- const updatedSettings = PreloadedUserSettings.fromBase64(settings);
+ "/",
+ route({
+ requestBody: "SettingsProtoUpdateSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
+ const { atomic } = req.query;
+ const updatedSettings = PreloadedUserSettings.fromBase64(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: PreloadedUserSettings.toBase64(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: PreloadedUserSettings.toBase64(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
//#region JSON
router.get(
- "/json",
- route({
- responses: {
- 200: {
- body: "SettingsProtoJsonResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/json",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoJsonResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: PreloadedUserSettings.toJson(userSettings.userSettings!),
- } as SettingsProtoJsonResponse);
- },
+ res.json({
+ settings: PreloadedUserSettings.toJson(userSettings.userSettings!),
+ } as SettingsProtoJsonResponse);
+ },
);
router.patch(
- "/json",
- route({
- requestBody: "SettingsProtoUpdateJsonSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateJsonResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
- const { atomic } = req.query;
- const updatedSettings = PreloadedUserSettings.fromJson(settings);
+ "/json",
+ route({
+ requestBody: "SettingsProtoUpdateJsonSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateJsonResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
+ const { atomic } = req.query;
+ const updatedSettings = PreloadedUserSettings.fromJson(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: PreloadedUserSettings.toJson(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: PreloadedUserSettings.toJson(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
async function patchUserSettings(userId: string, updatedSettings: PreloadedUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
- const userSettings = await UserSettingsProtos.getOrDefault(userId);
- let settings = userSettings.userSettings!;
+ const userSettings = await UserSettingsProtos.getOrDefault(userId);
+ let settings = userSettings.userSettings!;
- if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
- return {
- settings: settings,
- out_of_date: true,
- };
- }
+ if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
+ return {
+ settings: settings,
+ out_of_date: true,
+ };
+ }
- if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false")
- console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
+ if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false")
+ console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
- if (!atomic) {
- settings = PreloadedUserSettings.fromJson(
- Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- } else {
- settings = PreloadedUserSettings.fromJson(
- OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- }
+ if (!atomic) {
+ settings = PreloadedUserSettings.fromJson(
+ Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ } else {
+ settings = PreloadedUserSettings.fromJson(
+ OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ }
- settings.versions = {
- clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
- serverVersion: settings.versions?.serverVersion ?? 0,
- dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
- };
- userSettings.userSettings = settings;
- await userSettings.save();
+ settings.versions = {
+ clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
+ serverVersion: settings.versions?.serverVersion ?? 0,
+ dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
+ };
+ userSettings.userSettings = settings;
+ await userSettings.save();
- await emitEvent({
- user_id: userId,
- event: "USER_SETTINGS_PROTO_UPDATE",
- data: {
- settings: {
- proto: PreloadedUserSettings.toBase64(settings),
- type: 1,
- },
- json_settings: {
- proto: PreloadedUserSettings.toJson(settings),
- type: "user_settings",
- },
- partial: false, // Unsure how this should behave
- },
- });
- // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
- // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
+ await emitEvent({
+ user_id: userId,
+ event: "USER_SETTINGS_PROTO_UPDATE",
+ data: {
+ settings: {
+ proto: PreloadedUserSettings.toBase64(settings),
+ type: 1,
+ },
+ json_settings: {
+ proto: PreloadedUserSettings.toJson(settings),
+ type: "user_settings",
+ },
+ partial: false, // Unsure how this should behave
+ },
+ });
+ // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
+ // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
- return {
- settings: settings,
- out_of_date: false,
- };
+ return {
+ settings: settings,
+ out_of_date: false,
+ };
}
export default router;
diff --git a/src/api/routes/users/@me/settings-proto/2.ts b/src/api/routes/users/@me/settings-proto/2.ts
index b37805fd..809db848 100644
--- a/src/api/routes/users/@me/settings-proto/2.ts
+++ b/src/api/routes/users/@me/settings-proto/2.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true });
//#region Protobuf
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "SettingsProtoResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!),
- } as SettingsProtoResponse);
- },
+ res.json({
+ settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!),
+ } as SettingsProtoResponse);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "SettingsProtoUpdateSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
- const { atomic } = req.query;
- const updatedSettings = FrecencyUserSettings.fromBase64(settings);
+ "/",
+ route({
+ requestBody: "SettingsProtoUpdateSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
+ const { atomic } = req.query;
+ const updatedSettings = FrecencyUserSettings.fromBase64(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: FrecencyUserSettings.toBase64(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: FrecencyUserSettings.toBase64(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
//#region JSON
router.get(
- "/json",
- route({
- responses: {
- 200: {
- body: "SettingsProtoJsonResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
+ "/json",
+ route({
+ responses: {
+ 200: {
+ body: "SettingsProtoJsonResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
- res.json({
- settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!),
- } as SettingsProtoJsonResponse);
- },
+ res.json({
+ settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!),
+ } as SettingsProtoJsonResponse);
+ },
);
router.patch(
- "/json",
- route({
- requestBody: "SettingsProtoUpdateJsonSchema",
- responses: {
- 200: {
- body: "SettingsProtoUpdateJsonResponse",
- },
- },
- query: {
- atomic: {
- type: "boolean",
- description: "Whether to try to apply the settings update atomically (default false)",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
- const { atomic } = req.query;
- const updatedSettings = FrecencyUserSettings.fromJson(settings);
+ "/json",
+ route({
+ requestBody: "SettingsProtoUpdateJsonSchema",
+ responses: {
+ 200: {
+ body: "SettingsProtoUpdateJsonResponse",
+ },
+ },
+ query: {
+ atomic: {
+ type: "boolean",
+ description: "Whether to try to apply the settings update atomically (default false)",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
+ const { atomic } = req.query;
+ const updatedSettings = FrecencyUserSettings.fromJson(settings);
- const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
+ const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
- res.json({
- settings: FrecencyUserSettings.toJson(resultObj.settings),
- out_of_date: resultObj.out_of_date,
- });
- },
+ res.json({
+ settings: FrecencyUserSettings.toJson(resultObj.settings),
+ out_of_date: resultObj.out_of_date,
+ });
+ },
);
//#endregion
async function patchUserSettings(userId: string, updatedSettings: FrecencyUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
- const userSettings = await UserSettingsProtos.getOrDefault(userId);
- let settings = userSettings.frecencySettings!;
+ const userSettings = await UserSettingsProtos.getOrDefault(userId);
+ let settings = userSettings.frecencySettings!;
- if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
- return {
- settings: settings,
- out_of_date: true,
- };
- }
+ if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
+ return {
+ settings: settings,
+ out_of_date: true,
+ };
+ }
- if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false")
- console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
+ if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false")
+ console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
- if (!atomic) {
- settings = FrecencyUserSettings.fromJson(
- Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- } else {
- settings = FrecencyUserSettings.fromJson(
- OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
- );
- }
+ if (!atomic) {
+ settings = FrecencyUserSettings.fromJson(
+ Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ } else {
+ settings = FrecencyUserSettings.fromJson(
+ OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
+ );
+ }
- settings.versions = {
- clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
- serverVersion: settings.versions?.serverVersion ?? 0,
- dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
- };
- userSettings.frecencySettings = settings;
- await userSettings.save();
+ settings.versions = {
+ clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0,
+ serverVersion: settings.versions?.serverVersion ?? 0,
+ dataVersion: (settings.versions?.dataVersion ?? 0) + 1,
+ };
+ userSettings.frecencySettings = settings;
+ await userSettings.save();
- await emitEvent({
- user_id: userId,
- event: "USER_SETTINGS_PROTO_UPDATE",
- data: {
- settings: {
- proto: FrecencyUserSettings.toBase64(settings),
- type: 2,
- },
- json_settings: {
- proto: FrecencyUserSettings.toJson(settings),
- type: "frecency_settings",
- },
- partial: false, // Unsure how this should behave
- },
- });
- // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
- // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
+ await emitEvent({
+ user_id: userId,
+ event: "USER_SETTINGS_PROTO_UPDATE",
+ data: {
+ settings: {
+ proto: FrecencyUserSettings.toBase64(settings),
+ type: 2,
+ },
+ json_settings: {
+ proto: FrecencyUserSettings.toJson(settings),
+ type: "frecency_settings",
+ },
+ partial: false, // Unsure how this should behave
+ },
+ });
+ // This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
+ // when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.
- return {
- settings: settings,
- out_of_date: false,
- };
+ return {
+ settings: settings,
+ out_of_date: false,
+ };
}
export default router;
diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index d220b859..57ffba14 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -24,81 +24,81 @@ import { UserSettingsUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "UserSettings",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const settings = await UserSettings.getOrDefault(req.user_id);
- return res.json(settings);
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const settings = await UserSettings.getOrDefault(req.user_id);
+ return res.json(settings);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "UserSettingsUpdateSchema",
- responses: {
- 200: {
- body: "UserSettings",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 404: {
- body: "APIErrorResponse",
- },
- },
- }),
- async (req: Request, res: Response) => {
- const body = req.body as UserSettingsUpdateSchema;
- if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" });
- if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale
+ "/",
+ route({
+ requestBody: "UserSettingsUpdateSchema",
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserSettingsUpdateSchema;
+ if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" });
+ if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale
- const user = await User.findOneOrFail({
- where: { id: req.user_id, bot: false },
- relations: ["settings"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id, bot: false },
+ relations: ["settings"],
+ });
- if (!user.settings) user.settings = UserSettings.create<UserSettings>(body);
- else user.settings.assign(body);
+ if (!user.settings) user.settings = UserSettings.create<UserSettings>(body);
+ else user.settings.assign(body);
- if (body.guild_folders) user.settings.guild_folders = body.guild_folders;
+ if (body.guild_folders) user.settings.guild_folders = body.guild_folders;
- await user.settings.save();
- await user.save();
- if (body.status) {
- const [session] = (await Session.find({
- where: { user_id: user.id },
- })) as [Session | undefined];
- if (session) {
- session.status = body.status;
+ await user.settings.save();
+ await user.save();
+ if (body.status) {
+ const [session] = (await Session.find({
+ where: { user_id: user.id },
+ })) as [Session | undefined];
+ if (session) {
+ session.status = body.status;
- await Promise.all([
- emitEvent({
- event: "PRESENCE_UPDATE",
- user_id: user.id,
- data: {
- user: user.toPublicUser(),
- activities: session.activities,
- client_status: session?.client_status,
- status: session.getPublicStatus(),
- },
- } as PresenceUpdateEvent),
- session.save(),
- ]);
- }
- }
+ await Promise.all([
+ emitEvent({
+ event: "PRESENCE_UPDATE",
+ user_id: user.id,
+ data: {
+ user: user.toPublicUser(),
+ activities: session.activities,
+ client_status: session?.client_status,
+ status: session.getPublicStatus(),
+ },
+ } as PresenceUpdateEvent),
+ session.save(),
+ ]);
+ }
+ }
- res.json({ ...user.settings, index: undefined });
- },
+ res.json({ ...user.settings, index: undefined });
+ },
);
export default router;
diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index ba261231..a6759b55 100644
--- a/src/api/routes/voice/regions.ts
+++ b/src/api/routes/voice/regions.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express";
const router: Router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- responses: {
- 200: {
- body: "APIGuildVoiceRegion",
- },
- },
- }),
- async (req: Request, res: Response) => {
- res.json(await getVoiceRegions(req.ip!, true)); //vip true?
- },
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildVoiceRegion",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json(await getVoiceRegions(req.ip!, true)); //vip true?
+ },
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/#token/github.ts b/src/api/routes/webhooks/#webhook_id/#token/github.ts
index 78d6bbb8..81aa40a9 100644
--- a/src/api/routes/webhooks/#webhook_id/#token/github.ts
+++ b/src/api/routes/webhooks/#webhook_id/#token/github.ts
@@ -7,402 +7,402 @@ import { WebhookExecuteSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
const parseGitHubWebhook = (req: Request, res: Response, next: NextFunction) => {
- const eventType = req.headers["x-github-event"] as string;
- if (!eventType) {
- throw new HTTPError("Missing X-GitHub-Event header", 400);
- }
+ const eventType = req.headers["x-github-event"] as string;
+ if (!eventType) {
+ throw new HTTPError("Missing X-GitHub-Event header", 400);
+ }
- const discordPayload: WebhookExecuteSchema = {
- username: "GitHub",
- avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png",
- };
+ const discordPayload: WebhookExecuteSchema = {
+ username: "GitHub",
+ avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png",
+ };
- switch (eventType) {
- case "commit_comment":
- if (req.body.action !== "created") {
- return;
- }
+ switch (eventType) {
+ case "commit_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- url: req.body.comment.html_url,
- },
- ];
- break;
- case "create":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`,
- },
- ];
- break;
- case "delete":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`,
- },
- ];
- break;
- case "fork":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`,
- url: req.body.forkee.html_url,
- },
- ];
- break;
- case "issue_comment":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ url: req.body.comment.html_url,
+ },
+ ];
+ break;
+ case "create":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`,
+ },
+ ];
+ break;
+ case "delete":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`,
+ },
+ ];
+ break;
+ case "fork":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`,
+ url: req.body.forkee.html_url,
+ },
+ ];
+ break;
+ case "issue_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: "pull_request" in req.body.issue ? 12576191 : 15109472,
- title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${
- req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title
- }`,
- url: req.body.comment.html_url,
- description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- },
- ];
- break;
- case "issues":
- if (!["opened", "closed"].includes(req.body.action)) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: "pull_request" in req.body.issue ? 12576191 : 15109472,
+ title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${
+ req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title
+ }`,
+ url: req.body.comment.html_url,
+ description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ },
+ ];
+ break;
+ case "issues":
+ if (!["opened", "closed"].includes(req.body.action)) {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`,
- url: req.body.issue.html_url,
- },
- ];
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`,
+ url: req.body.issue.html_url,
+ },
+ ];
- if (req.body.action === "opened") {
- discordPayload.embeds[0].color = 15426592;
- discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body;
- }
- break;
- case "member":
- if (req.body.action !== "added") {
- return;
- }
+ if (req.body.action === "opened") {
+ discordPayload.embeds[0].color = 15426592;
+ discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body;
+ }
+ break;
+ case "member":
+ if (req.body.action !== "added") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`,
- url: req.body.member.html_url,
- },
- ];
- break;
- case "public":
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Now open sourced!`,
- },
- ];
- break;
- case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (!["opened", "closed"].includes(req.body.action)) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`,
+ url: req.body.member.html_url,
+ },
+ ];
+ break;
+ case "public":
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Now open sourced!`,
+ },
+ ];
+ break;
+ case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (!["opened", "closed"].includes(req.body.action)) {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- url: req.body.pull_request.html_url,
- },
- ];
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ url: req.body.pull_request.html_url,
+ },
+ ];
- if (req.body.action === "opened") {
- if (req.body.pull_request.body != null) {
- discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body;
- }
- discordPayload.embeds[0].color = 38912;
- }
- break;
- case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (req.body.action !== "submitted") {
- return;
- }
+ if (req.body.action === "opened") {
+ if (req.body.pull_request.body != null) {
+ discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body;
+ }
+ discordPayload.embeds[0].color = 38912;
+ }
+ break;
+ case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (req.body.action !== "submitted") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body,
- url: req.body.review.html_url,
- },
- ];
- break;
- case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body,
+ url: req.body.review.html_url,
+ },
+ ];
+ break;
+ case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 12576191,
- title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- url: req.body.comment.html_url,
- },
- ];
- break;
- case "push":
- if (!req.body.ref.startsWith("refs/heads/")) {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 12576191,
+ title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ url: req.body.comment.html_url,
+ },
+ ];
+ break;
+ case "push":
+ if (!req.body.ref.startsWith("refs/heads/")) {
+ return;
+ }
- if (req.body.forced) {
- discordPayload.embeds = [
- {
- color: 16525609,
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``,
- description: `[Compare changes](${req.body.compare})`,
- },
- ];
- } else {
- discordPayload.embeds = [
- {
- color: 7506394,
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`,
- url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url,
- description: req.body.commits
- .slice(0, 5) // Discord only shows 5 first commits
- .map(
- (c: { id: string; url: string; message: string; author: { username: string } }) =>
- `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`,
- )
- .join("\n"),
- },
- ];
- }
- break;
- case "release":
- if (req.body.action !== "created") {
- return;
- }
+ if (req.body.forced) {
+ discordPayload.embeds = [
+ {
+ color: 16525609,
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``,
+ description: `[Compare changes](${req.body.compare})`,
+ },
+ ];
+ } else {
+ discordPayload.embeds = [
+ {
+ color: 7506394,
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`,
+ url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url,
+ description: req.body.commits
+ .slice(0, 5) // Discord only shows 5 first commits
+ .map(
+ (c: { id: string; url: string; message: string; author: { username: string } }) =>
+ `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`,
+ )
+ .join("\n"),
+ },
+ ];
+ }
+ break;
+ case "release":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`,
- url: req.body.release.html_url,
- },
- ];
- break;
- case "watch":
- if (req.body.action !== "started") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`,
+ url: req.body.release.html_url,
+ },
+ ];
+ break;
+ case "watch":
+ if (req.body.action !== "started") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- title: `[${req.body.repository.full_name}] New star added`,
- url: req.body.repository.html_url,
- },
- ];
- break;
- case "check_run":
- if (req.body.action !== "completed") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ title: `[${req.body.repository.full_name}] New star added`,
+ url: req.body.repository.html_url,
+ },
+ ];
+ break;
+ case "check_run":
+ if (req.body.action !== "completed") {
+ return;
+ }
- discordPayload.embeds = [
- {
- color: req.body.check_run.conclusion == "success" ? 38912 : 16525609,
- title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`,
- url: req.body.check_run.html_url,
- },
- ];
- break;
- case "check_suite":
- if (req.body.action !== "completed") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ color: req.body.check_run.conclusion == "success" ? 38912 : 16525609,
+ title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`,
+ url: req.body.check_run.html_url,
+ },
+ ];
+ break;
+ case "check_suite":
+ if (req.body.action !== "completed") {
+ return;
+ }
- discordPayload.embeds = [
- {
- color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609,
- title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`,
- url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`,
- },
- ];
- break;
- case "discussion":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609,
+ title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`,
+ url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`,
+ },
+ ];
+ break;
+ case "discussion":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 15109472,
- title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
- url: req.body.discussion.html_url,
- description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body,
- },
- ];
- break;
- case "discussion_comment":
- if (req.body.action !== "created") {
- return;
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 15109472,
+ title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
+ url: req.body.discussion.html_url,
+ description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body,
+ },
+ ];
+ break;
+ case "discussion_comment":
+ if (req.body.action !== "created") {
+ return;
+ }
- discordPayload.embeds = [
- {
- author: {
- name: req.body.sender.login,
- icon_url: req.body.sender.avatar_url,
- proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
- url: req.body.sender.html_url,
- },
- color: 15109472,
- title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
- url: req.body.comment.html_url,
- description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
- },
- ];
- break;
- default:
- return res.status(204).end(); // Yes, discord sends 204 even on invalid event
- }
+ discordPayload.embeds = [
+ {
+ author: {
+ name: req.body.sender.login,
+ icon_url: req.body.sender.avatar_url,
+ proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80),
+ url: req.body.sender.html_url,
+ },
+ color: 15109472,
+ title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`,
+ url: req.body.comment.html_url,
+ description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body,
+ },
+ ];
+ break;
+ default:
+ return res.status(204).end(); // Yes, discord sends 204 even on invalid event
+ }
- req.body = discordPayload;
- req.query.wait ||= "true";
+ req.body = discordPayload;
+ req.query.wait ||= "true";
- next();
+ next();
};
router.post(
- "/",
- parseGitHubWebhook,
- (req, _res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
- next();
- },
- route({
- requestBody: "WebhookExecuteSchema",
- query: {
- wait: {
- type: "boolean",
- required: false,
- description: "waits for server confirmation of message send before response, and returns the created message body",
- },
- thread_id: {
- type: "string",
- required: false,
- description: "Send a message to the specified thread within a webhook's channel.",
- },
- },
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- executeWebhook,
+ "/",
+ parseGitHubWebhook,
+ (req, _res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
+ next();
+ },
+ route({
+ requestBody: "WebhookExecuteSchema",
+ query: {
+ wait: {
+ type: "boolean",
+ required: false,
+ description: "waits for server confirmation of message send before response, and returns the created message body",
+ },
+ thread_id: {
+ type: "string",
+ required: false,
+ description: "Send a message to the specified thread within a webhook's channel.",
+ },
+ },
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ executeWebhook,
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/#token/index.ts b/src/api/routes/webhooks/#webhook_id/#token/index.ts
index 1b589a7b..eecbfcae 100644
--- a/src/api/routes/webhooks/#webhook_id/#token/index.ts
+++ b/src/api/routes/webhooks/#webhook_id/#token/index.ts
@@ -8,179 +8,179 @@ import { WebhookUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a webhook object for the given id and token.",
- responses: {
- 200: {
- body: "APIWebhook",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a webhook object for the given id and token.",
+ responses: {
+ 200: {
+ body: "APIWebhook",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
- return res.json({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- });
- },
+ return res.json({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ });
+ },
);
// TODO: config max upload size
const messageUpload = multer({
- limits: {
- fileSize: Config.get().limits.message.maxAttachmentSize,
- fields: 10,
- // files: 1
- },
- storage: multer.memoryStorage(),
+ limits: {
+ fileSize: Config.get().limits.message.maxAttachmentSize,
+ fields: 10,
+ // files: 1
+ },
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
// https://discord.com/developers/docs/resources/webhook#execute-webhook
// TODO: Slack compatible hooks
router.post(
- "/",
- messageUpload.any(),
- (req, _res, next) => {
- if (req.body.payload_json) {
- req.body = JSON.parse(req.body.payload_json);
- }
+ "/",
+ messageUpload.any(),
+ (req, _res, next) => {
+ if (req.body.payload_json) {
+ req.body = JSON.parse(req.body.payload_json);
+ }
- next();
- },
- route({
- requestBody: "WebhookExecuteSchema",
- query: {
- wait: {
- type: "boolean",
- required: false,
- description: "waits for server confirmation of message send before response, and returns the created message body",
- },
- thread_id: {
- type: "string",
- required: false,
- description: "Send a message to the specified thread within a webhook's channel.",
- },
- },
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- executeWebhook,
+ next();
+ },
+ route({
+ requestBody: "WebhookExecuteSchema",
+ query: {
+ wait: {
+ type: "boolean",
+ required: false,
+ description: "waits for server confirmation of message send before response, and returns the created message body",
+ },
+ thread_id: {
+ type: "string",
+ required: false,
+ description: "Send a message to the specified thread within a webhook's channel.",
+ },
+ },
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ executeWebhook,
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["channel", "guild", "application"],
- });
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["channel", "guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
- const channel_id = webhook.channel_id;
- await Webhook.delete({ id: webhook_id });
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
+ const channel_id = webhook.channel_id;
+ await Webhook.delete({ id: webhook_id });
- await emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent);
+ await emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "WebhookUpdateSchema",
- responses: {
- 200: {
- body: "Message",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id, token } = req.params;
- const body = req.body as WebhookUpdateSchema;
+ "/",
+ route({
+ requestBody: "WebhookUpdateSchema",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id, token } = req.params;
+ const body = req.body as WebhookUpdateSchema;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
- const channel_id = webhook.channel_id;
- if (!body.name && !body.avatar) {
- throw new HTTPError("Empty webhook updates are not allowed", 50006);
- }
- if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
+ const channel_id = webhook.channel_id;
+ if (!body.name && !body.avatar) {
+ throw new HTTPError("Empty webhook updates are not allowed", 50006);
+ }
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
- if (body.name) {
- ValidateName(body.name);
- }
+ if (body.name) {
+ ValidateName(body.name);
+ }
- webhook.assign(body);
+ webhook.assign(body);
- await Promise.all([
- webhook.save(),
- emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent),
- ]);
- res.status(204);
- },
+ await Promise.all([
+ webhook.save(),
+ emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent),
+ ]);
+ res.status(204);
+ },
);
export default router;
diff --git a/src/api/routes/webhooks/#webhook_id/index.ts b/src/api/routes/webhooks/#webhook_id/index.ts
index 9c0a6418..8844b8ef 100644
--- a/src/api/routes/webhooks/#webhook_id/index.ts
+++ b/src/api/routes/webhooks/#webhook_id/index.ts
@@ -6,141 +6,141 @@ import { WebhookUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
router.get(
- "/",
- route({
- description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.",
- responses: {
- 200: {
- body: "APIWebhook",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ "/",
+ route({
+ description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.",
+ responses: {
+ 200: {
+ body: "APIWebhook",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- return res.json({
- ...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
- });
- },
+ return res.json({
+ ...webhook,
+ url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ });
+ },
);
router.delete(
- "/",
- route({
- responses: {
- 204: {},
- 400: {
- body: "APIErrorResponse",
- },
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
+ "/",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- const channel_id = webhook.channel_id;
- await Webhook.delete({ id: webhook_id });
+ const channel_id = webhook.channel_id;
+ await Webhook.delete({ id: webhook_id });
- await emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent);
+ await emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent);
- res.sendStatus(204);
- },
+ res.sendStatus(204);
+ },
);
router.patch(
- "/",
- route({
- requestBody: "WebhookUpdateSchema",
- responses: {
- 200: {
- body: "WebhookCreateResponse",
- },
- 400: {
- body: "APIErrorResponse",
- },
- 403: {},
- 404: {},
- },
- }),
- async (req: Request, res: Response) => {
- const { webhook_id } = req.params;
- const body = req.body as WebhookUpdateSchema;
+ "/",
+ route({
+ requestBody: "WebhookUpdateSchema",
+ responses: {
+ 200: {
+ body: "WebhookCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {},
+ 404: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { webhook_id } = req.params;
+ const body = req.body as WebhookUpdateSchema;
- const webhook = await Webhook.findOneOrFail({
- where: { id: webhook_id },
- relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
- });
+ const webhook = await Webhook.findOneOrFail({
+ where: { id: webhook_id },
+ relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"],
+ });
- if (webhook.guild_id) {
- const permission = await getPermission(req.user_id, webhook.guild_id);
+ if (webhook.guild_id) {
+ const permission = await getPermission(req.user_id, webhook.guild_id);
- if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- if (!body.name && !body.avatar && !body.channel_id) {
- throw new HTTPError("Empty webhook updates are not allowed", 50006);
- }
+ if (!body.name && !body.avatar && !body.channel_id) {
+ throw new HTTPError("Empty webhook updates are not allowed", 50006);
+ }
- if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
+ if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string);
- if (body.name) {
- ValidateName(body.name);
- }
+ if (body.name) {
+ ValidateName(body.name);
+ }
- const channel_id = body.channel_id || webhook.channel_id;
- webhook.assign(body);
+ const channel_id = body.channel_id || webhook.channel_id;
+ webhook.assign(body);
- if (body.channel_id)
- webhook.assign({
- channel: await Channel.findOneOrFail({
- where: { id: channel_id },
- }),
- });
+ if (body.channel_id)
+ webhook.assign({
+ channel: await Channel.findOneOrFail({
+ where: { id: channel_id },
+ }),
+ });
- await Promise.all([
- webhook.save(),
- emitEvent({
- event: "WEBHOOKS_UPDATE",
- channel_id,
- data: {
- channel_id,
- guild_id: webhook.guild_id,
- },
- } as WebhooksUpdateEvent),
- ]);
+ await Promise.all([
+ webhook.save(),
+ emitEvent({
+ event: "WEBHOOKS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: webhook.guild_id,
+ },
+ } as WebhooksUpdateEvent),
+ ]);
- res.json(webhook);
- },
+ res.json(webhook);
+ },
);
export default router;
diff --git a/src/api/start.ts b/src/api/start.ts
index cd3373be..96c0c348 100644
--- a/src/api/start.ts
+++ b/src/api/start.ts
@@ -28,30 +28,30 @@ import cluster from "cluster";
import os from "os";
let cores = 1;
try {
- cores = Number(process.env.THREADS) || os.cpus().length;
+ cores = Number(process.env.THREADS) || os.cpus().length;
} catch {
- console.log("[API] Failed to get thread count! Using 1...");
+ console.log("[API] Failed to get thread count! Using 1...");
}
if (cluster.isPrimary && process.env.NODE_ENV == "production") {
- console.log(`Primary PID: ${process.pid}`);
+ console.log(`Primary PID: ${process.pid}`);
- // Fork workers.
- for (let i = 0; i < cores; i++) {
- cluster.fork();
- }
+ // Fork workers.
+ for (let i = 0; i < cores; i++) {
+ cluster.fork();
+ }
- cluster.on("exit", (worker) => {
- console.log(`Worker ${worker.process.pid} died, restarting worker`);
- cluster.fork();
- });
+ cluster.on("exit", (worker) => {
+ console.log(`Worker ${worker.process.pid} died, restarting worker`);
+ cluster.fork();
+ });
} else {
- const port = Number(process.env.PORT) || 3001;
+ const port = Number(process.env.PORT) || 3001;
- const server = new SpacebarServer({ port });
- server.start().catch(console.error);
+ const server = new SpacebarServer({ port });
+ server.start().catch(console.error);
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- global.server = server;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ global.server = server;
}
diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 86dda74b..e71c3eda 100644
--- a/src/api/util/handlers/Instance.ts
+++ b/src/api/util/handlers/Instance.ts
@@ -21,34 +21,34 @@ import { Like } from "typeorm";
import { setInterval } from "timers";
export async function initInstance() {
- // TODO: clean up database and delete tombstone data
- // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal
+ // TODO: clean up database and delete tombstone data
+ // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal
- // create default guild and add it to auto join
- // TODO: check if any current user is not part of autoJoinGuilds
- // const { autoJoin } = Config.get().guild;
+ // create default guild and add it to auto join
+ // TODO: check if any current user is not part of autoJoinGuilds
+ // const { autoJoin } = Config.get().guild;
- // if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // const guild = await Guild.findOne({ where: {}, select: ["id"] });
- // if (guild) {
- // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- // }
- // }
+ // if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // const guild = await Guild.findOne({ where: {}, select: ["id"] });
+ // if (guild) {
+ // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ // }
+ // }
- // TODO: do no clear sessions for instance cluster
- // await Session.clear(); // This is now used as part of authentication...
- // ... but we can still expire temporary sessions for legacy tokens
- setInterval(
- async () => {
- for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) {
- // session object has all fields prefixed with `session_`... thanks typeorm
- if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) {
- console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`);
- await Session.delete({ session_id: session.session_session_id });
- }
- }
- },
- 1000 * 60 * 5,
- );
- // await Session.delete({ session_id: Like("TEMP_%") });
+ // TODO: do no clear sessions for instance cluster
+ // await Session.clear(); // This is now used as part of authentication...
+ // ... but we can still expire temporary sessions for legacy tokens
+ setInterval(
+ async () => {
+ for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) {
+ // session object has all fields prefixed with `session_`... thanks typeorm
+ if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) {
+ console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`);
+ await Session.delete({ session_id: session.session_session_id });
+ }
+ }
+ },
+ 1000 * 60 * 5,
+ );
+ // await Session.delete({ session_id: Like("TEMP_%") });
}
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 06a3e081..65583f53 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -18,36 +18,36 @@
import { EmbedHandlers } from "@spacebar/api";
import {
- Application,
- Attachment,
- Channel,
- Config,
- EmbedCache,
- emitEvent,
- EVERYONE_MENTION,
- getPermission,
- getRights,
- Guild,
- HERE_MENTION,
- Message,
- MessageCreateEvent,
- MessageUpdateEvent,
- Role,
- ROLE_MENTION,
- Sticker,
- User,
- //CHANNEL_MENTION,
- USER_MENTION,
- Webhook,
- handleFile,
- Permissions,
- normalizeUrl,
- DiscordApiErrors,
- CloudAttachment,
- ReadState,
- Member,
- Session,
- MessageFlags,
+ Application,
+ Attachment,
+ Channel,
+ Config,
+ EmbedCache,
+ emitEvent,
+ EVERYONE_MENTION,
+ getPermission,
+ getRights,
+ Guild,
+ HERE_MENTION,
+ Message,
+ MessageCreateEvent,
+ MessageUpdateEvent,
+ Role,
+ ROLE_MENTION,
+ Sticker,
+ User,
+ //CHANNEL_MENTION,
+ USER_MENTION,
+ Webhook,
+ handleFile,
+ Permissions,
+ normalizeUrl,
+ DiscordApiErrors,
+ CloudAttachment,
+ ReadState,
+ Member,
+ Session,
+ MessageFlags,
} from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { In, Or, Equal, IsNull } from "typeorm";
@@ -59,515 +59,515 @@ const allow_empty = false;
const LINK_REGEX = /<?https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)>?/g;
export async function handleMessage(opts: MessageOptions): Promise<Message> {
- const channel = await Channel.findOneOrFail({
- where: { id: opts.channel_id },
- relations: ["recipients"],
- });
- if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
+ const channel = await Channel.findOneOrFail({
+ where: { id: opts.channel_id },
+ relations: ["recipients"],
+ });
+ if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
- let permission: undefined | Permissions;
- const limit = channel.rate_limit_per_user;
+ let permission: undefined | Permissions;
+ const limit = channel.rate_limit_per_user;
- if (limit) {
- const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } }))
- ?.timestamp;
- if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) {
- permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
- //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks
- if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) {
- throw DiscordApiErrors.SLOWMODE_RATE_LIMIT;
- }
- }
- }
+ if (limit) {
+ const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } }))
+ ?.timestamp;
+ if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) {
+ permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
+ //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks
+ if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) {
+ throw DiscordApiErrors.SLOWMODE_RATE_LIMIT;
+ }
+ }
+ }
- const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
- // cloud attachments with indexes
- const cloudAttachments = opts.attachments?.reduce(
- (acc, att, index) => {
- if ("uploaded_filename" in att) {
- acc.push({ attachment: att, index });
- }
- return acc;
- },
- [] as { attachment: MessageCreateCloudAttachment; index: number }[],
- );
+ const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
+ // cloud attachments with indexes
+ const cloudAttachments = opts.attachments?.reduce(
+ (acc, att, index) => {
+ if ("uploaded_filename" in att) {
+ acc.push({ attachment: att, index });
+ }
+ return acc;
+ },
+ [] as { attachment: MessageCreateCloudAttachment; index: number }[],
+ );
- const message = Message.create({
- ...opts,
- poll: opts.poll,
- sticker_items: stickers,
- guild_id: channel.guild_id,
- channel_id: opts.channel_id,
- attachments: opts.attachments || [],
- embeds: opts.embeds || [],
- reactions: opts.reactions || [],
- type: opts.type ?? 0,
- mentions: [],
- components: opts.components ?? undefined, // Fix Discord-Go?
- });
- const ephermal = (message.flags & (1 << 6)) !== 0;
+ const message = Message.create({
+ ...opts,
+ poll: opts.poll,
+ sticker_items: stickers,
+ guild_id: channel.guild_id,
+ channel_id: opts.channel_id,
+ attachments: opts.attachments || [],
+ embeds: opts.embeds || [],
+ reactions: opts.reactions || [],
+ type: opts.type ?? 0,
+ mentions: [],
+ components: opts.components ?? undefined, // Fix Discord-Go?
+ });
+ const ephermal = (message.flags & (1 << 6)) !== 0;
- if (cloudAttachments && cloudAttachments.length > 0) {
- console.log("[Message] Processing attachments for message", message.id, ":", message.attachments);
- const uploadedAttachments = await Promise.all(
- cloudAttachments.map(async (att) => {
- const cAtt = att.attachment;
- const attEnt = await CloudAttachment.findOneOrFail({
- where: {
- uploadFilename: cAtt.uploaded_filename,
- },
- });
+ if (cloudAttachments && cloudAttachments.length > 0) {
+ console.log("[Message] Processing attachments for message", message.id, ":", message.attachments);
+ const uploadedAttachments = await Promise.all(
+ cloudAttachments.map(async (att) => {
+ const cAtt = att.attachment;
+ const attEnt = await CloudAttachment.findOneOrFail({
+ where: {
+ uploadFilename: cAtt.uploaded_filename,
+ },
+ });
- const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, {
- method: "POST",
- headers: {
- signature: Config.get().security.requestSignature || "",
- },
- });
+ const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, {
+ method: "POST",
+ headers: {
+ signature: Config.get().security.requestSignature || "",
+ },
+ });
- if (!cloneResponse.ok) {
- console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`);
- throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500);
- }
+ if (!cloneResponse.ok) {
+ console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`);
+ throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500);
+ }
- const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string };
+ const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string };
- const realAtt = Attachment.create({
- filename: attEnt.userFilename,
- url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
- proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
- size: attEnt.size,
- height: attEnt.height,
- width: attEnt.width,
- content_type: attEnt.contentType || attEnt.userOriginalContentType,
- });
- await realAtt.save();
- return { attachment: realAtt, index: att.index };
- }),
- );
- console.log("[Message] Processed attachments for message", message.id, ":", message.attachments);
+ const realAtt = Attachment.create({
+ filename: attEnt.userFilename,
+ url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
+ proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`,
+ size: attEnt.size,
+ height: attEnt.height,
+ width: attEnt.width,
+ content_type: attEnt.contentType || attEnt.userOriginalContentType,
+ });
+ await realAtt.save();
+ return { attachment: realAtt, index: att.index };
+ }),
+ );
+ console.log("[Message] Processed attachments for message", message.id, ":", message.attachments);
- for (const att of uploadedAttachments) {
- message.attachments![att.index] = att.attachment;
- }
- }
- // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments);
+ for (const att of uploadedAttachments) {
+ message.attachments![att.index] = att.attachment;
+ }
+ }
+ // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments);
- if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
- throw new HTTPError("Content length over max character limit");
- }
+ if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
+ throw new HTTPError("Content length over max character limit");
+ }
- if (opts.author_id) {
- message.author = await User.getPublicUser(opts.author_id);
- const rights = await getRights(opts.author_id);
- rights.hasThrow("SEND_MESSAGES");
- }
- if (opts.application_id) {
- message.application = await Application.findOneOrFail({
- where: { id: opts.application_id },
- });
- }
+ if (opts.author_id) {
+ message.author = await User.getPublicUser(opts.author_id);
+ const rights = await getRights(opts.author_id);
+ rights.hasThrow("SEND_MESSAGES");
+ }
+ if (opts.application_id) {
+ message.application = await Application.findOneOrFail({
+ where: { id: opts.application_id },
+ });
+ }
- if (opts.webhook_id) {
- message.webhook = await Webhook.findOneOrFail({
- where: { id: opts.webhook_id },
- });
+ if (opts.webhook_id) {
+ message.webhook = await Webhook.findOneOrFail({
+ where: { id: opts.webhook_id },
+ });
- message.author =
- (await User.findOne({
- where: { id: opts.webhook_id },
- })) || undefined;
+ message.author =
+ (await User.findOne({
+ where: { id: opts.webhook_id },
+ })) || undefined;
- if (!message.author) {
- message.author = User.create({
- id: opts.webhook_id,
- username: message.webhook.name,
- discriminator: "0000",
- avatar: message.webhook.avatar,
- public_flags: 0,
- premium: false,
- premium_type: 0,
- bot: true,
- created_at: new Date(),
- verified: true,
- rights: "0",
- data: {
- valid_tokens_since: new Date(),
- },
- });
+ if (!message.author) {
+ message.author = User.create({
+ id: opts.webhook_id,
+ username: message.webhook.name,
+ discriminator: "0000",
+ avatar: message.webhook.avatar,
+ public_flags: 0,
+ premium: false,
+ premium_type: 0,
+ bot: true,
+ created_at: new Date(),
+ verified: true,
+ rights: "0",
+ data: {
+ valid_tokens_since: new Date(),
+ },
+ });
- await message.author.save();
- }
+ await message.author.save();
+ }
- if (opts.username) {
- message.username = opts.username;
- message.author.username = message.username;
- }
- if (opts.avatar_url) {
- const avatarData = await fetch(opts.avatar_url);
- const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64"));
+ if (opts.username) {
+ message.username = opts.username;
+ message.author.username = message.username;
+ }
+ if (opts.avatar_url) {
+ const avatarData = await fetch(opts.avatar_url);
+ const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64"));
- const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64;
+ const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64;
- message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string);
- message.author.avatar = message.avatar;
- }
- } else {
- permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
- permission.hasThrow("SEND_MESSAGES");
- if (permission.cache.member) {
- message.member = permission.cache.member;
- }
+ message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string);
+ message.author.avatar = message.avatar;
+ }
+ } else {
+ permission ||= await getPermission(opts.author_id, channel.guild_id, channel);
+ permission.hasThrow("SEND_MESSAGES");
+ if (permission.cache.member) {
+ message.member = permission.cache.member;
+ }
- if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
- if (opts.message_reference) {
- permission.hasThrow("READ_MESSAGE_HISTORY");
- // code below has to be redone when we add custom message routing
- if (message.guild_id !== null) {
- const guild = await Guild.findOneOrFail({
- where: { id: channel.guild_id },
- });
- if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id;
- if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id;
+ if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
+ if (opts.message_reference) {
+ permission.hasThrow("READ_MESSAGE_HISTORY");
+ // code below has to be redone when we add custom message routing
+ if (message.guild_id !== null) {
+ const guild = await Guild.findOneOrFail({
+ where: { id: channel.guild_id },
+ });
+ if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id;
+ if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id;
- if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
- if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
- if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
- }
+ if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
+ if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
+ if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
+ }
- message.message_reference = opts.message_reference;
- message.referenced_message = await Message.findOneOrFail({
- where: {
- id: opts.message_reference.message_id,
- },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
- });
+ message.message_reference = opts.message_reference;
+ message.referenced_message = await Message.findOneOrFail({
+ where: {
+ id: opts.message_reference.message_id,
+ },
+ relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ });
- if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id)
- throw new HTTPError("Referenced message not found in the specified channel", 404);
- if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id)
- throw new HTTPError("Referenced message not found in the specified channel", 404);
- }
- /** Q: should be checked if the referenced message exists? ANSWER: NO
+ if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id)
+ throw new HTTPError("Referenced message not found in the specified channel", 404);
+ if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id)
+ throw new HTTPError("Referenced message not found in the specified channel", 404);
+ }
+ /** Q: should be checked if the referenced message exists? ANSWER: NO
otherwise backfilling won't work **/
- message.type = MessageType.REPLY;
- }
- }
+ message.type = MessageType.REPLY;
+ }
+ }
- // TODO: stickers/activity
- if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) {
- console.log("[Message] Rejecting empty message:", opts, message);
- throw new HTTPError("Empty messages are not allowed", 50006);
- }
+ // TODO: stickers/activity
+ if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) {
+ console.log("[Message] Rejecting empty message:", opts, message);
+ throw new HTTPError("Empty messages are not allowed", 50006);
+ }
- let content = opts.content;
+ let content = opts.content;
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- //const mention_channel_ids = [] as string[];
- const mention_role_ids = [] as string[];
- const mention_user_ids = [] as string[];
- let mention_everyone = false;
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ //const mention_channel_ids = [] as string[];
+ const mention_role_ids = [] as string[];
+ const mention_user_ids = [] as string[];
+ let mention_everyone = false;
- if (content) {
- // TODO: explicit-only mentions
- message.content = content.trim();
- content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
+ if (content) {
+ // TODO: explicit-only mentions
+ message.content = content.trim();
+ content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
if (!mention_channel_ids.includes(mention))
mention_channel_ids.push(mention);
}*/
- for (const [, mention] of content.matchAll(USER_MENTION)) {
- if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention);
- }
+ for (const [, mention] of content.matchAll(USER_MENTION)) {
+ if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention);
+ }
- await Promise.all(
- Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => {
- const role = await Role.findOneOrFail({
- where: { id: mention, guild_id: channel.guild_id },
- });
- if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) {
- mention_role_ids.push(mention);
- }
- }),
- );
+ await Promise.all(
+ Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => {
+ const role = await Role.findOneOrFail({
+ where: { id: mention, guild_id: channel.guild_id },
+ });
+ if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) {
+ mention_role_ids.push(mention);
+ }
+ }),
+ );
- if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) {
- mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
- }
- }
+ if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) {
+ mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
+ }
+ }
- if (message.message_reference?.message_id) {
- const referencedMessage = await Message.findOne({
- where: {
- id: message.message_reference.message_id,
- channel_id: message.channel_id,
- },
- });
- if (referencedMessage && referencedMessage.author_id !== message.author_id) {
- message.mentions.push(
- User.create({
- id: referencedMessage.author_id,
- }),
- );
- }
- }
+ if (message.message_reference?.message_id) {
+ const referencedMessage = await Message.findOne({
+ where: {
+ id: message.message_reference.message_id,
+ channel_id: message.channel_id,
+ },
+ });
+ if (referencedMessage && referencedMessage.author_id !== message.author_id) {
+ message.mentions.push(
+ User.create({
+ id: referencedMessage.author_id,
+ }),
+ );
+ }
+ }
- // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
- /*message.mention_channels = mention_channel_ids.map((x) =>
+ // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients.
+ /*message.mention_channels = mention_channel_ids.map((x) =>
Channel.create({ id: x }),
);*/
- message.mention_roles = (
- await Promise.all(
- mention_role_ids.map((x) => {
- return Role.findOne({ where: { id: x } });
- }),
- )
- ).filter((role) => role !== null);
+ message.mention_roles = (
+ await Promise.all(
+ mention_role_ids.map((x) => {
+ return Role.findOne({ where: { id: x } });
+ }),
+ )
+ ).filter((role) => role !== null);
- message.mentions = [
- ...message.mentions,
- ...(
- await Promise.all(
- mention_user_ids.map((x) => {
- return User.findOne({ where: { id: x } });
- }),
- )
- ).filter((user) => user !== null),
- ];
+ message.mentions = [
+ ...message.mentions,
+ ...(
+ await Promise.all(
+ mention_user_ids.map((x) => {
+ return User.findOne({ where: { id: x } });
+ }),
+ )
+ ).filter((user) => user !== null),
+ ];
- message.mention_everyone = mention_everyone;
- async function fillInMissingIDs(ids: string[]) {
- const states = await ReadState.findBy({
- user_id: Or(...ids.map((id) => Equal(id))),
- channel_id: channel.id,
- });
- const users = new Set(ids);
- states.forEach((state) => users.delete(state.user_id));
- if (!users.size) {
- return;
- }
- return Promise.all(
- [...users].map((user_id) => {
- return ReadState.create({ user_id, channel_id: channel.id }).save();
- }),
- );
- }
- if (ephermal) {
- const id = message.interaction_metadata?.user_id;
- if (id) {
- let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM;
- if (!pinged) pinged = !!message.mentions.find((user) => user.id === id);
- if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } }));
- if (pinged) {
- //stuff
- }
- }
- } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
- if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
- if (channel.recipients) {
- await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id));
- }
- } else {
- console.log(channel.guild_id);
- await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id));
- }
- const repository = ReadState.getRepository();
- const condition = { channel_id: channel.id };
- await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
- await repository.increment(condition, "mention_count", 1);
- } else {
- const users = new Set<string>([
- ...(message.mention_roles.length
- ? await Member.find({
- where: [
- ...message.mention_roles.map((role) => {
- return { roles: { id: role.id } };
- }),
- ],
- })
- : []
- ).map((member) => member.id),
- ...message.mentions.map((user) => user.id),
- ]);
- if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) {
- const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id);
- (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id));
- }
- if (users.size) {
- const repository = ReadState.getRepository();
- const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id };
+ message.mention_everyone = mention_everyone;
+ async function fillInMissingIDs(ids: string[]) {
+ const states = await ReadState.findBy({
+ user_id: Or(...ids.map((id) => Equal(id))),
+ channel_id: channel.id,
+ });
+ const users = new Set(ids);
+ states.forEach((state) => users.delete(state.user_id));
+ if (!users.size) {
+ return;
+ }
+ return Promise.all(
+ [...users].map((user_id) => {
+ return ReadState.create({ user_id, channel_id: channel.id }).save();
+ }),
+ );
+ }
+ if (ephermal) {
+ const id = message.interaction_metadata?.user_id;
+ if (id) {
+ let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM;
+ if (!pinged) pinged = !!message.mentions.find((user) => user.id === id);
+ if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } }));
+ if (pinged) {
+ //stuff
+ }
+ }
+ } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
+ if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
+ if (channel.recipients) {
+ await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id));
+ }
+ } else {
+ console.log(channel.guild_id);
+ await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id));
+ }
+ const repository = ReadState.getRepository();
+ const condition = { channel_id: channel.id };
+ await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
+ await repository.increment(condition, "mention_count", 1);
+ } else {
+ const users = new Set<string>([
+ ...(message.mention_roles.length
+ ? await Member.find({
+ where: [
+ ...message.mention_roles.map((role) => {
+ return { roles: { id: role.id } };
+ }),
+ ],
+ })
+ : []
+ ).map((member) => member.id),
+ ...message.mentions.map((user) => user.id),
+ ]);
+ if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) {
+ const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id);
+ (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id));
+ }
+ if (users.size) {
+ const repository = ReadState.getRepository();
+ const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id };
- await fillInMissingIDs([...users]);
+ await fillInMissingIDs([...users]);
- await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
- await repository.increment(condition, "mention_count", 1);
- }
- }
+ await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 });
+ await repository.increment(condition, "mention_count", 1);
+ }
+ }
- // TODO: check and put it all in the body
+ // TODO: check and put it all in the body
- return message;
+ return message;
}
// TODO: cache link result in db
export async function postHandleMessage(message: Message) {
- const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
+ const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
- const linkMatches = content?.match(LINK_REGEX) || [];
+ const linkMatches = content?.match(LINK_REGEX) || [];
- const data = { ...message };
+ const data = { ...message };
- const currentNormalizedUrls = new Set<string>();
- for (const link of linkMatches) {
- // Don't process links in <>
- if (link.startsWith("<") && link.endsWith(">")) {
- continue;
- }
- try {
- const normalized = normalizeUrl(link);
- currentNormalizedUrls.add(normalized);
- } catch (e) {
- continue;
- }
- }
+ const currentNormalizedUrls = new Set<string>();
+ for (const link of linkMatches) {
+ // Don't process links in <>
+ if (link.startsWith("<") && link.endsWith(">")) {
+ continue;
+ }
+ try {
+ const normalized = normalizeUrl(link);
+ currentNormalizedUrls.add(normalized);
+ } catch (e) {
+ continue;
+ }
+ }
- data.embeds.forEach((embed) => {
- if (!embed.type) {
- embed.type = EmbedType.rich;
- }
- });
- // Filter out embeds that could be links, start from scratch
- data.embeds = data.embeds.filter((embed) => embed.type === "rich");
+ data.embeds.forEach((embed) => {
+ if (!embed.type) {
+ embed.type = EmbedType.rich;
+ }
+ });
+ // Filter out embeds that could be links, start from scratch
+ data.embeds = data.embeds.filter((embed) => embed.type === "rich");
- const seenNormalizedUrls = new Set<string>();
- const uniqueLinks: string[] = [];
+ const seenNormalizedUrls = new Set<string>();
+ const uniqueLinks: string[] = [];
- for (const link of linkMatches.slice(0, 20)) {
- // embed max 20 links - TODO: make this configurable with instance policies
- // Don't embed links in <>
- if (link.startsWith("<") && link.endsWith(">")) continue;
+ for (const link of linkMatches.slice(0, 20)) {
+ // embed max 20 links - TODO: make this configurable with instance policies
+ // Don't embed links in <>
+ if (link.startsWith("<") && link.endsWith(">")) continue;
- try {
- const normalized = normalizeUrl(link);
+ try {
+ const normalized = normalizeUrl(link);
- if (!seenNormalizedUrls.has(normalized)) {
- seenNormalizedUrls.add(normalized);
- uniqueLinks.push(link);
- }
- } catch (e) {
- // Invalid URL, skip
- continue;
- }
- }
+ if (!seenNormalizedUrls.has(normalized)) {
+ seenNormalizedUrls.add(normalized);
+ uniqueLinks.push(link);
+ }
+ } catch (e) {
+ // Invalid URL, skip
+ continue;
+ }
+ }
- if (uniqueLinks.length === 0) {
- // No valid unique links found, update message to remove old embeds
- data.embeds = data.embeds.filter((embed) => embed.type === "rich");
- const author = data.author?.toPublicUser();
- const event = {
- event: "MESSAGE_UPDATE",
- channel_id: message.channel_id,
- data: {
- ...data,
- author,
- },
- } as MessageUpdateEvent;
- await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]);
- return;
- }
+ if (uniqueLinks.length === 0) {
+ // No valid unique links found, update message to remove old embeds
+ data.embeds = data.embeds.filter((embed) => embed.type === "rich");
+ const author = data.author?.toPublicUser();
+ const event = {
+ event: "MESSAGE_UPDATE",
+ channel_id: message.channel_id,
+ data: {
+ ...data,
+ author,
+ },
+ } as MessageUpdateEvent;
+ await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]);
+ return;
+ }
- const cachePromises = [];
+ const cachePromises = [];
- for (const link of uniqueLinks) {
- let url: URL;
- try {
- url = new URL(link);
- } catch (e) {
- // Skip invalid URLs
- continue;
- }
+ for (const link of uniqueLinks) {
+ let url: URL;
+ try {
+ url = new URL(link);
+ } catch (e) {
+ // Skip invalid URLs
+ continue;
+ }
- const normalizedUrl = normalizeUrl(link);
+ const normalizedUrl = normalizeUrl(link);
- // Check cache using normalized URL
- const cached = await EmbedCache.findOne({
- where: { url: normalizedUrl },
- });
+ // Check cache using normalized URL
+ const cached = await EmbedCache.findOne({
+ where: { url: normalizedUrl },
+ });
- if (cached) {
- data.embeds.push(cached.embed);
- continue;
- }
+ if (cached) {
+ data.embeds.push(cached.embed);
+ continue;
+ }
- // bit gross, but whatever!
- const endpointPublic = Config.get().cdn.endpointPublic; // lol
- const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
+ // bit gross, but whatever!
+ const endpointPublic = Config.get().cdn.endpointPublic; // lol
+ const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"];
- try {
- let res = await handler(url);
- if (!res) continue;
- // tried to use shorthand but types didn't like me L
- if (!Array.isArray(res)) res = [res];
+ try {
+ let res = await handler(url);
+ if (!res) continue;
+ // tried to use shorthand but types didn't like me L
+ if (!Array.isArray(res)) res = [res];
- for (const embed of res) {
- // Cache with normalized URL
- const cache = EmbedCache.create({
- url: normalizedUrl,
- embed: embed,
- });
- cachePromises.push(cache.save());
- data.embeds.push(embed);
- }
- } catch (e) {
- console.error(`[Embeds] Error while generating embed for ${link}`, e);
- }
- }
+ for (const embed of res) {
+ // Cache with normalized URL
+ const cache = EmbedCache.create({
+ url: normalizedUrl,
+ embed: embed,
+ });
+ cachePromises.push(cache.save());
+ data.embeds.push(embed);
+ }
+ } catch (e) {
+ console.error(`[Embeds] Error while generating embed for ${link}`, e);
+ }
+ }
- await Promise.all([
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id: message.channel_id,
- data,
- } as MessageUpdateEvent),
- Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }),
- ...cachePromises,
- ]);
+ await Promise.all([
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id: message.channel_id,
+ data,
+ } as MessageUpdateEvent),
+ Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }),
+ ...cachePromises,
+ ]);
}
export async function sendMessage(opts: MessageOptions) {
- const message = await handleMessage({ ...opts, timestamp: new Date() });
+ const message = await handleMessage({ ...opts, timestamp: new Date() });
- const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0;
- await Promise.all([
- Message.insert(message),
- emitEvent({
- event: "MESSAGE_CREATE",
- ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }),
- data: message.toJSON(),
- } as MessageCreateEvent),
- ]);
+ const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0;
+ await Promise.all([
+ Message.insert(message),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }),
+ data: message.toJSON(),
+ } as MessageCreateEvent),
+ ]);
- // no await as it should catch error non-blockingly
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ // no await as it should catch error non-blockingly
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- return message;
+ return message;
}
interface MessageOptions extends MessageCreateSchema {
- id?: string;
- type?: MessageType;
- pinned?: boolean;
- author_id?: string;
- webhook_id?: string;
- application_id?: string;
- embeds?: Embed[];
- reactions?: Reaction[];
- channel_id?: string;
- attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this?
- edited_timestamp?: Date;
- timestamp?: Date;
- username?: string;
- avatar_url?: string;
+ id?: string;
+ type?: MessageType;
+ pinned?: boolean;
+ author_id?: string;
+ webhook_id?: string;
+ application_id?: string;
+ embeds?: Embed[];
+ reactions?: Reaction[];
+ channel_id?: string;
+ attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this?
+ edited_timestamp?: Date;
+ timestamp?: Date;
+ username?: string;
+ avatar_url?: string;
}
diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index e34b6794..409338f7 100644
--- a/src/api/util/handlers/Voice.ts
+++ b/src/api/util/handlers/Voice.ts
@@ -20,31 +20,31 @@ import { Config, IpDataClient } from "@spacebar/util";
import { distanceBetweenLocations } from "../utility/ipAddress";
export async function getVoiceRegions(ipAddress: string, vip: boolean) {
- const regions = Config.get().regions;
- const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip));
- let optimalId = regions.default;
+ const regions = Config.get().regions;
+ const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip));
+ let optimalId = regions.default;
- if (!regions.useDefaultAsOptimal) {
- const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress);
+ if (!regions.useDefaultAsOptimal) {
+ const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress);
- let min = Number.POSITIVE_INFINITY;
+ let min = Number.POSITIVE_INFINITY;
- for (const ar of availableRegions) {
- //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
- const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!);
+ for (const ar of availableRegions) {
+ //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
+ const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!);
- if (dist < min) {
- min = dist;
- optimalId = ar.id;
- }
- }
- }
+ if (dist < min) {
+ min = dist;
+ optimalId = ar.id;
+ }
+ }
+ }
- return availableRegions.map((ar) => ({
- id: ar.id,
- name: ar.name,
- custom: ar.custom,
- deprecated: ar.deprecated,
- optimal: ar.id === optimalId,
- }));
+ return availableRegions.map((ar) => ({
+ id: ar.id,
+ name: ar.name,
+ custom: ar.custom,
+ deprecated: ar.deprecated,
+ optimal: ar.id === optimalId,
+ }));
}
diff --git a/src/api/util/handlers/Webhook.ts b/src/api/util/handlers/Webhook.ts
index 54ef26ec..cd0f2f6e 100644
--- a/src/api/util/handlers/Webhook.ts
+++ b/src/api/util/handlers/Webhook.ts
@@ -6,118 +6,118 @@ import { MoreThan } from "typeorm";
import { WebhookExecuteSchema } from "@spacebar/schemas";
export const executeWebhook = async (req: Request, res: Response) => {
- const body = req.body as WebhookExecuteSchema;
+ const body = req.body as WebhookExecuteSchema;
- const { webhook_id, token } = req.params;
+ const { webhook_id, token } = req.params;
- const webhook = await Webhook.findOne({
- where: {
- id: webhook_id,
- },
- relations: ["channel", "guild", "application"],
- });
+ const webhook = await Webhook.findOne({
+ where: {
+ id: webhook_id,
+ },
+ relations: ["channel", "guild", "application"],
+ });
- if (!webhook) {
- throw DiscordApiErrors.UNKNOWN_WEBHOOK;
- }
+ if (!webhook) {
+ throw DiscordApiErrors.UNKNOWN_WEBHOOK;
+ }
- if (webhook.token !== token) {
- throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
- }
+ if (webhook.token !== token) {
+ throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
+ }
- if (body.username) {
- ValidateName(body.username);
- }
+ if (body.username) {
+ ValidateName(body.username);
+ }
- // ensure one of content, embeds, components, or file is present
- if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) {
- throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
- }
+ // ensure one of content, embeds, components, or file is present
+ if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) {
+ throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE;
+ }
- const wait = req.query.wait === "true";
+ const wait = req.query.wait === "true";
- if (!wait) {
- res.status(204).send();
- }
+ if (!wait) {
+ res.status(204).send();
+ }
- const attachments: Attachment[] = [];
+ const attachments: Attachment[] = [];
- if (!webhook.channel.isWritable()) {
- if (wait) {
- throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400);
- } else {
- return;
- }
- }
+ if (!webhook.channel.isWritable()) {
+ if (wait) {
+ throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400);
+ } else {
+ return;
+ }
+ }
- // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one?
- const limits = Config.get().limits;
- if (limits.absoluteRate.register.enabled) {
- const count = await Message.count({
- where: {
- channel_id: webhook.channel_id,
- timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
- },
- });
+ // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one?
+ const limits = Config.get().limits;
+ if (limits.absoluteRate.register.enabled) {
+ const count = await Message.count({
+ where: {
+ channel_id: webhook.channel_id,
+ timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)),
+ },
+ });
- if (count >= limits.absoluteRate.sendMessage.limit)
- if (wait) {
- throw FieldErrors({
- channel_id: {
- code: "TOO_MANY_MESSAGES",
- message: req.t("common:toomany.MESSAGE"),
- },
- });
- } else {
- return;
- }
- }
+ if (count >= limits.absoluteRate.sendMessage.limit)
+ if (wait) {
+ throw FieldErrors({
+ channel_id: {
+ code: "TOO_MANY_MESSAGES",
+ message: req.t("common:toomany.MESSAGE"),
+ },
+ });
+ } else {
+ return;
+ }
+ }
- const files = (req.files as Express.Multer.File[]) ?? [];
- for (const currFile of files) {
- try {
- const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- } catch (error) {
- if (wait) res.status(400).json({ message: error?.toString() });
- return;
- }
- }
+ const files = (req.files as Express.Multer.File[]) ?? [];
+ for (const currFile of files) {
+ try {
+ const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile);
+ attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ } catch (error) {
+ if (wait) res.status(400).json({ message: error?.toString() });
+ return;
+ }
+ }
- const embeds = body.embeds || [];
- const message = await handleMessage({
- ...body,
- username: body.username || webhook.name,
- avatar_url: body.avatar_url || webhook.avatar,
- type: 0,
- pinned: false,
- webhook_id: webhook.id,
- application_id: webhook.application?.id,
- embeds,
- // TODO: Support thread_id/thread_name once threads are implemented
- channel_id: webhook.channel_id,
- attachments,
- timestamp: new Date(),
- });
+ const embeds = body.embeds || [];
+ const message = await handleMessage({
+ ...body,
+ username: body.username || webhook.name,
+ avatar_url: body.avatar_url || webhook.avatar,
+ type: 0,
+ pinned: false,
+ webhook_id: webhook.id,
+ application_id: webhook.application?.id,
+ embeds,
+ // TODO: Support thread_id/thread_name once threads are implemented
+ channel_id: webhook.channel_id,
+ attachments,
+ timestamp: new Date(),
+ });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore dont care2
- message.edited_timestamp = null;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore dont care2
+ message.edited_timestamp = null;
- webhook.channel.last_message_id = message.id;
+ webhook.channel.last_message_id = message.id;
- await Promise.all([
- message.save(),
- webhook.channel.save(),
- emitEvent({
- event: "MESSAGE_CREATE",
- channel_id: webhook.channel_id,
- data: message,
- } as MessageCreateEvent),
- ]);
+ await Promise.all([
+ message.save(),
+ webhook.channel.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: webhook.channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ ]);
- // no await as it shouldnt block the message send function and silently catch error
- postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
- if (wait) res.json(message);
- return;
+ // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
+ if (wait) res.json(message);
+ return;
};
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index bb38c530..7ae00a55 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -22,107 +22,107 @@ import { NextFunction, Request, Response } from "express";
import { ajv } from "@spacebar/schemas";
const ignoredRequestSchemas = [
- // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
- "SettingsProtoUpdateJsonSchema",
+ // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix?
+ "SettingsProtoUpdateJsonSchema",
];
declare global {
- // TODO: fix this
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace Express {
- interface Request {
- permission?: Permissions;
- }
- }
+ // TODO: fix this
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ permission?: Permissions;
+ }
+ }
}
export type RouteResponse = {
- status?: number;
- body?: `${string}Response`;
- headers?: Record<string, string>;
+ status?: number;
+ body?: `${string}Response`;
+ headers?: Record<string, string>;
};
export interface RouteOptions {
- permission?: PermissionResolvable;
- right?: RightResolvable;
- requestBody?: `${string}Schema`; // typescript interface name
- responses?: {
- [status: number]: {
- // body?: `${string}Response`;
- body?: string;
- };
- };
- event?: EVENT | EVENT[];
- summary?: string;
- description?: string;
- query?: {
- [key: string]: {
- type: string;
- required?: boolean;
- description?: string;
- values?: string[];
- };
- };
- deprecated?: boolean;
- // test?: {
- // response?: RouteResponse;
- // body?: unknown;
- // path?: string;
- // event?: EVENT | EVENT[];
- // headers?: Record<string, string>;
- // };
+ permission?: PermissionResolvable;
+ right?: RightResolvable;
+ requestBody?: `${string}Schema`; // typescript interface name
+ responses?: {
+ [status: number]: {
+ // body?: `${string}Response`;
+ body?: string;
+ };
+ };
+ event?: EVENT | EVENT[];
+ summary?: string;
+ description?: string;
+ query?: {
+ [key: string]: {
+ type: string;
+ required?: boolean;
+ description?: string;
+ values?: string[];
+ };
+ };
+ deprecated?: boolean;
+ // test?: {
+ // response?: RouteResponse;
+ // body?: unknown;
+ // path?: string;
+ // event?: EVENT | EVENT[];
+ // headers?: Record<string, string>;
+ // };
}
export function route(opts: RouteOptions) {
- let validate: AnyValidateFunction | undefined;
- if (opts.requestBody) {
- try {
- validate = ajv.getSchema(opts.requestBody);
- } catch (e) {
- console.error("AJV getSchema failed!");
- throw e;
- }
+ let validate: AnyValidateFunction | undefined;
+ if (opts.requestBody) {
+ try {
+ validate = ajv.getSchema(opts.requestBody);
+ } catch (e) {
+ console.error("AJV getSchema failed!");
+ throw e;
+ }
- if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
- }
+ if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`);
+ }
- return async (req: Request, res: Response, next: NextFunction) => {
- if (opts.permission) {
- req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
+ return async (req: Request, res: Response, next: NextFunction) => {
+ if (opts.permission) {
+ req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
- const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission];
- requiredPerms.forEach((perm) => {
- // bitfield comparison: check if user lacks certain permission
- if (!req.permission!.has(new Permissions(perm))) {
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string);
- }
- });
- }
+ const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission];
+ requiredPerms.forEach((perm) => {
+ // bitfield comparison: check if user lacks certain permission
+ if (!req.permission!.has(new Permissions(perm))) {
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string);
+ }
+ });
+ }
- if (opts.right) {
- const required = new Rights(opts.right);
- req.rights = await getRights(req.user_id);
+ if (opts.right) {
+ const required = new Rights(opts.right);
+ req.rights = await getRights(req.user_id);
- if (!req.rights || !req.rights.has(required)) {
- throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
- }
- }
+ if (!req.rights || !req.rights.has(required)) {
+ throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
+ }
+ }
- if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
- const valid = validate(req.body);
- if (!valid) {
- const fields: Record<string, { code?: string; message: string }> = {};
- validate.errors?.forEach(
- (x) =>
- (fields[x.instancePath.slice(1)] = {
- code: x.keyword,
- message: x.message || "",
- }),
- );
- if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
- throw FieldErrors(fields, validate.errors!);
- }
- }
- next();
- };
+ if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) {
+ const valid = validate(req.body);
+ if (!valid) {
+ const fields: Record<string, { code?: string; message: string }> = {};
+ validate.errors?.forEach(
+ (x) =>
+ (fields[x.instancePath.slice(1)] = {
+ code: x.keyword,
+ message: x.message || "",
+ }),
+ );
+ if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors);
+ throw FieldErrors(fields, validate.errors!);
+ }
+ }
+ next();
+ };
}
diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 7426d22b..78a56f63 100644
--- a/src/api/util/utility/Base64.ts
+++ b/src/api/util/utility/Base64.ts
@@ -25,41 +25,41 @@ const b2s = alphabet.split("");
// 123 == 'z'.charCodeAt(0) + 1
const s2b = new Array(123);
for (let i = 0; i < alphabet.length; i++) {
- s2b[alphabet.charCodeAt(i)] = i;
+ s2b[alphabet.charCodeAt(i)] = i;
}
// number to base64
export const ntob = (n: number): string => {
- if (n < 0) return `-${ntob(-n)}`;
+ if (n < 0) return `-${ntob(-n)}`;
- let lo = n >>> 0;
- let hi = (n / 4294967296) >>> 0;
+ let lo = n >>> 0;
+ let hi = (n / 4294967296) >>> 0;
- let right = "";
- while (hi > 0) {
- right = b2s[0x3f & lo] + right;
- lo >>>= 6;
- lo |= (0x3f & hi) << 26;
- hi >>>= 6;
- }
+ let right = "";
+ while (hi > 0) {
+ right = b2s[0x3f & lo] + right;
+ lo >>>= 6;
+ lo |= (0x3f & hi) << 26;
+ hi >>>= 6;
+ }
- let left = "";
- do {
- left = b2s[0x3f & lo] + left;
- lo >>>= 6;
- } while (lo > 0);
+ let left = "";
+ do {
+ left = b2s[0x3f & lo] + left;
+ lo >>>= 6;
+ } while (lo > 0);
- return left + right;
+ return left + right;
};
// base64 to number
export const bton = (base64: string) => {
- let number = 0;
- const sign = base64.charAt(0) === "-" ? 1 : 0;
+ let number = 0;
+ const sign = base64.charAt(0) === "-" ? 1 : 0;
- for (let i = sign; i < base64.length; i++) {
- number = number * 64 + s2b[base64.charCodeAt(i)];
- }
+ for (let i = sign; i < base64.length; i++) {
+ number = number * 64 + s2b[base64.charCodeAt(i)];
+ }
- return sign ? -number : number;
+ return sign ? -number : number;
};
diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 8bbd6283..2e50c536 100644
--- a/src/api/util/utility/EmbedHandlers.ts
+++ b/src/api/util/utility/EmbedHandlers.ts
@@ -24,494 +24,494 @@ import { yellow } from "picocolors";
import probe from "probe-image-size";
export const DEFAULT_FETCH_OPTIONS: RequestInit = {
- redirect: "follow",
- headers: {
- "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
- },
- // size: 1024 * 1024 * 5, // grabbed from config later
- method: "GET",
+ redirect: "follow",
+ headers: {
+ "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)",
+ },
+ // size: 1024 * 1024 * 5, // grabbed from config later
+ method: "GET",
};
const makeEmbedImage = (url: string | undefined, width: number | undefined, height: number | undefined): Required<EmbedImage> | undefined => {
- if (!url || !width || !height) return undefined;
- return {
- url,
- width,
- height,
- proxy_url: getProxyUrl(new URL(url), width, height),
- };
+ if (!url || !width || !height) return undefined;
+ return {
+ url,
+ width,
+ height,
+ proxy_url: getProxyUrl(new URL(url), width, height),
+ };
};
let hasWarnedAboutImagor = false;
export const getProxyUrl = (url: URL, width: number, height: number): string => {
- const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
- const secret = Config.get().security.requestSignature;
- width = Math.min(width || 500, resizeWidthMax || width);
- height = Math.min(height || 500, resizeHeightMax || width);
+ const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn;
+ const secret = Config.get().security.requestSignature;
+ width = Math.min(width || 500, resizeWidthMax || width);
+ height = Math.min(height || 500, resizeHeightMax || width);
- // Imagor
- if (imagorServerUrl) {
- const path = `${width}x${height}/${url.host}${url.pathname}`;
+ // Imagor
+ if (imagorServerUrl) {
+ const path = `${width}x${height}/${url.host}${url.pathname}`;
- const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
+ const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_");
- return `${imagorServerUrl}/${hash}/${path}`;
- }
+ return `${imagorServerUrl}/${hash}/${path}`;
+ }
- if (!hasWarnedAboutImagor) {
- hasWarnedAboutImagor = true;
- console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/"));
- }
+ if (!hasWarnedAboutImagor) {
+ hasWarnedAboutImagor = true;
+ console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/"));
+ }
- return url.toString();
+ return url.toString();
};
const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => {
- let elem = $(`meta[property="${name}"]`);
- if (!elem.length) elem = $(`meta[name="${name}"]`);
- const ret = elem.attr("content") || elem.text();
- return ret.trim().length == 0 ? undefined : ret;
+ let elem = $(`meta[property="${name}"]`);
+ if (!elem.length) elem = $(`meta[name="${name}"]`);
+ const ret = elem.attr("content") || elem.text();
+ return ret.trim().length == 0 ? undefined : ret;
};
const tryParseInt = (str: string | undefined) => {
- if (!str) return undefined;
- try {
- return parseInt(str);
- } catch (e) {
- return undefined;
- }
+ if (!str) return undefined;
+ try {
+ return parseInt(str);
+ } catch (e) {
+ return undefined;
+ }
};
export const getMetaDescriptions = (text: string) => {
- const $ = cheerio.load(text);
+ const $ = cheerio.load(text);
- return {
- type: getMeta($, "og:type"),
- title: getMeta($, "og:title") || $("title").first().text(),
- provider_name: getMeta($, "og:site_name"),
- author: getMeta($, "article:author"),
- description: getMeta($, "og:description") || getMeta($, "description"),
- image: getMeta($, "og:image") || getMeta($, "twitter:image"),
- image_fallback: $(`image`).attr("src"),
- video_fallback: $(`video`).attr("src"),
- width: tryParseInt(getMeta($, "og:image:width")),
- height: tryParseInt(getMeta($, "og:image:height")),
- url: getMeta($, "og:url"),
- youtube_embed: getMeta($, "og:video:secure_url"),
- site_name: getMeta($, "og:site_name"),
+ return {
+ type: getMeta($, "og:type"),
+ title: getMeta($, "og:title") || $("title").first().text(),
+ provider_name: getMeta($, "og:site_name"),
+ author: getMeta($, "article:author"),
+ description: getMeta($, "og:description") || getMeta($, "description"),
+ image: getMeta($, "og:image") || getMeta($, "twitter:image"),
+ image_fallback: $(`image`).attr("src"),
+ video_fallback: $(`video`).attr("src"),
+ width: tryParseInt(getMeta($, "og:image:width")),
+ height: tryParseInt(getMeta($, "og:image:height")),
+ url: getMeta($, "og:url"),
+ youtube_embed: getMeta($, "og:video:secure_url"),
+ site_name: getMeta($, "og:site_name"),
- $,
- };
+ $,
+ };
};
const doFetch = async (url: URL) => {
- try {
- const res = await fetch(url, {
- ...DEFAULT_FETCH_OPTIONS,
- });
- if (res.headers.get("content-length")) {
- const contentLength = parseInt(res.headers.get("content-length")!);
- if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) {
- return null;
- }
- }
- return res;
- } catch (e) {
- return null;
- }
+ try {
+ const res = await fetch(url, {
+ ...DEFAULT_FETCH_OPTIONS,
+ });
+ if (res.headers.get("content-length")) {
+ const contentLength = parseInt(res.headers.get("content-length")!);
+ if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) {
+ return null;
+ }
+ }
+ return res;
+ } catch (e) {
+ return null;
+ }
};
const genericImageHandler = async (url: URL): Promise<Embed | null> => {
- const type = await fetch(url, {
- ...DEFAULT_FETCH_OPTIONS,
- method: "HEAD",
- });
+ const type = await fetch(url, {
+ ...DEFAULT_FETCH_OPTIONS,
+ method: "HEAD",
+ });
- let image;
+ let image;
- if (type.headers.get("content-type")?.indexOf("image") !== -1) {
- const result = await probe(url.href);
- image = makeEmbedImage(url.href, result.width, result.height);
- } else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
- // TODO
- return null;
- } else {
- // have to download the page, unfortunately
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
- image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height);
- }
+ if (type.headers.get("content-type")?.indexOf("image") !== -1) {
+ const result = await probe(url.href);
+ image = makeEmbedImage(url.href, result.width, result.height);
+ } else if (type.headers.get("content-type")?.indexOf("video") !== -1) {
+ // TODO
+ return null;
+ } else {
+ // have to download the page, unfortunately
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
+ image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height);
+ }
- if (!image) return null;
+ if (!image) return null;
- return {
- url: url.href,
- type: EmbedType.image,
- thumbnail: image,
- };
+ return {
+ url: url.href,
+ type: EmbedType.image,
+ thumbnail: image,
+ };
};
export const EmbedHandlers: {
- [key: string]: (url: URL) => Promise<Embed | Embed[] | null>;
+ [key: string]: (url: URL) => Promise<Embed | Embed[] | null>;
} = {
- // the url does not have a special handler
- default: async (url: URL) => {
- const type = await fetch(url, {
- ...DEFAULT_FETCH_OPTIONS,
- method: "HEAD",
- });
- if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url);
+ // the url does not have a special handler
+ default: async (url: URL) => {
+ const type = await fetch(url, {
+ ...DEFAULT_FETCH_OPTIONS,
+ method: "HEAD",
+ });
+ if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url);
- const response = await doFetch(url);
- if (!response) return null;
+ const response = await doFetch(url);
+ if (!response) return null;
- const text = await response.text();
- const metas = getMetaDescriptions(text);
+ const text = await response.text();
+ const metas = getMetaDescriptions(text);
- // TODO: handle video
+ // TODO: handle video
- if (!metas.image) metas.image = metas.image_fallback;
+ if (!metas.image) metas.image = metas.image_fallback;
- if (metas.image && (!metas.width || !metas.height)) {
- metas.image = new URL(metas.image, url).toString();
- const result = await probe(metas.image);
- metas.width = result.width;
- metas.height = result.height;
- }
+ if (metas.image && (!metas.width || !metas.height)) {
+ metas.image = new URL(metas.image, url).toString();
+ const result = await probe(metas.image);
+ metas.width = result.width;
+ metas.height = result.height;
+ }
- if (!metas.image && (!metas.title || !metas.description)) {
- // we don't have any content to display
- return null;
- }
+ if (!metas.image && (!metas.title || !metas.description)) {
+ // we don't have any content to display
+ return null;
+ }
- let embedType = EmbedType.link;
- if (metas.type == "article") embedType = EmbedType.article;
- if (metas.type == "object") embedType = EmbedType.article; // github
- if (metas.type == "rich") embedType = EmbedType.rich;
+ let embedType = EmbedType.link;
+ if (metas.type == "article") embedType = EmbedType.article;
+ if (metas.type == "object") embedType = EmbedType.article; // github
+ if (metas.type == "rich") embedType = EmbedType.rich;
- return {
- url: url.href,
- type: embedType,
- title: metas.title,
- thumbnail: makeEmbedImage(metas.image, metas.width, metas.height),
- description: metas.description,
- provider: metas.site_name
- ? {
- name: metas.site_name,
- url: url.origin,
- }
- : undefined,
- };
- },
+ return {
+ url: url.href,
+ type: embedType,
+ title: metas.title,
+ thumbnail: makeEmbedImage(metas.image, metas.width, metas.height),
+ description: metas.description,
+ provider: metas.site_name
+ ? {
+ name: metas.site_name,
+ url: url.origin,
+ }
+ : undefined,
+ };
+ },
- "giphy.com": genericImageHandler,
- "media4.giphy.com": genericImageHandler,
- "tenor.com": genericImageHandler,
- "c.tenor.com": genericImageHandler,
- "media.tenor.com": genericImageHandler,
+ "giphy.com": genericImageHandler,
+ "media4.giphy.com": genericImageHandler,
+ "tenor.com": genericImageHandler,
+ "c.tenor.com": genericImageHandler,
+ "media.tenor.com": genericImageHandler,
- "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url),
- "www.facebook.com": async (url: URL) => {
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
+ "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url),
+ "www.facebook.com": async (url: URL) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
- return {
- url: url.href,
- type: EmbedType.link,
- title: metas.title,
- description: metas.description,
- thumbnail: makeEmbedImage(metas.image, 640, 640),
- color: 16777215,
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.link,
+ title: metas.title,
+ description: metas.description,
+ thumbnail: makeEmbedImage(metas.image, 640, 640),
+ color: 16777215,
+ };
+ },
- "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url),
- "www.twitter.com": async (url: URL) => {
- const token = Config.get().external.twitter;
- if (!token) return null;
+ "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url),
+ "www.twitter.com": async (url: URL) => {
+ const token = Config.get().external.twitter;
+ if (!token) return null;
- if (!url.href.includes("/status/")) return null; // TODO;
- const id = url.pathname.split("/")[3]; // super bad lol
- if (!parseInt(id)) return null;
- const endpointUrl =
- `https://api.twitter.com/2/tweets/${id}` +
- `?expansions=author_id,attachments.media_keys` +
- `&media.fields=url,width,height` +
- `&tweet.fields=created_at,public_metrics` +
- `&user.fields=profile_image_url`;
+ if (!url.href.includes("/status/")) return null; // TODO;
+ const id = url.pathname.split("/")[3]; // super bad lol
+ if (!parseInt(id)) return null;
+ const endpointUrl =
+ `https://api.twitter.com/2/tweets/${id}` +
+ `?expansions=author_id,attachments.media_keys` +
+ `&media.fields=url,width,height` +
+ `&tweet.fields=created_at,public_metrics` +
+ `&user.fields=profile_image_url`;
- const response = await fetch(endpointUrl, {
- ...DEFAULT_FETCH_OPTIONS,
- headers: {
- authorization: `Bearer ${token}`,
- },
- });
- const json = (await response.json()) as {
- errors?: never[];
- includes: {
- users: {
- profile_image_url: string;
- username: string;
- name: string;
- }[];
- media: {
- type: string;
- width: number;
- height: number;
- url: string;
- }[];
- };
- data: {
- text: string;
- created_at: string;
- public_metrics: { like_count: number; retweet_count: number };
- };
- };
- if (json.errors) return null;
- const author = json.includes.users[0];
- const text = json.data.text;
- const created_at = new Date(json.data.created_at);
- const metrics = json.data.public_metrics;
- const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo");
+ const response = await fetch(endpointUrl, {
+ ...DEFAULT_FETCH_OPTIONS,
+ headers: {
+ authorization: `Bearer ${token}`,
+ },
+ });
+ const json = (await response.json()) as {
+ errors?: never[];
+ includes: {
+ users: {
+ profile_image_url: string;
+ username: string;
+ name: string;
+ }[];
+ media: {
+ type: string;
+ width: number;
+ height: number;
+ url: string;
+ }[];
+ };
+ data: {
+ text: string;
+ created_at: string;
+ public_metrics: { like_count: number; retweet_count: number };
+ };
+ };
+ if (json.errors) return null;
+ const author = json.includes.users[0];
+ const text = json.data.text;
+ const created_at = new Date(json.data.created_at);
+ const metrics = json.data.public_metrics;
+ const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo");
- const embed: Embed = {
- type: EmbedType.rich,
- url: `${url.origin}${url.pathname}`,
- description: text,
- author: {
- url: `https://twitter.com/${author.username}`,
- name: `${author.name} (@${author.username})`,
- proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400),
- icon_url: author.profile_image_url,
- },
- timestamp: created_at,
- fields: [
- {
- inline: true,
- name: "Likes",
- value: metrics.like_count.toString(),
- },
- {
- inline: true,
- name: "Retweet",
- value: metrics.retweet_count.toString(),
- },
- ],
- color: 1942002,
- footer: {
- text: "Twitter",
- proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
- icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
- },
- // Discord doesn't send this?
- // provider: {
- // name: "Twitter",
- // url: "https://twitter.com"
- // },
- };
+ const embed: Embed = {
+ type: EmbedType.rich,
+ url: `${url.origin}${url.pathname}`,
+ description: text,
+ author: {
+ url: `https://twitter.com/${author.username}`,
+ name: `${author.name} (@${author.username})`,
+ proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400),
+ icon_url: author.profile_image_url,
+ },
+ timestamp: created_at,
+ fields: [
+ {
+ inline: true,
+ name: "Likes",
+ value: metrics.like_count.toString(),
+ },
+ {
+ inline: true,
+ name: "Retweet",
+ value: metrics.retweet_count.toString(),
+ },
+ ],
+ color: 1942002,
+ footer: {
+ text: "Twitter",
+ proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192),
+ icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png",
+ },
+ // Discord doesn't send this?
+ // provider: {
+ // name: "Twitter",
+ // url: "https://twitter.com"
+ // },
+ };
- if (media && media.length > 0) {
- embed.image = {
- width: media[0].width,
- height: media[0].height,
- url: media[0].url,
- proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height),
- };
- media.shift();
- }
+ if (media && media.length > 0) {
+ embed.image = {
+ width: media[0].width,
+ height: media[0].height,
+ url: media[0].url,
+ proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height),
+ };
+ media.shift();
+ }
- return embed;
+ return embed;
- // TODO: Client won't merge these into a single embed, for some reason.
- // return [embed, ...media.map((x: any) => ({
- // // generate new embeds for each additional attachment
- // type: EmbedType.rich,
- // url: url.href,
- // image: {
- // width: x.width,
- // height: x.height,
- // url: x.url,
- // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height)
- // }
- // }))];
- },
+ // TODO: Client won't merge these into a single embed, for some reason.
+ // return [embed, ...media.map((x: any) => ({
+ // // generate new embeds for each additional attachment
+ // type: EmbedType.rich,
+ // url: url.href,
+ // image: {
+ // width: x.width,
+ // height: x.height,
+ // url: x.url,
+ // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height)
+ // }
+ // }))];
+ },
- "open.spotify.com": async (url: URL) => {
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
+ "open.spotify.com": async (url: URL) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
- return {
- url: url.href,
- type: EmbedType.link,
- title: metas.title,
- description: metas.description,
- thumbnail: makeEmbedImage(metas.image, 640, 640),
- provider: {
- url: "https://spotify.com",
- name: "Spotify",
- },
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.link,
+ title: metas.title,
+ description: metas.description,
+ thumbnail: makeEmbedImage(metas.image, 640, 640),
+ provider: {
+ url: "https://spotify.com",
+ name: "Spotify",
+ },
+ };
+ },
- // TODO: docs: Pixiv won't work without Imagor
- "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url),
- "www.pixiv.net": async (url: URL) => {
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
+ // TODO: docs: Pixiv won't work without Imagor
+ "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url),
+ "www.pixiv.net": async (url: URL) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
- if (!metas.image) return null;
+ if (!metas.image) return null;
- return {
- url: url.href,
- type: EmbedType.image,
- title: metas.title,
- description: metas.description,
- image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
- provider: {
- url: "https://pixiv.net",
- name: "Pixiv",
- },
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.image,
+ title: metas.title,
+ description: metas.description,
+ image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
+ provider: {
+ url: "https://pixiv.net",
+ name: "Pixiv",
+ },
+ };
+ },
- "store.steampowered.com": async (url: URL) => {
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
- const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined;
- const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined;
- const releaseDate = metas.$(".release_date").find("div.date").text().trim();
- const isReleased = new Date(releaseDate) < new Date();
+ "store.steampowered.com": async (url: URL) => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
+ const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined;
+ const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined;
+ const releaseDate = metas.$(".release_date").find("div.date").text().trim();
+ const isReleased = new Date(releaseDate) < new Date();
- const fields: Embed["fields"] = [];
+ const fields: Embed["fields"] = [];
- if (numReviews)
- fields.push({
- name: "Reviews",
- value: numReviews,
- inline: true,
- });
+ if (numReviews)
+ fields.push({
+ name: "Reviews",
+ value: numReviews,
+ inline: true,
+ });
- if (price)
- fields.push({
- name: "Price",
- value: `$${price / 100}`,
- inline: true,
- });
+ if (price)
+ fields.push({
+ name: "Price",
+ value: `$${price / 100}`,
+ inline: true,
+ });
- // if the release date is in the past, it's already out
- if (releaseDate && !isReleased)
- fields.push({
- name: "Release Date",
- value: releaseDate,
- inline: true,
- });
+ // if the release date is in the past, it's already out
+ if (releaseDate && !isReleased)
+ fields.push({
+ name: "Release Date",
+ value: releaseDate,
+ inline: true,
+ });
- return {
- url: url.href,
- type: EmbedType.rich,
- title: metas.title,
- description: metas.description,
- image: {
- // TODO: meant to be thumbnail.
- // isn't this standard across all of steam?
- width: 460,
- height: 215,
- url: metas.image,
- proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined,
- },
- provider: {
- url: "https://store.steampowered.com",
- name: "Steam",
- },
- fields,
- // TODO: Video
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.rich,
+ title: metas.title,
+ description: metas.description,
+ image: {
+ // TODO: meant to be thumbnail.
+ // isn't this standard across all of steam?
+ width: 460,
+ height: 215,
+ url: metas.image,
+ proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined,
+ },
+ provider: {
+ url: "https://store.steampowered.com",
+ name: "Steam",
+ },
+ fields,
+ // TODO: Video
+ };
+ },
- "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url),
- "www.reddit.com": async (url: URL) => {
- const res = await EmbedHandlers["default"](url);
- return {
- ...res,
- color: 16777215,
- provider: {
- name: "reddit",
- },
- };
- },
+ "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url),
+ "www.reddit.com": async (url: URL) => {
+ const res = await EmbedHandlers["default"](url);
+ return {
+ ...res,
+ color: 16777215,
+ provider: {
+ name: "reddit",
+ },
+ };
+ },
- "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url),
- "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url),
- "www.youtube.com": async (url: URL): Promise<Embed | null> => {
- const response = await doFetch(url);
- if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
+ "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url),
+ "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url),
+ "www.youtube.com": async (url: URL): Promise<Embed | null> => {
+ const response = await doFetch(url);
+ if (!response) return null;
+ const metas = getMetaDescriptions(await response.text());
- return {
- video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height),
- url: url.href,
- type: metas.youtube_embed ? EmbedType.video : EmbedType.link,
- title: metas.title,
- thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
- provider: {
- url: "https://www.youtube.com",
- name: "YouTube",
- },
- description: metas.description,
- color: 16711680,
- author: metas.author
- ? {
- name: metas.author,
- // TODO: author channel url
- }
- : undefined,
- };
- },
+ return {
+ video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height),
+ url: url.href,
+ type: metas.youtube_embed ? EmbedType.video : EmbedType.link,
+ title: metas.title,
+ thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height),
+ provider: {
+ url: "https://www.youtube.com",
+ name: "YouTube",
+ },
+ description: metas.description,
+ color: 16711680,
+ author: metas.author
+ ? {
+ name: metas.author,
+ // TODO: author channel url
+ }
+ : undefined,
+ };
+ },
- "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url),
- "xkcd.com": async (url) => {
- const response = await doFetch(url);
- if (!response) return null;
+ "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url),
+ "xkcd.com": async (url) => {
+ const response = await doFetch(url);
+ if (!response) return null;
- const metas = getMetaDescriptions(await response.text());
- const hoverText = metas.$("#comic img").attr("title");
+ const metas = getMetaDescriptions(await response.text());
+ const hoverText = metas.$("#comic img").attr("title");
- if (!metas.image) return null;
+ if (!metas.image) return null;
- const { width, height } = await probe(metas.image);
+ const { width, height } = await probe(metas.image);
- return {
- url: url.href,
- type: EmbedType.rich,
- title: `xkcd: ${metas.title}`,
- image: makeEmbedImage(metas.image, width, height),
- footer: hoverText
- ? {
- text: hoverText,
- }
- : undefined,
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.rich,
+ title: `xkcd: ${metas.title}`,
+ image: makeEmbedImage(metas.image, width, height),
+ footer: hoverText
+ ? {
+ text: hoverText,
+ }
+ : undefined,
+ };
+ },
- // the url is an image from this instance
- self: async (url: URL): Promise<Embed | null> => {
- const result = await probe(url.href);
+ // the url is an image from this instance
+ self: async (url: URL): Promise<Embed | null> => {
+ const result = await probe(url.href);
- return {
- url: url.href,
- type: EmbedType.image,
- thumbnail: {
- width: result.width,
- height: result.height,
- url: url.href,
- proxy_url: url.href,
- },
- };
- },
+ return {
+ url: url.href,
+ type: EmbedType.image,
+ thumbnail: {
+ width: result.width,
+ height: result.height,
+ url: url.href,
+ proxy_url: url.href,
+ },
+ };
+ },
};
diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 3850df54..0718a736 100644
--- a/src/api/util/utility/RandomInviteID.ts
+++ b/src/api/util/utility/RandomInviteID.ts
@@ -23,42 +23,42 @@ import crypto from "crypto";
// And why is this even here? Just use cryto.randomBytes?
export function randomString(length = 6) {
- // Declare all characters
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ // Declare all characters
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- // Pick characers randomly
- let str = "";
- for (let i = 0; i < length; i++) {
- str += chars.charAt(Math.floor(crypto.randomInt(chars.length)));
- }
+ // Pick characers randomly
+ let str = "";
+ for (let i = 0; i < length; i++) {
+ str += chars.charAt(Math.floor(crypto.randomInt(chars.length)));
+ }
- return str;
+ return str;
}
export function snowflakeBasedInvite() {
- // Declare all characters
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- const base = BigInt(chars.length);
- let snowflake = Snowflake.generateWorkerProcess();
+ // Declare all characters
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ const base = BigInt(chars.length);
+ let snowflake = Snowflake.generateWorkerProcess();
- // snowflakes hold ~10.75 characters worth of entropy;
- // safe to generate a 8-char invite out of them
- const str = "";
- for (let i = 0; i < 10; i++) {
- str.concat(chars.charAt(Number(snowflake % base)));
- snowflake = snowflake / base;
- }
+ // snowflakes hold ~10.75 characters worth of entropy;
+ // safe to generate a 8-char invite out of them
+ const str = "";
+ for (let i = 0; i < 10; i++) {
+ str.concat(chars.charAt(Number(snowflake % base)));
+ snowflake = snowflake / base;
+ }
- return str.substr(3, 8).split("").reverse().join("");
+ return str.substr(3, 8).split("").reverse().join("");
}
export function randomUpperString(length: number = 10) {
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- let result = "";
- for (let i = 0; i < length; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length));
- }
+ let result = "";
+ for (let i = 0; i < length; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
+ }
- return result;
+ return result;
}
diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
index e5c1279a..e33613a3 100644
--- a/src/api/util/utility/String.ts
+++ b/src/api/util/utility/String.ts
@@ -21,18 +21,18 @@ import { ntob } from "./Base64";
import { FieldErrors, Random } from "@spacebar/util";
export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
- if (str.length < min || str.length > max) {
- throw FieldErrors({
- [key]: {
- code: "BASE_TYPE_BAD_LENGTH",
- message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
- length: `${min} - ${max}`,
- }),
- },
- });
- }
+ if (str.length < min || str.length > max) {
+ throw FieldErrors({
+ [key]: {
+ code: "BASE_TYPE_BAD_LENGTH",
+ message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
+ length: `${min} - ${max}`,
+ }),
+ },
+ });
+ }
}
export function generateCode() {
- return ntob(Date.now() + Random.nextInt(0, 10000));
+ return ntob(Date.now() + Random.nextInt(0, 10000));
}
diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts
index f6d4d0e1..c4d8c9a3 100644
--- a/src/api/util/utility/captcha.ts
+++ b/src/api/util/utility/captcha.ts
@@ -19,46 +19,46 @@
import { Config } from "@spacebar/util";
export interface hcaptchaResponse {
- success: boolean;
- challenge_ts: string;
- hostname: string;
- credit: boolean;
- "error-codes": string[];
- score: number; // enterprise only
- score_reason: string[]; // enterprise only
+ success: boolean;
+ challenge_ts: string;
+ hostname: string;
+ credit: boolean;
+ "error-codes": string[];
+ score: number; // enterprise only
+ score_reason: string[]; // enterprise only
}
export interface recaptchaResponse {
- success: boolean;
- score: number; // between 0 - 1
- action: string;
- challenge_ts: string;
- hostname: string;
- "error-codes"?: string[];
+ success: boolean;
+ score: number; // between 0 - 1
+ action: string;
+ challenge_ts: string;
+ hostname: string;
+ "error-codes"?: string[];
}
const verifyEndpoints = {
- hcaptcha: "https://hcaptcha.com/siteverify",
- recaptcha: "https://www.google.com/recaptcha/api/siteverify",
+ hcaptcha: "https://hcaptcha.com/siteverify",
+ recaptcha: "https://www.google.com/recaptcha/api/siteverify",
};
export async function verifyCaptcha(response: string, ip?: string) {
- const { security } = Config.get();
- const { service, secret, sitekey } = security.captcha;
+ const { security } = Config.get();
+ const { service, secret, sitekey } = security.captcha;
- if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/");
+ if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/");
- const res = await fetch(verifyEndpoints[service], {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- body:
- `response=${encodeURIComponent(response)}` +
- `&secret=${encodeURIComponent(secret)}` +
- `&sitekey=${encodeURIComponent(sitekey)}` +
- (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""),
- });
+ const res = await fetch(verifyEndpoints[service], {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body:
+ `response=${encodeURIComponent(response)}` +
+ `&secret=${encodeURIComponent(secret)}` +
+ `&sitekey=${encodeURIComponent(sitekey)}` +
+ (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""),
+ });
- return (await res.json()) as hcaptchaResponse | recaptchaResponse;
+ return (await res.json()) as hcaptchaResponse | recaptchaResponse;
}
diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index 19408253..399edb67 100644
--- a/src/api/util/utility/ipAddress.ts
+++ b/src/api/util/utility/ipAddress.ts
@@ -18,14 +18,14 @@
type Location = { latitude: number; longitude: number };
export function distanceBetweenLocations(loc1: Location, loc2: Location): number {
- return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude);
+ return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude);
}
//Haversine function
function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) {
- const p = 0.017453292519943295; // Math.PI / 180
- const c = Math.cos;
- const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
+ const p = 0.017453292519943295; // Math.PI / 180
+ const c = Math.cos;
+ const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
- return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
+ return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}
diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index beb277b0..7a9d4c5f 100644
--- a/src/api/util/utility/passwordStrength.ts
+++ b/src/api/util/utility/passwordStrength.ts
@@ -35,43 +35,43 @@ const reSYMBOLS = /[A-Za-z0-9]/g;
* Returns: 0 > pw > 1
*/
export function checkPassword(password: string): number {
- const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
- let strength = 0;
+ const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
+ let strength = 0;
- // checks for total password len
- if (password.length >= minLength - 1) {
- strength += 0.05;
- }
+ // checks for total password len
+ if (password.length >= minLength - 1) {
+ strength += 0.05;
+ }
- // checks for amount of Numbers
- if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) {
- strength += 0.05;
- }
+ // checks for amount of Numbers
+ if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) {
+ strength += 0.05;
+ }
- // checks for amount of Uppercase Letters
- if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) {
- strength += 0.05;
- }
+ // checks for amount of Uppercase Letters
+ if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) {
+ strength += 0.05;
+ }
- // checks for amount of symbols
- if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) {
- strength += 0.05;
- }
+ // checks for amount of symbols
+ if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) {
+ strength += 0.05;
+ }
- // checks if password only consists of numbers or only consists of chars
- if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) {
- strength = 0;
- }
+ // checks if password only consists of numbers or only consists of chars
+ if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) {
+ strength = 0;
+ }
- const entropyMap: { [key: string]: number } = {};
- for (let i = 0; i < password.length; i++) {
- if (entropyMap[password[i]]) entropyMap[password[i]]++;
- else entropyMap[password[i]] = 1;
- }
+ const entropyMap: { [key: string]: number } = {};
+ for (let i = 0; i < password.length; i++) {
+ if (entropyMap[password[i]]) entropyMap[password[i]]++;
+ else entropyMap[password[i]] = 1;
+ }
- const entropies = Object.values(entropyMap);
+ const entropies = Object.values(entropyMap);
- entropies.map((x) => x / entropyMap.length);
- strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length);
- return strength;
+ entropies.map((x) => x / entropyMap.length);
+ strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length);
+ return strength;
}
|