1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 02:12:40 +01:00

Add ESLint (#941)

* Add eslint, switch to lint-staged for precommit

* Fix all ESLint errors

* Update GH workflow to check prettier and eslint
This commit is contained in:
Madeline 2023-01-20 18:10:47 +11:00 committed by GitHub
parent 0c815fde91
commit 084dc0be08
157 changed files with 1905 additions and 668 deletions

7
.eslintignore Normal file
View File

@ -0,0 +1,7 @@
node_modules
dist
README.md
COPYING
src/webrtc
scripts/
assets

11
.eslintrc Normal file
View File

@ -0,0 +1,11 @@
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"root": true,
"rules": {
"no-mixed-spaces-and-tabs": "off",
"@typescript-eslint/no-inferrable-types": "off", // Required by typeorm
"@typescript-eslint/no-var-requires": "off" // Sometimes requred by typeorm to resolve circular deps
}
}

View File

@ -24,5 +24,7 @@ jobs:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
- run: npm ci - run: npm ci
- run: npx eslint .
- run: npx prettier --check .
- run: npm run build --if-present - run: npm run build --if-present
- run: npm run test --if-present - run: npm run test --if-present

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh" . "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged npx -y lint-staged

3
.lintstagedrc Normal file
View File

@ -0,0 +1,3 @@
{
"*.ts": ["eslint", "prettier --write"]
}

1170
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
"start:cdn": "node dist/cdn/start.js", "start:cdn": "node dist/cdn/start.js",
"start:gateway": "node dist/gateway/start.js", "start:gateway": "node dist/gateway/start.js",
"build": "tsc -p .", "build": "tsc -p .",
"lint": "eslint .",
"setup": "npm run build && npm run generate:schema", "setup": "npm run build && npm run generate:schema",
"sync:db": "npm run build && node scripts/syncronise.js", "sync:db": "npm run build && node scripts/syncronise.js",
"generate:rights": "node scripts/rights.js", "generate:rights": "node scripts/rights.js",
@ -53,11 +54,14 @@
"@types/probe-image-size": "^7.2.0", "@types/probe-image-size": "^7.2.0",
"@types/sharp": "^0.31.0", "@types/sharp": "^0.31.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"eslint": "^8.32.0",
"express": "^4.18.1", "express": "^4.18.1",
"husky": "^8.0.0", "husky": "^8.0.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"typescript": "^4.8.3" "typescript": "^4.9.4"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.178.0", "@aws-sdk/client-s3": "^3.178.0",

View File

@ -32,7 +32,7 @@ function registerPath(file, method, prefix, path, ...args) {
const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts");
const opts = args.find((x) => typeof x === "object"); const opts = args.find((x) => typeof x === "object");
if (opts) { if (opts) {
routes.set(urlPath + "|" + method, opts); // @ts-ignore routes.set(urlPath + "|" + method, opts);
opts.file = sourceFile; opts.file = sourceFile;
// console.log(method, urlPath, opts); // console.log(method, urlPath, opts);
} else { } else {
@ -46,7 +46,6 @@ function routeOptions(opts) {
return opts; return opts;
} }
// @ts-ignore
RouteUtility.route = routeOptions; RouteUtility.route = routeOptions;
express.Router = (opts) => { express.Router = (opts) => {

View File

@ -22,7 +22,7 @@ import { Authentication, CORS } from "./middlewares/";
import { Config, initDatabase, initEvent, Sentry } from "@fosscord/util"; import { Config, initDatabase, initEvent, Sentry } from "@fosscord/util";
import { ErrorHandler } from "./middlewares/ErrorHandler"; import { ErrorHandler } from "./middlewares/ErrorHandler";
import { BodyParser } from "./middlewares/BodyParser"; import { BodyParser } from "./middlewares/BodyParser";
import { Router, Request, Response, NextFunction } from "express"; import { Router, Request, Response } from "express";
import path from "path"; import path from "path";
import { initRateLimits } from "./middlewares/RateLimit"; import { initRateLimits } from "./middlewares/RateLimit";
import TestClient from "./middlewares/TestClient"; import TestClient from "./middlewares/TestClient";
@ -32,12 +32,12 @@ import { initInstance } from "./util/handlers/Instance";
import { registerRoutes } from "@fosscord/util"; import { registerRoutes } from "@fosscord/util";
import { red } from "picocolors"; import { red } from "picocolors";
export interface FosscordServerOptions extends ServerOptions {} export type FosscordServerOptions = ServerOptions;
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express { namespace Express {
interface Request { interface Request {
// @ts-ignore
server: FosscordServer; server: FosscordServer;
} }
} }
@ -47,6 +47,7 @@ export class FosscordServer extends Server {
public declare options: FosscordServerOptions; public declare options: FosscordServerOptions;
constructor(opts?: Partial<FosscordServerOptions>) { constructor(opts?: Partial<FosscordServerOptions>) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
super({ ...opts, errorHandler: false, jsonBody: false }); super({ ...opts, errorHandler: false, jsonBody: false });
} }
@ -58,12 +59,12 @@ export class FosscordServer extends Server {
await initInstance(); await initInstance();
await Sentry.init(this.app); await Sentry.init(this.app);
let logRequests = process.env["LOG_REQUESTS"] != undefined; const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) { if (logRequests) {
this.app.use( this.app.use(
morgan("combined", { morgan("combined", {
skip: (req, res) => { skip: (req, res) => {
var skip = !( let skip = !(
process.env["LOG_REQUESTS"]?.includes( process.env["LOG_REQUESTS"]?.includes(
res.statusCode.toString(), res.statusCode.toString(),
) ?? false ) ?? false
@ -80,7 +81,9 @@ export class FosscordServer extends Server {
this.app.use(BodyParser({ inflate: true, limit: "10mb" })); this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
const app = this.app; const app = this.app;
const api = Router(); // @ts-ignore const api = Router();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.app = api; this.app = api;
api.use(Authentication); api.use(Authentication);
@ -95,7 +98,7 @@ export class FosscordServer extends Server {
// 404 is not an error in express, so this should not be an error middleware // 404 is not an error in express, so this should not be an error middleware
// this is a fine place to put the 404 handler because its after we register the routes // this is a fine place to put the 404 handler because its after we register the routes
// and since its not an error middleware, our error handler below still works. // and since its not an error middleware, our error handler below still works.
api.use("*", (req: Request, res: Response, next: NextFunction) => { api.use("*", (req: Request, res: Response) => {
res.status(404).json({ res.status(404).json({
message: "404 endpoint not found", message: "404 endpoint not found",
code: 0, code: 0,

View File

@ -54,11 +54,12 @@ export const API_PREFIX = /^\/api(\/v\d+)?/;
export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//;
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express { namespace Express {
interface Request { interface Request {
user_id: string; user_id: string;
user_bot: boolean; user_bot: boolean;
token: string; token: { id: string; iat: number };
rights: Rights; rights: Rights;
} }
} }
@ -87,7 +88,7 @@ export async function Authentication(
try { try {
const { jwtSecret } = Config.get().security; const { jwtSecret } = Config.get().security;
const { decoded, user }: any = await checkToken( const { decoded, user } = await checkToken(
req.headers.authorization, req.headers.authorization,
jwtSecret, jwtSecret,
); );
@ -97,7 +98,8 @@ export async function Authentication(
req.user_bot = user.bot; req.user_bot = user.bot;
req.rights = new Rights(Number(user.rights)); req.rights = new Rights(Number(user.rights));
return next(); return next();
} catch (error: any) { } catch (error) {
return next(new HTTPError(error?.toString(), 400)); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return next(new HTTPError(error!.toString(), 400));
} }
} }

View File

@ -42,7 +42,7 @@ type RateLimit = {
expires_at: Date; expires_at: Date;
}; };
let Cache = new Map<string, RateLimit>(); const Cache = new Map<string, RateLimit>();
const EventRateLimit = "RATELIMIT"; const EventRateLimit = "RATELIMIT";
export default function rateLimit(opts: { export default function rateLimit(opts: {
@ -57,12 +57,8 @@ export default function rateLimit(opts: {
error?: boolean; error?: boolean;
success?: boolean; success?: boolean;
onlyIp?: boolean; onlyIp?: boolean;
}): any { }) {
return async ( return async (req: Request, res: Response, next: NextFunction) => {
req: Request,
res: Response,
next: NextFunction,
): Promise<any> => {
// exempt user? if so, immediately short circuit // exempt user? if so, immediately short circuit
if (req.user_id) { if (req.user_id) {
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
@ -85,7 +81,7 @@ export default function rateLimit(opts: {
) )
max_hits = opts.MODIFY; max_hits = opts.MODIFY;
let offender = Cache.get(executor_id + bucket_id); const offender = Cache.get(executor_id + bucket_id);
if (offender) { if (offender) {
let reset = offender.expires_at.getTime(); let reset = offender.expires_at.getTime();

View File

@ -64,8 +64,8 @@ router.post("/", route({}), async (req: Request, res: Response) => {
}); });
router.post("/reset", route({}), async (req: Request, res: Response) => { router.post("/reset", route({}), async (req: Request, res: Response) => {
let bot = await User.findOneOrFail({ where: { id: req.params.id } }); const bot = await User.findOneOrFail({ where: { id: req.params.id } });
let owner = await User.findOneOrFail({ where: { id: req.user_id } }); const owner = await User.findOneOrFail({ where: { id: req.user_id } });
if (owner.id != req.user_id) if (owner.id != req.user_id)
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
@ -80,7 +80,7 @@ router.post("/reset", route({}), async (req: Request, res: Response) => {
await bot.save(); await bot.save();
let token = await generateToken(bot.id); const token = await generateToken(bot.id);
res.json({ token }).status(200); res.json({ token }).status(200);
}); });

View File

@ -20,10 +20,8 @@ import { Request, Response, Router } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { import {
Application, Application,
OrmUtils,
DiscordApiErrors, DiscordApiErrors,
ApplicationModifySchema, ApplicationModifySchema,
User,
} from "@fosscord/util"; } from "@fosscord/util";
import { verifyToken } from "node-2fa"; import { verifyToken } from "node-2fa";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";

View File

@ -18,7 +18,6 @@
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { Application, OrmUtils, Team, trimSpecial, User } from "@fosscord/util";
const router: Router = Router(); const router: Router = Router();

View File

@ -28,7 +28,7 @@ import {
const router: Router = Router(); const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
let results = await Application.find({ const results = await Application.find({
where: { owner: { id: req.user_id } }, where: { owner: { id: req.user_id } },
relations: ["owner", "bot"], relations: ["owner", "bot"],
}); });

View File

@ -32,7 +32,7 @@ router.get(
? parseInt(req.query.length as string) ? parseInt(req.query.length as string)
: 255; : 255;
let tokens: ValidRegistrationToken[] = []; const tokens: ValidRegistrationToken[] = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const token = ValidRegistrationToken.create({ const token = ValidRegistrationToken.create({

View File

@ -74,7 +74,7 @@ router.post(
"totp_secret", "totp_secret",
"mfa_enabled", "mfa_enabled",
], ],
}).catch((e) => { }).catch(() => {
throw FieldErrors({ throw FieldErrors({
login: { login: {
message: req.t("auth:login.INVALID_LOGIN"), message: req.t("auth:login.INVALID_LOGIN"),

View File

@ -27,8 +27,8 @@ router.post(
"/", "/",
route({ body: "TotpSchema" }), route({ body: "TotpSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { code, ticket, gift_code_sku_id, login_source } = // const { code, ticket, gift_code_sku_id, login_source } =
req.body as TotpSchema; const { code, ticket } = req.body as TotpSchema;
const user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { where: {
@ -47,7 +47,7 @@ router.post(
}); });
if (!backup) { if (!backup) {
const ret = verifyToken(user.totp_secret!, code); const ret = verifyToken(user.totp_secret || "", code);
if (!ret || ret.delta != 0) if (!ret || ret.delta != 0)
throw new HTTPError( throw new HTTPError(
req.t("auth:login.INVALID_TOTP_CODE"), req.t("auth:login.INVALID_TOTP_CODE"),

View File

@ -36,7 +36,7 @@ import {
} from "@fosscord/api"; } from "@fosscord/api";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { LessThan, MoreThan } from "typeorm"; import { MoreThan } from "typeorm";
const router: Router = Router(); const router: Router = Router();
@ -53,12 +53,12 @@ router.post(
let regTokenUsed = false; let regTokenUsed = false;
if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) {
// eg theyre on https://staging.fosscord.com/register?token=whatever // eg theyre on https://staging.fosscord.com/register?token=whatever
const token = req.get("Referrer")!.split("token=")[1].split("&")[0]; const token = req.get("Referrer")?.split("token=")[1].split("&")[0];
if (token) { if (token) {
const regToken = await ValidRegistrationToken.findOne({ const regToken = await ValidRegistrationToken.findOneOrFail({
where: { token, expires_at: MoreThan(new Date()) }, where: { token, expires_at: MoreThan(new Date()) },
}); });
await ValidRegistrationToken.delete({ token }); await regToken.remove();
regTokenUsed = true; regTokenUsed = true;
console.log( console.log(
`[REGISTER] Registration token ${token} used for registration!`, `[REGISTER] Registration token ${token} used for registration!`,
@ -71,7 +71,7 @@ router.post(
} }
// email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick // email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
let email = adjustEmail(body.email); const email = adjustEmail(body.email);
// check if registration is allowed // check if registration is allowed
if (!regTokenUsed && !register.allowNewRegistration) { if (!regTokenUsed && !register.allowNewRegistration) {

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Router, Response, Request } from "express"; import { Router } from "express";
const router: Router = Router(); const router: Router = Router();
// TODO: // TODO:

View File

@ -92,7 +92,7 @@ router.patch(
"/", "/",
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
var payload = req.body as ChannelModifySchema; const payload = req.body as ChannelModifySchema;
const { channel_id } = req.params; const { channel_id } = req.params;
if (payload.icon) if (payload.icon)
payload.icon = await handleFile( payload.icon = await handleFile(

View File

@ -86,7 +86,6 @@ router.get(
"/", "/",
route({ permission: "MANAGE_CHANNELS" }), route({ permission: "MANAGE_CHANNELS" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { user_id } = req;
const { channel_id } = req.params; const { channel_id } = req.params;
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },

View File

@ -30,7 +30,6 @@ import {
Snowflake, Snowflake,
uploadFile, uploadFile,
MessageCreateSchema, MessageCreateSchema,
DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import { Router, Response, Request } from "express"; import { Router, Response, Request } from "express";
import multer from "multer"; import multer from "multer";
@ -59,7 +58,7 @@ router.patch(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params; const { message_id, channel_id } = req.params;
var body = req.body as MessageCreateSchema; let body = req.body as MessageCreateSchema;
const message = await Message.findOneOrFail({ const message = await Message.findOneOrFail({
where: { id: message_id, channel_id }, where: { id: message_id, channel_id },
@ -85,6 +84,7 @@ router.patch(
const new_message = await handleMessage({ const new_message = await handleMessage({
...message, ...message,
// TODO: should message_reference be overridable? // TODO: should message_reference be overridable?
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
message_reference: message.message_reference, message_reference: message.message_reference,
...body, ...body,
@ -127,7 +127,7 @@ router.put(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params; const { channel_id, message_id } = req.params;
var body = req.body as MessageCreateSchema; const body = req.body as MessageCreateSchema;
const attachments: Attachment[] = []; const attachments: Attachment[] = [];
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
@ -171,7 +171,7 @@ router.put(
const embeds = body.embeds || []; const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed); if (body.embed) embeds.push(body.embed);
let message = await handleMessage({ const message = await handleMessage({
...body, ...body,
type: 0, type: 0,
pinned: false, pinned: false,
@ -197,7 +197,10 @@ router.put(
channel.save(), channel.save(),
]); ]);
postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error // no await as it shouldnt block the message send function and silently catch error
postHandleMessage(message).catch((e) =>
console.error("[Message] post-message handler failed", e),
);
return res.json(message); return res.json(message);
}, },

View File

@ -165,13 +165,13 @@ router.put(
x.emoji.name === emoji.name, x.emoji.name === emoji.name,
); );
if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); if (!already_added) req.permission?.hasThrow("ADD_REACTIONS");
if (emoji.id) { if (emoji.id) {
const external_emoji = await Emoji.findOneOrFail({ const external_emoji = await Emoji.findOneOrFail({
where: { id: emoji.id }, where: { id: emoji.id },
}); });
if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); if (!already_added) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS");
emoji.animated = external_emoji.animated; emoji.animated = external_emoji.animated;
emoji.name = external_emoji.name; emoji.name = external_emoji.name;
} }
@ -214,7 +214,8 @@ router.delete(
"/:emoji/:user_id", "/:emoji/:user_id",
route({}), route({}),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
var { message_id, channel_id, user_id } = req.params; let { user_id } = req.params;
const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji); const emoji = getEmoji(req.params.emoji);

View File

@ -50,7 +50,7 @@ router.post(
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
rights.hasThrow("SELF_DELETE_MESSAGES"); rights.hasThrow("SELF_DELETE_MESSAGES");
let superuser = rights.has("MANAGE_MESSAGES"); const superuser = rights.has("MANAGE_MESSAGES");
const permission = await getPermission( const permission = await getPermission(
req.user_id, req.user_id,
channel?.guild_id, channel?.guild_id,

View File

@ -31,23 +31,16 @@ import {
Snowflake, Snowflake,
uploadFile, uploadFile,
Member, Member,
Role,
MessageCreateSchema, MessageCreateSchema,
ReadState, ReadState,
DiscordApiErrors,
getRights,
Rights, Rights,
Reaction,
User,
} from "@fosscord/util"; } from "@fosscord/util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { import { handleMessage, postHandleMessage, route } from "@fosscord/api";
handleMessage,
postHandleMessage,
route,
getIpAdress,
} from "@fosscord/api";
import multer from "multer"; import multer from "multer";
import { yellow } from "picocolors"; import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
import { URL } from "url"; import { URL } from "url";
const router: Router = Router(); const router: Router = Router();
@ -93,7 +86,7 @@ router.get("/", async (req: Request, res: Response) => {
if (limit < 1 || limit > 100) if (limit < 1 || limit > 100)
throw new HTTPError("limit must be between 1 and 100", 422); throw new HTTPError("limit must be between 1 and 100", 422);
var halfLimit = Math.floor(limit / 2); const halfLimit = Math.floor(limit / 2);
const permissions = await getPermission( const permissions = await getPermission(
req.user_id, req.user_id,
@ -103,7 +96,9 @@ router.get("/", async (req: Request, res: Response) => {
permissions.hasThrow("VIEW_CHANNEL"); permissions.hasThrow("VIEW_CHANNEL");
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
var query: FindManyOptions<Message> & { where: { id?: any } } = { const query: FindManyOptions<Message> & {
where: { id?: FindOperator<string> | FindOperator<string>[] };
} = {
order: { timestamp: "DESC" }, order: { timestamp: "DESC" },
take: limit, take: limit,
where: { channel_id }, where: { channel_id },
@ -140,23 +135,21 @@ router.get("/", async (req: Request, res: Response) => {
const endpoint = Config.get().cdn.endpointPublic; const endpoint = Config.get().cdn.endpointPublic;
return res.json( return res.json(
messages.map((x: any) => { messages.map((x: Partial<Message>) => {
(x.reactions || []).forEach((x: any) => { (x.reactions || []).forEach((y: Partial<Reaction>) => {
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
if ((x.user_ids || []).includes(req.user_id)) x.me = true; //@ts-ignore
// @ts-ignore if ((y.user_ids || []).includes(req.user_id)) y.me = true;
delete x.user_ids; delete y.user_ids;
}); });
// @ts-ignore
if (!x.author) if (!x.author)
x.author = { x.author = User.create({
id: "4", id: "4",
discriminator: "0000", discriminator: "0000",
username: "Fosscord Ghost", username: "Fosscord Ghost",
public_flags: "0", public_flags: 0,
avatar: null, });
}; x.attachments?.forEach((y: Attachment) => {
x.attachments?.forEach((y: any) => {
// dynamically set attachment proxy_url in case the endpoint changed // dynamically set attachment proxy_url in case the endpoint changed
const uri = y.proxy_url.startsWith("http") const uri = y.proxy_url.startsWith("http")
? y.proxy_url ? y.proxy_url
@ -168,7 +161,7 @@ router.get("/", async (req: Request, res: Response) => {
/** /**
Some clients ( discord.js ) only check if a property exists within the response, Some clients ( discord.js ) only check if a property exists within the response,
which causes erorrs when, say, the `application` property is `null`. which causes errors when, say, the `application` property is `null`.
**/ **/
// for (var curr in x) { // for (var curr in x) {
@ -216,7 +209,7 @@ router.post(
}), }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
var body = req.body as MessageCreateSchema; const body = req.body as MessageCreateSchema;
const attachments: Attachment[] = []; const attachments: Attachment[] = [];
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
@ -244,7 +237,7 @@ router.post(
} }
if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
var limits = Config.get().limits; const limits = Config.get().limits;
if (limits.absoluteRate.register.enabled) { if (limits.absoluteRate.register.enabled) {
const count = await Message.count({ const count = await Message.count({
where: { where: {
@ -269,7 +262,7 @@ router.post(
} }
const files = (req.files as Express.Multer.File[]) ?? []; const files = (req.files as Express.Multer.File[]) ?? [];
for (var currFile of files) { for (const currFile of files) {
try { try {
const file = await uploadFile( const file = await uploadFile(
`/attachments/${channel.id}`, `/attachments/${channel.id}`,
@ -279,13 +272,13 @@ router.post(
Attachment.create({ ...file, proxy_url: file.url }), Attachment.create({ ...file, proxy_url: file.url }),
); );
} catch (error) { } catch (error) {
return res.status(400).json({ message: error!.toString() }); return res.status(400).json({ message: error?.toString() });
} }
} }
const embeds = body.embeds || []; const embeds = body.embeds || [];
if (body.embed) embeds.push(body.embed); if (body.embed) embeds.push(body.embed);
let message = await handleMessage({ const message = await handleMessage({
...body, ...body,
type: 0, type: 0,
pinned: false, pinned: false,
@ -304,7 +297,7 @@ router.post(
// Only one recipients should be closed here, since in group DMs the recipient is deleted not closed // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
await Promise.all( await Promise.all(
channel.recipients!.map((recipient) => { channel.recipients?.map((recipient) => {
if (recipient.closed) { if (recipient.closed) {
recipient.closed = false; recipient.closed = false;
return Promise.all([ return Promise.all([
@ -318,7 +311,7 @@ router.post(
}), }),
]); ]);
} }
}), }) || [],
); );
} }
@ -332,6 +325,7 @@ router.post(
}); });
} }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
message.member.roles = message.member.roles message.member.roles = message.member.roles
.filter((x) => x.id != x.guild_id) .filter((x) => x.id != x.guild_id)
@ -362,7 +356,10 @@ router.post(
channel.save(), channel.save(),
]); ]);
postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error // no await as it shouldnt block the message send function and silently catch error
postHandleMessage(message).catch((e) =>
console.error("[Message] post-message handler failed", e),
);
return res.json(message); return res.json(message);
}, },

View File

@ -43,7 +43,7 @@ router.put(
const { channel_id, overwrite_id } = req.params; const { channel_id, overwrite_id } = req.params;
const body = req.body as ChannelPermissionOverwriteSchema; const body = req.body as ChannelPermissionOverwriteSchema;
var channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },
}); });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
@ -56,22 +56,24 @@ router.put(
throw new HTTPError("user not found", 404); throw new HTTPError("user not found", 404);
} else throw new HTTPError("type not supported", 501); } else throw new HTTPError("type not supported", 501);
//@ts-ignore let overwrite: ChannelPermissionOverwrite | undefined =
var overwrite: ChannelPermissionOverwrite =
channel.permission_overwrites?.find((x) => x.id === overwrite_id); channel.permission_overwrites?.find((x) => x.id === overwrite_id);
if (!overwrite) { if (!overwrite) {
// @ts-ignore
overwrite = { overwrite = {
id: overwrite_id, id: overwrite_id,
type: body.type, type: body.type,
allow: "0",
deny: "0",
}; };
channel.permission_overwrites!.push(overwrite); channel.permission_overwrites?.push(overwrite);
} }
overwrite.allow = String( overwrite.allow = String(
req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")), (req.permission?.bitfield || 0n) &
(BigInt(body.allow) || BigInt("0")),
); );
overwrite.deny = String( overwrite.deny = String(
req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")), (req.permission?.bitfield || 0n) &
(BigInt(body.deny) || BigInt("0")),
); );
await Promise.all([ await Promise.all([
@ -99,7 +101,7 @@ router.delete(
}); });
if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (!channel.guild_id) throw new HTTPError("Channel not found", 404);
channel.permission_overwrites = channel.permission_overwrites!.filter( channel.permission_overwrites = channel.permission_overwrites?.filter(
(x) => x.id === overwrite_id, (x) => x.id === overwrite_id,
); );

View File

@ -21,13 +21,11 @@ import {
ChannelPinsUpdateEvent, ChannelPinsUpdateEvent,
Config, Config,
emitEvent, emitEvent,
getPermission,
Message, Message,
MessageUpdateEvent, MessageUpdateEvent,
DiscordApiErrors, DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
const router: Router = Router(); const router: Router = Router();
@ -43,7 +41,7 @@ router.put(
}); });
// * in dm channels anyone can pin messages -> only check for guilds // * in dm channels anyone can pin messages -> only check for guilds
if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
const pinned_count = await Message.count({ const pinned_count = await Message.count({
where: { channel: { id: channel_id }, pinned: true }, where: { channel: { id: channel_id }, pinned: true },
@ -83,7 +81,7 @@ router.delete(
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
where: { id: channel_id }, where: { id: channel_id },
}); });
if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); if (channel.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES");
const message = await Message.findOneOrFail({ const message = await Message.findOneOrFail({
where: { id: message_id }, where: { id: message_id },
@ -120,7 +118,7 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id } = req.params; const { channel_id } = req.params;
let pins = await Message.find({ const pins = await Message.find({
where: { channel_id: channel_id, pinned: true }, where: { channel_id: channel_id, pinned: true },
}); });

View File

@ -19,10 +19,9 @@
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { isTextChannel } from "./messages"; import { isTextChannel } from "./messages";
import { FindManyOptions, Between, Not } from "typeorm"; import { FindManyOptions, Between, Not, FindOperator } from "typeorm";
import { import {
Channel, Channel,
Config,
emitEvent, emitEvent,
getPermission, getPermission,
getRights, getRights,
@ -69,7 +68,9 @@ router.post(
// TODO: send the deletion event bite-by-bite to prevent client stress // TODO: send the deletion event bite-by-bite to prevent client stress
var query: FindManyOptions<Message> & { where: { id?: any } } = { const query: FindManyOptions<Message> & {
where: { id?: FindOperator<string> };
} = {
order: { id: "ASC" }, order: { id: "ASC" },
// take: limit, // take: limit,
where: { where: {
@ -93,7 +94,6 @@ router.post(
}; };
const messages = await Message.find(query); const messages = await Message.find(query);
const endpoint = Config.get().cdn.endpointPublic;
if (messages.length == 0) { if (messages.length == 0) {
res.sendStatus(304); res.sendStatus(304);

View File

@ -41,7 +41,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
if (channel.type !== ChannelType.GROUP_DM) { if (channel.type !== ChannelType.GROUP_DM) {
const recipients = [ const recipients = [
...channel.recipients!.map((r) => r.user_id), ...(channel.recipients?.map((r) => r.user_id) || []),
user_id, user_id,
].unique(); ].unique();
@ -51,11 +51,11 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
); );
return res.status(201).json(new_channel); return res.status(201).json(new_channel);
} else { } else {
if (channel.recipients!.map((r) => r.user_id).includes(user_id)) { if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
} }
channel.recipients!.push( channel.recipients?.push(
Recipient.create({ channel_id: channel_id, user_id: user_id }), Recipient.create({ channel_id: channel_id, user_id: user_id }),
); );
await channel.save(); await channel.save();
@ -95,7 +95,7 @@ router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
) )
throw DiscordApiErrors.MISSING_PERMISSIONS; throw DiscordApiErrors.MISSING_PERMISSIONS;
if (!channel.recipients!.map((r) => r.user_id).includes(user_id)) { if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
} }

View File

@ -55,10 +55,10 @@ router.post(
const webhook_count = await Webhook.count({ where: { channel_id } }); const webhook_count = await Webhook.count({ where: { channel_id } });
const { maxWebhooks } = Config.get().limits.channel; const { maxWebhooks } = Config.get().limits.channel;
if (webhook_count > maxWebhooks) if (maxWebhooks && webhook_count > maxWebhooks)
throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks);
var { avatar, name } = req.body as WebhookCreateSchema; let { avatar, name } = req.body as WebhookCreateSchema;
name = trimSpecial(name); name = trimSpecial(name);
// TODO: move this // TODO: move this

View File

@ -26,8 +26,8 @@ const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { offset, limit, categories } = req.query; const { offset, limit, categories } = req.query;
var showAllGuilds = Config.get().guild.discovery.showAllGuilds; const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
var configLimit = Config.get().guild.discovery.limit; const configLimit = Config.get().guild.discovery.limit;
let guilds; let guilds;
if (categories == undefined) { if (categories == undefined) {
guilds = showAllGuilds guilds = showAllGuilds

View File

@ -26,7 +26,8 @@ router.get("/categories", route({}), async (req: Request, res: Response) => {
// TODO: // TODO:
// Get locale instead // Get locale instead
const { locale, primary_only } = req.query; // const { locale, primary_only } = req.query;
const { primary_only } = req.query;
const out = primary_only const out = primary_only
? await Categories.find() ? await Categories.find()

View File

@ -22,11 +22,6 @@ import { FieldErrors, Release } from "@fosscord/util";
const router = Router(); const router = Router();
/*
TODO: Putting the download route in /routes/download.ts doesn't register the route, for some reason
But putting it here *does*
*/
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { platform } = req.query; const { platform } = req.query;

View File

@ -41,7 +41,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}, },
); );
const { results } = (await response.json()) as any; // TODO: types const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200); res.json(results.map(parseGifResult)).status(200);
}); });

View File

@ -41,7 +41,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}, },
); );
const { results } = (await response.json()) as any; // TODO: types const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200); res.json(results.map(parseGifResult)).status(200);
}); });

View File

@ -25,7 +25,57 @@ import { HTTPError } from "lambert-server";
const router = Router(); const router = Router();
export function parseGifResult(result: any) { // TODO: Move somewhere else
enum TENOR_GIF_TYPES {
gif,
mediumgif,
tinygif,
nanogif,
mp4,
loopedmp4,
tinymp4,
nanomp4,
webm,
tinywebm,
nanowebm,
}
type TENOR_MEDIA = {
preview: string;
url: string;
dims: number[];
size: number;
};
type TENOR_GIF = {
created: number;
hasaudio: boolean;
id: string;
media: { [type in keyof typeof TENOR_GIF_TYPES]: TENOR_MEDIA }[];
tags: string[];
title: string;
itemurl: string;
hascaption: boolean;
url: string;
};
type TENOR_CATEGORY = {
searchterm: string;
path: string;
image: string;
name: string;
};
type TENOR_CATEGORIES_RESULTS = {
tags: TENOR_CATEGORY[];
};
type TENOR_TRENDING_RESULTS = {
next: string;
results: TENOR_GIF[];
};
export function parseGifResult(result: TENOR_GIF) {
return { return {
id: result.id, id: result.id,
title: result.title, title: result.title,
@ -50,7 +100,8 @@ export function getGifApiKey() {
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
// TODO: Custom providers // TODO: Custom providers
// TODO: return gifs as mp4 // TODO: return gifs as mp4
const { media_format, locale } = req.query; // const { media_format, locale } = req.query;
const { locale } = req.query;
const apiKey = getGifApiKey(); const apiKey = getGifApiKey();
@ -75,11 +126,11 @@ router.get("/", route({}), async (req: Request, res: Response) => {
), ),
]); ]);
const { tags } = (await responseSource.json()) as any; // TODO: types const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS;
const { results } = (await trendGifSource.json()) as any; //TODO: types; const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS;
res.json({ res.json({
categories: tags.map((x: any) => ({ categories: tags.map((x) => ({
name: x.searchterm, name: x.searchterm,
src: x.image, src: x.image,
})), })),

View File

@ -25,10 +25,11 @@ import { Like } from "typeorm";
const router = Router(); const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { limit, personalization_disabled } = req.query; // const { limit, personalization_disabled } = req.query;
var showAllGuilds = Config.get().guild.discovery.showAllGuilds; const { limit } = req.query;
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
const genLoadId = (size: Number) => const genLoadId = (size: number) =>
[...Array(size)] [...Array(size)]
.map(() => Math.floor(Math.random() * 16).toString(16)) .map(() => Math.floor(Math.random() * 16).toString(16))
.join(""); .join("");

View File

@ -41,8 +41,8 @@ router.get(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { guild_id } = req.params; const { guild_id } = req.params;
let bans = await Ban.find({ where: { guild_id: guild_id } }); const bans = await Ban.find({ where: { guild_id: guild_id } });
let promisesToAwait: object[] = []; const promisesToAwait: object[] = [];
const bansObj: 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
@ -104,14 +104,14 @@ router.put(
if ( if (
req.user_id === banned_user_id && req.user_id === banned_user_id &&
banned_user_id === req.permission!.cache.guild?.owner_id banned_user_id === req.permission?.cache.guild?.owner_id
) )
throw new HTTPError( throw new HTTPError(
"You are the guild owner, hence can't ban yourself", "You are the guild owner, hence can't ban yourself",
403, 403,
); );
if (req.permission!.cache.guild?.owner_id === banned_user_id) if (req.permission?.cache.guild?.owner_id === banned_user_id)
throw new HTTPError("You can't ban the owner", 400); throw new HTTPError("You can't ban the owner", 400);
const banned_user = await User.getPublicUser(banned_user_id); const banned_user = await User.getPublicUser(banned_user_id);
@ -149,7 +149,7 @@ router.put(
const banned_user = await User.getPublicUser(req.params.user_id); const banned_user = await User.getPublicUser(req.params.user_id);
if (req.permission!.cache.guild?.owner_id === req.params.user_id) if (req.permission?.cache.guild?.owner_id === req.params.user_id)
throw new HTTPError( throw new HTTPError(
"You are the guild owner, hence can't ban yourself", "You are the guild owner, hence can't ban yourself",
403, 403,
@ -186,7 +186,7 @@ router.delete(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { guild_id, user_id } = req.params; const { guild_id, user_id } = req.params;
let ban = await Ban.findOneOrFail({ const ban = await Ban.findOneOrFail({
where: { guild_id: guild_id, user_id: user_id }, where: { guild_id: guild_id, user_id: user_id },
}); });

View File

@ -68,7 +68,7 @@ router.patch(
400, 400,
); );
const opts: any = {}; const opts: Partial<Channel> = {};
if (x.position != null) opts.position = x.position; if (x.position != null) opts.position = x.position;
if (x.parent_id) { if (x.parent_id) {

View File

@ -16,17 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { import { emitEvent, GuildDeleteEvent, Guild } from "@fosscord/util";
Channel,
emitEvent,
GuildDeleteEvent,
Guild,
Member,
Message,
Role,
Invite,
Emoji,
} from "@fosscord/util";
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
@ -36,7 +26,7 @@ const router = Router();
// discord prefixes this route with /delete instead of using the delete method // discord prefixes this route with /delete instead of using the delete method
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
router.post("/", route({}), async (req: Request, res: Response) => { router.post("/", route({}), async (req: Request, res: Response) => {
var { guild_id } = req.params; const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ const guild = await Guild.findOneOrFail({
where: { id: guild_id }, where: { id: guild_id },

View File

@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Guild, Config } from "@fosscord/util";
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";

View File

@ -47,10 +47,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
401, 401,
); );
// @ts-ignore return res.send({
guild.joined_at = member?.joined_at; ...guild,
joined_at: member?.joined_at,
return res.send(guild); });
}); });
router.patch( router.patch(
@ -68,7 +68,7 @@ router.patch(
"MANAGE_GUILDS", "MANAGE_GUILDS",
); );
var guild = await Guild.findOneOrFail({ const guild = await Guild.findOneOrFail({
where: { id: guild_id }, where: { id: guild_id },
relations: ["emojis", "roles", "stickers"], relations: ["emojis", "roles", "stickers"],
}); });
@ -110,7 +110,7 @@ router.patch(
"DISCOVERABLE", "DISCOVERABLE",
]; ];
for (var feature of diff) { for (const feature of diff) {
if (MUTABLE_FEATURES.includes(feature)) continue; if (MUTABLE_FEATURES.includes(feature)) continue;
throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams( throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams(

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util"; import { Invite, PublicInviteRelation } from "@fosscord/util";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";

View File

@ -49,11 +49,12 @@ router.patch(
"/", "/",
route({ body: "MemberChangeSchema" }), route({ body: "MemberChangeSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let { guild_id, member_id } = req.params; const { guild_id } = req.params;
if (member_id === "@me") member_id = req.user_id; const member_id =
req.params.member_id === "@me" ? req.user_id : req.params.member_id;
const body = req.body as MemberChangeSchema; const body = req.body as MemberChangeSchema;
let member = await Member.findOneOrFail({ const member = await Member.findOneOrFail({
where: { id: member_id, guild_id }, where: { id: member_id, guild_id },
relations: ["roles", "user"], relations: ["roles", "user"],
}); });
@ -101,7 +102,8 @@ router.put("/", route({}), async (req: Request, res: Response) => {
const rights = await getRights(req.user_id); const rights = await getRights(req.user_id);
let { guild_id, member_id } = req.params; const { guild_id } = req.params;
let { member_id } = req.params;
if (member_id === "@me") { if (member_id === "@me") {
member_id = req.user_id; member_id = req.user_id;
rights.hasThrow("JOIN_GUILDS"); rights.hasThrow("JOIN_GUILDS");
@ -109,19 +111,19 @@ router.put("/", route({}), async (req: Request, res: Response) => {
// TODO: join others by controller // TODO: join others by controller
} }
var guild = await Guild.findOneOrFail({ const guild = await Guild.findOneOrFail({
where: { id: guild_id }, where: { id: guild_id },
}); });
var emoji = await Emoji.find({ const emoji = await Emoji.find({
where: { guild_id: guild_id }, where: { guild_id: guild_id },
}); });
var roles = await Role.find({ const roles = await Role.find({
where: { guild_id: guild_id }, where: { guild_id: guild_id },
}); });
var stickers = await Sticker.find({ const stickers = await Sticker.find({
where: { guild_id: guild_id }, where: { guild_id: guild_id },
}); });

View File

@ -26,12 +26,12 @@ router.patch(
"/", "/",
route({ body: "MemberNickChangeSchema" }), route({ body: "MemberNickChangeSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
var { guild_id, member_id } = req.params; const { guild_id } = req.params;
var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";
if (member_id === "@me") { const member_id =
member_id = req.user_id; req.params.member_id === "@me"
permissionString = "CHANGE_NICKNAME"; ? ((permissionString = "CHANGE_NICKNAME"), req.user_id)
} : req.params.member_id;
const perms = await getPermission(req.user_id, guild_id); const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow(permissionString); perms.hasThrow(permissionString);

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getPermission, Member } from "@fosscord/util"; import { Member } from "@fosscord/util";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";

View File

@ -17,7 +17,7 @@
*/ */
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; import { Member, PublicMemberProjection } from "@fosscord/util";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { MoreThan } from "typeorm"; import { MoreThan } from "typeorm";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { getPermission, FieldErrors, Message, Channel } from "@fosscord/util"; import { getPermission, FieldErrors, Message, Channel } from "@fosscord/util";
@ -28,10 +30,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { const {
channel_id, channel_id,
content, content,
include_nsfw, // TODO // include_nsfw, // TODO
offset, offset,
sort_order, sort_order,
sort_by, // TODO: Handle 'relevance' // sort_by, // TODO: Handle 'relevance'
limit, limit,
author_id, author_id,
} = req.query; } = req.query;
@ -62,7 +64,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
if (!permissions.has("READ_MESSAGE_HISTORY")) if (!permissions.has("READ_MESSAGE_HISTORY"))
return res.json({ messages: [], total_results: 0 }); return res.json({ messages: [], total_results: 0 });
var query: FindManyOptions<Message> = { const query: FindManyOptions<Message> = {
order: { order: {
timestamp: sort_order timestamp: sort_order
? (sort_order.toUpperCase() as "ASC" | "DESC") ? (sort_order.toUpperCase() as "ASC" | "DESC")
@ -87,7 +89,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
skip: offset ? Number(offset) : 0, skip: offset ? Number(offset) : 0,
}; };
//@ts-ignore //@ts-ignore
if (channel_id) query.where!.channel = { id: channel_id }; if (channel_id) query.where.channel = { id: channel_id };
else { else {
// get all channel IDs that this user can access // get all channel IDs that this user can access
const channels = await Channel.find({ const channels = await Channel.find({
@ -96,7 +98,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}); });
const ids = []; const ids = [];
for (var channel of channels) { for (const channel of channels) {
const perm = await getPermission( const perm = await getPermission(
req.user_id, req.user_id,
req.params.guild_id, req.params.guild_id,
@ -108,12 +110,12 @@ router.get("/", route({}), async (req: Request, res: Response) => {
} }
//@ts-ignore //@ts-ignore
query.where!.channel = { id: In(ids) }; query.where.channel = { id: In(ids) };
} }
//@ts-ignore //@ts-ignore
if (author_id) query.where!.author = { id: author_id }; if (author_id) query.where.author = { id: author_id };
//@ts-ignore //@ts-ignore
if (content) query.where!.content = Like(`%${content}%`); if (content) query.where.content = Like(`%${content}%`);
const messages: Message[] = await Message.find(query); const messages: Message[] = await Message.find(query);

View File

@ -33,8 +33,9 @@ router.patch(
"/:member_id", "/:member_id",
route({ body: "MemberChangeProfileSchema" }), route({ body: "MemberChangeProfileSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let { guild_id, member_id } = req.params; const { guild_id } = req.params;
if (member_id === "@me") member_id = req.user_id; // const member_id =
// req.params.member_id === "@me" ? req.user_id : req.params.member_id;
const body = req.body as MemberChangeProfileSchema; const body = req.body as MemberChangeProfileSchema;
let member = await Member.findOneOrFail({ let member = await Member.findOneOrFail({

View File

@ -29,16 +29,16 @@ export const inactiveMembers = async (
days: number, days: number,
roles: string[] = [], roles: string[] = [],
) => { ) => {
var date = new Date(); const date = new Date();
date.setDate(date.getDate() - days); date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar? //Snowflake should have `generateFromTime` method? Or similar?
var minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22);
/** /**
idea: ability to customise the cutoff variable idea: ability to customise the cutoff variable
possible candidates: public read receipt, last presence, last VC leave possible candidates: public read receipt, last presence, last VC leave
**/ **/
var members = await Member.find({ let members = await Member.find({
where: [ where: [
{ {
guild_id, guild_id,
@ -83,7 +83,7 @@ export const inactiveMembers = async (
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const days = parseInt(req.query.days as string); const days = parseInt(req.query.days as string);
var roles = req.query.include_roles; let roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; //express will return array otherwise if (typeof roles === "string") roles = [roles]; //express will return array otherwise
const members = await inactiveMembers( const members = await inactiveMembers(
@ -102,7 +102,7 @@ router.post(
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const days = parseInt(req.body.days); const days = parseInt(req.body.days);
var roles = req.query.include_roles; let roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; if (typeof roles === "string") roles = [roles];
const { guild_id } = req.params; const { guild_id } = req.params;

View File

@ -16,10 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Config, Guild, Member } from "@fosscord/util"; import { Guild } from "@fosscord/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { getVoiceRegions, route } from "@fosscord/api"; import { getVoiceRegions, route, getIpAdress } from "@fosscord/api";
import { getIpAdress } from "@fosscord/api";
const router = Router(); const router = Router();

View File

@ -87,7 +87,8 @@ router.patch(
role.assign({ role.assign({
...body, ...body,
permissions: String( permissions: String(
req.permission!.bitfield & BigInt(body.permissions || "0"), (req.permission?.bitfield || 0n) &
BigInt(body.permissions || "0"),
), ),
}); });

View File

@ -68,7 +68,8 @@ router.post(
guild_id: guild_id, guild_id: guild_id,
managed: false, managed: false,
permissions: String( permissions: String(
req.permission!.bitfield & BigInt(body.permissions || "0"), (req.permission?.bitfield || 0n) &
BigInt(body.permissions || "0"),
), ),
tags: undefined, tags: undefined,
icon: undefined, icon: undefined,

View File

@ -44,7 +44,7 @@ const TemplateGuildProjection: (keyof Guild)[] = [
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { guild_id } = req.params; const { guild_id } = req.params;
var templates = await Template.find({ const templates = await Template.find({
where: { source_guild_id: guild_id }, where: { source_guild_id: guild_id },
}); });
@ -60,9 +60,9 @@ router.post(
where: { id: guild_id }, where: { id: guild_id },
select: TemplateGuildProjection, select: TemplateGuildProjection,
}); });
const exists = await Template.findOneOrFail({ const exists = await Template.findOne({
where: { id: guild_id }, where: { id: guild_id },
}).catch((e) => {}); });
if (exists) throw new HTTPError("Template already exists", 400); if (exists) throw new HTTPError("Template already exists", 400);
const template = await Template.create({ const template = await Template.create({

View File

@ -37,8 +37,9 @@ router.patch(
route({ body: "VoiceStateUpdateSchema" }), route({ body: "VoiceStateUpdateSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema; const body = req.body as VoiceStateUpdateSchema;
var { guild_id, user_id } = req.params; const { guild_id } = req.params;
if (user_id === "@me") user_id = req.user_id; const user_id =
req.params.user_id === "@me" ? req.user_id : req.params.user_id;
const perms = await getPermission( const perms = await getPermission(
req.user_id, req.user_id,

View File

@ -17,14 +17,7 @@
*/ */
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { import { Permissions, Guild, Invite, Channel, Member } from "@fosscord/util";
Config,
Permissions,
Guild,
Invite,
Channel,
Member,
} from "@fosscord/util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import { random, route } from "@fosscord/api"; import { random, route } from "@fosscord/api";
@ -46,7 +39,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
// Fetch existing widget invite for widget channel // Fetch existing widget invite for widget channel
var invite = await Invite.findOne({ let invite = await Invite.findOne({
where: { channel_id: guild.widget_channel_id }, where: { channel_id: guild.widget_channel_id },
}); });
@ -70,7 +63,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
} }
// Fetch voice channels, and the @everyone permissions object // Fetch voice channels, and the @everyone permissions object
const channels = [] as any[]; const channels: { id: string; name: string; position: number }[] = [];
( (
await Channel.find({ await Channel.find({
@ -88,15 +81,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
) { ) {
channels.push({ channels.push({
id: doc.id, id: doc.id,
name: doc.name, name: doc.name ?? "Unknown channel",
position: doc.position, position: doc.position ?? 0,
}); });
} }
}); });
// Fetch members // Fetch members
// TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
let members = await Member.find({ where: { guild_id: guild_id } }); const members = await Member.find({ where: { guild_id: guild_id } });
// Construct object to respond with // Construct object to respond with
const data = { const data = {

View File

@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { Guild } from "@fosscord/util"; import { Guild } from "@fosscord/util";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
@ -161,8 +163,7 @@ async function drawIcon(
scale: number, scale: number,
icon: string, icon: string,
) { ) {
// @ts-ignore const img = new (require("canvas").Image)();
const img = new require("canvas").Image();
img.src = icon; img.src = icon;
// Do some canvas clipping magic! // Do some canvas clipping magic!

View File

@ -18,7 +18,6 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { import {
Role,
Guild, Guild,
Config, Config,
getRights, getRights,
@ -52,6 +51,7 @@ router.post(
const { autoJoin } = Config.get().guild; const { autoJoin } = Config.get().guild;
if (autoJoin.enabled && !autoJoin.guilds?.length) { if (autoJoin.enabled && !autoJoin.guilds?.length) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
} }

View File

@ -86,8 +86,8 @@ router.post(
const { const {
enabled, enabled,
allowTemplateCreation, allowTemplateCreation,
allowDiscordTemplates, // allowDiscordTemplates,
allowRaws, // allowRaws,
} = Config.get().templates; } = Config.get().templates;
if (!enabled) if (!enabled)
return res return res
@ -121,7 +121,7 @@ router.post(
const guild_id = Snowflake.generate(); const guild_id = Snowflake.generate();
const [guild, role] = await Promise.all([ const [guild] = await Promise.all([
Guild.create({ Guild.create({
...body, ...body,
...template.serialized_source_guild, ...template.serialized_source_guild,

View File

@ -27,16 +27,14 @@ import {
Member, Member,
Permissions, Permissions,
User, User,
getRights,
Rights,
MemberPrivateProjection,
} from "@fosscord/util"; } from "@fosscord/util";
const router = Router(); const router = Router();
// TODO: scopes, other oauth types // TODO: scopes, other oauth types
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { client_id, scope, response_type, redirect_url } = req.query; // const { client_id, scope, response_type, redirect_url } = req.query;
const { client_id } = req.query;
const app = await Application.findOne({ const app = await Application.findOne({
where: { where: {
@ -68,6 +66,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}, },
}, },
relations: ["guild", "roles"], relations: ["guild", "roles"],
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
// prettier-ignore // prettier-ignore
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"], select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
@ -139,7 +138,8 @@ router.post(
route({ body: "ApplicationAuthorizeSchema" }), route({ body: "ApplicationAuthorizeSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const body = req.body as ApplicationAuthorizeSchema; const body = req.body as ApplicationAuthorizeSchema;
const { client_id, scope, response_type, redirect_url } = req.query; // const { client_id, scope, response_type, redirect_url } = req.query;
const { client_id } = req.query;
// TODO: captcha verification // TODO: captcha verification
// TODO: MFA verification // TODO: MFA verification
@ -153,7 +153,7 @@ router.post(
// getPermission cache won't exist if we're owner // getPermission cache won't exist if we're owner
if ( if (
Object.keys(perms.cache || {}).length > 0 && Object.keys(perms.cache || {}).length > 0 &&
perms.cache.member!.user.bot perms.cache.member?.user.bot
) )
throw DiscordApiErrors.UNAUTHORIZED; throw DiscordApiErrors.UNAUTHORIZED;
perms.hasThrow("MANAGE_GUILD"); perms.hasThrow("MANAGE_GUILD");

View File

@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Guild, Config } from "@fosscord/util";
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";

View File

@ -19,7 +19,6 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { Config } from "@fosscord/util"; import { Config } from "@fosscord/util";
import { config } from "dotenv";
const router = Router(); const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {

View File

@ -23,7 +23,7 @@ const router: Router = Router();
router.get("/:id", route({}), async (req: Request, res: Response) => { router.get("/:id", route({}), async (req: Request, res: Response) => {
//TODO //TODO
const id = req.params.id; // const id = req.params.id;
res.json({ res.json({
id: "", id: "",
summary: "", summary: "",

View File

@ -23,7 +23,7 @@ const router: Router = Router();
router.get("/:id", route({}), async (req: Request, res: Response) => { router.get("/:id", route({}), async (req: Request, res: Response) => {
//TODO //TODO
const id = req.params.id; // const id = req.params.id;
res.json({ res.json({
id: "", id: "",
summary: "", summary: "",

View File

@ -18,12 +18,11 @@
import { Router, Response, Request } from "express"; import { Router, Response, Request } from "express";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import { Config, FieldErrors, Release } from "@fosscord/util"; import { FieldErrors, Release } from "@fosscord/util";
const router = Router(); const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => { router.get("/", route({}), async (req: Request, res: Response) => {
const { client } = Config.get();
const platform = req.query.platform; const platform = req.query.platform;
if (!platform) if (!platform)

View File

@ -23,7 +23,6 @@ import {
PrivateUserProjection, PrivateUserProjection,
User, User,
UserDeleteEvent, UserDeleteEvent,
UserDeleteSchema,
} from "@fosscord/util"; } from "@fosscord/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
@ -33,7 +32,7 @@ router.post(
"/", "/",
route({ right: "MANAGE_USERS" }), route({ right: "MANAGE_USERS" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
let user = await User.findOneOrFail({ await User.findOneOrFail({
where: { id: req.params.id }, where: { id: req.params.id },
select: [...PrivateUserProjection, "data"], select: [...PrivateUserProjection, "data"],
}); });

View File

@ -19,11 +19,9 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { import {
PublicConnectedAccount, PublicConnectedAccount,
PublicUser,
User, User,
UserPublic, UserPublic,
Member, Member,
Guild,
UserProfileModifySchema, UserProfileModifySchema,
handleFile, handleFile,
PrivateUserProjection, PrivateUserProjection,
@ -53,8 +51,8 @@ router.get(
relations: ["connected_accounts"], relations: ["connected_accounts"],
}); });
var mutual_guilds: object[] = []; const mutual_guilds: object[] = [];
var premium_guild_since; let premium_guild_since;
if (with_mutual_guilds == "true") { if (with_mutual_guilds == "true") {
const requested_member = await Member.find({ const requested_member = await Member.find({
@ -169,7 +167,7 @@ router.patch(
`/banners/${req.user_id}`, `/banners/${req.user_id}`,
body.banner as string, body.banner as string,
); );
let user = await User.findOneOrFail({ const user = await User.findOneOrFail({
where: { id: req.user_id }, where: { id: req.user_id },
select: [...PrivateUserProjection, "data"], select: [...PrivateUserProjection, "data"],
}); });
@ -177,6 +175,7 @@ router.patch(
user.assign(body); user.assign(body);
await user.save(); await user.save();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
delete user.data; delete user.data;

View File

@ -36,7 +36,7 @@ router.get(
"/", "/",
route({ test: { response: { body: "UserRelationsResponse" } } }), route({ test: { response: { body: "UserRelationsResponse" } } }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
var mutual_relations: object[] = []; const mutual_relations: object[] = [];
const requested_relations = await User.findOneOrFail({ const requested_relations = await User.findOneOrFail({
where: { id: req.params.id }, where: { id: req.params.id },
relations: ["relationships"], relations: ["relationships"],
@ -53,7 +53,7 @@ router.get(
rmem.type === 1 && rmem.type === 1 &&
rmem.to_id !== req.user_id rmem.to_id !== req.user_id
) { ) {
var relation_user = await User.getPublicUser(rmem.to_id); const relation_user = await User.getPublicUser(rmem.to_id);
mutual_relations.push({ mutual_relations.push({
id: relation_user.id, id: relation_user.id,

View File

@ -17,7 +17,7 @@
*/ */
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { Guild, Member, User } from "@fosscord/util"; import { Member, User } from "@fosscord/util";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";

View File

@ -43,7 +43,7 @@ router.patch(
const body = req.body as UserGuildSettingsSchema; const body = req.body as UserGuildSettingsSchema;
if (body.channel_overrides) { if (body.channel_overrides) {
for (var channel in body.channel_overrides) { for (const channel in body.channel_overrides) {
Channel.findOneOrFail({ where: { id: channel } }); Channel.findOneOrFail({ where: { id: channel } });
} }
} }

View File

@ -55,7 +55,7 @@ router.patch(
}); });
// Populated on password change // Populated on password change
var newToken: string | undefined; let newToken: string | undefined;
if (body.avatar) if (body.avatar)
body.avatar = await handleFile( body.avatar = await handleFile(
@ -120,7 +120,7 @@ router.patch(
} }
if (body.username) { if (body.username) {
var check_username = body?.username?.replace(/\s/g, ""); const check_username = body?.username?.replace(/\s/g, "");
if (!check_username) { if (!check_username) {
throw FieldErrors({ throw FieldErrors({
username: { username: {
@ -153,7 +153,8 @@ router.patch(
user.validate(); user.validate();
await user.save(); await user.save();
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
delete user.data; delete user.data;
// TODO: send update member list event in gateway // TODO: send update member list event in gateway

View File

@ -23,6 +23,7 @@ import {
generateMfaBackupCodes, generateMfaBackupCodes,
User, User,
CodesVerificationSchema, CodesVerificationSchema,
DiscordApiErrors,
} from "@fosscord/util"; } from "@fosscord/util";
const router = Router(); const router = Router();
@ -31,14 +32,17 @@ router.post(
"/", "/",
route({ body: "CodesVerificationSchema" }), route({ body: "CodesVerificationSchema" }),
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { key, nonce, regenerate } = req.body as CodesVerificationSchema; // const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
const { regenerate } = req.body as CodesVerificationSchema;
// TODO: We don't have email/etc etc, so can't send a verification code. // TODO: We don't have email/etc etc, so can't send a verification code.
// Once that's done, this route can verify `key` // Once that's done, this route can verify `key`
const user = await User.findOneOrFail({ where: { id: req.user_id } }); // const user = await User.findOneOrFail({ where: { id: req.user_id } });
if ((await User.count({ where: { id: req.user_id } })) === 0)
throw DiscordApiErrors.UNKNOWN_USER;
var codes: BackupCode[]; let codes: BackupCode[];
if (regenerate) { if (regenerate) {
await BackupCode.update( await BackupCode.update(
{ user: { id: req.user_id } }, { user: { id: req.user_id } },

View File

@ -51,7 +51,7 @@ router.post(
}); });
} }
var codes: BackupCode[]; let codes: BackupCode[];
if (regenerate) { if (regenerate) {
await BackupCode.update( await BackupCode.update(
{ user: { id: req.user_id } }, { user: { id: req.user_id } },

View File

@ -42,7 +42,7 @@ router.post(
const backup = await BackupCode.findOne({ where: { code: body.code } }); const backup = await BackupCode.findOne({ where: { code: body.code } });
if (!backup) { if (!backup) {
const ret = verifyToken(user.totp_secret!, body.code); const ret = verifyToken(user.totp_secret || "", body.code);
if (!ret || ret.delta != 0) if (!ret || ret.delta != 0)
throw new HTTPError( throw new HTTPError(
req.t("auth:login.INVALID_TOTP_CODE"), req.t("auth:login.INVALID_TOTP_CODE"),

View File

@ -57,7 +57,7 @@ router.post(
if (verifyToken(body.secret, body.code)?.delta != 0) if (verifyToken(body.secret, body.code)?.delta != 0)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
let backup_codes = generateMfaBackupCodes(req.user_id); const backup_codes = generateMfaBackupCodes(req.user_id);
await Promise.all(backup_codes.map((x) => x.save())); await Promise.all(backup_codes.map((x) => x.save()));
await User.update( await User.update(
{ id: req.user_id }, { id: req.user_id },

View File

@ -175,7 +175,7 @@ async function updateRelationship(
select: userProjection, select: userProjection,
}); });
var relationship = user.relationships.find((x) => x.to_id === id); let relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find( const friendRequest = friend.relationships.find(
(x) => x.to_id === req.user_id, (x) => x.to_id === req.user_id,
); );
@ -219,13 +219,13 @@ async function updateRelationship(
if (user.relationships.length >= maxFriends) if (user.relationships.length >= maxFriends)
throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
var incoming_relationship = Relationship.create({ let incoming_relationship = Relationship.create({
nickname: undefined, nickname: undefined,
type: RelationshipType.incoming, type: RelationshipType.incoming,
to: user, to: user,
from: friend, from: friend,
}); });
var outgoing_relationship = Relationship.create({ let outgoing_relationship = Relationship.create({
nickname: undefined, nickname: undefined,
type: RelationshipType.outgoing, type: RelationshipType.outgoing,
to: friend, to: friend,

View File

@ -17,7 +17,7 @@
*/ */
import { Router, Response, Request } from "express"; import { Router, Response, Request } from "express";
import { OrmUtils, User, UserSettingsSchema } from "@fosscord/util"; import { User, UserSettingsSchema } from "@fosscord/util";
import { route } from "@fosscord/api"; import { route } from "@fosscord/api";
const router = Router(); const router = Router();

View File

@ -26,7 +26,7 @@ config();
import { FosscordServer } from "./Server"; import { FosscordServer } from "./Server";
import cluster from "cluster"; import cluster from "cluster";
import os from "os"; import os from "os";
var cores = 1; let cores = 1;
try { try {
cores = Number(process.env.THREADS) || os.cpus().length; cores = Number(process.env.THREADS) || os.cpus().length;
} catch { } catch {
@ -41,16 +41,17 @@ if (cluster.isPrimary && process.env.NODE_ENV == "production") {
cluster.fork(); cluster.fork();
} }
cluster.on("exit", (worker, code, signal) => { cluster.on("exit", (worker) => {
console.log(`worker ${worker.process.pid} died, restart worker`); console.log(`worker ${worker.process.pid} died, restart worker`);
cluster.fork(); cluster.fork();
}); });
} else { } else {
var port = Number(process.env.PORT) || 3001; const port = Number(process.env.PORT) || 3001;
const server = new FosscordServer({ port }); const server = new FosscordServer({ port });
server.start().catch(console.error); server.start().catch(console.error);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
global.server = server; global.server = server;
} }

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Config, Guild, Session } from "@fosscord/util"; import { Session } from "@fosscord/util";
export async function initInstance() { export async function initInstance() {
// TODO: clean up database and delete tombstone data // TODO: clean up database and delete tombstone data
@ -24,15 +24,14 @@ export async function initInstance() {
// create default guild and add it to auto join // create default guild and add it to auto join
// TODO: check if any current user is not part of autoJoinGuilds // TODO: check if any current user is not part of autoJoinGuilds
const { autoJoin } = Config.get().guild; // const { autoJoin } = Config.get().guild;
if (autoJoin.enabled && !autoJoin.guilds?.length) { // if (autoJoin.enabled && !autoJoin.guilds?.length) {
let guild = await Guild.findOne({ where: {}, select: ["id"] }); // const guild = await Guild.findOne({ where: {}, select: ["id"] });
if (guild) { // if (guild) {
// @ts-ignore // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); // }
} // }
}
// TODO: do no clear sessions for instance cluster // TODO: do no clear sessions for instance cluster
await Session.delete({}); await Session.delete({});

View File

@ -51,7 +51,7 @@ const allow_empty = false;
// TODO: embed gifs/videos/images // TODO: embed gifs/videos/images
const LINK_REGEX = const LINK_REGEX =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
export async function handleMessage(opts: MessageOptions): Promise<Message> { export async function handleMessage(opts: MessageOptions): Promise<Message> {
const channel = await Channel.findOneOrFail({ const channel = await Channel.findOneOrFail({
@ -129,7 +129,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
} }
/** Q: should be checked if the referenced message exists? ANSWER: NO /** Q: should be checked if the referenced message exists? ANSWER: NO
otherwise backfilling won't work **/ otherwise backfilling won't work **/
// @ts-ignore
message.type = MessageType.REPLY; message.type = MessageType.REPLY;
} }
@ -144,29 +143,29 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
throw new HTTPError("Empty messages are not allowed", 50006); throw new HTTPError("Empty messages are not allowed", 50006);
} }
var content = opts.content; let content = opts.content;
var mention_channel_ids = [] as string[]; const mention_channel_ids = [] as string[];
var mention_role_ids = [] as string[]; const mention_role_ids = [] as string[];
var mention_user_ids = [] as string[]; const mention_user_ids = [] as string[];
var mention_everyone = false; let mention_everyone = false;
if (content) { if (content) {
// TODO: explicit-only mentions // TODO: explicit-only mentions
message.content = content.trim(); message.content = content.trim();
content = content.replace(/ *\`[^)]*\` */g, ""); // remove codeblocks content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
if (!mention_channel_ids.includes(mention)) if (!mention_channel_ids.includes(mention))
mention_channel_ids.push(mention); mention_channel_ids.push(mention);
} }
for (const [_, mention] of content.matchAll(USER_MENTION)) { for (const [, mention] of content.matchAll(USER_MENTION)) {
if (!mention_user_ids.includes(mention)) if (!mention_user_ids.includes(mention))
mention_user_ids.push(mention); mention_user_ids.push(mention);
} }
await Promise.all( await Promise.all(
Array.from(content.matchAll(ROLE_MENTION)).map( Array.from(content.matchAll(ROLE_MENTION)).map(
async ([_, mention]) => { async ([, mention]) => {
const role = await Role.findOneOrFail({ const role = await Role.findOneOrFail({
where: { id: mention, guild_id: channel.guild_id }, where: { id: mention, guild_id: channel.guild_id },
}); });
@ -198,8 +197,8 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
// TODO: cache link result in db // TODO: cache link result in db
export async function postHandleMessage(message: Message) { export async function postHandleMessage(message: Message) {
const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
var links = content?.match(LINK_REGEX); let links = content?.match(LINK_REGEX);
if (!links) return; if (!links) return;
const data = { ...message }; const data = { ...message };
@ -232,8 +231,8 @@ export async function postHandleMessage(message: Message) {
// tried to use shorthand but types didn't like me L // tried to use shorthand but types didn't like me L
if (!Array.isArray(res)) res = [res]; if (!Array.isArray(res)) res = [res];
for (var embed of res) { for (const embed of res) {
var cache = EmbedCache.create({ const cache = EmbedCache.create({
url: link, url: link,
embed: embed, embed: embed,
}); });
@ -279,7 +278,10 @@ export async function sendMessage(opts: MessageOptions) {
} as MessageCreateEvent), } as MessageCreateEvent),
]); ]);
postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly // no await as it should catch error non-blockingly
postHandleMessage(message).catch((e) =>
console.error("[Message] post-message handler failed", e),
);
return message; return message;
} }

View File

@ -31,7 +31,7 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
let min = Number.POSITIVE_INFINITY; let min = Number.POSITIVE_INFINITY;
for (let ar of availableRegions) { for (const ar of availableRegions) {
//TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
const dist = distanceBetweenLocations( const dist = distanceBetweenLocations(
clientIpAnalysis, clientIpAnalysis,

View File

@ -34,6 +34,8 @@ import { NextFunction, Request, Response } from "express";
import { AnyValidateFunction } from "ajv/dist/core"; import { AnyValidateFunction } from "ajv/dist/core";
declare global { declare global {
// TODO: fix this
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express { namespace Express {
interface Request { interface Request {
permission?: Permissions; permission?: Permissions;
@ -53,7 +55,7 @@ export interface RouteOptions {
body?: `${string}Schema`; // typescript interface name body?: `${string}Schema`; // typescript interface name
test?: { test?: {
response?: RouteResponse; response?: RouteResponse;
body?: any; body?: unknown;
path?: string; path?: string;
event?: EVENT | EVENT[]; event?: EVENT | EVENT[];
headers?: Record<string, string>; headers?: Record<string, string>;
@ -61,7 +63,7 @@ export interface RouteOptions {
} }
export function route(opts: RouteOptions) { export function route(opts: RouteOptions) {
var validate: AnyValidateFunction<any> | undefined; let validate: AnyValidateFunction | undefined;
if (opts.body) { if (opts.body) {
validate = ajv.getSchema(opts.body); validate = ajv.getSchema(opts.body);
if (!validate) throw new Error(`Body schema ${opts.body} not found`); if (!validate) throw new Error(`Body schema ${opts.body} not found`);

View File

@ -17,13 +17,13 @@
*/ */
import { Config, Embed, EmbedType } from "@fosscord/util"; import { Config, Embed, EmbedType } from "@fosscord/util";
import fetch, { Response } from "node-fetch"; import fetch, { RequestInit } from "node-fetch";
import * as cheerio from "cheerio"; import * as cheerio from "cheerio";
import probe from "probe-image-size"; import probe from "probe-image-size";
import crypto from "crypto"; import crypto from "crypto";
import { yellow } from "picocolors"; import { yellow } from "picocolors";
export const DEFAULT_FETCH_OPTIONS: any = { export const DEFAULT_FETCH_OPTIONS: RequestInit = {
redirect: "follow", redirect: "follow",
follow: 1, follow: 1,
headers: { headers: {
@ -50,7 +50,7 @@ export const getProxyUrl = (
// Imagor // Imagor
if (imagorServerUrl) { if (imagorServerUrl) {
let path = `${width}x${height}/${url.host}${url.pathname}`; const path = `${width}x${height}/${url.host}${url.pathname}`;
const hash = crypto const hash = crypto
.createHmac("sha1", secret) .createHmac("sha1", secret)
@ -92,8 +92,8 @@ export const getMetaDescriptions = (text: string) => {
image: getMeta($, "og:image") || getMeta($, "twitter:image"), image: getMeta($, "og:image") || getMeta($, "twitter:image"),
image_fallback: $(`image`).attr("src"), image_fallback: $(`image`).attr("src"),
video_fallback: $(`video`).attr("src"), video_fallback: $(`video`).attr("src"),
width: parseInt(getMeta($, "og:image:width")!) || 0, width: parseInt(getMeta($, "og:image:width") || "0"),
height: parseInt(getMeta($, "og:image:height")!) || 0, height: parseInt(getMeta($, "og:image:height") || "0"),
url: getMeta($, "og:url"), url: getMeta($, "og:url"),
youtube_embed: getMeta($, "og:video:secure_url"), youtube_embed: getMeta($, "og:video:secure_url"),
}; };
@ -192,8 +192,8 @@ export const EmbedHandlers: {
proxy_url: metas.image proxy_url: metas.image
? getProxyUrl( ? getProxyUrl(
new URL(metas.image), new URL(metas.image),
metas.width!, metas.width,
metas.height!, metas.height,
) )
: undefined, : undefined,
}, },
@ -239,9 +239,9 @@ export const EmbedHandlers: {
const text = json.data.text; const text = json.data.text;
const created_at = new Date(json.data.created_at); const created_at = new Date(json.data.created_at);
const metrics = json.data.public_metrics; const metrics = json.data.public_metrics;
let media = json.includes.media?.filter( const media = json.includes.media?.filter(
(x: any) => x.type == "photo", (x: { type: string }) => x.type == "photo",
) as any[]; // TODO: video );
const embed: Embed = { const embed: Embed = {
type: EmbedType.rich, type: EmbedType.rich,
@ -334,7 +334,7 @@ export const EmbedHandlers: {
width: 640, width: 640,
height: 640, height: 640,
proxy_url: metas.image proxy_url: metas.image
? getProxyUrl(new URL(metas.image!), 640, 640) ? getProxyUrl(new URL(metas.image), 640, 640)
: undefined, : undefined,
url: metas.image, url: metas.image,
}, },
@ -365,9 +365,9 @@ export const EmbedHandlers: {
url: url.href, url: url.href,
proxy_url: metas.image proxy_url: metas.image
? getProxyUrl( ? getProxyUrl(
new URL(metas.image!), new URL(metas.image),
metas.width!, metas.width,
metas.height!, metas.height,
) )
: undefined, : undefined,
}, },
@ -395,7 +395,7 @@ export const EmbedHandlers: {
height: 215, height: 215,
url: metas.image, url: metas.image,
proxy_url: metas.image proxy_url: metas.image
? getProxyUrl(new URL(metas.image!), 460, 215) ? getProxyUrl(new URL(metas.image), 460, 215)
: undefined, : undefined,
}, },
provider: { provider: {
@ -436,7 +436,7 @@ export const EmbedHandlers: {
// TODO: does this adjust with aspect ratio? // TODO: does this adjust with aspect ratio?
width: metas.width, width: metas.width,
height: metas.height, height: metas.height,
url: metas.youtube_embed!, url: metas.youtube_embed,
}, },
url: url.href, url: url.href,
type: EmbedType.video, type: EmbedType.video,
@ -447,9 +447,9 @@ export const EmbedHandlers: {
url: metas.image, url: metas.image,
proxy_url: metas.image proxy_url: metas.image
? getProxyUrl( ? getProxyUrl(
new URL(metas.image!), new URL(metas.image),
metas.width!, metas.width,
metas.height!, metas.height,
) )
: undefined, : undefined,
}, },

View File

@ -24,7 +24,7 @@ import crypto from "crypto";
export function random(length = 6) { export function random(length = 6) {
// Declare all characters // Declare all characters
let chars = const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// Pick characers randomly // Pick characers randomly
@ -38,14 +38,14 @@ export function random(length = 6) {
export function snowflakeBasedInvite() { export function snowflakeBasedInvite() {
// Declare all characters // Declare all characters
let chars = const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let base = BigInt(chars.length); const base = BigInt(chars.length);
let snowflake = Snowflake.generateWorkerProcess(); let snowflake = Snowflake.generateWorkerProcess();
// snowflakes hold ~10.75 characters worth of entropy; // snowflakes hold ~10.75 characters worth of entropy;
// safe to generate a 8-char invite out of them // safe to generate a 8-char invite out of them
let str = ""; const str = "";
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
str.concat(chars.charAt(Number(snowflake % base))); str.concat(chars.charAt(Number(snowflake % base)));
snowflake = snowflake / base; snowflake = snowflake / base;

View File

@ -47,7 +47,10 @@ export async function verifyCaptcha(response: string, ip?: string) {
const { security } = Config.get(); const { security } = Config.get();
const { service, secret, sitekey } = security.captcha; const { service, secret, sitekey } = security.captcha;
if (!service) throw new Error("Cannot verify captcha without service"); if (!service || !secret || !sitekey)
throw new Error(
"CAPTCHA is not configured correctly. https://docs.fosscord.com/setup/server/security/captcha/",
);
const res = await fetch(verifyEndpoints[service], { const res = await fetch(verifyEndpoints[service], {
method: "POST", method: "POST",
@ -56,9 +59,9 @@ export async function verifyCaptcha(response: string, ip?: string) {
}, },
body: body:
`response=${encodeURIComponent(response)}` + `response=${encodeURIComponent(response)}` +
`&secret=${encodeURIComponent(secret!)}` + `&secret=${encodeURIComponent(secret)}` +
`&sitekey=${encodeURIComponent(sitekey!)}` + `&sitekey=${encodeURIComponent(sitekey)}` +
(ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""), (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""),
}); });
return (await res.json()) as hcaptchaResponse | recaptchaResponse; return (await res.json()) as hcaptchaResponse | recaptchaResponse;

View File

@ -85,7 +85,7 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
return ( return (
await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`) await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)
).json() as any; // TODO: types ).json();
} }
export function isProxy(data: typeof exampleData) { export function isProxy(data: typeof exampleData) {
@ -97,14 +97,21 @@ export function isProxy(data: typeof exampleData) {
} }
export function getIpAdress(req: Request): string { export function getIpAdress(req: Request): string {
// TODO: express can do this (trustProxies: true)?
return ( return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
req.headers[Config.get().security.forwadedFor] || req.headers[Config.get().security.forwadedFor] ||
req.socket.remoteAddress req.socket.remoteAddress
); );
} }
export function distanceBetweenLocations(loc1: any, loc2: any): number { type Location = { latitude: number; longitude: number };
export function distanceBetweenLocations(
loc1: Location,
loc2: Location,
): number {
return distanceBetweenCoords( return distanceBetweenCoords(
loc1.latitude, loc1.latitude,
loc1.longitude, loc1.longitude,

View File

@ -23,7 +23,7 @@ const reNUMBER = /[0-9]/g;
const reUPPERCASELETTER = /[A-Z]/g; const reUPPERCASELETTER = /[A-Z]/g;
const reSYMBOLS = /[A-Z,a-z,0-9]/g; const reSYMBOLS = /[A-Z,a-z,0-9]/g;
const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db // const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db
/* /*
* https://en.wikipedia.org/wiki/Password_policy * https://en.wikipedia.org/wiki/Password_policy
* password must meet following criteria, to be perfect: * password must meet following criteria, to be perfect:
@ -38,7 +38,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
export function checkPassword(password: string): number { export function checkPassword(password: string): number {
const { minLength, minNumbers, minUpperCase, minSymbols } = const { minLength, minNumbers, minUpperCase, minSymbols } =
Config.get().register.password; Config.get().register.password;
var strength = 0; let strength = 0;
// checks for total password len // checks for total password len
if (password.length >= minLength - 1) { if (password.length >= minLength - 1) {
@ -68,13 +68,13 @@ export function checkPassword(password: string): number {
strength = 0; strength = 0;
} }
let entropyMap: { [key: string]: number } = {}; const entropyMap: { [key: string]: number } = {};
for (let i = 0; i < password.length; i++) { for (let i = 0; i < password.length; i++) {
if (entropyMap[password[i]]) entropyMap[password[i]]++; if (entropyMap[password[i]]) entropyMap[password[i]]++;
else entropyMap[password[i]] = 1; else entropyMap[password[i]] = 1;
} }
let entropies = Object.values(entropyMap); const entropies = Object.values(entropyMap);
entropies.map((x) => x / entropyMap.length); entropies.map((x) => x / entropyMap.length);
strength += strength +=

View File

@ -24,7 +24,7 @@ import * as Api from "@fosscord/api";
import * as Gateway from "@fosscord/gateway"; import * as Gateway from "@fosscord/gateway";
import { CDNServer } from "@fosscord/cdn"; import { CDNServer } from "@fosscord/cdn";
import express from "express"; import express from "express";
import { green, bold, yellow } from "picocolors"; import { green, bold } from "picocolors";
import { Config, initDatabase, Sentry } from "@fosscord/util"; import { Config, initDatabase, Sentry } from "@fosscord/util";
const app = express(); const app = express();

View File

@ -29,14 +29,15 @@ import { execSync } from "child_process";
const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1;
if (cluster.isPrimary) { function getCommitOrFail() {
function getCommitOrFail() { try {
try { return execSync("git rev-parse HEAD").toString().trim();
return execSync("git rev-parse HEAD").toString().trim(); } catch (e) {
} catch (e) { return null;
return null;
}
} }
}
if (cluster.isPrimary) {
const commit = getCommitOrFail(); const commit = getCommitOrFail();
console.log( console.log(
@ -81,14 +82,14 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)
// Fork workers. // Fork workers.
for (let i = 0; i < cores; i++) { for (let i = 0; i < cores; i++) {
// Delay each worker start if using sqlite database to prevent locking it // Delay each worker start if using sqlite database to prevent locking it
let delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; const delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000;
setTimeout(() => { setTimeout(() => {
cluster.fork(); cluster.fork();
console.log(`[Process] worker ${cyan(i)} started.`); console.log(`[Process] worker ${cyan(i)} started.`);
}, delay); }, delay);
} }
cluster.on("message", (sender: Worker, message: any) => { cluster.on("message", (sender: Worker, message) => {
for (const id in cluster.workers) { for (const id in cluster.workers) {
const worker = cluster.workers[id]; const worker = cluster.workers[id];
if (worker === sender || !worker) continue; if (worker === sender || !worker) continue;
@ -96,7 +97,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)
} }
}); });
cluster.on("exit", (worker: any, code: any, signal: any) => { cluster.on("exit", (worker) => {
console.log( console.log(
`[Worker] ${red( `[Worker] ${red(
`died with PID: ${worker.process.pid} , restarting ...`, `died with PID: ${worker.process.pid} , restarting ...`,

View File

@ -24,7 +24,7 @@ import guildProfilesRoute from "./routes/guild-profiles";
import iconsRoute from "./routes/role-icons"; import iconsRoute from "./routes/role-icons";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
export interface CDNServerOptions extends ServerOptions {} export type CDNServerOptions = ServerOptions;
export class CDNServer extends Server { export class CDNServer extends Server {
public declare options: CDNServerOptions; public declare options: CDNServerOptions;

View File

@ -41,7 +41,7 @@ router.post(
throw new HTTPError("Invalid request signature"); throw new HTTPError("Invalid request signature");
if (!req.file) throw new HTTPError("file missing"); if (!req.file) throw new HTTPError("file missing");
const { buffer, mimetype, size, originalname, fieldname } = req.file; const { buffer, mimetype, size, originalname } = req.file;
const { channel_id } = req.params; const { channel_id } = req.params;
const filename = originalname const filename = originalname
.replaceAll(" ", "_") .replaceAll(" ", "_")
@ -53,8 +53,8 @@ router.post(
Config.get()?.cdn.endpointPublic || "http://localhost:3003"; Config.get()?.cdn.endpointPublic || "http://localhost:3003";
await storage.set(path, buffer); await storage.set(path, buffer);
var width; let width;
var height; let height;
if (mimetype.includes("image")) { if (mimetype.includes("image")) {
const dimensions = imageSize(buffer); const dimensions = imageSize(buffer);
if (dimensions) { if (dimensions) {
@ -81,10 +81,10 @@ router.get(
"/:channel_id/:id/:filename", "/:channel_id/:id/:filename",
async (req: Request, res: Response) => { async (req: Request, res: Response) => {
const { channel_id, id, filename } = req.params; const { channel_id, id, filename } = req.params;
const { format } = req.query; // const { format } = req.query;
const path = `attachments/${channel_id}/${id}/${filename}`; const path = `attachments/${channel_id}/${id}/${filename}`;
let file = await storage.get(path); const file = await storage.get(path);
if (!file) throw new HTTPError("File not found"); if (!file) throw new HTTPError("File not found");
const type = await FileType.fromBuffer(file); const type = await FileType.fromBuffer(file);
let content_type = type?.mime || "application/octet-stream"; let content_type = type?.mime || "application/octet-stream";

View File

@ -48,10 +48,10 @@ router.post(
if (req.headers.signature !== Config.get().security.requestSignature) if (req.headers.signature !== Config.get().security.requestSignature)
throw new HTTPError("Invalid request signature"); throw new HTTPError("Invalid request signature");
if (!req.file) throw new HTTPError("Missing file"); if (!req.file) throw new HTTPError("Missing file");
const { buffer, mimetype, size, originalname, fieldname } = req.file; const { buffer, size } = req.file;
const { user_id } = req.params; const { user_id } = req.params;
var hash = crypto let hash = crypto
.createHash("md5") .createHash("md5")
.update(Snowflake.generate()) .update(Snowflake.generate())
.digest("hex"); .digest("hex");
@ -77,7 +77,7 @@ router.post(
); );
router.get("/:user_id", async (req: Request, res: Response) => { router.get("/:user_id", async (req: Request, res: Response) => {
var { user_id } = req.params; let { user_id } = req.params;
user_id = user_id.split(".")[0]; // remove .file extension user_id = user_id.split(".")[0]; // remove .file extension
const path = `avatars/${user_id}`; const path = `avatars/${user_id}`;
@ -92,7 +92,8 @@ router.get("/:user_id", async (req: Request, res: Response) => {
}); });
export const getAvatar = async (req: Request, res: Response) => { export const getAvatar = async (req: Request, res: Response) => {
var { user_id, hash } = req.params; const { user_id } = req.params;
let { hash } = req.params;
hash = hash.split(".")[0]; // remove .file extension hash = hash.split(".")[0]; // remove .file extension
const path = `avatars/${user_id}/${hash}`; const path = `avatars/${user_id}/${hash}`;

View File

@ -45,7 +45,7 @@ router.post("/", multer.single("file"), async (req: Request, res: Response) => {
if (req.headers.signature !== Config.get().security.requestSignature) if (req.headers.signature !== Config.get().security.requestSignature)
throw new HTTPError("Invalid request signature"); throw new HTTPError("Invalid request signature");
if (!req.file) throw new HTTPError("Missing file"); if (!req.file) throw new HTTPError("Missing file");
const { buffer, mimetype, size, originalname, fieldname } = req.file; const { buffer, size } = req.file;
const { guild_id, user_id } = req.params; const { guild_id, user_id } = req.params;
let hash = crypto let hash = crypto
@ -72,7 +72,8 @@ router.post("/", multer.single("file"), async (req: Request, res: Response) => {
}); });
router.get("/", async (req: Request, res: Response) => { router.get("/", async (req: Request, res: Response) => {
let { guild_id, user_id } = req.params; const { guild_id } = req.params;
let { user_id } = req.params;
user_id = user_id.split(".")[0]; // remove .file extension user_id = user_id.split(".")[0]; // remove .file extension
const path = `guilds/${guild_id}/users/${user_id}/avatars`; const path = `guilds/${guild_id}/users/${user_id}/avatars`;
@ -87,7 +88,8 @@ router.get("/", async (req: Request, res: Response) => {
}); });
router.get("/:hash", async (req: Request, res: Response) => { router.get("/:hash", async (req: Request, res: Response) => {
let { guild_id, user_id, hash } = req.params; const { guild_id, user_id } = req.params;
let { hash } = req.params;
hash = hash.split(".")[0]; // remove .file extension hash = hash.split(".")[0]; // remove .file extension
const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`;

View File

@ -48,10 +48,10 @@ router.post(
if (req.headers.signature !== Config.get().security.requestSignature) if (req.headers.signature !== Config.get().security.requestSignature)
throw new HTTPError("Invalid request signature"); throw new HTTPError("Invalid request signature");
if (!req.file) throw new HTTPError("Missing file"); if (!req.file) throw new HTTPError("Missing file");
const { buffer, mimetype, size, originalname, fieldname } = req.file; const { buffer, size } = req.file;
const { role_id } = req.params; const { role_id } = req.params;
var hash = crypto const hash = crypto
.createHash("md5") .createHash("md5")
.update(Snowflake.generate()) .update(Snowflake.generate())
.digest("hex"); .digest("hex");
@ -76,7 +76,7 @@ router.post(
); );
router.get("/:role_id", async (req: Request, res: Response) => { router.get("/:role_id", async (req: Request, res: Response) => {
var { role_id } = req.params; const { role_id } = req.params;
//role_id = role_id.split(".")[0]; // remove .file extension //role_id = role_id.split(".")[0]; // remove .file extension
const path = `role-icons/${role_id}`; const path = `role-icons/${role_id}`;
@ -91,7 +91,7 @@ router.get("/:role_id", async (req: Request, res: Response) => {
}); });
router.get("/:role_id/:hash", async (req: Request, res: Response) => { router.get("/:role_id/:hash", async (req: Request, res: Response) => {
var { role_id, hash } = req.params; const { role_id, hash } = req.params;
//hash = hash.split(".")[0]; // remove .file extension //hash = hash.split(".")[0]; // remove .file extension
const path = `role-icons/${role_id}/${hash}`; const path = `role-icons/${role_id}/${hash}`;

View File

@ -28,7 +28,7 @@ import ExifTransformer from "exif-be-gone";
function getPath(path: string) { function getPath(path: string) {
// STORAGE_LOCATION has a default value in start.ts // STORAGE_LOCATION has a default value in start.ts
const root = process.env.STORAGE_LOCATION || "../"; const root = process.env.STORAGE_LOCATION || "../";
var filename = join(root, path); const filename = join(root, path);
if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) if (path.indexOf("\0") !== -1 || !filename.startsWith(root))
throw new Error("invalid path"); throw new Error("invalid path");
@ -51,15 +51,15 @@ export class FileStorage implements Storage {
} }
} }
async set(path: string, value: any) { async set(path: string, value: Buffer) {
path = getPath(path); path = getPath(path);
if (!fs.existsSync(dirname(path))) if (!fs.existsSync(dirname(path)))
fs.mkdirSync(dirname(path), { recursive: true }); fs.mkdirSync(dirname(path), { recursive: true });
value = Readable.from(value); const ret = Readable.from(value);
const cleaned_file = fs.createWriteStream(path); const cleaned_file = fs.createWriteStream(path);
return value.pipe(new ExifTransformer()).pipe(cleaned_file); ret.pipe(new ExifTransformer()).pipe(cleaned_file);
} }
async delete(path: string) { async delete(path: string) {

View File

@ -19,7 +19,6 @@
import { FileStorage } from "./FileStorage"; import { FileStorage } from "./FileStorage";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import { bgCyan, black } from "picocolors";
import { S3 } from "@aws-sdk/client-s3"; import { S3 } from "@aws-sdk/client-s3";
import { S3Storage } from "./S3Storage"; import { S3Storage } from "./S3Storage";
process.cwd(); process.cwd();

View File

@ -56,7 +56,6 @@ export class Server {
} }
this.server.on("upgrade", (request, socket, head) => { this.server.on("upgrade", (request, socket, head) => {
// @ts-ignore
this.ws.handleUpgrade(request, socket, head, (socket) => { this.ws.handleUpgrade(request, socket, head, (socket) => {
this.ws.emit("connection", socket, request); this.ws.emit("connection", socket, request);
}); });

View File

@ -26,7 +26,7 @@ import {
User, User,
} from "@fosscord/util"; } from "@fosscord/util";
export async function Close(this: WebSocket, code: number, reason: string) { export async function Close(this: WebSocket, code: number, reason: Buffer) {
console.log("[WebSocket] closed", code, reason.toString()); console.log("[WebSocket] closed", code, reason.toString());
if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout);
if (this.readyTimeout) clearTimeout(this.readyTimeout); if (this.readyTimeout) clearTimeout(this.readyTimeout);

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import WS from "ws"; import WS from "ws";
import { genSessionId, WebSocket } from "@fosscord/gateway"; import { genSessionId, WebSocket } from "@fosscord/gateway";
import { Send } from "../util/Send"; import { Send } from "../util/Send";
@ -27,10 +28,12 @@ import { Message } from "./Message";
import { Deflate, Inflate } from "fast-zlib"; import { Deflate, Inflate } from "fast-zlib";
import { URL } from "url"; import { URL } from "url";
import { Config } from "@fosscord/util"; import { Config } from "@fosscord/util";
var erlpack: any; let erlpack: unknown;
try { try {
erlpack = require("@yukikaze-bot/erlpack"); erlpack = require("@yukikaze-bot/erlpack");
} catch (error) {} } catch (error) {
/* empty */
}
// TODO: check rate limit // TODO: check rate limit
// TODO: specify rate limit in config // TODO: specify rate limit in config

Some files were not shown because too many files have changed in this diff Show More