diff --git a/src/api/Server.ts b/src/api/Server.ts
index 4cf0917d..bd9bc4b9 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -12,7 +12,7 @@ import { initTranslation } from "./middlewares/Translation";
import morgan from "morgan";
import { initInstance } from "./util/handlers/Instance";
import { registerRoutes } from "@fosscord/util";
-import { red } from "picocolors"
+import { red } from "picocolors";
export interface FosscordServerOptions extends ServerOptions {}
@@ -44,13 +44,18 @@ export class FosscordServer extends Server {
this.app.use(
morgan("combined", {
skip: (req, res) => {
- var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
- if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip;
+ var skip = !(
+ process.env["LOG_REQUESTS"]?.includes(
+ res.statusCode.toString(),
+ ) ?? false
+ );
+ if (process.env["LOG_REQUESTS"]?.charAt(0) == "-")
+ skip = !skip;
return skip;
- }
- })
+ },
+ }),
);
- };
+ }
this.app.use(CORS);
this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
@@ -63,16 +68,22 @@ export class FosscordServer extends Server {
await initRateLimits(api);
await initTranslation(api);
- this.routes = await registerRoutes(this, path.join(__dirname, "routes", "/"));
+ this.routes = await registerRoutes(
+ this,
+ path.join(__dirname, "routes", "/"),
+ );
- api.use("*", (error: any, req: Request, res: Response, next: NextFunction) => {
- if (error) return next(error);
- res.status(404).json({
- message: "404 endpoint not found",
- code: 0
- });
- next();
- });
+ api.use(
+ "*",
+ (error: any, req: Request, res: Response, next: NextFunction) => {
+ if (error) return next(error);
+ res.status(404).json({
+ message: "404 endpoint not found",
+ code: 0,
+ });
+ next();
+ },
+ );
this.app = app;
@@ -87,8 +98,13 @@ export class FosscordServer extends Server {
this.app.use(ErrorHandler);
TestClient(this.app);
- 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();
}
-};
\ No newline at end of file
+}
diff --git a/src/api/index.ts b/src/api/index.ts
index 09663452..adc7649c 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,3 +1,3 @@
export * from "./Server";
export * from "./middlewares/";
-export * from "./util/";
\ No newline at end of file
+export * from "./util/";
diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 1df7911b..50048b12 100644
--- a/src/api/middlewares/Authentication.ts
+++ b/src/api/middlewares/Authentication.ts
@@ -10,7 +10,7 @@ export const NO_AUTHORIZATION_ROUTES = [
"/auth/mfa/totp",
// Routes with a seperate auth system
"/webhooks/",
- // Public information endpoints
+ // Public information endpoints
"/ping",
"/gateway",
"/experiments",
@@ -26,7 +26,7 @@ export const NO_AUTHORIZATION_ROUTES = [
// Public policy pages
"/policies/instance",
// Asset delivery
- /\/guilds\/\d+\/widget\.(json|png)/
+ /\/guilds\/\d+\/widget\.(json|png)/,
];
export const API_PREFIX = /^\/api(\/v\d+)?/;
@@ -43,7 +43,11 @@ declare global {
}
}
-export async function Authentication(req: Request, res: Response, next: NextFunction) {
+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 (url.startsWith("/invites") && req.method === "GET") return next();
@@ -54,12 +58,16 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
})
)
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 { jwtSecret } = Config.get().security;
- const { decoded, user }: any = await checkToken(req.headers.authorization, jwtSecret);
+ const { decoded, user }: any = await checkToken(
+ req.headers.authorization,
+ jwtSecret,
+ );
req.token = decoded;
req.user_id = decoded.id;
diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index 4cb376bc..7741f1fd 100644
--- a/src/api/middlewares/BodyParser.ts
+++ b/src/api/middlewares/BodyParser.ts
@@ -6,7 +6,8 @@ export function BodyParser(opts?: OptionsJson) {
const jsonParser = bodyParser.json(opts);
return (req: Request, res: Response, next: NextFunction) => {
- if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
+ if (!req.headers["content-type"])
+ req.headers["content-type"] = "application/json";
jsonParser(req, res, (err) => {
if (err) {
diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 20260cf9..2dce51c6 100644
--- a/src/api/middlewares/CORS.ts
+++ b/src/api/middlewares/CORS.ts
@@ -7,10 +7,16 @@ export function CORS(req: Request, res: Response, next: NextFunction) {
// 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';"
+ "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-Headers",
+ req.header("Access-Control-Request-Headers") || "*",
+ );
+ res.set(
+ "Access-Control-Allow-Methods",
+ req.header("Access-Control-Request-Methods") || "*",
);
- res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
- res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*");
next();
}
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 2012b91c..bf3b011a 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -3,7 +3,12 @@ import { HTTPError } from "lambert-server";
import { ApiError, FieldError } from "@fosscord/util";
const EntityNotFoundErrorRegex = /"(\w+)"/;
-export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
+export function ErrorHandler(
+ error: Error,
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) {
if (!error) return next();
try {
@@ -12,20 +17,28 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
let message = error?.toString();
let errors = undefined;
- if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+ 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`;
+ 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;
} else {
- console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body);
+ 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
@@ -39,6 +52,8 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
res.status(httpcode).json({ code: code, message, errors });
} catch (error) {
console.error(`[Internal Server Error] 500`, error);
- return res.status(500).json({ code: 500, message: "Internal Server Error" });
+ return res
+ .status(500)
+ .json({ code: 500, message: "Internal Server Error" });
}
}
diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 57645c0b..b3976a16 100644
--- a/src/api/middlewares/RateLimit.ts
+++ b/src/api/middlewares/RateLimit.ts
@@ -40,21 +40,32 @@ export default function rateLimit(opts: {
success?: boolean;
onlyIp?: boolean;
}): any {
- return async (req: Request, res: Response, next: NextFunction): Promise<any> => {
+ return async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+ ): Promise<any> => {
// 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, "");
+ const bucket_id =
+ opts.bucket ||
+ req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
let executor_id = getIpAdress(req);
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;
+ 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 offender = Cache.get(executor_id + bucket_id);
@@ -75,11 +86,15 @@ export default function rateLimit(opts: {
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);
+ 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,
+ });
return (
res
.status(429)
@@ -91,20 +106,33 @@ export default function rateLimit(opts: {
.set("Retry-After", `${Math.ceil(resetAfterSec)}`)
.set("X-RateLimit-Bucket", `${bucket_id}`)
// TODO: error rate limit message translation
- .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global })
+ .send({
+ message: "You are being rate limited.",
+ retry_after: resetAfterSec,
+ global,
+ })
);
}
}
next();
- const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window };
+ 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) {
+ } else if (
+ res.statusCode >= 200 &&
+ res.statusCode < 300 &&
+ opts.success
+ ) {
return hitRoute(hitRouteOpts);
}
});
@@ -141,8 +169,8 @@ export async function initRateLimits(app: Router) {
rateLimit({
bucket: "global",
onlyIp: true,
- ...ip
- })
+ ...ip,
+ }),
);
app.use(rateLimit({ bucket: "global", ...global }));
app.use(
@@ -150,17 +178,25 @@ export async function initRateLimits(app: Router) {
bucket: "error",
error: true,
onlyIp: true,
- ...error
- })
+ ...error,
+ }),
);
app.use("/guilds/:id", rateLimit(routes.guild));
app.use("/webhooks/:id", rateLimit(routes.webhook));
app.use("/channels/: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(
+ "/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; }) {
+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) {
@@ -169,7 +205,7 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
executor_id: opts.executor_id,
expires_at: new Date(Date.now() + opts.window * 1000),
hits: 0,
- blocked: false
+ blocked: false,
};
Cache.set(id, limit);
}
@@ -205,4 +241,4 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits
}
await ratelimit.save();
*/
-}
\ No newline at end of file
+}
diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index c0b7a4b8..05038040 100644
--- a/src/api/middlewares/Translation.ts
+++ b/src/api/middlewares/Translation.ts
@@ -9,8 +9,12 @@ 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 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)
@@ -21,9 +25,11 @@ export async function initTranslation(router: Router) {
fallbackLng: "en",
ns,
backend: {
- loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json",
+ loadPath:
+ path.join(ASSET_FOLDER_PATH, "locales") +
+ "/{{lng}}/{{ns}}.json",
},
- load: "all"
+ load: "all",
});
router.use(i18nextMiddleware.handle(i18next, {}));
diff --git a/src/api/routes/-/monitorz.ts b/src/api/routes/-/monitorz.ts
index f85cd099..630a832b 100644
--- a/src/api/routes/-/monitorz.ts
+++ b/src/api/routes/-/monitorz.ts
@@ -5,14 +5,18 @@ import os from "os";
const router = Router();
-router.get("/", route({ right: "OPERATOR" }), async (req: Request, res: Response) => {
- return res.json({
- load: os.loadavg(),
- procUptime: process.uptime(),
- sysUptime: os.uptime(),
- memPercent: 100 - ((os.freemem() / os.totalmem()) * 100),
- sessions: await Session.count(),
- })
-})
+router.get(
+ "/",
+ route({ right: "OPERATOR" }),
+ async (req: Request, res: Response) => {
+ return res.json({
+ load: os.loadavg(),
+ procUptime: process.uptime(),
+ sysUptime: os.uptime(),
+ memPercent: 100 - (os.freemem() / os.totalmem()) * 100,
+ sessions: await Session.count(),
+ });
+ },
+);
-export default router;
\ No newline at end of file
+export default router;
diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index f4c2bd16..0ae946ed 100644
--- a/src/api/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
@@ -3,11 +3,15 @@ import { route } from "@fosscord/api";
import { getIpAdress, IPAnalysis } from "@fosscord/api";
const router = Router();
-router.get("/",route({}), 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 IPAnalysis(getIpAdress(req))).country_code;
- res.json({ consent_required: false, country_code: country_code, promotional_email_opt_in: { required: true, pre_checked: false}});
+router.get("/", route({}), 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 IPAnalysis(getIpAdress(req))).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 9bed5aab..9ea2606c 100644
--- a/src/api/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -1,84 +1,127 @@
import { Request, Response, Router } from "express";
import { route, getIpAdress, verifyCaptcha } from "@fosscord/api";
import bcrypt from "bcrypt";
-import { Config, User, generateToken, adjustEmail, FieldErrors, LoginSchema } from "@fosscord/util";
+import {
+ Config,
+ User,
+ generateToken,
+ adjustEmail,
+ FieldErrors,
+ LoginSchema,
+} from "@fosscord/util";
import crypto from "crypto";
const router: Router = Router();
export default router;
-router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => {
- const { login, password, captcha_key, undelete } = req.body as LoginSchema;
- const email = adjustEmail(login);
- console.log("login", email);
+router.post(
+ "/",
+ route({ body: "LoginSchema" }),
+ async (req: Request, res: Response) => {
+ const { login, password, captcha_key, undelete } =
+ req.body as LoginSchema;
+ const email = adjustEmail(login);
+ console.log("login", email);
+
+ 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,
+ });
+ }
+
+ const ip = getIpAdress(req);
+ 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 config = Config.get();
+ const user = await User.findOneOrFail({
+ where: [{ phone: login }, { email: login }],
+ select: [
+ "data",
+ "id",
+ "disabled",
+ "deleted",
+ "settings",
+ "totp_secret",
+ "mfa_enabled",
+ ],
+ }).catch((e) => {
+ throw FieldErrors({
+ login: {
+ message: req.t("auth:login.INVALID_LOGIN"),
+ code: "INVALID_LOGIN",
+ },
+ });
+ });
+
+ 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 (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
+ // 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({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
});
}
- const ip = getIpAdress(req);
- 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
+ if (user.mfa_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 });
+
+ return res.json({
+ ticket: ticket,
+ mfa: true,
+ sms: false, // TODO
+ token: null,
});
}
- }
-
- const user = await User.findOneOrFail({
- where: [{ phone: login }, { email: login }],
- select: ["data", "id", "disabled", "deleted", "settings", "totp_secret", "mfa_enabled"]
- }).catch((e) => {
- throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } });
- });
-
- 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 });
- }
-
- // 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({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
- }
-
- if (user.mfa_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 });
-
- return res.json({
- ticket: ticket,
- mfa: true,
- sms: false, // TODO
- token: null,
- })
- }
-
- 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
-
- res.json({ token, settings: user.settings });
-});
+
+ 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
+
+ res.json({ token, settings: user.settings });
+ },
+);
/**
* POST /auth/login
diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index e806fed9..e1bdbea3 100644
--- a/src/api/routes/auth/logout.ts
+++ b/src/api/routes/auth/logout.ts
@@ -10,7 +10,8 @@ router.post("/", route({}), async (req: Request, res: Response) => {
} 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 (Object.keys(req.body).length != 0)
+ console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
}
res.status(204).send();
-});
\ No newline at end of file
+});
diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index 96a48b66..83cf7648 100644
--- a/src/api/routes/auth/mfa/totp.ts
+++ b/src/api/routes/auth/mfa/totp.ts
@@ -5,45 +5,48 @@ import { verifyToken } from "node-2fa";
import { HTTPError } from "lambert-server";
const router = Router();
-router.post("/", route({ body: "TotpSchema" }), async (req: Request, res: Response) => {
- const { code, ticket, gift_code_sku_id, login_source } = req.body as TotpSchema;
+router.post(
+ "/",
+ route({ body: "TotpSchema" }),
+ async (req: Request, res: Response) => {
+ const { code, ticket, gift_code_sku_id, login_source } =
+ req.body as TotpSchema;
- const user = await User.findOneOrFail({
- where: {
- totp_last_ticket: ticket,
- },
- select: [
- "id",
- "totp_secret",
- "settings",
- ],
- });
+ const user = await User.findOneOrFail({
+ where: {
+ totp_last_ticket: ticket,
+ },
+ select: ["id", "totp_secret", "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),
- 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 84f8f838..3479c4a0 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -1,156 +1,215 @@
import { Request, Response, Router } from "express";
-import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, RegisterSchema } from "@fosscord/util";
-import { route, getIpAdress, IPAnalysis, isProxy, verifyCaptcha } from "@fosscord/api";
+import {
+ Config,
+ generateToken,
+ Invite,
+ FieldErrors,
+ User,
+ adjustEmail,
+ RegisterSchema,
+} from "@fosscord/util";
+import {
+ route,
+ getIpAdress,
+ IPAnalysis,
+ isProxy,
+ verifyCaptcha,
+} from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
const router: Router = Router();
-router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => {
- const body = req.body as RegisterSchema;
- const { register, security } = Config.get();
- const ip = getIpAdress(req);
-
- // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
- let email = adjustEmail(body.email);
-
- // check if registration is allowed
- if (!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") }
- });
- }
-
- if (register.disabled) {
- throw FieldErrors({
- email: {
- code: "DISABLED",
- message: "registration is disabled on this instance"
- }
- });
- }
-
- if (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
+router.post(
+ "/",
+ route({ body: "RegisterSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as RegisterSchema;
+ const { register, security } = Config.get();
+ const ip = getIpAdress(req);
+
+ // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
+ let email = adjustEmail(body.email);
+
+ // check if registration is allowed
+ if (!register.allowNewRegistration) {
+ throw FieldErrors({
+ email: {
+ code: "REGISTRATION_DISABLED",
+ message: req.t("auth:register.REGISTRATION_DISABLED"),
+ },
});
}
- 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
+ // 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 (!register.allowMultipleAccounts) {
- // TODO: check if fingerprint was eligible generated
- const exists = await User.findOne({ where: { fingerprints: body.fingerprint }, select: ["id"] });
- if (exists) {
+ if (register.disabled) {
throw FieldErrors({
email: {
- code: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED")
- }
+ code: "DISABLED",
+ message: "registration is disabled on this instance",
+ },
});
}
- }
- if (register.blockProxies) {
- if (isProxy(await IPAnalysis(ip))) {
- console.log(`proxy ${ip} blocked from registration`);
- throw new HTTPError("Your IP is blocked from registration");
+ if (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,
+ });
+ }
}
- }
- // TODO: gift_code_sku_id?
- // TODO: check password strength
+ if (!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 (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") } });
+ if (register.blockProxies) {
+ if (isProxy(await IPAnalysis(ip))) {
+ console.log(`proxy ${ip} blocked from registration`);
+ throw new HTTPError("Your IP is blocked from registration");
+ }
}
- // check if there is already an account with this email
- const exists = await User.findOne({ where: { email: email } });
+ // TODO: gift_code_sku_id?
+ // TODO: check password strength
+
+ 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"),
+ },
+ });
+ }
- if (exists) {
+ // 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: "EMAIL_ALREADY_REGISTERED",
- message: req.t("auth:register.EMAIL_ALREADY_REGISTERED")
- }
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
});
}
- } 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);
- body.date_of_birth = new Date(body.date_of_birth as Date);
-
- // higher is younger
- if (body.date_of_birth > minimum) {
+
+ if (register.dateOfBirth.required && !body.date_of_birth) {
throw FieldErrors({
date_of_birth: {
- code: "DATE_OF_BIRTH_UNDERAGE",
- message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { years: register.dateOfBirth.minimum })
- }
+ 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,
+ );
+ body.date_of_birth = new Date(body.date_of_birth as Date);
+
+ // higher is younger
+ if (body.date_of_birth > 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) {
+ // 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.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 (body.password) {
- // 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.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") }
- });
- }
-
- 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);
- }
-
- console.log("register", body.email, body.username, ip);
-
- return res.json({ token: await generateToken(user.id) });
-});
+
+ 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);
+ }
+
+ console.log("register", body.email, body.username, ip);
+
+ return res.json({ token: await generateToken(user.id) });
+ },
+);
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 24de8ec5..65f0a57c 100644
--- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts
+++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -4,19 +4,31 @@ import { FieldErrors, User, BackupCodesChallengeSchema } from "@fosscord/util";
import bcrypt from "bcrypt";
const router = Router();
-router.post("/", route({ body: "BackupCodesChallengeSchema" }), async (req: Request, res: Response) => {
- const { password } = req.body as BackupCodesChallengeSchema;
+router.post(
+ "/",
+ route({ body: "BackupCodesChallengeSchema" }),
+ 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/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 8dbefe1b..a164fff6 100644
--- a/src/api/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -6,7 +6,7 @@ import {
emitEvent,
Recipient,
handleFile,
- ChannelModifySchema
+ ChannelModifySchema,
} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
@@ -15,56 +15,89 @@ const router: Router = Router();
// TODO: delete channel
// TODO: Get channel
-router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
- return res.send(channel);
-});
+ return res.send(channel);
+ },
+);
-router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_CHANNELS" }),
+ 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 {
- await Promise.all([
- Channel.delete({ id: channel_id }),
- emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent)
- ]);
- }
+ 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 {
+ await Promise.all([
+ Channel.delete({ id: channel_id }),
+ emitEvent({
+ event: "CHANNEL_DELETE",
+ data: channel,
+ channel_id,
+ } as ChannelDeleteEvent),
+ ]);
+ }
- res.send(channel);
-});
+ res.send(channel);
+ },
+);
-router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- var payload = req.body as ChannelModifySchema;
- const { channel_id } = req.params;
- if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
+router.patch(
+ "/",
+ route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ var 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 246a2c69..afaabf47 100644
--- a/src/api/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -2,16 +2,33 @@ import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
import { random } from "@fosscord/api";
-import { Channel, Invite, InviteCreateEvent, emitEvent, User, Guild, PublicInviteRelation } from "@fosscord/util";
+import {
+ Channel,
+ Invite,
+ InviteCreateEvent,
+ emitEvent,
+ User,
+ Guild,
+ PublicInviteRelation,
+} from "@fosscord/util";
import { isTextChannel } from "./messages";
const router: Router = Router();
-router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }),
+router.post(
+ "/",
+ route({
+ body: "InviteCreateSchema",
+ permission: "CREATE_INSTANT_INVITE",
+ right: "CREATE_INVITES",
+ }),
async (req: Request, res: Response) => {
const { user_id } = req;
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] });
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ select: ["id", "name", "type", "guild_id"],
+ });
isTextChannel(channel.type);
if (!channel.guild_id) {
@@ -31,30 +48,44 @@ router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT
created_at: new Date(),
guild_id,
channel_id: channel_id,
- inviter_id: user_id
+ inviter_id: user_id,
}).save();
const data = invite.toJSON();
data.inviter = await User.getPublicUser(req.user_id);
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);
- });
+ },
+);
-router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- const { user_id } = req;
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+router.get(
+ "/",
+ route({ permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ const { user_id } = req;
+ 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 }, relations: PublicInviteRelation });
+ const invites = await Invite.find({
+ where: { guild_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 bedd453c..1a30143f 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
@@ -1,4 +1,9 @@
-import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util";
+import {
+ emitEvent,
+ getPermission,
+ MessageAckEvent,
+ ReadState,
+} from "@fosscord/util";
import { Request, Response, Router } from "express";
import { route } from "@fosscord/api";
@@ -8,29 +13,40 @@ const router = Router();
// TODO: send read state event to all channel members
// TODO: advance-only notification cursor
-router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
+router.post(
+ "/",
+ route({ body: "MessageAcknowledgeSchema" }),
+ 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");
-
- 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;
-
- await read_state.save();
-
- await emitEvent({
- event: "MESSAGE_ACK",
- user_id: req.user_id,
- data: {
+ const permission = await getPermission(
+ req.user_id,
+ undefined,
channel_id,
- message_id,
- version: 3763
- }
- } as MessageAckEvent);
-
- res.json({ token: null });
-});
+ );
+ 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;
+
+ await read_state.save();
+
+ await emitEvent({
+ event: "MESSAGE_ACK",
+ user_id: req.user_id,
+ data: {
+ channel_id,
+ message_id,
+ version: 3763,
+ },
+ } as MessageAckEvent);
+
+ 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 b2cb6763..d8b55ccd 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
@@ -3,26 +3,36 @@ import { route } from "@fosscord/api";
const router = Router();
-router.post("/", route({ permission: "MANAGE_MESSAGES" }), (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: []
- }).status(200);
-});
+router.post(
+ "/",
+ route({ permission: "MANAGE_MESSAGES" }),
+ (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: [],
+ }).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 46b0d6bd..3abfebe8 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
@@ -26,55 +26,69 @@ const messageUpload = multer({
limits: {
fileSize: 1024 * 1024 * 100,
fields: 10,
- files: 1
+ files: 1,
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
-router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
- var body = req.body as MessageCreateSchema;
+router.patch(
+ "/",
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ }),
+ async (req: Request, res: Response) => {
+ const { message_id, channel_id } = req.params;
+ var body = req.body as MessageCreateSchema;
- 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");
-
- const new_message = await handleMessage({
- ...message,
- // TODO: should message_reference be overridable?
- // @ts-ignore
- 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",
+ 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");
+
+ const new_message = await handleMessage({
+ ...message,
+ // TODO: should message_reference be overridable?
+ // @ts-ignore
+ message_reference: message.message_reference,
+ ...body,
+ author_id: message.author_id,
channel_id,
- data: { ...new_message, nonce: undefined }
- } as MessageUpdateEvent)
- ]);
+ id: message_id,
+ edited_timestamp: new Date(),
+ });
- postHandleMessage(message);
+ await Promise.all([
+ new_message!.save(),
+ await emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: { ...new_message, nonce: undefined },
+ } as MessageUpdateEvent),
+ ]);
- return res.json(message);
-});
+ postHandleMessage(message);
+ return res.json(message);
+ },
+);
// Backfill message with specific timestamp
router.put(
@@ -87,7 +101,11 @@ router.put(
next();
},
- route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_BACKDATED_EVENTS" }),
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_BACKDATED_EVENTS",
+ }),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
var body = req.body as MessageCreateSchema;
@@ -107,20 +125,30 @@ router.put(
throw FosscordApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE;
}
- const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id } });
+ const exists = await Message.findOne({
+ where: { id: message_id, channel_id: channel_id },
+ });
if (exists) {
throw FosscordApiErrors.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 }));
+ 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 channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: ["recipients", "recipients.user"],
+ });
const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed);
@@ -142,27 +170,43 @@ router.put(
await Promise.all([
message.save(),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent),
- channel.save()
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: channel_id,
+ data: message,
+ } as MessageCreateEvent),
+ channel.save(),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
return res.json(message);
- }
+ },
);
-router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "VIEW_CHANNEL" }),
+ 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({}), async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
@@ -172,9 +216,13 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
const rights = await getRights(req.user_id);
- if ((message.author_id !== 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);
+ const permission = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
permission.hasThrow("MANAGE_MESSAGES");
}
} else rights.hasThrow("SELF_DELETE_MESSAGES");
@@ -187,8 +235,8 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
data: {
id: message_id,
channel_id,
- guild_id: channel.guild_id
- }
+ guild_id: channel.guild_id,
+ },
} as MessageDeleteEvent);
res.sendStatus(204);
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 c3cca05d..9f774682 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
@@ -11,7 +11,7 @@ import {
MessageReactionRemoveEvent,
PartialEmoji,
PublicUserProjection,
- User
+ User,
} from "@fosscord/util";
import { route } from "@fosscord/api";
import { Router, Response, Request } from "express";
@@ -27,159 +27,224 @@ function getEmoji(emoji: string): PartialEmoji {
if (parts)
return {
name: parts[0],
- id: parts[1]
+ id: parts[1],
};
return {
id: undefined,
- name: emoji
+ name: emoji,
};
}
-router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_MESSAGES" }),
+ 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: {
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE_ALL",
channel_id,
- message_id,
- guild_id: channel.guild_id
+ data: {
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageReactionRemoveAllEvent);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:emoji",
+ route({ permission: "MANAGE_MESSAGES" }),
+ 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 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);
+ message.reactions.remove(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),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.get(
+ "/:emoji",
+ route({ permission: "VIEW_CHANNEL" }),
+ 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 users = await User.find({
+ where: {
+ id: In(reaction.user_ids),
+ },
+ select: PublicUserProjection,
+ });
+
+ res.json(users);
+ },
+);
+
+router.put(
+ "/:emoji/:user_id",
+ route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }),
+ 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,
+ );
+
+ if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
+
+ if (emoji.id) {
+ const external_emoji = await Emoji.findOneOrFail({
+ where: { id: emoji.id },
+ });
+ if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS");
+ emoji.animated = external_emoji.animated;
+ emoji.name = external_emoji.name;
}
- } as MessageReactionRemoveAllEvent);
-
- res.sendStatus(204);
-});
-router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), 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 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);
- message.reactions.remove(already_added);
-
- await Promise.all([
- message.save(),
- emitEvent({
- event: "MESSAGE_REACTION_REMOVE_EMOJI",
+ 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++;
+ } else
+ message.reactions.push({
+ count: 1,
+ emoji,
+ user_ids: [req.user_id],
+ });
+
+ await message.save();
+
+ const member =
+ channel.guild_id &&
+ (await Member.findOneOrFail({ where: { id: req.user_id } }));
+
+ await emitEvent({
+ event: "MESSAGE_REACTION_ADD",
channel_id,
data: {
+ user_id: req.user_id,
channel_id,
message_id,
- guild_id: message.guild_id,
- emoji
- }
- } as MessageReactionRemoveEmojiEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), 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 users = await User.find({
- where: {
- id: In(reaction.user_ids)
- },
- select: PublicUserProjection
- });
-
- res.json(users);
-});
-
-router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), 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);
-
- if (!already_added) req.permission!.hasThrow("ADD_REACTIONS");
-
- if (emoji.id) {
- const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id } });
- if (!already_added) 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++;
- } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] });
-
- await message.save();
-
- const member = channel.guild_id && (await Member.findOneOrFail({ where: { id: req.user_id } }));
-
- 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
+ guild_id: channel.guild_id,
+ emoji,
+ member,
+ },
+ } as MessageReactionAddEvent);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:emoji/:user_id",
+ route({}),
+ async (req: Request, res: Response) => {
+ var { message_id, channel_id, user_id } = req.params;
+
+ 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 },
+ });
+
+ if (user_id === "@me") user_id = req.user_id;
+ else {
+ const permissions = await getPermission(
+ req.user_id,
+ undefined,
+ channel_id,
+ );
+ permissions.hasThrow("MANAGE_MESSAGES");
}
- } as MessageReactionAddEvent);
- res.sendStatus(204);
-});
+ 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);
-router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => {
- var { message_id, channel_id, user_id } = req.params;
+ already_added.count--;
- const emoji = getEmoji(req.params.emoji);
+ if (already_added.count <= 0) message.reactions.remove(already_added);
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } });
+ await message.save();
- 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);
-
- already_added.count--;
-
- if (already_added.count <= 0) message.reactions.remove(already_added);
-
- await message.save();
-
- await emitEvent({
- event: "MESSAGE_REACTION_REMOVE",
- channel_id,
- data: {
- user_id: req.user_id,
+ await emitEvent({
+ event: "MESSAGE_REACTION_REMOVE",
channel_id,
- message_id,
- guild_id: channel.guild_id,
- emoji
- }
- } as MessageReactionRemoveEvent);
-
- res.sendStatus(204);
-});
+ data: {
+ user_id: req.user_id,
+ channel_id,
+ message_id,
+ guild_id: channel.guild_id,
+ emoji,
+ },
+ } as MessageReactionRemoveEvent);
+
+ 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 6493c16a..553ab17e 100644
--- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,5 +1,13 @@
import { Router, Response, Request } from "express";
-import { Channel, Config, emitEvent, getPermission, getRights, MessageDeleteBulkEvent, Message } from "@fosscord/util";
+import {
+ Channel,
+ Config,
+ emitEvent,
+ getPermission,
+ getRights,
+ MessageDeleteBulkEvent,
+ Message,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -10,33 +18,48 @@ export default router;
// should users be able to bulk delete messages or only bots? ANSWER: all users
// 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({ body: "BulkDeleteSchema" }), 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");
-
- let superuser = rights.has("MANAGE_MESSAGES");
- const permission = await getPermission(req.user_id, channel?.guild_id, channel_id);
-
- 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`);
- }
-
- await Message.delete(messages);
-
- await emitEvent({
- event: "MESSAGE_DELETE_BULK",
- channel_id,
- data: { ids: messages, channel_id, guild_id: channel.guild_id }
- } as MessageDeleteBulkEvent);
-
- res.sendStatus(204);
-});
+router.post(
+ "/",
+ route({ body: "BulkDeleteSchema" }),
+ 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");
+
+ let superuser = rights.has("MANAGE_MESSAGES");
+ const permission = await getPermission(
+ req.user_id,
+ channel?.guild_id,
+ channel_id,
+ );
+
+ 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`,
+ );
+ }
+
+ await Message.delete(messages);
+
+ await emitEvent({
+ event: "MESSAGE_DELETE_BULK",
+ channel_id,
+ data: { ids: messages, channel_id, guild_id: channel.guild_id },
+ } as MessageDeleteBulkEvent);
+
+ 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 bee93e80..631074c6 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -61,36 +61,50 @@ router.get("/", async (req: Request, res: Response) => {
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);
+ if (limit < 1 || limit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
var halfLimit = Math.floor(limit / 2);
- const permissions = await getPermission(req.user_id, channel.guild_id, channel_id);
+ 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([]);
- var query: FindManyOptions<Message> & { where: { id?: any; }; } = {
+ var query: FindManyOptions<Message> & { where: { id?: any } } = {
order: { timestamp: "DESC" },
take: limit,
where: { channel_id },
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"]
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
};
if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate())) return res.status(422);
+ if (BigInt(after) > BigInt(Snowflake.generate()))
+ return res.status(422);
query.where.id = MoreThan(after);
- }
- else if (before) {
- if (BigInt(before) < BigInt(req.params.channel_id)) return res.status(422);
+ } else if (before) {
+ if (BigInt(before) < BigInt(req.params.channel_id))
+ return res.status(422);
query.where.id = LessThan(before);
- }
- else if (around) {
+ } else if (around) {
query.where.id = [
MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
- LessThan((BigInt(around) + BigInt(halfLimit)).toString())
+ LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
];
- return res.json([]); // TODO: fix around
+ return res.json([]); // TODO: fix around
}
const messages = await Message.find(query);
@@ -105,11 +119,22 @@ router.get("/", async (req: Request, res: Response) => {
delete x.user_ids;
});
// @ts-ignore
- if (!x.author) x.author = { id: "4", discriminator: "0000", username: "Fosscord Ghost", public_flags: "0", avatar: null };
+ if (!x.author)
+ x.author = {
+ id: "4",
+ discriminator: "0000",
+ username: "Fosscord Ghost",
+ public_flags: "0",
+ avatar: null,
+ };
x.attachments?.forEach((y: any) => {
// 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}`;
- y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`;
+ const uri = y.proxy_url.startsWith("http")
+ ? y.proxy_url
+ : `https://example.org${y.proxy_url}`;
+ y.proxy_url = `${endpoint == null ? "" : endpoint}${
+ new URL(uri).pathname
+ }`;
});
/**
@@ -123,7 +148,7 @@ router.get("/", async (req: Request, res: Response) => {
// }
return x;
- })
+ }),
);
});
@@ -134,7 +159,7 @@ const messageUpload = multer({
fields: 10,
// files: 1
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}); // max upload 50 mb
/**
TODO: dynamically change limit of MessageCreateSchema with config
@@ -155,24 +180,38 @@ router.post(
next();
},
- route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }),
+ route({
+ body: "MessageCreateSchema",
+ permission: "SEND_MESSAGES",
+ right: "SEND_MESSAGES",
+ }),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
var body = req.body as MessageCreateSchema;
const attachments: Attachment[] = [];
- const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
+ 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);
+ throw new HTTPError(
+ `Cannot send messages to channel of type ${channel.type}`,
+ 400,
+ );
}
- const files = req.files as Express.Multer.File[] ?? [];
+ const files = (req.files as Express.Multer.File[]) ?? [];
for (var currFile of files) {
try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
- }
- catch (error) {
+ const file = await uploadFile(
+ `/attachments/${channel.id}`,
+ currFile,
+ );
+ attachments.push(
+ Attachment.create({ ...file, proxy_url: file.url }),
+ );
+ } catch (error) {
return res.status(400).json(error);
}
}
@@ -188,7 +227,7 @@ router.post(
channel_id,
attachments,
edited_timestamp: undefined,
- timestamp: new Date()
+ timestamp: new Date(),
});
channel.last_message_id = message.id;
@@ -205,32 +244,47 @@ router.post(
recipient.save(),
emitEvent({
event: "CHANNEL_CREATE",
- data: channel_dto.excludedRecipients([recipient.user_id]),
- user_id: recipient.user_id
- })
+ data: channel_dto.excludedRecipients([
+ recipient.user_id,
+ ]),
+ user_id: recipient.user_id,
+ }),
]);
}
- })
+ }),
);
}
- const member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] });
- member.roles = member.roles.filter((role: Role) => {
- return role.id !== role.guild_id;
- }).map((role: Role) => {
- return role.id;
- }) as any;
+ const member = await Member.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["roles"],
+ });
+ member.roles = member.roles
+ .filter((role: Role) => {
+ return role.id !== role.guild_id;
+ })
+ .map((role: Role) => {
+ return role.id;
+ }) as any;
await Promise.all([
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()
+ 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(),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
return res.json(message);
- }
+ },
);
-
diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index e74a0255..89be843f 100644
--- a/src/api/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -6,7 +6,7 @@ import {
emitEvent,
getPermission,
Member,
- Role
+ Role,
} from "@fosscord/util";
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server";
@@ -16,69 +16,90 @@ const router: Router = Router();
// 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)
-export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite { }
+export interface ChannelPermissionOverwriteSchema
+ extends ChannelPermissionOverwrite {}
router.put(
"/:overwrite_id",
- route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }),
+ route({
+ body: "ChannelPermissionOverwriteSchema",
+ permission: "MANAGE_ROLES",
+ }),
async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params;
const body = req.body as ChannelPermissionOverwriteSchema;
- var channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+ var channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
if (body.type === 0) {
- if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404);
+ if (!(await Role.count({ where: { id: overwrite_id } })))
+ throw new HTTPError("role not found", 404);
} else if (body.type === 1) {
- if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404);
+ if (!(await Member.count({ where: { id: overwrite_id } })))
+ throw new HTTPError("user not found", 404);
} else throw new HTTPError("type not supported", 501);
- // @ts-ignore
- var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id);
+ //@ts-ignore
+ var overwrite: ChannelPermissionOverwrite =
+ channel.permission_overwrites?.find((x) => x.id === overwrite_id);
if (!overwrite) {
// @ts-ignore
overwrite = {
id: overwrite_id,
- type: body.type
+ type: body.type,
};
channel.permission_overwrites!.push(overwrite);
}
- overwrite.allow = String(req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")));
- overwrite.deny = String(req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")));
+ overwrite.allow = String(
+ req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")),
+ );
+ overwrite.deny = String(
+ req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")),
+ );
await Promise.all([
channel.save(),
emitEvent({
event: "CHANNEL_UPDATE",
channel_id,
- data: channel
- } as ChannelUpdateEvent)
+ data: channel,
+ } as ChannelUpdateEvent),
]);
return res.sendStatus(204);
- }
+ },
);
// TODO: check permission hierarchy
-router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { channel_id, overwrite_id } = req.params;
+router.delete(
+ "/:overwrite_id",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ 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 30507c71..d3f6960a 100644
--- a/src/api/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -6,7 +6,7 @@ import {
getPermission,
Message,
MessageUpdateEvent,
- DiscordApiErrors
+ DiscordApiErrors,
} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
@@ -14,77 +14,100 @@ import { route } from "@fosscord/api";
const router: Router = Router();
-router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
-
- const message = await Message.findOneOrFail({ where: { id: message_id } });
-
- // * 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: true } });
- const { maxPins } = Config.get().limits.channel;
- if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
-
- await Promise.all([
- Message.update({ id: message_id }, { pinned: true }),
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message
- } as MessageUpdateEvent),
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
+router.put(
+ "/:message_id",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
+
+ // * 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: true },
+ });
+ const { maxPins } = Config.get().limits.channel;
+ if (pinned_count >= maxPins)
+ throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins);
+
+ await Promise.all([
+ Message.update({ id: message_id }, { pinned: true }),
+ emitEvent({
+ event: "MESSAGE_UPDATE",
channel_id,
- guild_id: message.guild_id,
- last_pin_timestamp: undefined
- }
- } as ChannelPinsUpdateEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
- const { channel_id, message_id } = req.params;
-
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
- if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
-
- const message = await Message.findOneOrFail({ where: { id: message_id } });
- message.pinned = false;
-
- await Promise.all([
- message.save(),
-
- emitEvent({
- event: "MESSAGE_UPDATE",
- channel_id,
- data: message
- } as MessageUpdateEvent),
-
- emitEvent({
- event: "CHANNEL_PINS_UPDATE",
- channel_id,
- data: {
+ data: message,
+ } as MessageUpdateEvent),
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
channel_id,
- guild_id: channel.guild_id,
- last_pin_timestamp: undefined
- }
- } as ChannelPinsUpdateEvent)
- ]);
-
- res.sendStatus(204);
-});
-
-router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
-
- let pins = await Message.find({ where: { channel_id: channel_id, pinned: true } });
+ data: {
+ channel_id,
+ guild_id: message.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.delete(
+ "/:message_id",
+ route({ permission: "VIEW_CHANNEL" }),
+ async (req: Request, res: Response) => {
+ const { channel_id, message_id } = req.params;
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+ if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES");
+
+ const message = await Message.findOneOrFail({
+ where: { id: message_id },
+ });
+ message.pinned = false;
+
+ await Promise.all([
+ message.save(),
+
+ emitEvent({
+ event: "MESSAGE_UPDATE",
+ channel_id,
+ data: message,
+ } as MessageUpdateEvent),
- res.send(pins);
-});
+ emitEvent({
+ event: "CHANNEL_PINS_UPDATE",
+ channel_id,
+ data: {
+ channel_id,
+ guild_id: channel.guild_id,
+ last_pin_timestamp: undefined,
+ },
+ } as ChannelPinsUpdateEvent),
+ ]);
+
+ res.sendStatus(204);
+ },
+);
+
+router.get(
+ "/",
+ route({ permission: ["READ_MESSAGE_HISTORY"] }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+
+ let pins = await Message.find({
+ where: { channel_id: channel_id, pinned: true },
+ });
+
+ 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 9fe6b658..a9f88662 100644
--- a/src/api/routes/channels/#channel_id/purge.ts
+++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -21,52 +21,79 @@ export default router;
/**
TODO: apply the delete bit by bit to prevent client and database stress
**/
-router.post("/", route({ /*body: "PurgeSchema",*/ }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
+router.post(
+ "/",
+ route({
+ /*body: "PurgeSchema",*/
+ }),
+ 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
-
- var query: FindManyOptions<Message> & { where: { id?: any; }; } = {
- 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"]
- };
+ // TODO: send the deletion event bite-by-bite to prevent client stress
+ var query: FindManyOptions<Message> & { where: { id?: any } } = {
+ 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 endpoint = Config.get().cdn.endpointPublic;
+ const messages = await Message.find(query);
+ const endpoint = Config.get().cdn.endpointPublic;
- 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 25854415..cc7e5756 100644
--- a/src/api/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -8,7 +8,7 @@ import {
emitEvent,
PublicUserProjection,
Recipient,
- User
+ User,
} from "@fosscord/util";
import { route } from "@fosscord/api";
@@ -16,34 +16,48 @@ const router: Router = Router();
router.put("/:user_id", route({}), async (req: Request, res: Response) => {
const { channel_id, user_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.GROUP_DM) {
- const recipients = [...channel.recipients!.map((r) => r.user_id), user_id].unique();
+ const recipients = [
+ ...channel.recipients!.map((r) => r.user_id),
+ user_id,
+ ].unique();
- const new_channel = await Channel.createDMChannel(recipients, req.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?
}
- channel.recipients!.push(Recipient.create({ channel_id: channel_id, user_id: user_id }));
+ 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
+ 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 })
+ user: await User.findOneOrFail({
+ where: { id: user_id },
+ select: PublicUserProjection,
+ }),
},
- channel_id: channel_id
+ channel_id: channel_id,
} as ChannelRecipientAddEvent);
return res.sendStatus(204);
}
@@ -51,8 +65,16 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
router.delete("/:user_id", route({}), 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)))
+ 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)) {
diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 99460f6e..03f76205 100644
--- a/src/api/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -4,26 +4,42 @@ import { Router, Request, Response } from "express";
const router: Router = Router();
-router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
- const { channel_id } = req.params;
- const user_id = req.user_id;
- const timestamp = Date.now();
- 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"] });
+router.post(
+ "/",
+ route({ permission: "SEND_MESSAGES" }),
+ async (req: Request, res: Response) => {
+ const { channel_id } = req.params;
+ const user_id = req.user_id;
+ const timestamp = Date.now();
+ 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, roles: member?.roles?.map((x) => x.id) } } : null),
- channel_id,
- timestamp,
- user_id,
- guild_id: channel.guild_id
- }
- } as TypingStartEvent);
+ await emitEvent({
+ event: "TYPING_START",
+ channel_id: channel_id,
+ data: {
+ ...(member
+ ? {
+ member: {
+ ...member,
+ 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 99c104ca..da8fe73c 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -13,22 +13,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
// TODO: use Image Data Type for avatar instead of String
-router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), 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);
-
- const webhook_count = await Webhook.count({ where: { channel_id } });
- const { maxWebhooks } = Config.get().limits.channel;
- if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
-
- var { avatar, name } = req.body as { name: string; avatar?: string };
- name = trimSpecial(name);
- if (name === "clyde") throw new HTTPError("Invalid name", 400);
-
- // TODO: save webhook in database and send response
-});
+router.post(
+ "/",
+ route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }),
+ 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);
+
+ const webhook_count = await Webhook.count({ where: { channel_id } });
+ const { maxWebhooks } = Config.get().limits.channel;
+ if (webhook_count > maxWebhooks)
+ throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
+
+ var { avatar, name } = req.body as { name: string; avatar?: string };
+ name = trimSpecial(name);
+ if (name === "clyde") throw new HTTPError("Invalid name", 400);
+
+ // TODO: save webhook in database and send response
+ },
+);
export default router;
diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index 383e2b24..0e7cfbab 100644
--- a/src/api/routes/discoverable-guilds.ts
+++ b/src/api/routes/discoverable-guilds.ts
@@ -17,19 +17,33 @@ router.get("/", route({}), async (req: Request, res: Response) => {
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)) });
+ : 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() },
+ take: Math.abs(Number(limit || configLimit)),
+ })
: await Guild.find({
- where: { primary_category_id: categories.toString(), features: Like("%DISCOVERABLE%") },
- take: Math.abs(Number(limit || configLimit))
- });
+ where: {
+ primary_category_id: categories.toString(),
+ features: Like("%DISCOVERABLE%"),
+ },
+ take: Math.abs(Number(limit || configLimit)),
+ });
}
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 6ab2cc13..90450035 100644
--- a/src/api/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -10,7 +10,9 @@ router.get("/categories", route({}), async (req: Request, res: Response) => {
const { locale, primary_only } = req.query;
- const out = primary_only ? await Categories.find() : await Categories.find({ where: { is_primary: true } });
+ const out = primary_only
+ ? await Categories.find()
+ : await Categories.find({ where: { is_primary: true } });
res.send(out);
});
diff --git a/src/api/routes/downloads.ts b/src/api/routes/downloads.ts
index df3df911..bc0750f7 100644
--- a/src/api/routes/downloads.ts
+++ b/src/api/routes/downloads.ts
@@ -10,9 +10,12 @@ router.get("/:branch", route({}), async (req: Request, res: Response) => {
const { platform } = req.query;
//TODO
- if (!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404);
+ if (!platform || !["linux", "osx", "win"].includes(platform.toString()))
+ return res.status(404);
- const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } });
+ const release = await Release.findOneOrFail({
+ where: { name: client.releases.upstreamVersion },
+ });
res.redirect(release[`win_url`]);
});
diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts
index 7be86fb8..b2b7d724 100644
--- a/src/api/routes/experiments.ts
+++ b/src/api/routes/experiments.ts
@@ -5,7 +5,7 @@ const router = Router();
router.get("/", route({}), (req: Request, res: Response) => {
// TODO:
- res.send({ fingerprint: "", assignments: [], guild_experiments:[] });
+ res.send({ fingerprint: "", assignments: [], guild_experiments: [] });
});
export default router;
diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index f1dbb9df..2e26d019 100644
--- a/src/api/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
@@ -18,9 +18,9 @@ export interface GatewayBotResponse {
const options: RouteOptions = {
test: {
response: {
- body: "GatewayBotResponse"
- }
- }
+ body: "GatewayBotResponse",
+ },
+ },
};
router.get("/", route(options), (req: Request, res: Response) => {
@@ -32,8 +32,8 @@ router.get("/", route(options), (req: Request, res: Response) => {
total: 1000,
remaining: 999,
reset_after: 14400000,
- max_concurrency: 1
- }
+ max_concurrency: 1,
+ },
});
});
diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 9bad7478..a6ed9dc4 100644
--- a/src/api/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
@@ -11,14 +11,16 @@ export interface GatewayResponse {
const options: RouteOptions = {
test: {
response: {
- body: "GatewayResponse"
- }
- }
+ body: "GatewayResponse",
+ },
+ },
};
router.get("/", route(options), (req: Request, res: Response) => {
const { endpointPublic } = Config.get().gateway;
- res.json({ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3002" });
+ res.json({
+ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3002",
+ });
});
export default router;
diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index c7468641..54352215 100644
--- a/src/api/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { getGifApiKey, parseGifResult } from "./trending";
@@ -11,16 +11,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { q, media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
- const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- agent,
- 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}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json() as any; // TODO: types
+ const { results } = (await response.json()) as any; // TODO: types
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index 52a8969d..e4b28e24 100644
--- a/src/api/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { getGifApiKey, parseGifResult } from "./trending";
@@ -11,16 +11,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
- const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, {
- agent,
- 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}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json() as any; // TODO: types
+ const { results } = (await response.json()) as any; // TODO: types
res.json(results.map(parseGifResult)).status(200);
});
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index aa976c3f..58044ea5 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -1,6 +1,6 @@
import { Router, Response, Request } from "express";
import fetch from "node-fetch";
-import ProxyAgent from 'proxy-agent';
+import ProxyAgent from "proxy-agent";
import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
import { HTTPError } from "lambert-server";
@@ -16,14 +16,15 @@ export function parseGifResult(result: any) {
gif_src: result.media[0].gif.url,
width: result.media[0].mp4.dims[0],
height: result.media[0].mp4.dims[1],
- preview: result.media[0].mp4.preview
+ preview: result.media[0].mp4.preview,
};
}
export function getGifApiKey() {
const { enabled, provider, apiKey } = Config.get().gif;
if (!enabled) throw new HTTPError(`Gifs are disabled`);
- if (provider !== "tenor" || !apiKey) throw new HTTPError(`${provider} gif provider not supported`);
+ if (provider !== "tenor" || !apiKey)
+ throw new HTTPError(`${provider} gif provider not supported`);
return apiKey;
}
@@ -34,28 +35,37 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { media_format, locale } = req.query;
const apiKey = getGifApiKey();
-
+
const agent = new ProxyAgent();
const [responseSource, trendGifSource] = await Promise.all([
- fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- }),
- fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" }
- })
+ fetch(
+ `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
+ fetch(
+ `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
]);
- const { tags } = await responseSource.json() as any; // TODO: types
- const { results } = await trendGifSource.json() as any; //TODO: types;
+ const { tags } = (await responseSource.json()) as any; // TODO: types
+ const { results } = (await trendGifSource.json()) as any; //TODO: types;
res.json({
- categories: tags.map((x: any) => ({ name: x.searchterm, src: x.image })),
- gifs: [parseGifResult(results[0])]
+ categories: tags.map((x: any) => ({
+ name: x.searchterm,
+ src: x.image,
+ })),
+ gifs: [parseGifResult(results[0])],
}).status(200);
});
diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index b851d710..bda37973 100644
--- a/src/api/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -13,12 +13,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: implement this with default typeorm query
// const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) });
- 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);
+ : 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/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index b54835fc..76a11f6b 100644
--- a/src/api/routes/guilds/#guild_id/audit-logs.ts
+++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -11,7 +11,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
webhooks: [],
guild_scheduled_events: [],
threads: [],
- application_commands: []
+ application_commands: [],
});
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index ed00f9c0..930985d7 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -1,5 +1,15 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, GuildBanAddEvent, GuildBanRemoveEvent, Ban, User, Member, BanRegistrySchema, BanModeratorSchema } from "@fosscord/util";
+import {
+ DiscordApiErrors,
+ emitEvent,
+ GuildBanAddEvent,
+ GuildBanRemoveEvent,
+ Ban,
+ User,
+ Member,
+ BanRegistrySchema,
+ BanModeratorSchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { getIpAdress, route } from "@fosscord/api";
@@ -7,150 +17,184 @@ const router: Router = Router();
/* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */
-router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- let bans = await Ban.find({ where: { guild_id: guild_id } });
- let promisesToAwait: object[] = [];
- const bansObj: object[] = [];
+ let bans = await Ban.find({ where: { guild_id: guild_id } });
+ let promisesToAwait: object[] = [];
+ const bansObj: object[] = [];
- bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
+ bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
- bans.forEach((ban) => {
- promisesToAwait.push(User.getPublicUser(ban.user_id));
- });
-
- const bannedUsers: object[] = await Promise.all(promisesToAwait);
-
- bans.forEach((ban, index) => {
- const user = bannedUsers[index] as User;
- bansObj.push({
- reason: ban.reason,
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- avatar: user.avatar,
- public_flags: user.public_flags
- }
+ bans.forEach((ban) => {
+ promisesToAwait.push(User.getPublicUser(ban.user_id));
});
- });
-
- return res.json(bansObj);
-});
-
-router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const user_id = req.params.ban;
-
- let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } }) as BanRegistrySchema;
-
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // pretend self-bans don't exist to prevent victim chasing
-
- /* Filter secret from registry. */
-
- ban = ban as BanModeratorSchema;
-
- delete ban.ip;
-
- return res.json(ban);
-});
-
-router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const banned_user_id = req.params.user_id;
- if ((req.user_id === banned_user_id) && (banned_user_id === req.permission!.cache.guild?.owner_id))
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-
- if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400);
-
- const banned_user = await User.getPublicUser(banned_user_id);
+ const bannedUsers: object[] = await Promise.all(promisesToAwait);
+
+ bans.forEach((ban, index) => {
+ const user = bannedUsers[index] as User;
+ bansObj.push({
+ reason: ban.reason,
+ user: {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ avatar: user.avatar,
+ public_flags: user.public_flags,
+ },
+ });
+ });
- const ban = Ban.create({
- user_id: banned_user_id,
- guild_id: guild_id,
- ip: getIpAdress(req),
- executor_id: req.user_id,
- reason: req.body.reason // || otherwise empty
- });
+ return res.json(bansObj);
+ },
+);
+
+router.get(
+ "/:user",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const user_id = req.params.ban;
+
+ let ban = (await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ })) as BanRegistrySchema;
+
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // pretend self-bans don't exist to prevent victim chasing
+
+ /* Filter secret from registry. */
+
+ ban = ban as BanModeratorSchema;
+
+ delete ban.ip;
+
+ return res.json(ban);
+ },
+);
+
+router.put(
+ "/:user_id",
+ route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const banned_user_id = req.params.user_id;
+
+ if (
+ req.user_id === banned_user_id &&
+ banned_user_id === req.permission!.cache.guild?.owner_id
+ )
+ throw new HTTPError(
+ "You are the guild owner, hence can't ban yourself",
+ 403,
+ );
+
+ if (req.permission!.cache.guild?.owner_id === banned_user_id)
+ throw new HTTPError("You can't ban the owner", 400);
+
+ const banned_user = await User.getPublicUser(banned_user_id);
+
+ const ban = Ban.create({
+ user_id: banned_user_id,
+ guild_id: guild_id,
+ ip: getIpAdress(req),
+ executor_id: req.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- await Promise.all([
- Member.removeFromGuild(banned_user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
- guild_id: guild_id,
- user: banned_user
- },
- guild_id: guild_id
- } as GuildBanAddEvent)
- ]);
-
- return res.json(ban);
-});
-
-router.put("/@me", route({ body: "BanCreateSchema" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
-
- const banned_user = await User.getPublicUser(req.params.user_id);
-
- if (req.permission!.cache.guild?.owner_id === req.params.user_id)
- throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
-
- const ban = Ban.create({
- user_id: req.params.user_id,
- guild_id: guild_id,
- ip: getIpAdress(req),
- executor_id: req.params.user_id,
- reason: req.body.reason // || otherwise empty
- });
-
- await Promise.all([
- Member.removeFromGuild(req.user_id, guild_id),
- ban.save(),
- emitEvent({
- event: "GUILD_BAN_ADD",
- data: {
+ await Promise.all([
+ Member.removeFromGuild(banned_user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user,
+ },
guild_id: guild_id,
- user: banned_user
- },
- guild_id: guild_id
- } as GuildBanAddEvent)
- ]);
+ } as GuildBanAddEvent),
+ ]);
+
+ return res.json(ban);
+ },
+);
+
+router.put(
+ "/@me",
+ route({ body: "BanCreateSchema" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+
+ const banned_user = await User.getPublicUser(req.params.user_id);
+
+ if (req.permission!.cache.guild?.owner_id === req.params.user_id)
+ throw new HTTPError(
+ "You are the guild owner, hence can't ban yourself",
+ 403,
+ );
+
+ const ban = Ban.create({
+ user_id: req.params.user_id,
+ guild_id: guild_id,
+ ip: getIpAdress(req),
+ executor_id: req.params.user_id,
+ reason: req.body.reason, // || otherwise empty
+ });
- return res.json(ban);
-});
+ await Promise.all([
+ Member.removeFromGuild(req.user_id, guild_id),
+ ban.save(),
+ emitEvent({
+ event: "GUILD_BAN_ADD",
+ data: {
+ guild_id: guild_id,
+ user: banned_user,
+ },
+ guild_id: guild_id,
+ } as GuildBanAddEvent),
+ ]);
-router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const { guild_id, user_id } = req.params;
+ return res.json(ban);
+ },
+);
- let ban = await Ban.findOneOrFail({ where: { guild_id: guild_id, user_id: user_id } });
+router.delete(
+ "/:user_id",
+ route({ permission: "BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, user_id } = req.params;
- if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
- // make self-bans irreversible and hide them from view to avoid victim chasing
+ let ban = await Ban.findOneOrFail({
+ where: { guild_id: guild_id, user_id: user_id },
+ });
- const banned_user = await User.getPublicUser(user_id);
+ if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN;
+ // make self-bans irreversible and hide them from view to avoid victim chasing
- await Promise.all([
- Ban.delete({
- user_id: user_id,
- guild_id
- }),
+ const banned_user = await User.getPublicUser(user_id);
- emitEvent({
- event: "GUILD_BAN_REMOVE",
- data: {
+ await Promise.all([
+ Ban.delete({
+ user_id: user_id,
guild_id,
- user: banned_user
- },
- guild_id
- } as GuildBanRemoveEvent)
- ]);
-
- return res.status(204).send();
-});
+ }),
+
+ emitEvent({
+ event: "GUILD_BAN_REMOVE",
+ data: {
+ guild_id,
+ user: banned_user,
+ },
+ guild_id,
+ } as GuildBanRemoveEvent),
+ ]);
+
+ return res.status(204).send();
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index 7a5b50d1..af17465d 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -1,5 +1,10 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelUpdateEvent, emitEvent, ChannelModifySchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelUpdateEvent,
+ emitEvent,
+ ChannelModifySchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
const router = Router();
@@ -11,49 +16,77 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.json(channels);
});
-router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
- const { guild_id } = req.params;
- const body = req.body as ChannelModifySchema;
+router.post(
+ "/",
+ route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
+ const { guild_id } = req.params;
+ const body = req.body as ChannelModifySchema;
- const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
+ const channel = await Channel.createChannel(
+ { ...body, guild_id },
+ req.user_id,
+ );
- res.status(201).json(channel);
-});
+ res.status(201).json(channel);
+ },
+);
-export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string; }[];
+export type ChannelReorderSchema = {
+ id: string;
+ position?: number;
+ lock_permissions?: boolean;
+ parent_id?: string;
+}[];
-router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
- // changes guild channel position
- const { guild_id } = req.params;
- const body = req.body as ChannelReorderSchema;
+router.patch(
+ "/",
+ route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }),
+ async (req: Request, res: Response) => {
+ // changes guild channel position
+ const { guild_id } = req.params;
+ const body = req.body as ChannelReorderSchema;
- await Promise.all([
- body.map(async (x) => {
- if (x.position == null && !x.parent_id) throw new HTTPError(`You need to at least specify position or parent_id`, 400);
+ await Promise.all([
+ body.map(async (x) => {
+ if (x.position == null && !x.parent_id)
+ throw new HTTPError(
+ `You need to at least specify position or parent_id`,
+ 400,
+ );
- const opts: any = {};
- if (x.position != null) opts.position = x.position;
+ const opts: any = {};
+ if (x.position != null) opts.position = x.position;
- if (x.parent_id) {
- opts.parent_id = x.parent_id;
- const parent_channel = await Channel.findOneOrFail({
- where: { id: x.parent_id, guild_id },
- select: ["permission_overwrites"]
- });
- if (x.lock_permissions) {
- opts.permission_overwrites = parent_channel.permission_overwrites;
+ if (x.parent_id) {
+ opts.parent_id = x.parent_id;
+ const parent_channel = await Channel.findOneOrFail({
+ where: { id: x.parent_id, guild_id },
+ select: ["permission_overwrites"],
+ });
+ if (x.lock_permissions) {
+ opts.permission_overwrites =
+ parent_channel.permission_overwrites;
+ }
}
- }
- await Channel.update({ guild_id, id: x.id }, opts);
- const channel = await Channel.findOneOrFail({ where: { guild_id, id: x.id } });
+ await Channel.update({ guild_id, id: x.id }, opts);
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id, id: x.id },
+ });
- await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent);
- })
- ]);
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: channel,
+ channel_id: x.id,
+ guild_id,
+ } as ChannelUpdateEvent);
+ }),
+ ]);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index bd158c56..b951e4f4 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -1,4 +1,14 @@
-import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util";
+import {
+ Channel,
+ emitEvent,
+ GuildDeleteEvent,
+ Guild,
+ Member,
+ Message,
+ Role,
+ Invite,
+ Emoji,
+} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -10,18 +20,22 @@ const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
var { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] });
- if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401);
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id !== req.user_id)
+ throw new HTTPError("You are not the owner of this guild", 401);
await Promise.all([
Guild.delete({ id: guild_id }), // this will also delete all guild related data
emitEvent({
event: "GUILD_DELETE",
data: {
- id: guild_id
+ id: guild_id,
},
- guild_id: guild_id
- } as GuildDeleteEvent)
+ guild_id: guild_id,
+ } as GuildDeleteEvent),
]);
return res.sendStatus(204);
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index ad20633f..7e63c06b 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -6,33 +6,33 @@ import { route } from "@fosscord/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
res.send({
guild_id: guild_id,
safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0
- },
- minimum_size: 0
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
});
});
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index cf9d742a..6e8570eb 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -1,5 +1,17 @@
import { Router, Request, Response } from "express";
-import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User, EmojiCreateSchema, EmojiModifySchema } from "@fosscord/util";
+import {
+ Config,
+ DiscordApiErrors,
+ emitEvent,
+ Emoji,
+ GuildEmojisUpdateEvent,
+ handleFile,
+ Member,
+ Snowflake,
+ User,
+ EmojiCreateSchema,
+ EmojiModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
@@ -9,7 +21,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emojis = await Emoji.find({ where: { guild_id: guild_id }, relations: ["user"] });
+ const emojis = await Emoji.find({
+ where: { guild_id: guild_id },
+ relations: ["user"],
+ });
return res.json(emojis);
});
@@ -19,89 +34,115 @@ router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emoji = await Emoji.findOneOrFail({ where: { guild_id: guild_id, id: emoji_id }, relations: ["user"] });
+ const emoji = await Emoji.findOneOrFail({
+ where: { guild_id: guild_id, id: emoji_id },
+ relations: ["user"],
+ });
return res.json(emoji);
});
-router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as EmojiCreateSchema;
-
- const id = Snowflake.generate();
- const emoji_count = await Emoji.count({ where: { guild_id: guild_id } });
- const { maxEmojis } = Config.get().limits.guild;
-
- if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
- if (body.require_colons == null) body.require_colons = true;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
- body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
-
- const emoji = await Emoji.create({
- id: id,
- guild_id: guild_id,
- ...body,
- require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
- user: user,
- managed: false,
- animated: false, // TODO: Add support animated emojis
- available: true,
- roles: []
- }).save();
-
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
+router.post(
+ "/",
+ route({
+ body: "EmojiCreateSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as EmojiCreateSchema;
+
+ const id = Snowflake.generate();
+ const emoji_count = await Emoji.count({
+ where: { guild_id: guild_id },
+ });
+ const { maxEmojis } = Config.get().limits.guild;
+
+ if (emoji_count >= maxEmojis)
+ throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(
+ maxEmojis,
+ );
+ if (body.require_colons == null) body.require_colons = true;
+
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+ body.image = (await handleFile(`/emojis/${id}`, body.image)) as string;
+
+ const emoji = await Emoji.create({
+ id: id,
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
- } as GuildEmojisUpdateEvent);
+ ...body,
+ require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
+ user: user,
+ managed: false,
+ animated: false, // TODO: Add support animated emojis
+ available: true,
+ roles: [],
+ }).save();
- return res.status(201).json(emoji);
-});
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
+ guild_id: guild_id,
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
+
+ return res.status(201).json(emoji);
+ },
+);
router.patch(
"/:emoji_id",
- route({ body: "EmojiModifySchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ route({
+ body: "EmojiModifySchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;
const body = req.body as EmojiModifySchema;
- const emoji = await Emoji.create({ ...body, id: emoji_id, guild_id: guild_id }).save();
+ const emoji = await Emoji.create({
+ ...body,
+ id: emoji_id,
+ guild_id: guild_id,
+ }).save();
await emitEvent({
event: "GUILD_EMOJIS_UPDATE",
guild_id: guild_id,
data: {
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
} as GuildEmojisUpdateEvent);
return res.json(emoji);
- }
+ },
);
-router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { emoji_id, guild_id } = req.params;
+router.delete(
+ "/:emoji_id",
+ route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ async (req: Request, res: Response) => {
+ const { emoji_id, guild_id } = req.params;
- await Emoji.delete({
- id: emoji_id,
- guild_id: guild_id
- });
+ await Emoji.delete({
+ id: emoji_id,
+ guild_id: guild_id,
+ });
- await emitEvent({
- event: "GUILD_EMOJIS_UPDATE",
- guild_id: guild_id,
- data: {
+ await emitEvent({
+ event: "GUILD_EMOJIS_UPDATE",
guild_id: guild_id,
- emojis: await Emoji.find({ where: { guild_id: guild_id } })
- }
- } as GuildEmojisUpdateEvent);
+ data: {
+ guild_id: guild_id,
+ emojis: await Emoji.find({ where: { guild_id: guild_id } }),
+ },
+ } as GuildEmojisUpdateEvent);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index afeb0938..715a3835 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -1,5 +1,15 @@
import { Request, Response, Router } from "express";
-import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member, GuildCreateSchema } from "@fosscord/util";
+import {
+ DiscordApiErrors,
+ emitEvent,
+ getPermission,
+ getRights,
+ Guild,
+ GuildUpdateEvent,
+ handleFile,
+ Member,
+ GuildCreateSchema,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
@@ -26,9 +36,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const [guild, member] = await Promise.all([
Guild.findOneOrFail({ where: { id: guild_id } }),
- Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })
+ Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
]);
- if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
+ if (!member)
+ throw new HTTPError(
+ "You are not a member of the guild you are trying to access",
+ 401,
+ );
// @ts-ignore
guild.joined_at = member?.joined_at;
@@ -36,39 +50,57 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.send(guild);
});
-router.patch("/", route({ body: "GuildUpdateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as GuildUpdateSchema;
- const { guild_id } = req.params;
-
-
- const rights = await getRights(req.user_id);
- const permission = await getPermission(req.user_id, guild_id);
-
- if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD"))
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD");
-
- // TODO: guild update check image
-
- if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
- if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
- if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
-
- var guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- relations: ["emojis", "roles", "stickers"]
- });
- // TODO: check if body ids are valid
- guild.assign(body);
-
- const data = guild.toJSON();
- // TODO: guild hashes
- // TODO: fix vanity_url_code, template_id
- delete data.vanity_url_code;
- delete data.template_id;
-
- await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data, guild_id } as GuildUpdateEvent)]);
-
- return res.json(data);
-});
+router.patch(
+ "/",
+ route({ body: "GuildUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as GuildUpdateSchema;
+ const { guild_id } = req.params;
+
+ const rights = await getRights(req.user_id);
+ const permission = await getPermission(req.user_id, guild_id);
+
+ if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD"))
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
+ "MANAGE_GUILD",
+ );
+
+ // TODO: guild update check image
+
+ if (body.icon)
+ body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
+ if (body.banner)
+ body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
+ if (body.splash)
+ body.splash = await handleFile(
+ `/splashes/${guild_id}`,
+ body.splash,
+ );
+
+ var guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ relations: ["emojis", "roles", "stickers"],
+ });
+ // TODO: check if body ids are valid
+ guild.assign(body);
+
+ const data = guild.toJSON();
+ // TODO: guild hashes
+ // TODO: fix vanity_url_code, template_id
+ delete data.vanity_url_code;
+ delete data.template_id;
+
+ await Promise.all([
+ guild.save(),
+ emitEvent({
+ event: "GUILD_UPDATE",
+ data,
+ guild_id,
+ } as GuildUpdateEvent),
+ ]);
+
+ return res.json(data);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index b7534e31..4d033e9c 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -4,12 +4,19 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+router.get(
+ "/",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation });
+ const invites = await Invite.find({
+ where: { guild_id },
+ relations: PublicInviteRelation,
+ });
- return res.json(invites);
-});
+ return res.json(invites);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 265a1b35..c2f946b2 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -2,12 +2,12 @@ import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: member verification
res.status(404).json({
message: "Unknown Guild Member Verification Form",
- code: 10068
+ code: 10068,
});
});
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 407619d3..2d867920 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -1,5 +1,16 @@
import { Request, Response, Router } from "express";
-import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Guild, MemberChangeSchema } from "@fosscord/util";
+import {
+ Member,
+ getPermission,
+ getRights,
+ Role,
+ GuildMemberUpdateEvent,
+ emitEvent,
+ Sticker,
+ Emoji,
+ Guild,
+ MemberChangeSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
@@ -8,48 +19,63 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- const member = await Member.findOneOrFail({ where: { id: member_id, guild_id } });
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ });
return res.json(member);
});
-router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => {
- let { guild_id, member_id } = req.params;
- if (member_id === "@me") member_id = req.user_id;
- const body = req.body as MemberChangeSchema;
-
- const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] });
- const permission = await getPermission(req.user_id, guild_id);
- const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } });
-
- if (body.roles) {
- permission.hasThrow("MANAGE_ROLES");
-
- if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id);
- member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist
- }
-
- if ('nick' in body) {
- permission.hasThrow(req.user_id == member.user.id ? "CHANGE_NICKNAME" : "MANAGE_NICKNAMES");
- member.nick = body.nick?.trim() || undefined;
- }
-
- await member.save();
-
- member.roles = member.roles.filter((x) => x.id !== everyone.id);
-
- // do not use promise.all as we have to first write to db before emitting the event to catch errors
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- guild_id,
- data: { ...member, roles: member.roles.map((x) => x.id) }
- } as GuildMemberUpdateEvent);
-
- res.json(member);
-});
+router.patch(
+ "/",
+ route({ body: "MemberChangeSchema" }),
+ async (req: Request, res: Response) => {
+ let { guild_id, member_id } = req.params;
+ if (member_id === "@me") member_id = req.user_id;
+ const body = req.body as MemberChangeSchema;
+
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ relations: ["roles", "user"],
+ });
+ const permission = await getPermission(req.user_id, guild_id);
+ const everyone = await Role.findOneOrFail({
+ where: { guild_id: guild_id, name: "@everyone", position: 0 },
+ });
+
+ if (body.roles) {
+ permission.hasThrow("MANAGE_ROLES");
+
+ if (body.roles.indexOf(everyone.id) === -1)
+ body.roles.push(everyone.id);
+ member.roles = body.roles.map((x) => Role.create({ id: x })); // foreign key constraint will fail if role doesn't exist
+ }
+
+ if ("nick" in body) {
+ permission.hasThrow(
+ req.user_id == member.user.id
+ ? "CHANGE_NICKNAME"
+ : "MANAGE_NICKNAMES",
+ );
+ member.nick = body.nick?.trim() || undefined;
+ }
+
+ await member.save();
+
+ member.roles = member.roles.filter((x) => x.id !== everyone.id);
+
+ // do not use promise.all as we have to first write to db before emitting the event to catch errors
+ await emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ guild_id,
+ data: { ...member, roles: member.roles.map((x) => x.id) },
+ } as GuildMemberUpdateEvent);
+
+ res.json(member);
+ },
+);
router.put("/", route({}), async (req: Request, res: Response) => {
-
// TODO: Lurker mode
const rights = await getRights(req.user_id);
@@ -59,23 +85,23 @@ router.put("/", route({}), async (req: Request, res: Response) => {
member_id = req.user_id;
rights.hasThrow("JOIN_GUILDS");
} else {
- // TODO: join others by controller
+ // TODO: join others by controller
}
var guild = await Guild.findOneOrFail({
- where: { id: guild_id }
+ where: { id: guild_id },
});
var emoji = await Emoji.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
var roles = await Role.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
var stickers = await Sticker.find({
- where: { guild_id: guild_id }
+ where: { guild_id: guild_id },
});
await Member.addToGuild(member_id, guild_id);
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index edd47605..20443821 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -4,19 +4,23 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => {
- var { guild_id, member_id } = req.params;
- var permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
- if (member_id === "@me") {
- member_id = req.user_id;
- permissionString = "CHANGE_NICKNAME";
- }
+router.patch(
+ "/",
+ route({ body: "MemberNickChangeSchema" }),
+ async (req: Request, res: Response) => {
+ var { guild_id, member_id } = req.params;
+ var permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ permissionString = "CHANGE_NICKNAME";
+ }
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow(permissionString);
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow(permissionString);
- await Member.changeNickname(member_id, guild_id, req.body.nick);
- res.status(200).send();
-});
+ await Member.changeNickname(member_id, guild_id, req.body.nick);
+ res.status(200).send();
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 8f5ca7ba..c0383912 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -4,18 +4,26 @@ import { Request, Response, Router } from "express";
const router = Router();
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+router.delete(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.removeRole(member_id, guild_id, role_id);
- res.sendStatus(204);
-});
+ await Member.removeRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
+);
-router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id, member_id } = req.params;
+router.put(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id, member_id } = req.params;
- await Member.addRole(member_id, guild_id, role_id);
- res.sendStatus(204);
-});
+ await Member.addRole(member_id, guild_id, role_id);
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index b730a4e7..b516b9e9 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -12,7 +12,8 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const limit = Number(req.query.limit) || 1;
- if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000");
+ if (limit > 1000 || limit < 1)
+ throw new HTTPError("Limit must be between 1 and 1000");
const after = `${req.query.after}`;
const query = after ? { id: MoreThan(after) } : {};
@@ -22,7 +23,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
where: { guild_id, ...query },
select: PublicMemberProjection,
take: limit,
- order: { id: "ASC" }
+ order: { id: "ASC" },
});
return res.json(members);
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index a7516ebd..f2d8087e 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -10,36 +10,62 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const {
channel_id,
content,
- include_nsfw, // TODO
+ include_nsfw, // TODO
offset,
sort_order,
- sort_by, // TODO: Handle 'relevance'
+ sort_by, // TODO: Handle 'relevance'
limit,
author_id,
} = req.query;
const parsedLimit = Number(limit) || 50;
- if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422);
+ if (parsedLimit < 1 || parsedLimit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
if (sort_order) {
- if (typeof sort_order != "string"
- || ["desc", "asc"].indexOf(sort_order) == -1)
- throw FieldErrors({ sort_order: { message: "Value must be one of ('desc', 'asc').", code: "BASE_TYPE_CHOICES" } }); // todo this is wrong
+ if (
+ typeof sort_order != "string" ||
+ ["desc", "asc"].indexOf(sort_order) == -1
+ )
+ throw FieldErrors({
+ sort_order: {
+ message: "Value must be one of ('desc', 'asc').",
+ code: "BASE_TYPE_CHOICES",
+ },
+ }); // todo this is wrong
}
- const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string);
+ const permissions = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ channel_id as string,
+ );
permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 });
+ if (!permissions.has("READ_MESSAGE_HISTORY"))
+ return res.json({ messages: [], total_results: 0 });
var query: FindManyOptions<Message> = {
- order: { timestamp: sort_order ? sort_order.toUpperCase() as "ASC" | "DESC" : "DESC" },
+ order: {
+ timestamp: sort_order
+ ? (sort_order.toUpperCase() as "ASC" | "DESC")
+ : "DESC",
+ },
take: parsedLimit || 0,
where: {
guild: {
id: req.params.guild_id,
},
},
- relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"],
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
skip: offset ? Number(offset) : 0,
};
//@ts-ignore
@@ -51,32 +77,34 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const messages: Message[] = await Message.find(query);
- const messagesDto = messages.map(x => [{
- id: x.id,
- type: x.type,
- content: x.content,
- channel_id: x.channel_id,
- author: {
- id: x.author?.id,
- username: x.author?.username,
- avatar: x.author?.avatar,
- avatar_decoration: null,
- discriminator: x.author?.discriminator,
- public_flags: x.author?.public_flags,
+ const messagesDto = messages.map((x) => [
+ {
+ id: x.id,
+ type: x.type,
+ content: x.content,
+ channel_id: x.channel_id,
+ author: {
+ id: x.author?.id,
+ username: x.author?.username,
+ avatar: x.author?.avatar,
+ avatar_decoration: null,
+ discriminator: x.author?.discriminator,
+ public_flags: x.author?.public_flags,
+ },
+ attachments: x.attachments,
+ embeds: x.embeds,
+ mentions: x.mentions,
+ mention_roles: x.mention_roles,
+ pinned: x.pinned,
+ mention_everyone: x.mention_everyone,
+ tts: x.tts,
+ timestamp: x.timestamp,
+ edited_timestamp: x.edited_timestamp,
+ flags: x.flags,
+ components: x.components,
+ hit: true,
},
- attachments: x.attachments,
- embeds: x.embeds,
- mentions: x.mentions,
- mention_roles: x.mention_roles,
- pinned: x.pinned,
- mention_everyone: x.mention_everyone,
- tts: x.tts,
- timestamp: x.timestamp,
- edited_timestamp: x.edited_timestamp,
- flags: x.flags,
- components: x.components,
- hit: true,
- }]);
+ ]);
return res.json({
messages: messagesDto,
@@ -84,4 +112,4 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
});
-export default router;
\ No newline at end of file
+export default router;
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 2e674349..d11244b1 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -5,7 +5,12 @@ import { route } from "@fosscord/api";
const router = Router();
//Returns all inactive members, respecting role hierarchy
-export const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
+export const inactiveMembers = async (
+ guild_id: string,
+ user_id: string,
+ days: number,
+ roles: string[] = [],
+) => {
var date = new Date();
date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar?
@@ -19,21 +24,27 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
where: [
{
guild_id,
- last_message_id: LessThan(minId.toString())
+ last_message_id: LessThan(minId.toString()),
},
{
- last_message_id: IsNull()
- }
+ last_message_id: IsNull(),
+ },
],
- relations: ["roles"]
+ relations: ["roles"],
});
console.log(members);
if (!members.length) return [];
//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
- if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
-
- const me = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["roles"] });
+ if (roles.length && members.length)
+ members = members.filter((user) =>
+ user.roles?.some((role) => roles.includes(role.id)),
+ );
+
+ const me = await Member.findOneOrFail({
+ where: { id: user_id, guild_id },
+ relations: ["roles"],
+ });
const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || []));
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@@ -44,8 +55,8 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n
member.roles?.some(
(role) =>
role.position < myHighestRole || //roles higher than me can't be kicked
- me.id === guild.owner_id //owner can kick anyone
- )
+ me.id === guild.owner_id, //owner can kick anyone
+ ),
);
return members;
@@ -57,23 +68,39 @@ router.get("/", route({}), async (req: Request, res: Response) => {
var roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
- const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
+ const members = await inactiveMembers(
+ req.params.guild_id,
+ req.user_id,
+ days,
+ roles as string[],
+ );
res.send({ pruned: members.length });
});
-router.post("/", route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), async (req: Request, res: Response) => {
- const days = parseInt(req.body.days);
-
- var roles = req.query.include_roles;
- if (typeof roles === "string") roles = [roles];
-
- const { guild_id } = req.params;
- const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
-
- await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
-
- res.send({ purged: members.length });
-});
+router.post(
+ "/",
+ route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
+ async (req: Request, res: Response) => {
+ const days = parseInt(req.body.days);
+
+ var roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles];
+
+ const { guild_id } = req.params;
+ const members = await inactiveMembers(
+ guild_id,
+ req.user_id,
+ days,
+ roles as string[],
+ );
+
+ await Promise.all(
+ members.map((x) => Member.removeFromGuild(x.id, guild_id)),
+ );
+
+ res.send({ purged: members.length });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index 308d5ee5..0b275ea4 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -9,7 +9,12 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
//TODO we should use an enum for guild's features and not hardcoded strings
- return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
+ return res.json(
+ await getVoiceRegions(
+ getIpAdress(req),
+ guild.features.includes("VIP_REGIONS"),
+ ),
+ );
});
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index 87cf5261..e274e3d0 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -1,5 +1,13 @@
import { Router, Request, Response } from "express";
-import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile, RoleModifySchema } from "@fosscord/util";
+import {
+ Role,
+ Member,
+ GuildRoleUpdateEvent,
+ GuildRoleDeleteEvent,
+ emitEvent,
+ handleFile,
+ RoleModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -12,57 +20,72 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.json(role);
});
-router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { guild_id, role_id } = req.params;
- if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role");
+router.delete(
+ "/",
+ route({ permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, role_id } = req.params;
+ if (role_id === guild_id)
+ throw new HTTPError("You can't delete the @everyone role");
- await Promise.all([
- Role.delete({
- id: role_id,
- guild_id: guild_id
- }),
- emitEvent({
- event: "GUILD_ROLE_DELETE",
- guild_id,
- data: {
+ await Promise.all([
+ Role.delete({
+ id: role_id,
+ guild_id: guild_id,
+ }),
+ emitEvent({
+ event: "GUILD_ROLE_DELETE",
guild_id,
- role_id
- }
- } as GuildRoleDeleteEvent)
- ]);
+ data: {
+ guild_id,
+ role_id,
+ },
+ } as GuildRoleDeleteEvent),
+ ]);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
// TODO: check role hierarchy
-router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const { role_id, guild_id } = req.params;
- const body = req.body as RoleModifySchema;
+router.patch(
+ "/",
+ route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const { role_id, guild_id } = req.params;
+ const body = req.body as RoleModifySchema;
- if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string);
- else body.icon = undefined;
+ if (body.icon && body.icon.length)
+ body.icon = await handleFile(
+ `/role-icons/${role_id}`,
+ body.icon as string,
+ );
+ else body.icon = undefined;
- const role = Role.create({
- ...body,
- id: role_id,
- guild_id,
- permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0"))
- });
-
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_UPDATE",
+ const role = Role.create({
+ ...body,
+ id: role_id,
guild_id,
- data: {
+ permissions: String(
+ req.permission!.bitfield & BigInt(body.permissions || "0"),
+ ),
+ });
+
+ await Promise.all([
+ role.save(),
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
guild_id,
- role
- }
- } as GuildRoleUpdateEvent)
- ]);
+ data: {
+ guild_id,
+ role,
+ },
+ } as GuildRoleUpdateEvent),
+ ]);
- res.json(role);
-});
+ res.json(role);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index c5a86400..e3c7373e 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -29,70 +29,87 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.json(roles);
});
-router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as RoleModifySchema;
-
- const role_count = await Role.count({ where: { guild_id } });
- const { maxRoles } = Config.get().limits.guild;
-
- if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
-
- const role = Role.create({
- // values before ...body are default and can be overriden
- position: 0,
- hoist: false,
- color: 0,
- mentionable: false,
- ...body,
- guild_id: guild_id,
- managed: false,
- permissions: String(req.permission!.bitfield & BigInt(body.permissions || "0")),
- tags: undefined,
- icon: undefined,
- unicode_emoji: undefined
- });
-
- await Promise.all([
- role.save(),
- emitEvent({
- event: "GUILD_ROLE_CREATE",
- guild_id,
- data: {
- guild_id,
- role: role
- }
- } as GuildRoleCreateEvent)
- ]);
-
- res.json(role);
-});
-
-router.patch("/", route({ body: "RolePositionUpdateSchema" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as RolePositionUpdateSchema;
-
- const perms = await getPermission(req.user_id, guild_id);
- perms.hasThrow("MANAGE_ROLES");
-
- await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position })));
-
- const roles = await Role.find({ where: body.map((x) => ({ id: x.id, guild_id })) });
-
- await Promise.all(
- roles.map((x) =>
+router.post(
+ "/",
+ route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as RoleModifySchema;
+
+ const role_count = await Role.count({ where: { guild_id } });
+ const { maxRoles } = Config.get().limits.guild;
+
+ if (role_count > maxRoles)
+ throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles);
+
+ const role = Role.create({
+ // values before ...body are default and can be overriden
+ position: 0,
+ hoist: false,
+ color: 0,
+ mentionable: false,
+ ...body,
+ guild_id: guild_id,
+ managed: false,
+ permissions: String(
+ req.permission!.bitfield & BigInt(body.permissions || "0"),
+ ),
+ tags: undefined,
+ icon: undefined,
+ unicode_emoji: undefined,
+ });
+
+ await Promise.all([
+ role.save(),
emitEvent({
- event: "GUILD_ROLE_UPDATE",
+ event: "GUILD_ROLE_CREATE",
guild_id,
data: {
guild_id,
- role: x
- }
- } as GuildRoleUpdateEvent)
- )
- );
-
- res.json(roles);
-});
+ role: role,
+ },
+ } as GuildRoleCreateEvent),
+ ]);
+
+ res.json(role);
+ },
+);
+
+router.patch(
+ "/",
+ route({ body: "RolePositionUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as RolePositionUpdateSchema;
+
+ const perms = await getPermission(req.user_id, guild_id);
+ perms.hasThrow("MANAGE_ROLES");
+
+ await Promise.all(
+ body.map(async (x) =>
+ Role.update({ guild_id, id: x.id }, { position: x.position }),
+ ),
+ );
+
+ const roles = await Role.find({
+ where: body.map((x) => ({ id: x.id, guild_id })),
+ });
+
+ await Promise.all(
+ roles.map((x) =>
+ emitEvent({
+ event: "GUILD_ROLE_UPDATE",
+ guild_id,
+ data: {
+ guild_id,
+ role: x,
+ },
+ } as GuildRoleUpdateEvent),
+ ),
+ );
+
+ res.json(roles);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index fc0f49ab..3b1f5f8e 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -26,15 +26,18 @@ const bodyParser = multer({
limits: {
fileSize: 1024 * 1024 * 100,
fields: 10,
- files: 1
+ files: 1,
},
- storage: multer.memoryStorage()
+ storage: multer.memoryStorage(),
}).single("file");
router.post(
"/",
bodyParser,
- route({ permission: "MANAGE_EMOJIS_AND_STICKERS", body: "ModifyGuildStickerSchema" }),
+ route({
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ body: "ModifyGuildStickerSchema",
+ }),
async (req: Request, res: Response) => {
if (!req.file) throw new HTTPError("missing file");
@@ -49,15 +52,15 @@ router.post(
id,
type: StickerType.GUILD,
format_type: getStickerFormat(req.file.mimetype),
- available: true
+ available: true,
}).save(),
- uploadFile(`/stickers/${id}`, req.file)
+ uploadFile(`/stickers/${id}`, req.file),
]);
await sendStickerUpdateEvent(guild_id);
res.json(sticker);
- }
+ },
);
export function getStickerFormat(mime_type: string) {
@@ -71,7 +74,9 @@ export function getStickerFormat(mime_type: string) {
case "image/gif":
return StickerFormatType.GIF;
default:
- throw new HTTPError("invalid sticker format: must be png, apng or lottie");
+ throw new HTTPError(
+ "invalid sticker format: must be png, apng or lottie",
+ );
}
}
@@ -79,21 +84,30 @@ router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }));
+ res.json(
+ await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }),
+ );
});
router.patch(
"/:sticker_id",
- route({ body: "ModifyGuildStickerSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ route({
+ body: "ModifyGuildStickerSchema",
+ permission: "MANAGE_EMOJIS_AND_STICKERS",
+ }),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
const body = req.body as ModifyGuildStickerSchema;
- const sticker = await Sticker.create({ ...body, guild_id, id: sticker_id }).save();
+ const sticker = await Sticker.create({
+ ...body,
+ guild_id,
+ id: sticker_id,
+ }).save();
await sendStickerUpdateEvent(guild_id);
return res.json(sticker);
- }
+ },
);
async function sendStickerUpdateEvent(guild_id: string) {
@@ -102,18 +116,22 @@ async function sendStickerUpdateEvent(guild_id: string) {
guild_id: guild_id,
data: {
guild_id: guild_id,
- stickers: await Sticker.find({ where: { guild_id: guild_id } })
- }
+ stickers: await Sticker.find({ where: { guild_id: guild_id } }),
+ },
} as GuildStickersUpdateEvent);
}
-router.delete("/:sticker_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), async (req: Request, res: Response) => {
- const { guild_id, sticker_id } = req.params;
+router.delete(
+ "/:sticker_id",
+ route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
+ async (req: Request, res: Response) => {
+ const { guild_id, sticker_id } = req.params;
- await Sticker.delete({ guild_id, id: sticker_id });
- await sendStickerUpdateEvent(guild_id);
+ await Sticker.delete({ guild_id, id: sticker_id });
+ await sendStickerUpdateEvent(guild_id);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 628321f5..3b5eddaa 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -20,63 +20,97 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"afk_channel_id",
"system_channel_id",
"system_channel_flags",
- "icon"
+ "icon",
];
router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params;
- var templates = await Template.find({ where: { source_guild_id: guild_id } });
-
- return res.json(templates);
-});
-
-router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
- const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => { });
- if (exists) throw new HTTPError("Template already exists", 400);
-
- const template = await Template.create({
- ...req.body,
- code: generateCode(),
- creator_id: req.user_id,
- created_at: new Date(),
- updated_at: new Date(),
- source_guild_id: guild_id,
- serialized_source_guild: guild
- }).save();
-
- res.json(template);
-});
-
-router.delete("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
-
- const template = await Template.delete({
- code,
- source_guild_id: guild_id
+ var templates = await Template.find({
+ where: { source_guild_id: guild_id },
});
- res.json(template);
-});
-
-router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
-
- const template = await Template.create({ code, serialized_source_guild: guild }).save();
-
- res.json(template);
+ return res.json(templates);
});
-router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { code, guild_id } = req.params;
- const { name, description } = req.body;
-
- const template = await Template.create({ code, name: name, description: description, source_guild_id: guild_id }).save();
-
- res.json(template);
-});
+router.post(
+ "/",
+ route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
+ const exists = await Template.findOneOrFail({
+ where: { id: guild_id },
+ }).catch((e) => {});
+ if (exists) throw new HTTPError("Template already exists", 400);
+
+ const template = await Template.create({
+ ...req.body,
+ code: generateCode(),
+ creator_id: req.user_id,
+ created_at: new Date(),
+ updated_at: new Date(),
+ source_guild_id: guild_id,
+ serialized_source_guild: guild,
+ }).save();
+
+ res.json(template);
+ },
+);
+
+router.delete(
+ "/:code",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+
+ const template = await Template.delete({
+ code,
+ source_guild_id: guild_id,
+ });
+
+ res.json(template);
+ },
+);
+
+router.put(
+ "/:code",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: TemplateGuildProjection,
+ });
+
+ const template = await Template.create({
+ code,
+ serialized_source_guild: guild,
+ }).save();
+
+ res.json(template);
+ },
+);
+
+router.patch(
+ "/:code",
+ route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { code, guild_id } = req.params;
+ const { name, description } = req.body;
+
+ const template = await Template.create({
+ code,
+ name: name,
+ description: description,
+ source_guild_id: guild_id,
+ }).save();
+
+ res.json(template);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index d1fe4726..9a96b066 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -1,4 +1,10 @@
-import { Channel, ChannelType, Guild, Invite, VanityUrlSchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelType,
+ Guild,
+ Invite,
+ VanityUrlSchema,
+} from "@fosscord/util";
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -7,52 +13,70 @@ const router = Router();
const InviteRegex = /\W/g;
-router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-
- if (!guild.features.includes("ALIASABLE_NAMES")) {
- const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } });
- if (!invite) return res.json({ code: null });
-
- return res.json({ code: invite.code, uses: invite.uses });
- } else {
- const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } });
- if (!invite || invite.length == 0) return res.json({ code: null });
-
- return res.json(invite.map((x) => ({ code: x.code, uses: x.uses })));
- }
-});
-
-router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- const body = req.body as VanityUrlSchema;
- const code = body.code?.replace(InviteRegex, "");
-
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls");
-
- if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty");
-
- const invite = await Invite.findOne({ where: { code } });
- if (invite) throw new HTTPError("Invite already exists");
-
- const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } });
-
- await Invite.create({
- vanity_url: true,
- code: code,
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: 0,
- created_at: new Date(),
- expires_at: new Date(),
- guild_id: guild_id,
- channel_id: id
- }).save();
-
- return res.json({ code: code });
-});
+router.get(
+ "/",
+ route({ permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+
+ if (!guild.features.includes("ALIASABLE_NAMES")) {
+ const invite = await Invite.findOne({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite) return res.json({ code: null });
+
+ return res.json({ code: invite.code, uses: invite.uses });
+ } else {
+ const invite = await Invite.find({
+ where: { guild_id: guild_id, vanity_url: true },
+ });
+ if (!invite || invite.length == 0) return res.json({ code: null });
+
+ return res.json(
+ invite.map((x) => ({ code: x.code, uses: x.uses })),
+ );
+ }
+ },
+);
+
+router.patch(
+ "/",
+ route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const body = req.body as VanityUrlSchema;
+ const code = body.code?.replace(InviteRegex, "");
+
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.features.includes("VANITY_URL"))
+ throw new HTTPError("Your guild doesn't support vanity urls");
+
+ if (!code || code.length === 0)
+ throw new HTTPError("Code cannot be null or empty");
+
+ const invite = await Invite.findOne({ where: { code } });
+ if (invite) throw new HTTPError("Invite already exists");
+
+ const { id } = await Channel.findOneOrFail({
+ where: { guild_id, type: ChannelType.GUILD_TEXT },
+ });
+
+ await Invite.create({
+ vanity_url: true,
+ code: code,
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: 0,
+ created_at: new Date(),
+ expires_at: new Date(),
+ guild_id: guild_id,
+ channel_id: id,
+ }).save();
+
+ return res.json({ code: code });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 006e997f..af03a07e 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -1,52 +1,71 @@
-import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent, VoiceStateUpdateSchema } from "@fosscord/util";
+import {
+ Channel,
+ ChannelType,
+ DiscordApiErrors,
+ emitEvent,
+ getPermission,
+ VoiceState,
+ VoiceStateUpdateEvent,
+ VoiceStateUpdateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { Request, Response, Router } from "express";
const router = Router();
//TODO need more testing when community guild and voice stage channel are working
-router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as VoiceStateUpdateSchema;
- var { guild_id, user_id } = req.params;
- if (user_id === "@me") user_id = req.user_id;
+router.patch(
+ "/",
+ route({ body: "VoiceStateUpdateSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as VoiceStateUpdateSchema;
+ var { guild_id, user_id } = req.params;
+ if (user_id === "@me") user_id = req.user_id;
- const perms = await getPermission(req.user_id, guild_id, body.channel_id);
+ const perms = await getPermission(
+ req.user_id,
+ guild_id,
+ body.channel_id,
+ );
- /*
+ /*
From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself.
You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
*/
- if (body.suppress && user_id !== req.user_id) {
- perms.hasThrow("MUTE_MEMBERS");
- }
- if (!body.suppress) body.request_to_speak_timestamp = new Date();
- if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
-
- const voice_state = await VoiceState.findOne({
- where: {
- guild_id,
- channel_id: body.channel_id,
- user_id
+ if (body.suppress && user_id !== req.user_id) {
+ perms.hasThrow("MUTE_MEMBERS");
+ }
+ if (!body.suppress) body.request_to_speak_timestamp = new Date();
+ if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK");
+
+ const voice_state = await VoiceState.findOne({
+ where: {
+ guild_id,
+ channel_id: body.channel_id,
+ user_id,
+ },
+ });
+ if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
+
+ voice_state.assign(body);
+ const channel = await Channel.findOneOrFail({
+ where: { guild_id, id: body.channel_id },
+ });
+ if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
+ throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
}
- });
- if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
-
- voice_state.assign(body);
- const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } });
- if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
- throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
- }
-
- await Promise.all([
- voice_state.save(),
- emitEvent({
- event: "VOICE_STATE_UPDATE",
- data: voice_state,
- guild_id
- } as VoiceStateUpdateEvent)
- ]);
- return res.sendStatus(204);
-});
+
+ await Promise.all([
+ voice_state.save(),
+ emitEvent({
+ event: "VOICE_STATE_UPDATE",
+ data: voice_state,
+ guild_id,
+ } as VoiceStateUpdateEvent),
+ ]);
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index 57da062d..80ab138b 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -14,20 +14,30 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.json(guild.welcome_screen);
});
-router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
- const body = req.body as GuildUpdateWelcomeScreenSchema;
-
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
-
- if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400);
- if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
- if (body.description) guild.welcome_screen.description = body.description;
- if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
-
- await guild.save();
-
- res.sendStatus(204);
-});
+router.patch(
+ "/",
+ route({
+ body: "GuildUpdateWelcomeScreenSchema",
+ permission: "MANAGE_GUILD",
+ }),
+ async (req: Request, res: Response) => {
+ const guild_id = req.params.guild_id;
+ const body = req.body as GuildUpdateWelcomeScreenSchema;
+
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+
+ if (!guild.welcome_screen.enabled)
+ throw new HTTPError("Welcome screen disabled", 400);
+ if (body.welcome_channels)
+ guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid
+ if (body.description)
+ guild.welcome_screen.description = body.description;
+ if (body.enabled != null) guild.welcome_screen.enabled = body.enabled;
+
+ await guild.save();
+
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index be5bf23f..2c3124a2 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -1,5 +1,12 @@
import { Request, Response, Router } from "express";
-import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
+import {
+ Config,
+ Permissions,
+ Guild,
+ Invite,
+ Channel,
+ Member,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { random, route } from "@fosscord/api";
@@ -21,7 +28,9 @@ router.get("/", route({}), async (req: Request, res: Response) => {
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
// Fetch existing widget invite for widget channel
- var invite = await Invite.findOne({ where: { channel_id: guild.widget_channel_id } });
+ var invite = await Invite.findOne({
+ where: { channel_id: guild.widget_channel_id },
+ });
if (guild.widget_channel_id && !invite) {
// Create invite for channel if none exists
@@ -45,16 +54,24 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch voice channels, and the @everyone permissions object
const channels = [] as any[];
- (await Channel.find({ where: { guild_id: guild_id, type: 2 }, order: { position: "ASC" } })).filter((doc) => {
+ (
+ await Channel.find({
+ where: { guild_id: guild_id, type: 2 },
+ order: { position: "ASC" },
+ })
+ ).filter((doc) => {
// Only return channels where @everyone has the CONNECT permission
if (
doc.permission_overwrites === undefined ||
- Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT
+ Permissions.channelPermission(
+ doc.permission_overwrites,
+ Permissions.FLAGS.CONNECT,
+ ) === Permissions.FLAGS.CONNECT
) {
channels.push({
id: doc.id,
name: doc.name,
- position: doc.position
+ position: doc.position,
});
}
});
@@ -70,7 +87,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
instant_invite: invite?.code,
channels: channels,
members: members,
- presence_count: guild.presence_count
+ presence_count: guild.presence_count,
};
res.set("Cache-Control", "public, max-age=300");
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index c17d511e..eaec8f07 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -24,8 +24,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch parameter
const style = req.query.style?.toString() || "shield";
- if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) {
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ if (
+ !["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
+ ) {
+ throw new HTTPError(
+ "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+ 400,
+ );
}
// Setup canvas
@@ -34,7 +39,17 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const sizeOf = require("image-size");
// TODO: Widget style templates need Fosscord branding
- const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`);
+ const source = path.join(
+ __dirname,
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "assets",
+ "widget",
+ `${style}.png`,
+ );
if (!fs.existsSync(source)) {
throw new HTTPError("Widget template does not exist.", 400);
}
@@ -50,30 +65,68 @@ router.get("/", route({}), async (req: Request, res: Response) => {
switch (style) {
case "shield":
ctx.textAlign = "center";
- await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence);
+ await drawText(
+ ctx,
+ 73,
+ 13,
+ "#FFFFFF",
+ "thin 10px Verdana",
+ presence,
+ );
break;
case "banner1":
if (icon) await drawIcon(ctx, 20, 27, 50, icon);
await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
- await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 83,
+ 66,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner2":
if (icon) await drawIcon(ctx, 13, 19, 36, icon);
await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
- await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 62,
+ 49,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner3":
if (icon) await drawIcon(ctx, 20, 20, 50, icon);
await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
- await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence);
+ await drawText(
+ ctx,
+ 83,
+ 58,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
break;
case "banner4":
if (icon) await drawIcon(ctx, 21, 136, 50, icon);
await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
- await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence);
+ await drawText(
+ ctx,
+ 84,
+ 171,
+ "#C9D2F0FF",
+ "thin 12px Verdana",
+ presence,
+ );
break;
default:
- throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400);
+ throw new HTTPError(
+ "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+ 400,
+ );
}
// Return final image
@@ -83,7 +136,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return res.send(buffer);
});
-async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) {
+async function drawIcon(
+ canvas: any,
+ x: number,
+ y: number,
+ scale: number,
+ icon: string,
+) {
// @ts-ignore
const img = new require("canvas").Image();
img.src = icon;
@@ -101,10 +160,19 @@ async function drawIcon(canvas: any, x: number, y: number, scale: number, icon:
canvas.restore();
}
-async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) {
+async function drawText(
+ canvas: any,
+ x: number,
+ y: number,
+ color: string,
+ font: string,
+ text: string,
+ maxcharacters?: number,
+) {
canvas.fillStyle = color;
canvas.font = font;
- if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "...";
+ if (text.length > (maxcharacters || 0) && maxcharacters)
+ text = text.slice(0, maxcharacters) + "...";
canvas.fillText(text, x, y);
}
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index dbb4cc0c..108339e1 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -10,18 +10,31 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null });
+ return res.json({
+ enabled: guild.widget_enabled || false,
+ channel_id: guild.widget_channel_id || null,
+ });
});
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
-router.patch("/", route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
- const body = req.body as WidgetModifySchema;
- const { guild_id } = req.params;
-
- await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id });
- // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
-
- return res.json(body);
-});
+router.patch(
+ "/",
+ route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as WidgetModifySchema;
+ const { guild_id } = req.params;
+
+ await Guild.update(
+ { id: guild_id },
+ {
+ widget_enabled: body.enabled,
+ widget_channel_id: body.channel_id,
+ },
+ );
+ // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request
+
+ return res.json(body);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 0807cb96..69575aea 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -1,32 +1,47 @@
import { Router, Request, Response } from "express";
-import { Role, Guild, Config, getRights, Member, DiscordApiErrors, GuildCreateSchema } from "@fosscord/util";
+import {
+ Role,
+ Guild,
+ Config,
+ getRights,
+ Member,
+ DiscordApiErrors,
+ GuildCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
//TODO: create default channel
-router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), 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 guild = await Guild.createGuild({ ...body, owner_id: req.user_id });
-
- const { autoJoin } = Config.get().guild;
- if (autoJoin.enabled && !autoJoin.guilds?.length) {
- // @ts-ignore
- await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
- }
-
- await Member.addToGuild(req.user_id, guild.id);
-
- res.status(201).json({ id: guild.id });
-});
+router.post(
+ "/",
+ route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }),
+ 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 guild = await Guild.createGuild({
+ ...body,
+ owner_id: req.user_id,
+ });
+
+ const { autoJoin } = Config.get().guild;
+ if (autoJoin.enabled && !autoJoin.guilds?.length) {
+ // @ts-ignore
+ await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
+ }
+
+ await Member.addToGuild(req.user_id, guild.id);
+
+ res.status(201).json({ id: guild.id });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 4e7abcc5..240bf074 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -1,29 +1,58 @@
import { Request, Response, Router } from "express";
-import { Template, Guild, Role, Snowflake, Config, Member, GuildTemplateCreateSchema } from "@fosscord/util";
+import {
+ Template,
+ Guild,
+ Role,
+ Snowflake,
+ Config,
+ Member,
+ GuildTemplateCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { DiscordApiErrors } from "@fosscord/util";
import fetch from "node-fetch";
const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
- const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates;
- if (!enabled) res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
+ const { allowDiscordTemplates, allowRaws, enabled } =
+ Config.get().templates;
+ if (!enabled)
+ res.json({
+ code: 403,
+ message: "Template creation & usage is disabled on this instance.",
+ }).sendStatus(403);
const { code } = req.params;
if (code.startsWith("discord:")) {
- if (!allowDiscordTemplates) return res.json({ code: 403, message: "Discord templates cannot be used on this instance." }).sendStatus(403);
+ if (!allowDiscordTemplates)
+ return res
+ .json({
+ code: 403,
+ message:
+ "Discord templates cannot be used on this instance.",
+ })
+ .sendStatus(403);
const discordTemplateID = code.split("discord:", 2)[1];
- const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, {
- method: "get",
- headers: { "Content-Type": "application/json" }
- });
+ const discordTemplateData = await fetch(
+ `https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
+ {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
return res.json(await discordTemplateData.json());
}
if (code.startsWith("external:")) {
- if (!allowRaws) return res.json({ code: 403, message: "Importing raws is disabled on this instance." }).sendStatus(403);
+ if (!allowRaws)
+ return res
+ .json({
+ code: 403,
+ message: "Importing raws is disabled on this instance.",
+ })
+ .sendStatus(403);
return res.json(code.split("external:", 2)[1]);
}
@@ -32,48 +61,72 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
res.json(template);
});
-router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => {
- const { enabled, allowTemplateCreation, allowDiscordTemplates, allowRaws } = Config.get().templates;
- if (!enabled) return res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403);
- if (!allowTemplateCreation) return res.json({ code: 403, message: "Template creation is disabled on this instance." }).sendStatus(403);
-
- const { code } = req.params;
- const body = req.body as GuildTemplateCreateSchema;
-
- 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 template = await Template.findOneOrFail({ where: { code: code } });
+router.post(
+ "/:code",
+ route({ body: "GuildTemplateCreateSchema" }),
+ async (req: Request, res: Response) => {
+ const {
+ enabled,
+ allowTemplateCreation,
+ allowDiscordTemplates,
+ allowRaws,
+ } = Config.get().templates;
+ if (!enabled)
+ return res
+ .json({
+ code: 403,
+ message:
+ "Template creation & usage is disabled on this instance.",
+ })
+ .sendStatus(403);
+ if (!allowTemplateCreation)
+ return res
+ .json({
+ code: 403,
+ message: "Template creation is disabled on this instance.",
+ })
+ .sendStatus(403);
+
+ const { code } = req.params;
+ const body = req.body as GuildTemplateCreateSchema;
+
+ 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 template = await Template.findOneOrFail({
+ where: { code: code },
+ });
- const guild_id = Snowflake.generate();
-
- const [guild, role] = await Promise.all([
- Guild.create({
- ...body,
- ...template.serialized_source_guild,
- id: guild_id,
- owner_id: req.user_id
- }).save(),
- Role.create({
- id: guild_id,
- guild_id: guild_id,
- color: 0,
- hoist: false,
- managed: true,
- mentionable: true,
- name: "@everyone",
- permissions: BigInt("2251804225").toString(), // TODO: where did this come from?
- position: 0,
- }).save()
- ]);
-
- await Member.addToGuild(req.user_id, guild_id);
-
- res.status(201).json({ id: guild.id });
-});
+ const guild_id = Snowflake.generate();
+
+ const [guild, role] = await Promise.all([
+ Guild.create({
+ ...body,
+ ...template.serialized_source_guild,
+ id: guild_id,
+ owner_id: req.user_id,
+ }).save(),
+ Role.create({
+ id: guild_id,
+ guild_id: guild_id,
+ color: 0,
+ hoist: false,
+ managed: true,
+ mentionable: true,
+ name: "@everyone",
+ permissions: BigInt("2251804225").toString(), // TODO: where did this come from?
+ position: 0,
+ }).save(),
+ ]);
+
+ await Member.addToGuild(req.user_id, guild_id);
+
+ res.status(201).json({ id: guild.id });
+ },
+);
export default router;
diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index c268085f..ce0ba982 100644
--- a/src/api/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -1,5 +1,13 @@
import { Router, Request, Response } from "express";
-import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, User, PublicInviteRelation } from "@fosscord/util";
+import {
+ emitEvent,
+ getPermission,
+ Guild,
+ Invite,
+ InviteDeleteEvent,
+ User,
+ PublicInviteRelation,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import { HTTPError } from "lambert-server";
@@ -8,24 +16,45 @@ const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
const { code } = req.params;
- const invite = await Invite.findOneOrFail({ where: { code }, relations: PublicInviteRelation });
+ const invite = await Invite.findOneOrFail({
+ where: { code },
+ relations: PublicInviteRelation,
+ });
res.status(200).send(invite);
});
-router.post("/:code", route({ right: "USE_MASS_INVITES" }), async (req: Request, res: Response) => {
- const { code } = req.params;
- const { guild_id } = await Invite.findOneOrFail({ where: { code: code } });
- const { features } = await Guild.findOneOrFail({ where: { id: guild_id } });
- const { public_flags } = await User.findOneOrFail({ where: { id: req.user_id } });
+router.post(
+ "/:code",
+ route({ right: "USE_MASS_INVITES" }),
+ async (req: Request, res: Response) => {
+ const { code } = req.params;
+ const { guild_id } = await Invite.findOneOrFail({
+ where: { code: code },
+ });
+ const { features } = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
+ const { public_flags } = await User.findOneOrFail({
+ where: { id: req.user_id },
+ });
- if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) throw new HTTPError("Only intended for the staff of this server.", 401);
- if (features.includes("INVITES_CLOSED")) throw new HTTPError("Sorry, this guild has joins closed.", 403);
+ if (
+ features.includes("INTERNAL_EMPLOYEE_ONLY") &&
+ (public_flags & 1) !== 1
+ )
+ throw new HTTPError(
+ "Only intended for the staff of this server.",
+ 401,
+ );
+ if (features.includes("INVITES_CLOSED"))
+ throw new HTTPError("Sorry, this guild has joins closed.", 403);
- const invite = await Invite.joinGuild(req.user_id, code);
+ const invite = await Invite.joinGuild(req.user_id, code);
- res.json(invite);
-});
+ res.json(invite);
+ },
+);
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete("/:code", route({}), async (req: Request, res: Response) => {
@@ -36,7 +65,10 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => {
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);
+ throw new HTTPError(
+ "You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
+ 401,
+ );
await Promise.all([
Invite.delete({ code }),
@@ -46,9 +78,9 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => {
data: {
channel_id: channel_id,
guild_id: guild_id,
- code: code
- }
- } as InviteDeleteEvent)
+ code: code,
+ },
+ } as InviteDeleteEvent),
]);
res.json({ invite: invite });
diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 545c5c78..7e63c06b 100644
--- a/src/api/routes/partners/#guild_id/requirements.ts
+++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -1,4 +1,3 @@
-
import { Guild, Config } from "@fosscord/util";
import { Router, Request, Response } from "express";
@@ -7,33 +6,33 @@ import { route } from "@fosscord/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
res.send({
guild_id: guild_id,
safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0
- },
- minimum_size: 0
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
});
});
diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 20cd07ba..f22eac17 100644
--- a/src/api/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
@@ -1,16 +1,19 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
-import { config } from "dotenv"
+import { config } from "dotenv";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
- const { cdn, gateway } = Config.get();
-
- const IdentityForm = {
- cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
- gateway: gateway.endpointPublic || process.env.GATEWAY || "ws://localhost:3002"
- };
+router.get("/", route({}), async (req: Request, res: Response) => {
+ const { cdn, gateway } = Config.get();
+
+ const IdentityForm = {
+ cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
+ gateway:
+ gateway.endpointPublic ||
+ process.env.GATEWAY ||
+ "ws://localhost:3002",
+ };
res.json(IdentityForm);
});
diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index e3da014f..1c1afa09 100644
--- a/src/api/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
@@ -3,8 +3,7 @@ import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
const router = Router();
-
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
const { general } = Config.get();
res.json(general);
});
diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 7de1476b..06f14f83 100644
--- a/src/api/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
@@ -3,7 +3,7 @@ import { route } from "@fosscord/api";
import { Config } from "@fosscord/util";
const router = Router();
-router.get("/",route({}), async (req: Request, res: Response) => {
+router.get("/", route({}), async (req: Request, res: Response) => {
const { limits } = Config.get();
res.json(limits);
});
diff --git a/src/api/routes/scheduled-maintenances/upcoming_json.ts b/src/api/routes/scheduled-maintenances/upcoming_json.ts
index 83092e44..e42723a1 100644
--- a/src/api/routes/scheduled-maintenances/upcoming_json.ts
+++ b/src/api/routes/scheduled-maintenances/upcoming_json.ts
@@ -2,11 +2,15 @@ import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
const router = Router();
-router.get("/scheduled-maintenances/upcoming.json",route({}), async (req: Request, res: Response) => {
- res.json({
- "page": {},
- "scheduled_maintenances": {}
- });
-});
+router.get(
+ "/scheduled-maintenances/upcoming.json",
+ route({}),
+ async (req: Request, res: Response) => {
+ res.json({
+ page: {},
+ scheduled_maintenances: {},
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index 7f8b78ba..78abb9d7 100644
--- a/src/api/routes/stop.ts
+++ b/src/api/routes/stop.ts
@@ -6,17 +6,19 @@ const router: Router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
//EXPERIMENTAL: have an "OPERATOR" platform permission implemented for this API route
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["rights"] });
- if((Number(user.rights) << Number(0))%Number(2)==Number(1)) {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["rights"],
+ });
+ if ((Number(user.rights) << Number(0)) % Number(2) == Number(1)) {
console.log("user that POSTed to the API was ALLOWED");
console.log(user.rights);
- res.sendStatus(200)
- process.kill(process.pid, 'SIGTERM')
- }
- else {
+ res.sendStatus(200);
+ process.kill(process.pid, "SIGTERM");
+ } else {
console.log("operation failed");
console.log(user.rights);
- res.sendStatus(403)
+ res.sendStatus(403);
}
});
diff --git a/src/api/routes/store/published-listings/applications.ts b/src/api/routes/store/published-listings/applications.ts
index 060a4c3d..6156f43e 100644
--- a/src/api/routes/store/published-listings/applications.ts
+++ b/src/api/routes/store/published-listings/applications.ts
@@ -41,29 +41,29 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
publishers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
developers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
system_requirements: {},
show_age_gate: false,
price: {
amount: 0,
- currency: "EUR"
+ currency: "EUR",
},
- locales: []
+ locales: [],
},
tagline: "",
description: "",
carousel_items: [
{
- asset_id: ""
- }
+ asset_id: "",
+ },
],
header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
header_logo_light_theme: {},
@@ -71,8 +71,8 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
thumbnail: {},
header_background: {},
hero_background: {},
- assets: []
- }
+ assets: [],
+ },
}).status(200);
});
diff --git a/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
index 54151ae5..845cdfe7 100644
--- a/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
@@ -17,8 +17,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
fallback_currency: "eur",
currency: "eur",
price: 4199,
- price_tier: null
- }
+ price_tier: null,
+ },
]).status(200);
});
diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index 060a4c3d..6156f43e 100644
--- a/src/api/routes/store/published-listings/skus.ts
+++ b/src/api/routes/store/published-listings/skus.ts
@@ -41,29 +41,29 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
publishers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
developers: [
{
id: "",
- name: ""
- }
+ name: "",
+ },
],
system_requirements: {},
show_age_gate: false,
price: {
amount: 0,
- currency: "EUR"
+ currency: "EUR",
},
- locales: []
+ locales: [],
},
tagline: "",
description: "",
carousel_items: [
{
- asset_id: ""
- }
+ asset_id: "",
+ },
],
header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160}
header_logo_light_theme: {},
@@ -71,8 +71,8 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
thumbnail: {},
header_background: {},
hero_background: {},
- assets: []
- }
+ assets: [],
+ },
}).status(200);
});
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 03162ec8..33151056 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
@@ -17,8 +17,8 @@ const skus = new Map([
currency: "usd",
price: 0,
price_tier: null,
- }
- ]
+ },
+ ],
],
[
"521842865731534868",
@@ -32,7 +32,7 @@ const skus = new Map([
sku_id: "521842865731534868",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651860671627264",
@@ -43,9 +43,9 @@ const skus = new Map([
sku_id: "521842865731534868",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"521846918637420545",
@@ -59,7 +59,7 @@ const skus = new Map([
sku_id: "521846918637420545",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651876987469824",
@@ -70,9 +70,9 @@ const skus = new Map([
sku_id: "521846918637420545",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"521847234246082599",
@@ -86,7 +86,7 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651880837840896",
@@ -97,7 +97,7 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "511651885459963904",
@@ -108,9 +108,9 @@ const skus = new Map([
sku_id: "521847234246082599",
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
+ price_tier: null,
+ },
+ ],
],
[
"590663762298667008",
@@ -125,7 +125,7 @@ const skus = new Map([
discount_price: 0,
currency: "usd",
price: 0,
- price_tier: null
+ price_tier: null,
},
{
id: "590665538238152709",
@@ -137,10 +137,10 @@ const skus = new Map([
discount_price: 0,
currency: "usd",
price: 0,
- price_tier: null
- }
- ]
- ]
+ price_tier: null,
+ },
+ ],
+ ],
]);
router.get("/", route({}), async (req: Request, res: Response) => {
diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts
index 42f77323..8fe6fc2a 100644
--- a/src/api/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -7,13 +7,15 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const { client } = Config.get();
- const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } });
+ const release = await Release.findOneOrFail({
+ where: { name: client.releases.upstreamVersion },
+ });
res.json({
name: release.name,
pub_date: release.pub_date,
url: release.url,
- notes: release.notes
+ notes: release.notes,
});
});
diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index de96422a..ebea805b 100644
--- a/src/api/routes/users/#id/profile.ts
+++ b/src/api/routes/users/#id/profile.ts
@@ -1,5 +1,12 @@
import { Router, Request, Response } from "express";
-import { PublicConnectedAccount, PublicUser, User, UserPublic, Member, Guild } from "@fosscord/util";
+import {
+ PublicConnectedAccount,
+ PublicUser,
+ User,
+ UserPublic,
+ Member,
+ Guild,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -11,81 +18,102 @@ export interface UserProfileResponse {
premium_since?: Date;
}
-router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
- if (req.params.id === "@me") req.params.id = req.user_id;
+router.get(
+ "/",
+ route({ test: { response: { body: "UserProfileResponse" } } }),
+ async (req: Request, res: Response) => {
+ if (req.params.id === "@me") req.params.id = req.user_id;
- const { guild_id, with_mutual_guilds } = req.query;
+ const { guild_id, with_mutual_guilds } = req.query;
- const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
+ const user = await User.getPublicUser(req.params.id, {
+ relations: ["connected_accounts"],
+ });
- var mutual_guilds: object[] = [];
- var premium_guild_since;
+ var mutual_guilds: object[] = [];
+ var premium_guild_since;
- if (with_mutual_guilds == "true") {
- const requested_member = await Member.find({ where: { id: req.params.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.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) {
+ 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;
}
- } 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 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.id, guild_id: guild_id }, relations: ["roles"] })
- : undefined;
+ const guild_member =
+ guild_id && typeof guild_id == "string"
+ ? await Member.findOneOrFail({
+ where: { id: req.params.id, guild_id: guild_id },
+ relations: ["roles"],
+ })
+ : undefined;
- // TODO: make proper DTO's in util?
+ // TODO: make proper DTO's in util?
- const userDto = {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- public_flags: user.public_flags,
- avatar: user.avatar,
- accent_color: user.accent_color,
- banner: user.banner,
- bio: req.user_bot ? null : user.bio,
- bot: user.bot
- };
+ const userDto = {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ public_flags: user.public_flags,
+ avatar: user.avatar,
+ accent_color: user.accent_color,
+ banner: user.banner,
+ bio: req.user_bot ? null : user.bio,
+ bot: user.bot,
+ };
- const guildMemberDto = guild_member ? {
- avatar: user.avatar, // TODO
- banner: user.banner, // TODO
- bio: req.user_bot ? null : user.bio, // TODO
- communication_disabled_until: null, // TODO
- deaf: guild_member.deaf,
- flags: user.flags,
- is_pending: guild_member.pending,
- pending: guild_member.pending, // why is this here twice, discord?
- joined_at: guild_member.joined_at,
- mute: guild_member.mute,
- nick: guild_member.nick,
- premium_since: guild_member.premium_since,
- roles: guild_member.roles.map(x => x.id).filter(id => id != guild_id),
- user: userDto
- } : undefined;
+ const guildMemberDto = guild_member
+ ? {
+ avatar: user.avatar, // TODO
+ banner: user.banner, // TODO
+ bio: req.user_bot ? null : user.bio, // TODO
+ communication_disabled_until: null, // TODO
+ deaf: guild_member.deaf,
+ flags: user.flags,
+ is_pending: guild_member.pending,
+ pending: guild_member.pending, // why is this here twice, discord?
+ joined_at: guild_member.joined_at,
+ mute: guild_member.mute,
+ nick: guild_member.nick,
+ premium_since: guild_member.premium_since,
+ roles: guild_member.roles
+ .map((x) => x.id)
+ .filter((id) => id != guild_id),
+ user: userDto,
+ }
+ : undefined;
- res.json({
- connected_accounts: user.connected_accounts,
- premium_guild_since: premium_guild_since, // TODO
- premium_since: user.premium_since, // TODO
- mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
- user: userDto,
- guild_member: guildMemberDto,
- });
-});
+ res.json({
+ connected_accounts: user.connected_accounts,
+ premium_guild_since: premium_guild_since, // TODO
+ premium_since: user.premium_since, // TODO
+ mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
+ user: userDto,
+ guild_member: guildMemberDto,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts
index de7cb9d3..c6480567 100644
--- a/src/api/routes/users/#id/relationships.ts
+++ b/src/api/routes/users/#id/relationships.ts
@@ -6,36 +6,49 @@ const router: Router = Router();
export interface UserRelationsResponse {
object: {
- id?: string,
- username?: string,
- avatar?: string,
- discriminator?: string,
- public_flags?: number
- }
+ id?: string;
+ username?: string;
+ avatar?: string;
+ discriminator?: string;
+ public_flags?: number;
+ };
}
+router.get(
+ "/",
+ route({ test: { response: { body: "UserRelationsResponse" } } }),
+ async (req: Request, res: Response) => {
+ var mutual_relations: object[] = [];
+ const requested_relations = await User.findOneOrFail({
+ where: { id: req.params.id },
+ relations: ["relationships"],
+ });
+ const self_relations = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships"],
+ });
-router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => {
- var mutual_relations: object[] = [];
- const requested_relations = await User.findOneOrFail({
- where: { id: req.params.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) {
- var 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
+ ) {
+ var 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/channels.ts b/src/api/routes/users/@me/channels.ts
index ad483529..237be102 100644
--- a/src/api/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -1,5 +1,10 @@
import { Request, Response, Router } from "express";
-import { Recipient, DmChannelDTO, Channel, DmChannelCreateSchema } from "@fosscord/util";
+import {
+ Recipient,
+ DmChannelDTO,
+ Channel,
+ DmChannelCreateSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router: Router = Router();
@@ -7,14 +12,28 @@ const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const recipients = await Recipient.find({
where: { user_id: req.user_id, closed: false },
- relations: ["channel", "channel.recipients"]
+ relations: ["channel", "channel.recipients"],
});
- res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
+ res.json(
+ await Promise.all(
+ recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])),
+ ),
+ );
});
-router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
- const body = req.body as DmChannelCreateSchema;
- res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
-});
+router.post(
+ "/",
+ route({ body: "DmChannelCreateSchema" }),
+ 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/delete.ts b/src/api/routes/users/@me/delete.ts
index c24c3f1e..a9f8167c 100644
--- a/src/api/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -7,7 +7,10 @@ import { HTTPError } from "lambert-server";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
let correctpass = true;
if (user.data.hash) {
@@ -21,7 +24,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
// TODO: decrement guild member count
if (correctpass) {
- await Promise.all([User.delete({ id: req.user_id }), Member.delete({ id: req.user_id })]);
+ await Promise.all([
+ User.delete({ id: req.user_id }),
+ Member.delete({ id: req.user_id }),
+ ]);
res.sendStatus(204);
} else {
diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 4aff3774..313a888f 100644
--- a/src/api/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
@@ -6,7 +6,10 @@ import bcrypt from "bcrypt";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); //User object
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
+ }); //User object
let correctpass = true;
if (user.data.hash) {
@@ -19,7 +22,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
res.sendStatus(204);
} else {
- res.status(400).json({ message: "Password does not match", code: 50018 });
+ res.status(400).json({
+ message: "Password does not match",
+ code: 50018,
+ });
}
});
diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index 3114984e..a2834b89 100644
--- a/src/api/routes/users/@me/email-settings.ts
+++ b/src/api/routes/users/@me/email-settings.ts
@@ -11,9 +11,9 @@ router.get("/", route({}), (req: Request, res: Response) => {
communication: true,
tips: false,
updates_and_announcements: false,
- recommendations_and_events: false
+ recommendations_and_events: false,
},
- initialized: false
+ initialized: false,
}).status(200);
});
diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index 754a240e..e12bf258 100644
--- a/src/api/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -1,12 +1,23 @@
import { Router, Request, Response } from "express";
-import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent, Config } from "@fosscord/util";
+import {
+ Guild,
+ Member,
+ User,
+ GuildDeleteEvent,
+ GuildMemberRemoveEvent,
+ emitEvent,
+ Config,
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } });
+ const members = await Member.find({
+ relations: ["guild"],
+ where: { id: req.user_id },
+ });
let guild = members.map((x) => x.guild);
@@ -21,11 +32,19 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.delete("/:guild_id", route({}), 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"] });
+ 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) {
+ 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);
}
@@ -34,10 +53,10 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "GUILD_DELETE",
data: {
- id: guild_id
+ id: guild_id,
},
- user_id: req.user_id
- } as GuildDeleteEvent)
+ user_id: req.user_id,
+ } as GuildDeleteEvent),
]);
const user = await User.getPublicUser(req.user_id);
@@ -46,9 +65,9 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
event: "GUILD_MEMBER_REMOVE",
data: {
guild_id: guild_id,
- user: user
+ user: user,
},
- guild_id: guild_id
+ guild_id: guild_id,
} as GuildMemberRemoveEvent);
return res.sendStatus(204);
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 f09be25b..4b806cfb 100644
--- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts
+++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -1,39 +1,51 @@
import { Router, Response, Request } from "express";
-import { Channel, ChannelOverride, Member, UserGuildSettings } from "@fosscord/util";
+import {
+ Channel,
+ ChannelOverride,
+ Member,
+ UserGuildSettings,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
const router = Router();
// This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema
-export interface UserGuildSettingsSchema extends Partial<Omit<UserGuildSettings, 'channel_overrides'>> {
+export interface UserGuildSettingsSchema
+ extends Partial<Omit<UserGuildSettings, "channel_overrides">> {
channel_overrides: {
[channel_id: string]: Partial<ChannelOverride>;
- },
+ };
}
// GET doesn't exist on discord.com
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await Member.findOneOrFail({
where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"]
+ select: ["settings"],
});
return res.json(user.settings);
});
-router.patch("/", route({ body: "UserGuildSettingsSchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserGuildSettings;
+router.patch(
+ "/",
+ route({ body: "UserGuildSettingsSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserGuildSettings;
- if (body.channel_overrides) {
- for (var channel in body.channel_overrides) {
- Channel.findOneOrFail({ where: { id: channel } });
+ if (body.channel_overrides) {
+ for (var 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 } });
- user.settings = { ...user.settings, ...body };
- await user.save();
+ const user = await Member.findOneOrFail({
+ where: { id: req.user_id, guild_id: req.params.guild_id },
+ });
+ user.settings = { ...user.settings, ...body };
+ await user.save();
- res.json(user.settings);
-});
+ res.json(user.settings);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index e849b72a..5eba4665 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -1,5 +1,15 @@
import { Router, Request, Response } from "express";
-import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors, adjustEmail, Config, UserModifySchema } from "@fosscord/util";
+import {
+ User,
+ PrivateUserProjection,
+ emitEvent,
+ UserUpdateEvent,
+ handleFile,
+ FieldErrors,
+ adjustEmail,
+ Config,
+ UserModifySchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
@@ -7,79 +17,134 @@ import { HTTPError } from "lambert-server";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
+ res.json(
+ await User.findOne({
+ select: PrivateUserProjection,
+ where: { id: req.user_id },
+ }),
+ );
});
-router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserModifySchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] });
-
- if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400);
+router.patch(
+ "/",
+ route({ body: "UserModifySchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserModifySchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: [...PrivateUserProjection, "data"],
+ });
+
+ if (user.email == "demo@maddy.k.vu")
+ throw new HTTPError("Demo user, sorry", 400);
+
+ 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.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.email) {
+ body.email = adjustEmail(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:register.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
- 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" } });
+ if (body.new_password) {
+ if (!body.password && !user.email) {
+ throw FieldErrors({
+ password: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
}
- } else {
- user.data.hash = await bcrypt.hash(body.password, 12);
+ user.data.hash = await bcrypt.hash(body.new_password, 12);
}
- }
-
- if (body.email) {
- body.email = adjustEmail(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:register.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);
- }
-
- if (body.username) {
- var check_username = body?.username?.replace(/\s/g, '');
- if (!check_username) {
- throw FieldErrors({
- username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
- });
+
+ if (body.username) {
+ var check_username = body?.username?.replace(/\s/g, "");
+ if (!check_username) {
+ throw FieldErrors({
+ username: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+ }
}
- }
- 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.",
+ },
+ });
+ }
}
- }
- user.assign(body);
- await user.save();
+ user.assign(body);
+ await user.save();
- // @ts-ignore
- delete user.data;
+ // @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);
-});
+ res.json(user);
+ },
+);
export default router;
// {"message": "Invalid two-factor code", "code": 60008}
diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index 071c71fa..3411605b 100644
--- a/src/api/routes/users/@me/mfa/codes-verification.ts
+++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -1,41 +1,49 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { BackupCode, generateMfaBackupCodes, User, CodesVerificationSchema } from "@fosscord/util";
+import {
+ BackupCode,
+ generateMfaBackupCodes,
+ User,
+ CodesVerificationSchema,
+} from "@fosscord/util";
const router = Router();
-router.post("/", route({ body: "CodesVerificationSchema" }), async (req: Request, res: Response) => {
- const { key, nonce, 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`
-
- const user = await User.findOneOrFail({ where: { id: req.user_id } });
-
- var 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,
+router.post(
+ "/",
+ route({ body: "CodesVerificationSchema" }),
+ async (req: Request, res: Response) => {
+ const { key, nonce, 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`
+
+ const user = await User.findOneOrFail({ where: { id: req.user_id } });
+
+ var 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,
},
- 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 58466b9c..33053028 100644
--- a/src/api/routes/users/@me/mfa/codes.ts
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -1,45 +1,62 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { BackupCode, FieldErrors, generateMfaBackupCodes, User, MfaCodesSchema } from "@fosscord/util";
+import {
+ BackupCode,
+ FieldErrors,
+ generateMfaBackupCodes,
+ User,
+ MfaCodesSchema,
+} from "@fosscord/util";
import bcrypt from "bcrypt";
const router = Router();
// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients
-router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => {
- const { password, regenerate } = req.body as MfaCodesSchema;
-
- 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" } });
- }
-
- var 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,
- }
+router.post(
+ "/",
+ route({ body: "MfaCodesSchema" }),
+ async (req: Request, res: Response) => {
+ const { password, regenerate } = req.body as MfaCodesSchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data"],
});
- }
- return res.json({
- backup_codes: codes.map(x => ({ ...x, expired: undefined })),
- });
-});
+ if (!(await bcrypt.compare(password, user.data.hash || ""))) {
+ throw FieldErrors({
+ password: {
+ message: req.t("auth:login.INVALID_PASSWORD"),
+ code: "INVALID_PASSWORD",
+ },
+ });
+ }
+
+ var 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,
+ },
+ });
+ }
+
+ 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 2fe9355c..7916e598 100644
--- a/src/api/routes/users/@me/mfa/totp/disable.ts
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -1,41 +1,56 @@
import { Router, Request, Response } from "express";
import { route } from "@fosscord/api";
-import { verifyToken } from 'node-2fa';
+import { verifyToken } from "node-2fa";
import { HTTPError } from "lambert-server";
-import { User, generateToken, BackupCode, TotpDisableSchema } from "@fosscord/util";
+import {
+ User,
+ generateToken,
+ BackupCode,
+ TotpDisableSchema,
+} from "@fosscord/util";
const router = Router();
-router.post("/", route({ body: "TotpDisableSchema" }), 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 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 BackupCode.update(
- { user: { id: req.user_id } },
- {
- expired: true,
+router.post(
+ "/",
+ route({ body: "TotpDisableSchema" }),
+ 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 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,
+ );
}
- );
- return res.json({
- token: await generateToken(user.id),
- });
-});
-
-export default router;
\ No newline at end of file
+ await User.update(
+ { id: req.user_id },
+ {
+ mfa_enabled: false,
+ totp_secret: "",
+ },
+ );
+
+ await BackupCode.update(
+ { user: { id: req.user_id } },
+ {
+ expired: true,
+ },
+ );
+
+ 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 adafe180..75c64425 100644
--- a/src/api/routes/users/@me/mfa/totp/enable.ts
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -1,46 +1,62 @@
import { Router, Request, Response } from "express";
-import { User, generateToken, generateMfaBackupCodes, TotpEnableSchema } from "@fosscord/util";
+import {
+ User,
+ generateToken,
+ generateMfaBackupCodes,
+ TotpEnableSchema,
+} from "@fosscord/util";
import { route } from "@fosscord/api";
import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server";
-import { verifyToken } from 'node-2fa';
+import { verifyToken } from "node-2fa";
const router = Router();
-router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => {
- const body = req.body as TotpEnableSchema;
-
- const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data", "email"] });
-
- if (user.email == "demo@maddy.k.vu") throw new HTTPError("Demo user, sorry", 400);
-
- // 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"));
+router.post(
+ "/",
+ route({ body: "TotpEnableSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as TotpEnableSchema;
+
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: ["data", "email"],
+ });
+
+ if (user.email == "demo@maddy.k.vu")
+ throw new HTTPError("Demo user, sorry", 400);
+
+ // 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.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);
-
- let 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 })),
- });
-});
-export default router;
\ No newline at end of file
+ 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 (verifyToken(body.secret, body.code)?.delta != 0)
+ throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+
+ let 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,
+ })),
+ });
+ },
+);
+
+export default router;
diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index f938f088..e54eb897 100644
--- a/src/api/routes/users/@me/notes.ts
+++ b/src/api/routes/users/@me/notes.ts
@@ -11,7 +11,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
where: {
owner: { id: req.user_id },
target: { id: id },
- }
+ },
});
return res.json({
@@ -24,32 +24,40 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
router.put("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
+ const target = await User.findOneOrFail({ where: { id: 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 } } })) {
+ 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 }
+ { owner, target, content: note },
);
+ } else {
+ Note.insert({
+ id: Snowflake.generate(),
+ 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 } });
+ } else {
+ await Note.delete({
+ owner: { id: owner.id },
+ target: { id: target.id },
+ });
}
await emitEvent({
event: "USER_NOTE_UPDATE",
data: {
note: note,
- id: target.id
+ id: target.id,
},
user_id: owner.id,
});
diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index cd33704d..3eec704b 100644
--- a/src/api/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -6,7 +6,7 @@ import {
RelationshipRemoveEvent,
emitEvent,
Relationship,
- Config
+ Config,
} from "@fosscord/util";
import { Router, Response, Request } from "express";
import { HTTPError } from "lambert-server";
@@ -15,13 +15,16 @@ import { route } from "@fosscord/api";
const router = Router();
-const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
+const userProjection: (keyof User)[] = [
+ "relationships",
+ ...PublicUserProjection,
+];
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
relations: ["relationships", "relationships.to"],
- select: ["id", "relationships"]
+ select: ["id", "relationships"],
});
//TODO DTO
@@ -30,49 +33,76 @@ router.get("/", route({}), async (req: Request, res: Response) => {
id: r.to.id,
type: r.type,
nickname: null,
- user: r.to.toPublicUser()
+ user: r.to.toPublicUser(),
};
});
return res.json(related_users);
});
-router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => {
- return await updateRelationship(
- req,
- res,
- await User.findOneOrFail({ where: { id: req.params.id }, relations: ["relationships", "relationships.to"], select: userProjection }),
- req.body.type ?? RelationshipType.friends
- );
-});
+router.put(
+ "/:id",
+ route({ body: "RelationshipPutSchema" }),
+ async (req: Request, res: Response) => {
+ return await updateRelationship(
+ req,
+ res,
+ await User.findOneOrFail({
+ where: { id: req.params.id },
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
+ }),
+ req.body.type ?? RelationshipType.friends,
+ );
+ },
+);
-router.post("/", route({ body: "RelationshipPostSchema" }), 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.post(
+ "/",
+ route({ body: "RelationshipPostSchema" }),
+ 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("/:id", route({}), async (req: Request, res: Response) => {
const { id } = req.params;
- if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
+ if (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: 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: id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
const relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.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
@@ -81,8 +111,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "RELATIONSHIP_REMOVE",
user_id: req.user_id,
- data: relationship.toPublicRelationship()
- } as RelationshipRemoveEvent)
+ data: relationship.toPublicRelationship(),
+ } as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
}
@@ -92,8 +122,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
await emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
- user_id: id
- } as RelationshipRemoveEvent)
+ user_id: id,
+ } as RelationshipRemoveEvent),
]);
}
@@ -102,8 +132,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: relationship.toPublicRelationship(),
- user_id: req.user_id
- } as RelationshipRemoveEvent)
+ user_id: req.user_id,
+ } as RelationshipRemoveEvent),
]);
return res.sendStatus(204);
@@ -111,26 +141,40 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => {
export default router;
-async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) {
+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");
+ 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
+ relations: ["relationships", "relationships.to"],
+ select: userProjection,
});
var relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find((x) => x.to_id === req.user_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");
+ 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();
+ relationship = await Relationship.create({
+ to_id: id,
+ type: RelationshipType.blocked,
+ from_id: req.user_id,
+ }).save();
}
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
@@ -139,43 +183,56 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest.toPublicRelationship(),
- user_id: id
- } as RelationshipRemoveEvent)
+ user_id: id,
+ } as RelationshipRemoveEvent),
]);
}
await emitEvent({
event: "RELATIONSHIP_ADD",
data: relationship.toPublicRelationship(),
- user_id: req.user_id
+ user_id: req.user_id,
} as RelationshipAddEvent);
return res.sendStatus(204);
}
const { maxFriends } = Config.get().limits.user;
- if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
+ if (user.relationships.length >= maxFriends)
+ throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
- var incoming_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend });
+ var incoming_relationship = Relationship.create({
+ nickname: undefined,
+ type: RelationshipType.incoming,
+ to: user,
+ from: friend,
+ });
var outgoing_relationship = Relationship.create({
nickname: undefined,
type: RelationshipType.outgoing,
to: friend,
- from: user
+ 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");
+ 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");
+ 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;
}
@@ -186,16 +243,16 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
emitEvent({
event: "RELATIONSHIP_ADD",
data: outgoing_relationship.toPublicRelationship(),
- user_id: req.user_id
+ user_id: req.user_id,
} as RelationshipAddEvent),
emitEvent({
event: "RELATIONSHIP_ADD",
data: {
...incoming_relationship.toPublicRelationship(),
- should_notify: true
+ should_notify: true,
},
- user_id: id
- } as RelationshipAddEvent)
+ user_id: id,
+ } as RelationshipAddEvent),
]);
return res.sendStatus(204);
diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index 9060baf7..30e5969c 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -4,25 +4,31 @@ import { route } from "@fosscord/api";
const router = Router();
-export interface UserSettingsSchema extends Partial<UserSettings> { }
+export interface UserSettingsSchema extends Partial<UserSettings> {}
router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
- select: ["settings"]
+ select: ["settings"],
});
return res.json(user.settings);
});
-router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => {
- const body = req.body as UserSettings;
- if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
+router.patch(
+ "/",
+ route({ body: "UserSettingsSchema" }),
+ async (req: Request, res: Response) => {
+ const body = req.body as UserSettings;
+ if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
- const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false } });
- user.settings = { ...user.settings, ...body };
- await user.save();
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id, bot: false },
+ });
+ user.settings = { ...user.settings, ...body };
+ await user.save();
- res.json(user.settings);
-});
+ res.json(user.settings);
+ },
+);
export default router;
diff --git a/src/api/start.ts b/src/api/start.ts
index ccb4d108..fa120e59 100644
--- a/src/api/start.ts
+++ b/src/api/start.ts
@@ -11,7 +11,7 @@ var cores = 1;
try {
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.isMaster && process.env.NODE_ENV == "production") {
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 0d29c2e6..d44b368f 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -32,24 +32,32 @@ const allow_empty = false;
// TODO: check webhook, application, system author, stickers
// TODO: embed gifs/videos/images
-const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
+const LINK_REGEX =
+ /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
const DEFAULT_FETCH_OPTIONS: any = {
redirect: "follow",
follow: 1,
headers: {
- "user-agent": "Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)"
+ "user-agent":
+ "Mozilla/5.0 (compatible; Fosscord/1.0; +https://github.com/fosscord/fosscord)",
},
// size: 1024 * 1024 * 5, // grabbed from config later
compress: true,
- method: "GET"
+ method: "GET",
};
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);
- const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined;
+ const stickers = opts.sticker_ids
+ ? await Sticker.find({ where: { id: In(opts.sticker_ids) } })
+ : undefined;
const message = Message.create({
...opts,
id: Snowflake.generate(),
@@ -58,11 +66,14 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
channel_id: opts.channel_id,
attachments: opts.attachments || [],
embeds: opts.embeds || [],
- reactions: /*opts.reactions ||*/[],
+ reactions: /*opts.reactions ||*/ [],
type: opts.type ?? 0,
});
- if (message.content && message.content.length > Config.get().limits.message.maxCharacters) {
+ if (
+ message.content &&
+ message.content.length > Config.get().limits.message.maxCharacters
+ ) {
throw new HTTPError("Content length over max character limit");
}
@@ -72,13 +83,21 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
rights.hasThrow("SEND_MESSAGES");
}
if (opts.application_id) {
- message.application = await Application.findOneOrFail({ where: { id: 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 } });
+ message.webhook = await Webhook.findOneOrFail({
+ where: { id: opts.webhook_id },
+ });
}
- const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id);
+ const permission = await getPermission(
+ opts.author_id,
+ channel.guild_id,
+ opts.channel_id,
+ );
permission.hasThrow("SEND_MESSAGES");
if (permission.cache.member) {
message.member = permission.cache.member;
@@ -89,10 +108,18 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
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 } });
+ const guild = await Guild.findOneOrFail({
+ where: { id: channel.guild_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 (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",
+ );
}
}
/** Q: should be checked if the referenced message exists? ANSWER: NO
@@ -102,7 +129,13 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
}
// TODO: stickers/activity
- if (!allow_empty && (!opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length)) {
+ if (
+ !allow_empty &&
+ !opts.content &&
+ !opts.embeds?.length &&
+ !opts.attachments?.length &&
+ !opts.sticker_ids?.length
+ ) {
throw new HTTPError("Empty messages are not allowed", 50006);
}
@@ -112,31 +145,42 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
var mention_user_ids = [] as string[];
var mention_everyone = false;
- if (content) { // TODO: explicit-only mentions
+ if (content) {
+ // TODO: explicit-only mentions
message.content = content.trim();
for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) {
- if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(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);
+ 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 || permission.has("MANAGE_ROLES")) {
- mention_role_ids.push(mention);
- }
- })
+ 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 || permission.has("MANAGE_ROLES")) {
+ mention_role_ids.push(mention);
+ }
+ },
+ ),
);
if (permission.has("MENTION_EVERYONE")) {
- mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION);
+ mention_everyone =
+ !!content.match(EVERYONE_MENTION) ||
+ !!content.match(HERE_MENTION);
}
}
- message.mention_channels = mention_channel_ids.map((x) => Channel.create({ id: x }));
+ message.mention_channels = mention_channel_ids.map((x) =>
+ Channel.create({ id: x }),
+ );
message.mention_roles = mention_role_ids.map((x) => Role.create({ id: x }));
message.mentions = mention_user_ids.map((x) => User.create({ id: x }));
message.mention_everyone = mention_everyone;
@@ -156,7 +200,8 @@ export async function postHandleMessage(message: Message) {
links = links.slice(0, 20) as RegExpMatchArray; // embed max 20 links — TODO: make this configurable with instance policies
- const { endpointPublic, resizeWidthMax, resizeHeightMax } = Config.get().cdn;
+ const { endpointPublic, resizeWidthMax, resizeHeightMax } =
+ Config.get().cdn;
for (const link of links) {
try {
@@ -176,45 +221,64 @@ export async function postHandleMessage(message: Message) {
},
image: {
// can't be bothered rn
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(link)}?width=500&height=400`,
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ link,
+ )}?width=500&height=400`,
url: link,
width: 500,
- height: 400
- }
+ height: 400,
+ },
};
data.embeds.push(embed);
- }
- else {
+ } else {
const text = await request.text();
const $ = cheerio.load(text);
const title = $('meta[property="og:title"]').attr("content");
const provider_name = $('meta[property="og:site_name"]').text();
- const author_name = $('meta[property="article:author"]').attr("content");
- const description = $('meta[property="og:description"]').attr("content") || $('meta[property="description"]').attr("content");
+ const author_name = $('meta[property="article:author"]').attr(
+ "content",
+ );
+ const description =
+ $('meta[property="og:description"]').attr("content") ||
+ $('meta[property="description"]').attr("content");
const image = $('meta[property="og:image"]').attr("content");
- const width = parseInt($('meta[property="og:image:width"]').attr("content") || "") || undefined;
- const height = parseInt($('meta[property="og:image:height"]').attr("content") || "") || undefined;
+ const width =
+ parseInt(
+ $('meta[property="og:image:width"]').attr("content") ||
+ "",
+ ) || undefined;
+ const height =
+ parseInt(
+ $('meta[property="og:image:height"]').attr("content") ||
+ "",
+ ) || undefined;
const url = $('meta[property="og:url"]').attr("content");
// TODO: color
embed = {
provider: {
url: link,
- name: provider_name
- }
+ name: provider_name,
+ },
};
const resizeWidth = Math.min(resizeWidthMax ?? 1, width ?? 100);
- const resizeHeight = Math.min(resizeHeightMax ?? 1, height ?? 100);
+ const resizeHeight = Math.min(
+ resizeHeightMax ?? 1,
+ height ?? 100,
+ );
if (author_name) embed.author = { name: author_name };
- if (image) embed.thumbnail = {
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image)}?width=${resizeWidth}&height=${resizeHeight}`,
- url: image,
- width: width,
- height: height
- };
+ if (image)
+ embed.thumbnail = {
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ image,
+ )}?width=${resizeWidth}&height=${resizeHeight}`,
+ url: image,
+ width: width,
+ height: height,
+ };
if (title) embed.title = title;
if (url) embed.url = url;
if (description) embed.description = description;
@@ -227,18 +291,25 @@ export async function postHandleMessage(message: Message) {
// very bad code below
// don't care lol
- if (embed?.thumbnail?.url && approvedProviders.indexOf(new URL(embed.thumbnail.url).hostname) !== -1) {
+ if (
+ embed?.thumbnail?.url &&
+ approvedProviders.indexOf(
+ new URL(embed.thumbnail.url).hostname,
+ ) !== -1
+ ) {
embed = {
provider: {
url: link,
name: new URL(link).hostname,
},
image: {
- proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(image!)}?width=${resizeWidth}&height=${resizeHeight}`,
+ proxy_url: `${endpointPublic}/external/resize/${encodeURIComponent(
+ image!,
+ )}?width=${resizeWidth}&height=${resizeHeight}`,
url: image,
width: width,
- height: height
- }
+ height: height,
+ },
};
}
@@ -246,16 +317,19 @@ export async function postHandleMessage(message: Message) {
data.embeds.push(embed);
}
}
- } catch (error) { }
+ } catch (error) {}
}
await Promise.all([
emitEvent({
event: "MESSAGE_UPDATE",
channel_id: message.channel_id,
- data
+ data,
} as MessageUpdateEvent),
- Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })
+ Message.update(
+ { id: message.id, channel_id: message.channel_id },
+ { embeds: data.embeds },
+ ),
]);
}
@@ -264,10 +338,14 @@ export async function sendMessage(opts: MessageOptions) {
await Promise.all([
Message.insert(message),
- emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent)
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: opts.channel_id,
+ data: message.toJSON(),
+ } as MessageCreateEvent),
]);
- postHandleMessage(message).catch((e) => { }); // no await as it should catch error non-blockingly
+ postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly
return message;
}
diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index 4d60eb91..88e266a1 100644
--- a/src/api/util/handlers/Voice.ts
+++ b/src/api/util/handlers/Voice.ts
@@ -3,7 +3,9 @@ import { distanceBetweenLocations, IPAnalysis } 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));
+ const availableRegions = regions.available.filter((ar) =>
+ vip ? true : !ar.vip,
+ );
let optimalId = regions.default;
if (!regions.useDefaultAsOptimal) {
@@ -13,7 +15,10 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
for (let 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 IPAnalysis(ar.endpoint)));
+ const dist = distanceBetweenLocations(
+ clientIpAnalysis,
+ ar.location || (await IPAnalysis(ar.endpoint)),
+ );
if (dist < min) {
min = dist;
@@ -27,6 +32,6 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
name: ar.name,
custom: ar.custom,
deprecated: ar.deprecated,
- optimal: ar.id === optimalId
+ optimal: ar.id === optimalId,
}));
}
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index c245b411..5dcae953 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -10,7 +10,7 @@ import {
PermissionResolvable,
Permissions,
RightResolvable,
- Rights
+ Rights,
} from "@fosscord/util";
import { NextFunction, Request, Response } from "express";
import { AnyValidateFunction } from "ajv/dist/core";
@@ -23,7 +23,11 @@ declare global {
}
}
-export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record<string, string> };
+export type RouteResponse = {
+ status?: number;
+ body?: `${string}Response`;
+ headers?: Record<string, string>;
+};
export interface RouteOptions {
permission?: PermissionResolvable;
@@ -48,11 +52,17 @@ export function route(opts: RouteOptions) {
return async (req: Request, res: Response, next: NextFunction) => {
if (opts.permission) {
const required = new Permissions(opts.permission);
- req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
+ req.permission = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ req.params.channel_id,
+ );
// bitfield comparison: check if user lacks certain permission
if (!req.permission.has(required)) {
- throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string);
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(
+ opts.permission as string,
+ );
}
}
@@ -61,15 +71,26 @@ export function route(opts: RouteOptions) {
req.rights = await getRights(req.user_id);
if (!req.rights || !req.rights.has(required)) {
- throw FosscordApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
+ throw FosscordApiErrors.MISSING_RIGHTS.withParams(
+ opts.right as string,
+ );
}
}
if (validate) {
const valid = validate(normalizeBody(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 || "" }));
+ const fields: Record<
+ string,
+ { code?: string; message: string }
+ > = {};
+ validate.errors?.forEach(
+ (x) =>
+ (fields[x.instancePath.slice(1)] = {
+ code: x.keyword,
+ message: x.message || "",
+ }),
+ );
throw FieldErrors(fields);
}
}
diff --git a/src/api/util/index.ts b/src/api/util/index.ts
index de6b6064..9f375f72 100644
--- a/src/api/util/index.ts
+++ b/src/api/util/index.ts
@@ -6,4 +6,4 @@ export * from "./utility/RandomInviteID";
export * from "./handlers/route";
export * from "./utility/String";
export * from "./handlers/Voice";
-export * from "./utility/captcha";
\ No newline at end of file
+export * from "./utility/captcha";
diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 46cff77a..c10176f2 100644
--- a/src/api/util/utility/Base64.ts
+++ b/src/api/util/utility/Base64.ts
@@ -1,4 +1,5 @@
-const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+";
+const alphabet =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+";
// binary to string lookup table
const b2s = alphabet.split("");
diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 7ea344e0..bfed65bb 100644
--- a/src/api/util/utility/RandomInviteID.ts
+++ b/src/api/util/utility/RandomInviteID.ts
@@ -2,7 +2,8 @@ import { Snowflake } from "@fosscord/util";
export function random(length = 6) {
// Declare all characters
- let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// Pick characers randomly
let str = "";
@@ -15,18 +16,18 @@ export function random(length = 6) {
export function snowflakeBasedInvite() {
// Declare all characters
- let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let 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
let str = "";
- for (let i=0; i < 10; i++) {
-
+ 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("");
}
diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
index 982b7e11..0913013e 100644
--- a/src/api/util/utility/String.ts
+++ b/src/api/util/utility/String.ts
@@ -2,13 +2,21 @@ import { Request } from "express";
import { ntob } from "./Base64";
import { FieldErrors } from "@fosscord/util";
-export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
+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}` })
- }
+ message: req.t("common:field.BASE_TYPE_BAD_LENGTH", {
+ length: `${min} - ${max}`,
+ }),
+ },
});
}
}
diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts
index 739647d2..50e2c91a 100644
--- a/src/api/util/utility/captcha.ts
+++ b/src/api/util/utility/captcha.ts
@@ -7,8 +7,8 @@ export interface hcaptchaResponse {
hostname: string;
credit: boolean;
"error-codes": string[];
- score: number; // enterprise only
- score_reason: string[]; // enterprise only
+ score: number; // enterprise only
+ score_reason: string[]; // enterprise only
}
export interface recaptchaResponse {
@@ -23,7 +23,7 @@ export interface recaptchaResponse {
const verifyEndpoints = {
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();
@@ -36,11 +36,12 @@ export async function verifyCaptcha(response: string, ip?: string) {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
- body: `response=${encodeURIComponent(response)}`
- + `&secret=${encodeURIComponent(secret!)}`
- + `&sitekey=${encodeURIComponent(sitekey!)}`
- + (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""),
+ body:
+ `response=${encodeURIComponent(response)}` +
+ `&secret=${encodeURIComponent(secret!)}` +
+ `&sitekey=${encodeURIComponent(sitekey!)}` +
+ (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""),
});
- return await res.json() as hcaptchaResponse | recaptchaResponse;
-}
\ No newline at end of file
+ return (await res.json()) as hcaptchaResponse | recaptchaResponse;
+}
diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index f17b145e..d166ebc5 100644
--- a/src/api/util/utility/ipAddress.ts
+++ b/src/api/util/utility/ipAddress.ts
@@ -25,27 +25,27 @@ const exampleData = {
name: "",
domain: "",
route: "",
- type: "isp"
+ type: "isp",
},
languages: [
{
name: "",
- native: ""
- }
+ native: "",
+ },
],
currency: {
name: "",
code: "",
symbol: "",
native: "",
- plural: ""
+ plural: "",
},
time_zone: {
name: "",
abbr: "",
offset: "",
is_dst: true,
- current_time: ""
+ current_time: "",
},
threat: {
is_tor: false,
@@ -54,10 +54,10 @@ const exampleData = {
is_known_attacker: false,
is_known_abuser: false,
is_threat: false,
- is_bogon: false
+ is_bogon: false,
},
count: 0,
- status: 200
+ status: 200,
};
//TODO add function that support both ip and domain names
@@ -65,7 +65,9 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
const { ipdataApiKey } = Config.get().security;
if (!ipdataApiKey) return { ...exampleData, ip };
- return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json() as any; // TODO: types
+ return (
+ await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)
+ ).json() as any; // TODO: types
}
export function isProxy(data: typeof exampleData) {
@@ -77,19 +79,35 @@ export function isProxy(data: typeof exampleData) {
}
export function getIpAdress(req: Request): string {
- // @ts-ignore
- return req.headers[Config.get().security.forwadedFor] || req.socket.remoteAddress;
+ return (
+ // @ts-ignore
+ req.headers[Config.get().security.forwadedFor] ||
+ req.socket.remoteAddress
+ );
}
export function distanceBetweenLocations(loc1: any, loc2: any): 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) {
+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 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
}
diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index 439700d0..35c55999 100644
--- a/src/api/util/utility/passwordStrength.ts
+++ b/src/api/util/utility/passwordStrength.ts
@@ -18,7 +18,8 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
* Returns: 0 > pw > 1
*/
export function checkPassword(password: string): number {
- const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
+ const { minLength, minNumbers, minUpperCase, minSymbols } =
+ Config.get().register.password;
var strength = 0;
// checks for total password len
@@ -42,19 +43,24 @@ export function checkPassword(password: string): number {
}
// checks if password only consists of numbers or only consists of chars
- if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) {
+ if (
+ password.length == password.count(reNUMBER) ||
+ password.length === password.count(reUPPERCASELETTER)
+ ) {
strength = 0;
}
-
+
let entropyMap: { [key: string]: number } = {};
for (let i = 0; i < password.length; i++) {
if (entropyMap[password[i]]) entropyMap[password[i]]++;
else entropyMap[password[i]] = 1;
}
-
+
let 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);
+
+ entropies.map((x) => x / entropyMap.length);
+ strength +=
+ entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) /
+ Math.log2(password.length);
return strength;
}
|