diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2023-01-20 18:10:47 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-20 18:10:47 +1100 |
commit | 084dc0be08555891cad4c2bb984822a62ec5ec9f (patch) | |
tree | ed2ca0fafefa2224ae32761f955f63935422a97d /src | |
parent | fix: route file regex (#956) (diff) | |
download | server-084dc0be08555891cad4c2bb984822a62ec5ec9f.tar.xz |
Add ESLint (#941)
* Add eslint, switch to lint-staged for precommit * Fix all ESLint errors * Update GH workflow to check prettier and eslint
Diffstat (limited to 'src')
149 files changed, 706 insertions, 661 deletions
diff --git a/src/api/Server.ts b/src/api/Server.ts index 4660e6b1..0177be40 100644 --- a/src/api/Server.ts +++ b/src/api/Server.ts @@ -22,7 +22,7 @@ import { Authentication, CORS } from "./middlewares/"; import { Config, initDatabase, initEvent, Sentry } from "@fosscord/util"; import { ErrorHandler } from "./middlewares/ErrorHandler"; import { BodyParser } from "./middlewares/BodyParser"; -import { Router, Request, Response, NextFunction } from "express"; +import { Router, Request, Response } from "express"; import path from "path"; import { initRateLimits } from "./middlewares/RateLimit"; import TestClient from "./middlewares/TestClient"; @@ -32,12 +32,12 @@ import { initInstance } from "./util/handlers/Instance"; import { registerRoutes } from "@fosscord/util"; import { red } from "picocolors"; -export interface FosscordServerOptions extends ServerOptions {} +export type FosscordServerOptions = ServerOptions; declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Express { interface Request { - // @ts-ignore server: FosscordServer; } } @@ -47,6 +47,7 @@ export class FosscordServer extends Server { public declare options: FosscordServerOptions; constructor(opts?: Partial<FosscordServerOptions>) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore super({ ...opts, errorHandler: false, jsonBody: false }); } @@ -58,12 +59,12 @@ export class FosscordServer extends Server { await initInstance(); await Sentry.init(this.app); - let logRequests = process.env["LOG_REQUESTS"] != undefined; + const logRequests = process.env["LOG_REQUESTS"] != undefined; if (logRequests) { this.app.use( morgan("combined", { skip: (req, res) => { - var skip = !( + let skip = !( process.env["LOG_REQUESTS"]?.includes( res.statusCode.toString(), ) ?? false @@ -80,7 +81,9 @@ export class FosscordServer extends Server { this.app.use(BodyParser({ inflate: true, limit: "10mb" })); 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; 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 // 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. - api.use("*", (req: Request, res: Response, next: NextFunction) => { + api.use("*", (req: Request, res: Response) => { res.status(404).json({ message: "404 endpoint not found", code: 0, diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts index 208c54d6..8e0dcc7c 100644 --- a/src/api/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts @@ -54,11 +54,12 @@ export const API_PREFIX = /^\/api(\/v\d+)?/; export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Express { interface Request { user_id: string; user_bot: boolean; - token: string; + token: { id: string; iat: number }; rights: Rights; } } @@ -87,7 +88,7 @@ export async function Authentication( try { const { jwtSecret } = Config.get().security; - const { decoded, user }: any = await checkToken( + const { decoded, user } = await checkToken( req.headers.authorization, jwtSecret, ); @@ -97,7 +98,8 @@ export async function Authentication( req.user_bot = user.bot; req.rights = new Rights(Number(user.rights)); return next(); - } catch (error: any) { - return next(new HTTPError(error?.toString(), 400)); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return next(new HTTPError(error!.toString(), 400)); } } diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts index ae102c94..1a28f356 100644 --- a/src/api/middlewares/RateLimit.ts +++ b/src/api/middlewares/RateLimit.ts @@ -42,7 +42,7 @@ type RateLimit = { expires_at: Date; }; -let Cache = new Map<string, RateLimit>(); +const Cache = new Map<string, RateLimit>(); const EventRateLimit = "RATELIMIT"; export default function rateLimit(opts: { @@ -57,12 +57,8 @@ export default function rateLimit(opts: { error?: boolean; success?: boolean; onlyIp?: boolean; -}): any { - return async ( - req: Request, - res: Response, - next: NextFunction, - ): Promise<any> => { +}) { + return async (req: Request, res: Response, next: NextFunction) => { // exempt user? if so, immediately short circuit if (req.user_id) { const rights = await getRights(req.user_id); @@ -85,7 +81,7 @@ export default function rateLimit(opts: { ) max_hits = opts.MODIFY; - let offender = Cache.get(executor_id + bucket_id); + const offender = Cache.get(executor_id + bucket_id); if (offender) { let reset = offender.expires_at.getTime(); diff --git a/src/api/routes/applications/#id/bot/index.ts b/src/api/routes/applications/#id/bot/index.ts index 7d0a637e..9bc3c571 100644 --- a/src/api/routes/applications/#id/bot/index.ts +++ b/src/api/routes/applications/#id/bot/index.ts @@ -64,8 +64,8 @@ router.post("/", 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 } }); - let owner = await User.findOneOrFail({ where: { id: req.user_id } }); + const bot = await User.findOneOrFail({ where: { id: req.params.id } }); + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; @@ -80,7 +80,7 @@ router.post("/reset", route({}), async (req: Request, res: Response) => { await bot.save(); - let token = await generateToken(bot.id); + const token = await generateToken(bot.id); res.json({ token }).status(200); }); diff --git a/src/api/routes/applications/#id/index.ts b/src/api/routes/applications/#id/index.ts index 2b283880..59e90168 100644 --- a/src/api/routes/applications/#id/index.ts +++ b/src/api/routes/applications/#id/index.ts @@ -20,10 +20,8 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; import { Application, - OrmUtils, DiscordApiErrors, ApplicationModifySchema, - User, } from "@fosscord/util"; import { verifyToken } from "node-2fa"; import { HTTPError } from "lambert-server"; diff --git a/src/api/routes/applications/#id/skus.ts b/src/api/routes/applications/#id/skus.ts index 23e6eb6b..5a3a479f 100644 --- a/src/api/routes/applications/#id/skus.ts +++ b/src/api/routes/applications/#id/skus.ts @@ -18,7 +18,6 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { Application, OrmUtils, Team, trimSpecial, User } from "@fosscord/util"; const router: Router = Router(); diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts index 6ea24870..859ee145 100644 --- a/src/api/routes/applications/index.ts +++ b/src/api/routes/applications/index.ts @@ -28,7 +28,7 @@ import { const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - let results = await Application.find({ + const results = await Application.find({ where: { owner: { id: req.user_id } }, relations: ["owner", "bot"], }); diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts index 64e3b0a6..c79d2a59 100644 --- a/src/api/routes/auth/generate-registration-tokens.ts +++ b/src/api/routes/auth/generate-registration-tokens.ts @@ -32,7 +32,7 @@ router.get( ? parseInt(req.query.length as string) : 255; - let tokens: ValidRegistrationToken[] = []; + const tokens: ValidRegistrationToken[] = []; for (let i = 0; i < count; i++) { const token = ValidRegistrationToken.create({ diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts index 5f1b7a14..4d367546 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts @@ -74,7 +74,7 @@ router.post( "totp_secret", "mfa_enabled", ], - }).catch((e) => { + }).catch(() => { throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts index 42485535..65cdd397 100644 --- a/src/api/routes/auth/mfa/totp.ts +++ b/src/api/routes/auth/mfa/totp.ts @@ -27,8 +27,8 @@ router.post( "/", route({ body: "TotpSchema" }), async (req: Request, res: Response) => { - const { code, ticket, gift_code_sku_id, login_source } = - req.body as TotpSchema; + // const { code, ticket, gift_code_sku_id, login_source } = + const { code, ticket } = req.body as TotpSchema; const user = await User.findOneOrFail({ where: { @@ -47,7 +47,7 @@ router.post( }); if (!backup) { - const ret = verifyToken(user.totp_secret!, code); + const ret = verifyToken(user.totp_secret || "", code); if (!ret || ret.delta != 0) throw new HTTPError( req.t("auth:login.INVALID_TOTP_CODE"), diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index b98f17c5..0bf8efae 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -36,7 +36,7 @@ import { } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; -import { LessThan, MoreThan } from "typeorm"; +import { MoreThan } from "typeorm"; const router: Router = Router(); @@ -53,12 +53,12 @@ router.post( let regTokenUsed = false; if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { // 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) { - const regToken = await ValidRegistrationToken.findOne({ + const regToken = await ValidRegistrationToken.findOneOrFail({ where: { token, expires_at: MoreThan(new Date()) }, }); - await ValidRegistrationToken.delete({ token }); + await regToken.remove(); regTokenUsed = true; console.log( `[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 - let email = adjustEmail(body.email); + const email = adjustEmail(body.email); // check if registration is allowed if (!regTokenUsed && !register.allowNewRegistration) { diff --git a/src/api/routes/channels/#channel_id/followers.ts b/src/api/routes/channels/#channel_id/followers.ts index 0ff784df..a9d5d4ee 100644 --- a/src/api/routes/channels/#channel_id/followers.ts +++ b/src/api/routes/channels/#channel_id/followers.ts @@ -16,7 +16,7 @@ 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(); // TODO: diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts index 5bcd3a84..4a2023d2 100644 --- a/src/api/routes/channels/#channel_id/index.ts +++ b/src/api/routes/channels/#channel_id/index.ts @@ -92,7 +92,7 @@ router.patch( "/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - var payload = req.body as ChannelModifySchema; + const payload = req.body as ChannelModifySchema; const { channel_id } = req.params; if (payload.icon) payload.icon = await handleFile( diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts index b9105bea..49620aaf 100644 --- a/src/api/routes/channels/#channel_id/invites.ts +++ b/src/api/routes/channels/#channel_id/invites.ts @@ -86,7 +86,6 @@ router.get( "/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - const { user_id } = req; const { channel_id } = req.params; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts index 3d9a69be..9ea33340 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts @@ -30,7 +30,6 @@ import { Snowflake, uploadFile, MessageCreateSchema, - DiscordApiErrors, } from "@fosscord/util"; import { Router, Response, Request } from "express"; import multer from "multer"; @@ -59,7 +58,7 @@ router.patch( }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - var body = req.body as MessageCreateSchema; + let body = req.body as MessageCreateSchema; const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, @@ -85,6 +84,7 @@ router.patch( const new_message = await handleMessage({ ...message, // TODO: should message_reference be overridable? + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore message_reference: message.message_reference, ...body, @@ -127,7 +127,7 @@ router.put( }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - var body = req.body as MessageCreateSchema; + const body = req.body as MessageCreateSchema; const attachments: Attachment[] = []; const rights = await getRights(req.user_id); @@ -171,7 +171,7 @@ router.put( const embeds = body.embeds || []; if (body.embed) embeds.push(body.embed); - let message = await handleMessage({ + const message = await handleMessage({ ...body, type: 0, pinned: false, @@ -197,7 +197,10 @@ router.put( 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); }, diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts index bf6d43e5..c3598b24 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -165,13 +165,13 @@ router.put( x.emoji.name === emoji.name, ); - if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); + if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); if (emoji.id) { const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id }, }); - if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); + if (!already_added) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS"); emoji.animated = external_emoji.animated; emoji.name = external_emoji.name; } @@ -214,7 +214,8 @@ router.delete( "/:emoji/:user_id", route({}), 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); diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts index ad5d24c8..ee039d3e 100644 --- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts @@ -50,7 +50,7 @@ router.post( const rights = await getRights(req.user_id); rights.hasThrow("SELF_DELETE_MESSAGES"); - let superuser = rights.has("MANAGE_MESSAGES"); + const superuser = rights.has("MANAGE_MESSAGES"); const permission = await getPermission( req.user_id, channel?.guild_id, diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index 6e4f06a2..76f6a0dc 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -31,23 +31,16 @@ import { Snowflake, uploadFile, Member, - Role, MessageCreateSchema, ReadState, - DiscordApiErrors, - getRights, Rights, + Reaction, + User, } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { - handleMessage, - postHandleMessage, - route, - getIpAdress, -} from "@fosscord/api"; +import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import multer from "multer"; -import { yellow } from "picocolors"; -import { FindManyOptions, LessThan, MoreThan } from "typeorm"; +import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm"; import { URL } from "url"; const router: Router = Router(); @@ -93,7 +86,7 @@ router.get("/", async (req: Request, res: Response) => { if (limit < 1 || limit > 100) 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( req.user_id, @@ -103,7 +96,9 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); 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" }, take: limit, where: { channel_id }, @@ -140,23 +135,21 @@ router.get("/", async (req: Request, res: Response) => { const endpoint = Config.get().cdn.endpointPublic; return res.json( - messages.map((x: any) => { - (x.reactions || []).forEach((x: any) => { - // @ts-ignore - if ((x.user_ids || []).includes(req.user_id)) x.me = true; - // @ts-ignore - delete x.user_ids; + messages.map((x: Partial<Message>) => { + (x.reactions || []).forEach((y: Partial<Reaction>) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + if ((y.user_ids || []).includes(req.user_id)) y.me = true; + delete y.user_ids; }); - // @ts-ignore if (!x.author) - x.author = { + x.author = User.create({ id: "4", discriminator: "0000", username: "Fosscord Ghost", - public_flags: "0", - avatar: null, - }; - x.attachments?.forEach((y: any) => { + public_flags: 0, + }); + x.attachments?.forEach((y: Attachment) => { // dynamically set attachment proxy_url in case the endpoint changed const uri = y.proxy_url.startsWith("http") ? y.proxy_url @@ -168,7 +161,7 @@ router.get("/", async (req: Request, res: 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) { @@ -216,7 +209,7 @@ router.post( }), async (req: Request, res: Response) => { const { channel_id } = req.params; - var body = req.body as MessageCreateSchema; + const body = req.body as MessageCreateSchema; const attachments: Attachment[] = []; const channel = await Channel.findOneOrFail({ @@ -244,7 +237,7 @@ router.post( } if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { - var limits = Config.get().limits; + const limits = Config.get().limits; if (limits.absoluteRate.register.enabled) { const count = await Message.count({ where: { @@ -269,7 +262,7 @@ router.post( } const files = (req.files as Express.Multer.File[]) ?? []; - for (var currFile of files) { + for (const currFile of files) { try { const file = await uploadFile( `/attachments/${channel.id}`, @@ -279,13 +272,13 @@ router.post( Attachment.create({ ...file, proxy_url: file.url }), ); } catch (error) { - return res.status(400).json({ message: error!.toString() }); + return res.status(400).json({ message: error?.toString() }); } } const embeds = body.embeds || []; if (body.embed) embeds.push(body.embed); - let message = await handleMessage({ + const message = await handleMessage({ ...body, type: 0, 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 await Promise.all( - channel.recipients!.map((recipient) => { + channel.recipients?.map((recipient) => { if (recipient.closed) { recipient.closed = false; 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 message.member.roles = message.member.roles .filter((x) => x.id != x.guild_id) @@ -362,7 +356,10 @@ router.post( 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); }, diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts index 7aa29700..da448678 100644 --- a/src/api/routes/channels/#channel_id/permissions.ts +++ b/src/api/routes/channels/#channel_id/permissions.ts @@ -43,7 +43,7 @@ router.put( const { channel_id, overwrite_id } = req.params; const body = req.body as ChannelPermissionOverwriteSchema; - var channel = await Channel.findOneOrFail({ + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); @@ -56,22 +56,24 @@ router.put( throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); - //@ts-ignore - var overwrite: ChannelPermissionOverwrite = + let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id); if (!overwrite) { - // @ts-ignore overwrite = { id: overwrite_id, type: body.type, + allow: "0", + deny: "0", }; - channel.permission_overwrites!.push(overwrite); + channel.permission_overwrites?.push(overwrite); } overwrite.allow = String( - req.permission!.bitfield & (BigInt(body.allow) || BigInt("0")), + (req.permission?.bitfield || 0n) & + (BigInt(body.allow) || BigInt("0")), ); overwrite.deny = String( - req.permission!.bitfield & (BigInt(body.deny) || BigInt("0")), + (req.permission?.bitfield || 0n) & + (BigInt(body.deny) || BigInt("0")), ); await Promise.all([ @@ -99,7 +101,7 @@ router.delete( }); 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, ); diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index f48e0ff5..28419383 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -21,13 +21,11 @@ import { ChannelPinsUpdateEvent, Config, emitEvent, - getPermission, Message, MessageUpdateEvent, DiscordApiErrors, } from "@fosscord/util"; import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; const router: Router = Router(); @@ -43,7 +41,7 @@ router.put( }); // * 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({ where: { channel: { id: channel_id }, pinned: true }, @@ -83,7 +81,7 @@ router.delete( const channel = await Channel.findOneOrFail({ 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({ where: { id: message_id }, @@ -120,7 +118,7 @@ router.get( async (req: Request, res: Response) => { const { channel_id } = req.params; - let pins = await Message.find({ + const pins = await Message.find({ where: { channel_id: channel_id, pinned: true }, }); diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts index 05660acf..04d8cfa2 100644 --- a/src/api/routes/channels/#channel_id/purge.ts +++ b/src/api/routes/channels/#channel_id/purge.ts @@ -19,10 +19,9 @@ import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; import { isTextChannel } from "./messages"; -import { FindManyOptions, Between, Not } from "typeorm"; +import { FindManyOptions, Between, Not, FindOperator } from "typeorm"; import { Channel, - Config, emitEvent, getPermission, getRights, @@ -69,7 +68,9 @@ router.post( // 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" }, // take: limit, where: { @@ -93,7 +94,6 @@ router.post( }; const messages = await Message.find(query); - const endpoint = Config.get().cdn.endpointPublic; if (messages.length == 0) { res.sendStatus(304); diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts index 6928dd34..252a8ef0 100644 --- a/src/api/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts @@ -41,7 +41,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => { if (channel.type !== ChannelType.GROUP_DM) { const recipients = [ - ...channel.recipients!.map((r) => r.user_id), + ...(channel.recipients?.map((r) => r.user_id) || []), user_id, ].unique(); @@ -51,11 +51,11 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => { ); return res.status(201).json(new_channel); } 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? } - channel.recipients!.push( + channel.recipients?.push( Recipient.create({ channel_id: channel_id, user_id: user_id }), ); await channel.save(); @@ -95,7 +95,7 @@ router.delete("/:user_id", route({}), async (req: Request, res: Response) => { ) 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? } diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts index 511933c3..31cae747 100644 --- a/src/api/routes/channels/#channel_id/webhooks.ts +++ b/src/api/routes/channels/#channel_id/webhooks.ts @@ -55,10 +55,10 @@ router.post( const webhook_count = await Webhook.count({ where: { channel_id } }); const { maxWebhooks } = Config.get().limits.channel; - if (webhook_count > maxWebhooks) + if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - var { avatar, name } = req.body as WebhookCreateSchema; + let { avatar, name } = req.body as WebhookCreateSchema; name = trimSpecial(name); // TODO: move this diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts index 522861eb..8f90d73c 100644 --- a/src/api/routes/discoverable-guilds.ts +++ b/src/api/routes/discoverable-guilds.ts @@ -26,8 +26,8 @@ const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { offset, limit, categories } = req.query; - var showAllGuilds = Config.get().guild.discovery.showAllGuilds; - var configLimit = Config.get().guild.discovery.limit; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; + const configLimit = Config.get().guild.discovery.limit; let guilds; if (categories == undefined) { guilds = showAllGuilds diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts index 411c3bc5..1414a617 100644 --- a/src/api/routes/discovery.ts +++ b/src/api/routes/discovery.ts @@ -26,7 +26,8 @@ router.get("/categories", route({}), async (req: Request, res: Response) => { // TODO: // Get locale instead - const { locale, primary_only } = req.query; + // const { locale, primary_only } = req.query; + const { primary_only } = req.query; const out = primary_only ? await Categories.find() diff --git a/src/api/routes/download/index.ts b/src/api/routes/download.ts index 246c8834..b6c03a48 100644 --- a/src/api/routes/download/index.ts +++ b/src/api/routes/download.ts @@ -22,11 +22,6 @@ import { FieldErrors, Release } from "@fosscord/util"; 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) => { const { platform } = req.query; diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts index 02022004..ae63d643 100644 --- a/src/api/routes/gifs/search.ts +++ b/src/api/routes/gifs/search.ts @@ -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); }); diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts index d3fdb00a..d0698fa0 100644 --- a/src/api/routes/gifs/trending-gifs.ts +++ b/src/api/routes/gifs/trending-gifs.ts @@ -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); }); diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts index 5dc43e85..5c872df8 100644 --- a/src/api/routes/gifs/trending.ts +++ b/src/api/routes/gifs/trending.ts @@ -25,7 +25,57 @@ import { HTTPError } from "lambert-server"; 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 { id: result.id, title: result.title, @@ -50,7 +100,8 @@ export function getGifApiKey() { router.get("/", route({}), async (req: Request, res: Response) => { // TODO: Custom providers // TODO: return gifs as mp4 - const { media_format, locale } = req.query; + // const { media_format, locale } = req.query; + const { locale } = req.query; 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 { results } = (await trendGifSource.json()) as any; //TODO: types; + const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS; + const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS; res.json({ - categories: tags.map((x: any) => ({ + categories: tags.map((x) => ({ name: x.searchterm, src: x.image, })), diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts index 13c08734..ac2ad9e7 100644 --- a/src/api/routes/guild-recommendations.ts +++ b/src/api/routes/guild-recommendations.ts @@ -25,10 +25,11 @@ import { Like } from "typeorm"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const { limit, personalization_disabled } = req.query; - var showAllGuilds = Config.get().guild.discovery.showAllGuilds; + // const { limit, personalization_disabled } = req.query; + const { limit } = req.query; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; - const genLoadId = (size: Number) => + const genLoadId = (size: number) => [...Array(size)] .map(() => Math.floor(Math.random() * 16).toString(16)) .join(""); diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts index efb06fa0..b044689f 100644 --- a/src/api/routes/guilds/#guild_id/bans.ts +++ b/src/api/routes/guilds/#guild_id/bans.ts @@ -41,8 +41,8 @@ router.get( async (req: Request, res: Response) => { const { guild_id } = req.params; - let bans = await Ban.find({ where: { guild_id: guild_id } }); - let promisesToAwait: object[] = []; + const bans = await Ban.find({ where: { guild_id: guild_id } }); + const promisesToAwait: object[] = []; const bansObj: object[] = []; 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 ( 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( "You are the guild owner, hence can't ban yourself", 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); 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); - 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( "You are the guild owner, hence can't ban yourself", 403, @@ -186,7 +186,7 @@ router.delete( async (req: Request, res: Response) => { 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 }, }); diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts index b72f5ddb..acdb5f19 100644 --- a/src/api/routes/guilds/#guild_id/channels.ts +++ b/src/api/routes/guilds/#guild_id/channels.ts @@ -68,7 +68,7 @@ router.patch( 400, ); - const opts: any = {}; + const opts: Partial<Channel> = {}; if (x.position != null) opts.position = x.position; if (x.parent_id) { diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts index 551c6829..9a13c9b4 100644 --- a/src/api/routes/guilds/#guild_id/delete.ts +++ b/src/api/routes/guilds/#guild_id/delete.ts @@ -16,17 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { - Channel, - emitEvent, - GuildDeleteEvent, - Guild, - Member, - Message, - Role, - Invite, - Emoji, -} from "@fosscord/util"; +import { emitEvent, GuildDeleteEvent, Guild } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; @@ -36,7 +26,7 @@ const router = Router(); // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild router.post("/", route({}), async (req: Request, res: Response) => { - var { guild_id } = req.params; + const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts index 11dcc33e..de2da6ee 100644 --- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts +++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts @@ -16,8 +16,6 @@ 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 { route } from "@fosscord/api"; diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts index 0df90f56..c262a088 100644 --- a/src/api/routes/guilds/#guild_id/index.ts +++ b/src/api/routes/guilds/#guild_id/index.ts @@ -47,10 +47,10 @@ router.get("/", route({}), async (req: Request, res: Response) => { 401, ); - // @ts-ignore - guild.joined_at = member?.joined_at; - - return res.send(guild); + return res.send({ + ...guild, + joined_at: member?.joined_at, + }); }); router.patch( @@ -68,7 +68,7 @@ router.patch( "MANAGE_GUILDS", ); - var guild = await Guild.findOneOrFail({ + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["emojis", "roles", "stickers"], }); @@ -110,7 +110,7 @@ router.patch( "DISCOVERABLE", ]; - for (var feature of diff) { + for (const feature of diff) { if (MUTABLE_FEATURES.includes(feature)) continue; throw FosscordApiErrors.FEATURE_IS_IMMUTABLE.withParams( diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts index 6e9cc3e6..dd099992 100644 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts @@ -16,7 +16,7 @@ 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 { Request, Response, Router } from "express"; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts index 2cf7c08b..2daa7d9b 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts @@ -49,11 +49,12 @@ router.patch( "/", route({ body: "MemberChangeSchema" }), async (req: Request, res: Response) => { - let { guild_id, member_id } = req.params; - if (member_id === "@me") member_id = req.user_id; + const { guild_id } = req.params; + const member_id = + req.params.member_id === "@me" ? req.user_id : req.params.member_id; const body = req.body as MemberChangeSchema; - let member = await Member.findOneOrFail({ + const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"], }); @@ -101,7 +102,8 @@ router.put("/", route({}), async (req: Request, res: Response) => { 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") { member_id = req.user_id; rights.hasThrow("JOIN_GUILDS"); @@ -109,19 +111,19 @@ router.put("/", route({}), async (req: Request, res: Response) => { // TODO: join others by controller } - var guild = await Guild.findOneOrFail({ + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, }); - var emoji = await Emoji.find({ + const emoji = await Emoji.find({ where: { guild_id: guild_id }, }); - var roles = await Role.find({ + const roles = await Role.find({ where: { guild_id: guild_id }, }); - var stickers = await Sticker.find({ + const stickers = await Sticker.find({ where: { guild_id: guild_id }, }); diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts index 619b66f7..c93eab08 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -26,12 +26,12 @@ router.patch( "/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { - var { guild_id, member_id } = req.params; - var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; - if (member_id === "@me") { - member_id = req.user_id; - permissionString = "CHANGE_NICKNAME"; - } + const { guild_id } = req.params; + let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; + const member_id = + req.params.member_id === "@me" + ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) + : req.params.member_id; const perms = await getPermission(req.user_id, guild_id); perms.hasThrow(permissionString); diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index e64893b7..16c5e789 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -16,7 +16,7 @@ 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 { Request, Response, Router } from "express"; diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts index b96210f3..51e9eb1f 100644 --- a/src/api/routes/guilds/#guild_id/members/index.ts +++ b/src/api/routes/guilds/#guild_id/members/index.ts @@ -17,7 +17,7 @@ */ 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 { MoreThan } from "typeorm"; import { HTTPError } from "lambert-server"; diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts index 7061b5f0..601167ee 100644 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ b/src/api/routes/guilds/#guild_id/messages/search.ts @@ -16,6 +16,8 @@ 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 { route } from "@fosscord/api"; import { getPermission, FieldErrors, Message, Channel } from "@fosscord/util"; @@ -28,10 +30,10 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { channel_id, content, - include_nsfw, // TODO + // include_nsfw, // TODO offset, sort_order, - sort_by, // TODO: Handle 'relevance' + // sort_by, // TODO: Handle 'relevance' limit, author_id, } = req.query; @@ -62,7 +64,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); - var query: FindManyOptions<Message> = { + const query: FindManyOptions<Message> = { order: { timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") @@ -87,7 +89,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { skip: offset ? Number(offset) : 0, }; //@ts-ignore - if (channel_id) query.where!.channel = { id: channel_id }; + if (channel_id) query.where.channel = { id: channel_id }; else { // get all channel IDs that this user can access const channels = await Channel.find({ @@ -96,7 +98,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { }); const ids = []; - for (var channel of channels) { + for (const channel of channels) { const perm = await getPermission( req.user_id, req.params.guild_id, @@ -108,12 +110,12 @@ router.get("/", route({}), async (req: Request, res: Response) => { } //@ts-ignore - query.where!.channel = { id: In(ids) }; + query.where.channel = { id: In(ids) }; } //@ts-ignore - if (author_id) query.where!.author = { id: author_id }; + if (author_id) query.where.author = { id: author_id }; //@ts-ignore - if (content) query.where!.content = Like(`%${content}%`); + if (content) query.where.content = Like(`%${content}%`); const messages: Message[] = await Message.find(query); diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts index 5771fbf1..cbf0ff6a 100644 --- a/src/api/routes/guilds/#guild_id/profile/index.ts +++ b/src/api/routes/guilds/#guild_id/profile/index.ts @@ -33,8 +33,9 @@ router.patch( "/:member_id", route({ body: "MemberChangeProfileSchema" }), async (req: Request, res: Response) => { - let { guild_id, member_id } = req.params; - if (member_id === "@me") member_id = req.user_id; + const { guild_id } = req.params; + // const member_id = + // req.params.member_id === "@me" ? req.user_id : req.params.member_id; const body = req.body as MemberChangeProfileSchema; let member = await Member.findOneOrFail({ diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts index 1199df54..37b70f63 100644 --- a/src/api/routes/guilds/#guild_id/prune.ts +++ b/src/api/routes/guilds/#guild_id/prune.ts @@ -29,16 +29,16 @@ export const inactiveMembers = async ( days: number, roles: string[] = [], ) => { - var date = new Date(); + const date = new Date(); date.setDate(date.getDate() - days); //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 possible candidates: public read receipt, last presence, last VC leave **/ - var members = await Member.find({ + let members = await Member.find({ where: [ { guild_id, @@ -83,7 +83,7 @@ export const inactiveMembers = async ( router.get("/", route({}), async (req: Request, res: Response) => { 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 const members = await inactiveMembers( @@ -102,7 +102,7 @@ router.post( async (req: Request, res: Response) => { const days = parseInt(req.body.days); - var roles = req.query.include_roles; + let roles = req.query.include_roles; if (typeof roles === "string") roles = [roles]; const { guild_id } = req.params; diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts index a9c04a39..61ba00bf 100644 --- a/src/api/routes/guilds/#guild_id/regions.ts +++ b/src/api/routes/guilds/#guild_id/regions.ts @@ -16,10 +16,9 @@ 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 { getVoiceRegions, route } from "@fosscord/api"; -import { getIpAdress } from "@fosscord/api"; +import { getVoiceRegions, route, getIpAdress } from "@fosscord/api"; const router = Router(); diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts index 22eb439a..48e77897 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts @@ -87,7 +87,8 @@ router.patch( role.assign({ ...body, permissions: String( - req.permission!.bitfield & BigInt(body.permissions || "0"), + (req.permission?.bitfield || 0n) & + BigInt(body.permissions || "0"), ), }); diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts index feab84ef..54d4b12c 100644 --- a/src/api/routes/guilds/#guild_id/roles/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/index.ts @@ -68,7 +68,8 @@ router.post( guild_id: guild_id, managed: false, permissions: String( - req.permission!.bitfield & BigInt(body.permissions || "0"), + (req.permission?.bitfield || 0n) & + BigInt(body.permissions || "0"), ), tags: undefined, icon: undefined, diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts index f5244313..284bbccf 100644 --- a/src/api/routes/guilds/#guild_id/templates.ts +++ b/src/api/routes/guilds/#guild_id/templates.ts @@ -44,7 +44,7 @@ const TemplateGuildProjection: (keyof Guild)[] = [ router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - var templates = await Template.find({ + const templates = await Template.find({ where: { source_guild_id: guild_id }, }); @@ -60,9 +60,9 @@ router.post( where: { id: guild_id }, select: TemplateGuildProjection, }); - const exists = await Template.findOneOrFail({ + const exists = await Template.findOne({ where: { id: guild_id }, - }).catch((e) => {}); + }); if (exists) throw new HTTPError("Template already exists", 400); const template = await Template.create({ diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts index 9883ef0d..3577df17 100644 --- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts @@ -37,8 +37,9 @@ router.patch( route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; - var { guild_id, user_id } = req.params; - if (user_id === "@me") user_id = req.user_id; + const { guild_id } = req.params; + const user_id = + req.params.user_id === "@me" ? req.user_id : req.params.user_id; const perms = await getPermission( req.user_id, diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts index 46b8aa8b..9319d058 100644 --- a/src/api/routes/guilds/#guild_id/widget.json.ts +++ b/src/api/routes/guilds/#guild_id/widget.json.ts @@ -17,14 +17,7 @@ */ import { Request, Response, Router } from "express"; -import { - Config, - Permissions, - Guild, - Invite, - Channel, - Member, -} from "@fosscord/util"; +import { Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; 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); // Fetch existing widget invite for widget channel - var invite = await Invite.findOne({ + let invite = await Invite.findOne({ 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 - const channels = [] as any[]; + const channels: { id: string; name: string; position: number }[] = []; ( await Channel.find({ @@ -88,15 +81,15 @@ router.get("/", route({}), async (req: Request, res: Response) => { ) { channels.push({ id: doc.id, - name: doc.name, - position: doc.position, + name: doc.name ?? "Unknown channel", + position: doc.position ?? 0, }); } }); // Fetch members // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - 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 const data = { diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts index 1c6a62ac..65de8978 100644 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts @@ -16,6 +16,8 @@ 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 { Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; @@ -161,8 +163,7 @@ async function drawIcon( scale: number, icon: string, ) { - // @ts-ignore - const img = new require("canvas").Image(); + const img = new (require("canvas").Image)(); img.src = icon; // Do some canvas clipping magic! diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts index 125d25b0..64af4bd1 100644 --- a/src/api/routes/guilds/index.ts +++ b/src/api/routes/guilds/index.ts @@ -18,7 +18,6 @@ import { Router, Request, Response } from "express"; import { - Role, Guild, Config, getRights, @@ -52,6 +51,7 @@ router.post( const { autoJoin } = Config.get().guild; if (autoJoin.enabled && !autoJoin.guilds?.length) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); } diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts index f3bb3ef1..a43337d8 100644 --- a/src/api/routes/guilds/templates/index.ts +++ b/src/api/routes/guilds/templates/index.ts @@ -86,8 +86,8 @@ router.post( const { enabled, allowTemplateCreation, - allowDiscordTemplates, - allowRaws, + // allowDiscordTemplates, + // allowRaws, } = Config.get().templates; if (!enabled) return res @@ -121,7 +121,7 @@ router.post( const guild_id = Snowflake.generate(); - const [guild, role] = await Promise.all([ + const [guild] = await Promise.all([ Guild.create({ ...body, ...template.serialized_source_guild, diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts index be9b39b4..e238b72f 100644 --- a/src/api/routes/oauth2/authorize.ts +++ b/src/api/routes/oauth2/authorize.ts @@ -27,16 +27,14 @@ import { Member, Permissions, User, - getRights, - Rights, - MemberPrivateProjection, } from "@fosscord/util"; const router = Router(); // TODO: scopes, other oauth types 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({ where: { @@ -68,6 +66,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { }, }, relations: ["guild", "roles"], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore // prettier-ignore select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"], @@ -139,7 +138,8 @@ router.post( route({ body: "ApplicationAuthorizeSchema" }), async (req: Request, res: Response) => { 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: MFA verification @@ -153,7 +153,7 @@ router.post( // getPermission cache won't exist if we're owner if ( Object.keys(perms.cache || {}).length > 0 && - perms.cache.member!.user.bot + perms.cache.member?.user.bot ) throw DiscordApiErrors.UNAUTHORIZED; perms.hasThrow("MANAGE_GUILD"); diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts index 11dcc33e..de2da6ee 100644 --- a/src/api/routes/partners/#guild_id/requirements.ts +++ b/src/api/routes/partners/#guild_id/requirements.ts @@ -16,8 +16,6 @@ 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 { route } from "@fosscord/api"; diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts index 929cf65c..33c06765 100644 --- a/src/api/routes/policies/instance/domains.ts +++ b/src/api/routes/policies/instance/domains.ts @@ -19,7 +19,6 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; import { Config } from "@fosscord/util"; -import { config } from "dotenv"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { diff --git a/src/api/routes/store/published-listings/applications.ts b/src/api/routes/store/published-listings/applications.ts index 16604960..ec9168b1 100644 --- a/src/api/routes/store/published-listings/applications.ts +++ b/src/api/routes/store/published-listings/applications.ts @@ -23,7 +23,7 @@ const router: Router = Router(); router.get("/:id", route({}), async (req: Request, res: Response) => { //TODO - const id = req.params.id; + // const id = req.params.id; res.json({ id: "", summary: "", diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts index 16604960..ec9168b1 100644 --- a/src/api/routes/store/published-listings/skus.ts +++ b/src/api/routes/store/published-listings/skus.ts @@ -23,7 +23,7 @@ const router: Router = Router(); router.get("/:id", route({}), async (req: Request, res: Response) => { //TODO - const id = req.params.id; + // const id = req.params.id; res.json({ id: "", summary: "", diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts index 5555fcda..5c237465 100644 --- a/src/api/routes/updates.ts +++ b/src/api/routes/updates.ts @@ -18,12 +18,11 @@ import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; -import { Config, FieldErrors, Release } from "@fosscord/util"; +import { FieldErrors, Release } from "@fosscord/util"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - const { client } = Config.get(); const platform = req.query.platform; if (!platform) diff --git a/src/api/routes/users/#id/delete.ts b/src/api/routes/users/#id/delete.ts index e7caeb05..9bc3f9f8 100644 --- a/src/api/routes/users/#id/delete.ts +++ b/src/api/routes/users/#id/delete.ts @@ -23,7 +23,6 @@ import { PrivateUserProjection, User, UserDeleteEvent, - UserDeleteSchema, } from "@fosscord/util"; import { Request, Response, Router } from "express"; @@ -33,7 +32,7 @@ router.post( "/", route({ right: "MANAGE_USERS" }), async (req: Request, res: Response) => { - let user = await User.findOneOrFail({ + await User.findOneOrFail({ where: { id: req.params.id }, select: [...PrivateUserProjection, "data"], }); diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts index 1103bb48..dbf95a52 100644 --- a/src/api/routes/users/#id/profile.ts +++ b/src/api/routes/users/#id/profile.ts @@ -19,11 +19,9 @@ import { Router, Request, Response } from "express"; import { PublicConnectedAccount, - PublicUser, User, UserPublic, Member, - Guild, UserProfileModifySchema, handleFile, PrivateUserProjection, @@ -53,8 +51,8 @@ router.get( relations: ["connected_accounts"], }); - var mutual_guilds: object[] = []; - var premium_guild_since; + const mutual_guilds: object[] = []; + let premium_guild_since; if (with_mutual_guilds == "true") { const requested_member = await Member.find({ @@ -169,7 +167,7 @@ router.patch( `/banners/${req.user_id}`, body.banner as string, ); - let user = await User.findOneOrFail({ + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"], }); @@ -177,6 +175,7 @@ router.patch( user.assign(body); await user.save(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore delete user.data; diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts index aa36967a..e915e3ff 100644 --- a/src/api/routes/users/#id/relationships.ts +++ b/src/api/routes/users/#id/relationships.ts @@ -36,7 +36,7 @@ router.get( "/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => { - var mutual_relations: object[] = []; + const mutual_relations: object[] = []; const requested_relations = await User.findOneOrFail({ where: { id: req.params.id }, relations: ["relationships"], @@ -53,7 +53,7 @@ router.get( rmem.type === 1 && 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({ id: relation_user.id, diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts index a6ae2d13..8043eae3 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts @@ -17,7 +17,7 @@ */ 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 bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts index 0525bea2..72c95d6b 100644 --- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts +++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts @@ -43,7 +43,7 @@ router.patch( const body = req.body as UserGuildSettingsSchema; if (body.channel_overrides) { - for (var channel in body.channel_overrides) { + for (const channel in body.channel_overrides) { Channel.findOneOrFail({ where: { id: channel } }); } } diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index 596e2575..0d3c3135 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -55,7 +55,7 @@ router.patch( }); // Populated on password change - var newToken: string | undefined; + let newToken: string | undefined; if (body.avatar) body.avatar = await handleFile( @@ -120,7 +120,7 @@ router.patch( } if (body.username) { - var check_username = body?.username?.replace(/\s/g, ""); + const check_username = body?.username?.replace(/\s/g, ""); if (!check_username) { throw FieldErrors({ username: { @@ -153,7 +153,8 @@ router.patch( user.validate(); await user.save(); - // @ts-ignore + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore delete user.data; // TODO: send update member list event in gateway diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts index ac16f7e7..24f018c9 100644 --- a/src/api/routes/users/@me/mfa/codes-verification.ts +++ b/src/api/routes/users/@me/mfa/codes-verification.ts @@ -23,6 +23,7 @@ import { generateMfaBackupCodes, User, CodesVerificationSchema, + DiscordApiErrors, } from "@fosscord/util"; const router = Router(); @@ -31,14 +32,17 @@ router.post( "/", route({ body: "CodesVerificationSchema" }), 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. // 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) { await BackupCode.update( { user: { id: req.user_id } }, diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts index 09b9b329..e2600400 100644 --- a/src/api/routes/users/@me/mfa/codes.ts +++ b/src/api/routes/users/@me/mfa/codes.ts @@ -51,7 +51,7 @@ router.post( }); } - var codes: BackupCode[]; + let codes: BackupCode[]; if (regenerate) { await BackupCode.update( { user: { id: req.user_id } }, diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts index c399ba33..e35691ae 100644 --- a/src/api/routes/users/@me/mfa/totp/disable.ts +++ b/src/api/routes/users/@me/mfa/totp/disable.ts @@ -42,7 +42,7 @@ router.post( const backup = await BackupCode.findOne({ where: { code: body.code } }); if (!backup) { - const ret = verifyToken(user.totp_secret!, body.code); + const ret = verifyToken(user.totp_secret || "", body.code); if (!ret || ret.delta != 0) throw new HTTPError( req.t("auth:login.INVALID_TOTP_CODE"), diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts index a59983ac..f6519ad0 100644 --- a/src/api/routes/users/@me/mfa/totp/enable.ts +++ b/src/api/routes/users/@me/mfa/totp/enable.ts @@ -57,7 +57,7 @@ router.post( if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - let backup_codes = generateMfaBackupCodes(req.user_id); + const backup_codes = generateMfaBackupCodes(req.user_id); await Promise.all(backup_codes.map((x) => x.save())); await User.update( { id: req.user_id }, diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts index de684a34..4dfb4c33 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts @@ -175,7 +175,7 @@ async function updateRelationship( 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( (x) => x.to_id === req.user_id, ); @@ -219,13 +219,13 @@ async function updateRelationship( if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - var incoming_relationship = Relationship.create({ + let incoming_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend, }); - var outgoing_relationship = Relationship.create({ + let outgoing_relationship = Relationship.create({ nickname: undefined, type: RelationshipType.outgoing, to: friend, diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts index ad922084..c883bb30 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts @@ -17,7 +17,7 @@ */ import { Router, Response, Request } from "express"; -import { OrmUtils, User, UserSettingsSchema } from "@fosscord/util"; +import { User, UserSettingsSchema } from "@fosscord/util"; import { route } from "@fosscord/api"; const router = Router(); diff --git a/src/api/start.ts b/src/api/start.ts index e80a7d4a..7975d085 100644 --- a/src/api/start.ts +++ b/src/api/start.ts @@ -26,7 +26,7 @@ config(); import { FosscordServer } from "./Server"; import cluster from "cluster"; import os from "os"; -var cores = 1; +let cores = 1; try { cores = Number(process.env.THREADS) || os.cpus().length; } catch { @@ -41,16 +41,17 @@ if (cluster.isPrimary && process.env.NODE_ENV == "production") { cluster.fork(); } - cluster.on("exit", (worker, code, signal) => { + cluster.on("exit", (worker) => { console.log(`worker ${worker.process.pid} died, restart worker`); cluster.fork(); }); } else { - var port = Number(process.env.PORT) || 3001; + const port = Number(process.env.PORT) || 3001; const server = new FosscordServer({ port }); server.start().catch(console.error); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore global.server = server; } diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts index acac1fb8..08157208 100644 --- a/src/api/util/handlers/Instance.ts +++ b/src/api/util/handlers/Instance.ts @@ -16,7 +16,7 @@ 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() { // 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 // 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) { - let guild = await Guild.findOne({ where: {}, select: ["id"] }); - if (guild) { - // @ts-ignore - await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); - } - } + // if (autoJoin.enabled && !autoJoin.guilds?.length) { + // const guild = await Guild.findOne({ where: {}, select: ["id"] }); + // if (guild) { + // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); + // } + // } // TODO: do no clear sessions for instance cluster await Session.delete({}); diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index 2371358f..42325681 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -51,7 +51,7 @@ const allow_empty = false; // TODO: embed gifs/videos/images const LINK_REGEX = - /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; + /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g; export async function handleMessage(opts: MessageOptions): Promise<Message> { const channel = await Channel.findOneOrFail({ @@ -129,7 +129,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { } /** Q: should be checked if the referenced message exists? ANSWER: NO otherwise backfilling won't work **/ - // @ts-ignore 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); } - var content = opts.content; - var mention_channel_ids = [] as string[]; - var mention_role_ids = [] as string[]; - var mention_user_ids = [] as string[]; - var mention_everyone = false; + let content = opts.content; + const mention_channel_ids = [] as string[]; + const mention_role_ids = [] as string[]; + const mention_user_ids = [] as string[]; + let mention_everyone = false; if (content) { // TODO: explicit-only mentions message.content = content.trim(); - content = content.replace(/ *\`[^)]*\` */g, ""); // remove codeblocks - for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { + content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks + for (const [, mention] of content.matchAll(CHANNEL_MENTION)) { if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); } - for (const [_, mention] of content.matchAll(USER_MENTION)) { + for (const [, mention] of content.matchAll(USER_MENTION)) { if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention); } await Promise.all( Array.from(content.matchAll(ROLE_MENTION)).map( - async ([_, mention]) => { + async ([, mention]) => { const role = await Role.findOneOrFail({ 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 export async function postHandleMessage(message: Message) { - const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown - var links = content?.match(LINK_REGEX); + const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown + let links = content?.match(LINK_REGEX); if (!links) return; const data = { ...message }; @@ -232,8 +231,8 @@ export async function postHandleMessage(message: Message) { // tried to use shorthand but types didn't like me L if (!Array.isArray(res)) res = [res]; - for (var embed of res) { - var cache = EmbedCache.create({ + for (const embed of res) { + const cache = EmbedCache.create({ url: link, embed: embed, }); @@ -279,7 +278,10 @@ export async function sendMessage(opts: MessageOptions) { } 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; } diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts index d8d5c279..24bfa7b3 100644 --- a/src/api/util/handlers/Voice.ts +++ b/src/api/util/handlers/Voice.ts @@ -31,7 +31,7 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) { 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 const dist = distanceBetweenLocations( clientIpAnalysis, diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts index 6fcc6c73..cb160637 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts @@ -34,6 +34,8 @@ import { NextFunction, Request, Response } from "express"; import { AnyValidateFunction } from "ajv/dist/core"; declare global { + // TODO: fix this + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Express { interface Request { permission?: Permissions; @@ -53,7 +55,7 @@ export interface RouteOptions { body?: `${string}Schema`; // typescript interface name test?: { response?: RouteResponse; - body?: any; + body?: unknown; path?: string; event?: EVENT | EVENT[]; headers?: Record<string, string>; @@ -61,7 +63,7 @@ export interface RouteOptions { } export function route(opts: RouteOptions) { - var validate: AnyValidateFunction<any> | undefined; + let validate: AnyValidateFunction | undefined; if (opts.body) { validate = ajv.getSchema(opts.body); if (!validate) throw new Error(`Body schema ${opts.body} not found`); diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts index 522ff82b..8466a374 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts @@ -17,13 +17,13 @@ */ 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 probe from "probe-image-size"; import crypto from "crypto"; import { yellow } from "picocolors"; -export const DEFAULT_FETCH_OPTIONS: any = { +export const DEFAULT_FETCH_OPTIONS: RequestInit = { redirect: "follow", follow: 1, headers: { @@ -50,7 +50,7 @@ export const getProxyUrl = ( // Imagor if (imagorServerUrl) { - let path = `${width}x${height}/${url.host}${url.pathname}`; + const path = `${width}x${height}/${url.host}${url.pathname}`; const hash = crypto .createHmac("sha1", secret) @@ -92,8 +92,8 @@ export const getMetaDescriptions = (text: string) => { image: getMeta($, "og:image") || getMeta($, "twitter:image"), image_fallback: $(`image`).attr("src"), video_fallback: $(`video`).attr("src"), - width: parseInt(getMeta($, "og:image:width")!) || 0, - height: parseInt(getMeta($, "og:image:height")!) || 0, + width: parseInt(getMeta($, "og:image:width") || "0"), + height: parseInt(getMeta($, "og:image:height") || "0"), url: getMeta($, "og:url"), youtube_embed: getMeta($, "og:video:secure_url"), }; @@ -192,8 +192,8 @@ export const EmbedHandlers: { proxy_url: metas.image ? getProxyUrl( new URL(metas.image), - metas.width!, - metas.height!, + metas.width, + metas.height, ) : undefined, }, @@ -239,9 +239,9 @@ export const EmbedHandlers: { const text = json.data.text; const created_at = new Date(json.data.created_at); const metrics = json.data.public_metrics; - let media = json.includes.media?.filter( - (x: any) => x.type == "photo", - ) as any[]; // TODO: video + const media = json.includes.media?.filter( + (x: { type: string }) => x.type == "photo", + ); const embed: Embed = { type: EmbedType.rich, @@ -334,7 +334,7 @@ export const EmbedHandlers: { width: 640, height: 640, proxy_url: metas.image - ? getProxyUrl(new URL(metas.image!), 640, 640) + ? getProxyUrl(new URL(metas.image), 640, 640) : undefined, url: metas.image, }, @@ -365,9 +365,9 @@ export const EmbedHandlers: { url: url.href, proxy_url: metas.image ? getProxyUrl( - new URL(metas.image!), - metas.width!, - metas.height!, + new URL(metas.image), + metas.width, + metas.height, ) : undefined, }, @@ -395,7 +395,7 @@ export const EmbedHandlers: { height: 215, url: metas.image, proxy_url: metas.image - ? getProxyUrl(new URL(metas.image!), 460, 215) + ? getProxyUrl(new URL(metas.image), 460, 215) : undefined, }, provider: { @@ -436,7 +436,7 @@ export const EmbedHandlers: { // TODO: does this adjust with aspect ratio? width: metas.width, height: metas.height, - url: metas.youtube_embed!, + url: metas.youtube_embed, }, url: url.href, type: EmbedType.video, @@ -447,9 +447,9 @@ export const EmbedHandlers: { url: metas.image, proxy_url: metas.image ? getProxyUrl( - new URL(metas.image!), - metas.width!, - metas.height!, + new URL(metas.image), + metas.width, + metas.height, ) : undefined, }, diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts index e95b4d1d..7ce54ad2 100644 --- a/src/api/util/utility/RandomInviteID.ts +++ b/src/api/util/utility/RandomInviteID.ts @@ -24,7 +24,7 @@ import crypto from "crypto"; export function random(length = 6) { // Declare all characters - let chars = + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // Pick characers randomly @@ -38,14 +38,14 @@ export function random(length = 6) { export function snowflakeBasedInvite() { // Declare all characters - let chars = + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let base = BigInt(chars.length); + const base = BigInt(chars.length); let snowflake = Snowflake.generateWorkerProcess(); // snowflakes hold ~10.75 characters worth of entropy; // safe to generate a 8-char invite out of them - let str = ""; + const str = ""; for (let i = 0; i < 10; i++) { str.concat(chars.charAt(Number(snowflake % base))); snowflake = snowflake / base; diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts index 2d31f891..bd05582f 100644 --- a/src/api/util/utility/captcha.ts +++ b/src/api/util/utility/captcha.ts @@ -47,7 +47,10 @@ export async function verifyCaptcha(response: string, ip?: string) { const { security } = Config.get(); 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], { method: "POST", @@ -56,9 +59,9 @@ export async function verifyCaptcha(response: string, ip?: string) { }, body: `response=${encodeURIComponent(response)}` + - `&secret=${encodeURIComponent(secret!)}` + - `&sitekey=${encodeURIComponent(sitekey!)}` + - (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""), + `&secret=${encodeURIComponent(secret)}` + + `&sitekey=${encodeURIComponent(sitekey)}` + + (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""), }); return (await res.json()) as hcaptchaResponse | recaptchaResponse; diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts index 785844ce..71a48682 100644 --- a/src/api/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts @@ -85,7 +85,7 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> { return ( await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`) - ).json() as any; // TODO: types + ).json(); } export function isProxy(data: typeof exampleData) { @@ -97,14 +97,21 @@ export function isProxy(data: typeof exampleData) { } export function getIpAdress(req: Request): string { + // TODO: express can do this (trustProxies: true)? + return ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore req.headers[Config.get().security.forwadedFor] || 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( loc1.latitude, loc1.longitude, diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts index c4dcd509..b293b856 100644 --- a/src/api/util/utility/passwordStrength.ts +++ b/src/api/util/utility/passwordStrength.ts @@ -23,7 +23,7 @@ const reNUMBER = /[0-9]/g; const reUPPERCASELETTER = /[A-Z]/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 * 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 { const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; - var strength = 0; + let strength = 0; // checks for total password len if (password.length >= minLength - 1) { @@ -68,13 +68,13 @@ export function checkPassword(password: string): number { strength = 0; } - let entropyMap: { [key: string]: number } = {}; + const entropyMap: { [key: string]: number } = {}; for (let i = 0; i < password.length; i++) { if (entropyMap[password[i]]) entropyMap[password[i]]++; else entropyMap[password[i]] = 1; } - let entropies = Object.values(entropyMap); + const entropies = Object.values(entropyMap); entropies.map((x) => x / entropyMap.length); strength += diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts index a5f96c3a..96f6a149 100644 --- a/src/bundle/Server.ts +++ b/src/bundle/Server.ts @@ -24,7 +24,7 @@ import * as Api from "@fosscord/api"; import * as Gateway from "@fosscord/gateway"; import { CDNServer } from "@fosscord/cdn"; import express from "express"; -import { green, bold, yellow } from "picocolors"; +import { green, bold } from "picocolors"; import { Config, initDatabase, Sentry } from "@fosscord/util"; const app = express(); diff --git a/src/bundle/start.ts b/src/bundle/start.ts index 10fb7c36..8da3cc20 100644 --- a/src/bundle/start.ts +++ b/src/bundle/start.ts @@ -29,14 +29,15 @@ import { execSync } from "child_process"; const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; -if (cluster.isPrimary) { - function getCommitOrFail() { - try { - return execSync("git rev-parse HEAD").toString().trim(); - } catch (e) { - return null; - } +function getCommitOrFail() { + try { + return execSync("git rev-parse HEAD").toString().trim(); + } catch (e) { + return null; } +} + +if (cluster.isPrimary) { const commit = getCommitOrFail(); console.log( @@ -81,14 +82,14 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) // Fork workers. for (let i = 0; i < cores; i++) { // 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(() => { cluster.fork(); console.log(`[Process] worker ${cyan(i)} started.`); }, delay); } - cluster.on("message", (sender: Worker, message: any) => { + cluster.on("message", (sender: Worker, message) => { for (const id in cluster.workers) { const worker = cluster.workers[id]; 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( `[Worker] ${red( `died with PID: ${worker.process.pid} , restarting ...`, diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts index bb7c9edf..37317bff 100644 --- a/src/cdn/Server.ts +++ b/src/cdn/Server.ts @@ -24,7 +24,7 @@ import guildProfilesRoute from "./routes/guild-profiles"; import iconsRoute from "./routes/role-icons"; import bodyParser from "body-parser"; -export interface CDNServerOptions extends ServerOptions {} +export type CDNServerOptions = ServerOptions; export class CDNServer extends Server { public declare options: CDNServerOptions; diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts index 76824925..d7764bd7 100644 --- a/src/cdn/routes/attachments.ts +++ b/src/cdn/routes/attachments.ts @@ -41,7 +41,7 @@ router.post( throw new HTTPError("Invalid request signature"); 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 filename = originalname .replaceAll(" ", "_") @@ -53,8 +53,8 @@ router.post( Config.get()?.cdn.endpointPublic || "http://localhost:3003"; await storage.set(path, buffer); - var width; - var height; + let width; + let height; if (mimetype.includes("image")) { const dimensions = imageSize(buffer); if (dimensions) { @@ -81,10 +81,10 @@ router.get( "/:channel_id/:id/:filename", async (req: Request, res: Response) => { const { channel_id, id, filename } = req.params; - const { format } = req.query; + // const { format } = req.query; 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"); const type = await FileType.fromBuffer(file); let content_type = type?.mime || "application/octet-stream"; diff --git a/src/cdn/routes/avatars.ts b/src/cdn/routes/avatars.ts index e2d80d11..2c078c29 100644 --- a/src/cdn/routes/avatars.ts +++ b/src/cdn/routes/avatars.ts @@ -48,10 +48,10 @@ router.post( if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); 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; - var hash = crypto + let hash = crypto .createHash("md5") .update(Snowflake.generate()) .digest("hex"); @@ -77,7 +77,7 @@ router.post( ); 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 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) => { - var { user_id, hash } = req.params; + const { user_id } = req.params; + let { hash } = req.params; hash = hash.split(".")[0]; // remove .file extension const path = `avatars/${user_id}/${hash}`; diff --git a/src/cdn/routes/guild-profiles.ts b/src/cdn/routes/guild-profiles.ts index 517550b7..a02d152c 100644 --- a/src/cdn/routes/guild-profiles.ts +++ b/src/cdn/routes/guild-profiles.ts @@ -45,7 +45,7 @@ router.post("/", multer.single("file"), async (req: Request, res: Response) => { if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); 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; let hash = crypto @@ -72,7 +72,8 @@ router.post("/", multer.single("file"), 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 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) => { - 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 const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; diff --git a/src/cdn/routes/role-icons.ts b/src/cdn/routes/role-icons.ts index b6e6812e..c3ad51c1 100644 --- a/src/cdn/routes/role-icons.ts +++ b/src/cdn/routes/role-icons.ts @@ -48,10 +48,10 @@ router.post( if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); 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; - var hash = crypto + const hash = crypto .createHash("md5") .update(Snowflake.generate()) .digest("hex"); @@ -76,7 +76,7 @@ router.post( ); 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 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) => { - var { role_id, hash } = req.params; + const { role_id, hash } = req.params; //hash = hash.split(".")[0]; // remove .file extension const path = `role-icons/${role_id}/${hash}`; diff --git a/src/cdn/util/FileStorage.ts b/src/cdn/util/FileStorage.ts index c8473e30..ee087c85 100644 --- a/src/cdn/util/FileStorage.ts +++ b/src/cdn/util/FileStorage.ts @@ -28,7 +28,7 @@ import ExifTransformer from "exif-be-gone"; function getPath(path: string) { // STORAGE_LOCATION has a default value in start.ts const root = process.env.STORAGE_LOCATION || "../"; - var filename = join(root, path); + const filename = join(root, path); if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) 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); if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true }); - value = Readable.from(value); + const ret = Readable.from(value); 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) { diff --git a/src/cdn/util/Storage.ts b/src/cdn/util/Storage.ts index ee4ae889..0d55bbd0 100644 --- a/src/cdn/util/Storage.ts +++ b/src/cdn/util/Storage.ts @@ -19,7 +19,6 @@ import { FileStorage } from "./FileStorage"; import path from "path"; import fs from "fs"; -import { bgCyan, black } from "picocolors"; import { S3 } from "@aws-sdk/client-s3"; import { S3Storage } from "./S3Storage"; process.cwd(); diff --git a/src/gateway/Server.ts b/src/gateway/Server.ts index f1ef7a27..81e4b7f4 100644 --- a/src/gateway/Server.ts +++ b/src/gateway/Server.ts @@ -56,7 +56,6 @@ export class Server { } this.server.on("upgrade", (request, socket, head) => { - // @ts-ignore this.ws.handleUpgrade(request, socket, head, (socket) => { this.ws.emit("connection", socket, request); }); diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts index 6b069d0b..296ab5ee 100644 --- a/src/gateway/events/Close.ts +++ b/src/gateway/events/Close.ts @@ -26,7 +26,7 @@ import { User, } 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()); if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); if (this.readyTimeout) clearTimeout(this.readyTimeout); diff --git a/src/gateway/events/Connection.ts b/src/gateway/events/Connection.ts index 85ffb1ed..82081266 100644 --- a/src/gateway/events/Connection.ts +++ b/src/gateway/events/Connection.ts @@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import WS from "ws"; import { genSessionId, WebSocket } from "@fosscord/gateway"; import { Send } from "../util/Send"; @@ -27,10 +28,12 @@ import { Message } from "./Message"; import { Deflate, Inflate } from "fast-zlib"; import { URL } from "url"; import { Config } from "@fosscord/util"; -var erlpack: any; +let erlpack: unknown; try { erlpack = require("@yukikaze-bot/erlpack"); -} catch (error) {} +} catch (error) { + /* empty */ +} // TODO: check rate limit // TODO: specify rate limit in config diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts index 57899a23..b949f273 100644 --- a/src/gateway/events/Message.ts +++ b/src/gateway/events/Message.ts @@ -27,14 +27,16 @@ import path from "path"; import fs from "fs/promises"; const bigIntJson = BigIntJson({ storeAsString: true }); -var erlpack: any; +let erlpack: { unpack: (buffer: Buffer) => Payload }; try { erlpack = require("@yukikaze-bot/erlpack"); -} catch (error) {} +} catch (error) { + /* empty */ +} export async function Message(this: WebSocket, buffer: WS.Data) { // TODO: compression - var data: Payload; + let data: Payload; if ( (buffer instanceof Buffer && buffer[0] === 123) || // ASCII 123 = `{`. Bad check for JSON @@ -44,9 +46,9 @@ export async function Message(this: WebSocket, buffer: WS.Data) { } else if (this.encoding === "json" && buffer instanceof Buffer) { if (this.inflate) { try { - buffer = this.inflate.process(buffer) as any; + buffer = this.inflate.process(buffer); } catch { - buffer = buffer.toString() as any; + buffer = buffer.toString(); } } data = bigIntJson.parse(buffer as string); @@ -78,7 +80,6 @@ export async function Message(this: WebSocket, buffer: WS.Data) { check.call(this, PayloadSchema, data); - // @ts-ignore const OPCodeHandler = OPCodeHandlers[data.op]; if (!OPCodeHandler) { console.error("[Gateway] Unkown opcode " + data.op); @@ -100,7 +101,7 @@ export async function Message(this: WebSocket, buffer: WS.Data) { : undefined; try { - var ret = await OPCodeHandler.call(this, data); + const ret = await OPCodeHandler.call(this, data); Sentry.withScope((scope) => { scope.setSpan(transaction); scope.setUser({ id: this.user_id }); diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts index 9bdee86a..824341f9 100644 --- a/src/gateway/listener/listener.ts +++ b/src/gateway/listener/listener.ts @@ -79,7 +79,10 @@ export async function setupListener(this: WebSocket) { const guilds = members.map((x) => x.guild); const dm_channels = recipients.map((x) => x.channel); - const opts: { acknowledge: boolean; channel?: AMQChannel } = { + const opts: { + acknowledge: boolean; + channel?: AMQChannel & { queues?: unknown }; + } = { acknowledge: true, }; this.listen_options = opts; @@ -87,7 +90,6 @@ export async function setupListener(this: WebSocket) { if (RabbitMQ.connection) { opts.channel = await RabbitMQ.connection.createChannel(); - // @ts-ignore opts.channel.queues = {}; } @@ -113,7 +115,7 @@ export async function setupListener(this: WebSocket) { guild.channels.forEach(async (channel) => { if ( permission - .overwriteChannel(channel.permission_overwrites!) + .overwriteChannel(channel.permission_overwrites ?? []) .has("VIEW_CHANNEL") ) { this.events[channel.id] = await listenEvent( @@ -128,7 +130,7 @@ export async function setupListener(this: WebSocket) { this.once("close", () => { if (opts.channel) opts.channel.close(); else { - Object.values(this.events).forEach((x) => x()); + Object.values(this.events).forEach((x) => x?.()); Object.values(this.member_events).forEach((x) => x()); } }); @@ -137,7 +139,7 @@ export async function setupListener(this: WebSocket) { // TODO: only subscribe for events that are in the connection intents async function consume(this: WebSocket, opts: EventOpts) { const { data, event } = opts; - let id = data.id as string; + const id = data.id as string; const permission = this.permissions[id] || new Permissions("ADMINISTRATOR"); // default permission for dm const consumer = consume.bind(this); @@ -150,6 +152,7 @@ async function consume(this: WebSocket, opts: EventOpts) { case "GUILD_MEMBER_REMOVE": this.member_events[data.user.id]?.(); delete this.member_events[data.user.id]; + break; case "GUILD_MEMBER_ADD": if (this.member_events[data.user.id]) break; // already subscribed this.member_events[data.user.id] = await listenEvent( @@ -158,7 +161,7 @@ async function consume(this: WebSocket, opts: EventOpts) { this.listen_options, ); break; - case "GUILD_MEMBER_REMOVE": + case "GUILD_MEMBER_UPDATE": if (!this.member_events[data.user.id]) break; this.member_events[data.user.id](); break; @@ -188,9 +191,8 @@ async function consume(this: WebSocket, opts: EventOpts) { case "GUILD_CREATE": this.events[id] = await listenEvent(id, consumer, listenOpts); break; - case "CHANNEL_UPDATE": + case "CHANNEL_UPDATE": { const exists = this.events[id]; - // @ts-ignore if ( permission .overwriteChannel(data.permission_overwrites) @@ -204,6 +206,7 @@ async function consume(this: WebSocket, opts: EventOpts) { delete this.events[id]; } break; + } } // permission checking @@ -218,8 +221,7 @@ async function consume(this: WebSocket, opts: EventOpts) { break; case "GUILD_MEMBER_ADD": case "GUILD_MEMBER_REMOVE": - case "GUILD_MEMBER_UPDATE": - // only send them, if the user subscribed for this part of the member list, or is a bot + case "GUILD_MEMBER_UPDATE": // only send them, if the user subscribed for this part of the member list, or is a bot case "PRESENCE_UPDATE": // exception if user is friend break; case "GUILD_BAN_ADD": diff --git a/src/gateway/opcodes/Heartbeat.ts b/src/gateway/opcodes/Heartbeat.ts index b7f21a56..77c8671f 100644 --- a/src/gateway/opcodes/Heartbeat.ts +++ b/src/gateway/opcodes/Heartbeat.ts @@ -16,11 +16,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Payload, WebSocket } from "@fosscord/gateway"; +import { WebSocket } from "@fosscord/gateway"; import { setHeartbeat } from "../util/Heartbeat"; import { Send } from "../util/Send"; -export async function onHeartbeat(this: WebSocket, data: Payload) { +export async function onHeartbeat(this: WebSocket) { // TODO: validate payload setHeartbeat(this); diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 8d762967..030ca66e 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -42,13 +42,13 @@ import { UserGuildSettings, ReadyGuildDTO, Guild, + UserTokenData, } from "@fosscord/util"; import { Send } from "../util/Send"; import { CLOSECODES, OPCODES } from "../util/Constants"; -import { genSessionId } from "../util/SessionUtils"; import { setupListener } from "../listener/listener"; // import experiments from "./experiments.json"; -const experiments: any = []; +const experiments: unknown[] = []; import { check } from "./instanceOf"; import { Recipient } from "@fosscord/util"; @@ -56,6 +56,8 @@ import { Recipient } from "@fosscord/util"; // TODO: check privileged intents, if defined in the config // TODO: check if already identified +// TODO: Refactor identify ( and lazyrequest, tbh ) + export async function onIdentify(this: WebSocket, data: Payload) { clearTimeout(this.readyTimeout); // TODO: is this needed now that we use `json-bigint`? @@ -65,15 +67,16 @@ export async function onIdentify(this: WebSocket, data: Payload) { const identify: IdentifySchema = data.d; + let decoded: UserTokenData["decoded"]; try { const { jwtSecret } = Config.get().security; - var { decoded } = await checkToken(identify.token, jwtSecret); // will throw an error if invalid + decoded = (await checkToken(identify.token, jwtSecret)).decoded; // will throw an error if invalid } catch (error) { console.error("invalid token", error); return this.close(CLOSECODES.Authentication_failed); } this.user_id = decoded.id; - let session_id = this.session_id; + const session_id = this.session_id; const [user, read_states, members, recipients, session, application] = await Promise.all([ @@ -144,7 +147,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Invalid_shard); } } - var users: PublicUser[] = []; + let users: PublicUser[] = []; const merged_members = members.map((x: Member) => { return [ @@ -156,18 +159,18 @@ export async function onIdentify(this: WebSocket, data: Payload) { }, ]; }) as PublicMember[][]; - let guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); + // TODO: This type is bad. + let guilds: Partial<Guild>[] = members.map((x) => ({ + ...x.guild, + joined_at: x.joined_at, + })); const pending_guilds: typeof guilds = []; - // @ts-ignore - guilds = guilds.map((guild) => { - if (user.bot) { + if (user.bot) + guilds = guilds.map((guild) => { pending_guilds.push(guild); return { id: guild.id, unavailable: true }; - } - - return guild; - }); + }); // TODO: Rewrite this. Perhaps a DTO? const user_guild_settings_entries = members.map((x) => ({ @@ -180,24 +183,25 @@ export async function onIdentify(this: WebSocket, data: Payload) { ...y[1], channel_id: y[0], })), - })) as any as UserGuildSettings[]; + })) as unknown as UserGuildSettings[]; const channels = recipients.map((x) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore - x.channel.recipients = x.channel.recipients?.map((x) => + x.channel.recipients = x.channel.recipients.map((x) => x.user.toPublicUser(), ); //TODO is this needed? check if users in group dm that are not friends are sent in the READY event users = users.concat(x.channel.recipients as unknown as User[]); if (x.channel.isDm()) { - x.channel.recipients = x.channel.recipients!.filter( + x.channel.recipients = x.channel.recipients?.filter( (x) => x.id !== this.user_id, ); } return x.channel; }); - for (let relation of user.relationships) { + for (const relation of user.relationships) { const related_user = relation.to; const public_related_user = { username: related_user.username, @@ -236,7 +240,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { } as PresenceUpdateEvent); }); - read_states.forEach((s: any) => { + read_states.forEach((s: Partial<ReadState>) => { s.id = s.channel_id; delete s.user_id; delete s.channel_id; @@ -275,10 +279,11 @@ export async function onIdentify(this: WebSocket, data: Payload) { }, //TODO: check this code! user: privateUser, user_settings: user.settings, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - guilds: guilds.map((x) => { + guilds: guilds.map((x: Guild & { joined_at: Date }) => { return { - ...new ReadyGuildDTO(x as Guild & { joined_at: Date }).toJSON(), + ...new ReadyGuildDTO(x).toJSON(), guild_hashes: {}, joined_at: x.joined_at, }; @@ -307,6 +312,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { }, country_code: user.settings.locale, friend_suggestion_count: 0, // TODO + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts index d4b612b8..93524058 100644 --- a/src/gateway/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts @@ -46,24 +46,25 @@ async function getMembers(guild_id: string, range: [number, number]) { let members: Member[] = []; try { - members = await getDatabase()! - .getRepository(Member) - .createQueryBuilder("member") - .where("member.guild_id = :guild_id", { guild_id }) - .leftJoinAndSelect("member.roles", "role") - .leftJoinAndSelect("member.user", "user") - .leftJoinAndSelect("user.sessions", "session") - .addSelect("user.settings") - .addSelect( - "CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END", - "_status", - ) - .orderBy("role.position", "DESC") - .addOrderBy("_status", "DESC") - .addOrderBy("user.username", "ASC") - .offset(Number(range[0]) || 0) - .limit(Number(range[1]) || 100) - .getMany(); + members = + (await getDatabase() + ?.getRepository(Member) + .createQueryBuilder("member") + .where("member.guild_id = :guild_id", { guild_id }) + .leftJoinAndSelect("member.roles", "role") + .leftJoinAndSelect("member.user", "user") + .leftJoinAndSelect("user.sessions", "session") + .addSelect("user.settings") + .addSelect( + "CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END", + "_status", + ) + .orderBy("role.position", "DESC") + .addOrderBy("_status", "DESC") + .addOrderBy("user.username", "ASC") + .offset(Number(range[0]) || 0) + .limit(Number(range[1]) || 100) + .getMany()) ?? []; } catch (e) { console.error(`LazyRequest`, e); } @@ -77,7 +78,7 @@ async function getMembers(guild_id: string, range: [number, number]) { }; } - const groups = [] as any[]; + const groups = []; const items = []; const member_roles = members .map((m) => m.roles) @@ -93,10 +94,9 @@ async function getMembers(guild_id: string, range: [number, number]) { const offlineItems = []; for (const role of member_roles) { - // @ts-ignore - const [role_members, other_members]: Member[][] = partition( + const [role_members, other_members] = partition( members, - (m: Member) => m.roles.find((r) => r.id === role.id), + (m: Member) => !!m.roles.find((r) => r.id === role.id), ); const group = { count: role_members.length, @@ -126,7 +126,7 @@ async function getMembers(guild_id: string, range: [number, number]) { (a.activities.length - b.activities.length) * 2 ); }); - var session: Session | undefined = sessions.first(); + const session: Session | undefined = sessions.first(); if (session?.status == "offline") { session.status = member?.user?.settings?.status || "online"; @@ -189,7 +189,9 @@ async function getMembers(guild_id: string, range: [number, number]) { export async function onLazyRequest(this: WebSocket, { d }: Payload) { // TODO: check data check.call(this, LazyRequestSchema, d); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { guild_id, typing, channels, activities } = d as LazyRequestSchema; + if (!channels) throw new Error("Must provide channel ranges"); const channel_id = Object.keys(channels || {}).first(); if (!channel_id) return; @@ -197,7 +199,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { const permissions = await getPermission(this.user_id, guild_id, channel_id); permissions.hasThrow("VIEW_CHANNEL"); - const ranges = channels![channel_id]; + const ranges = channels[channel_id]; if (!Array.isArray(ranges)) throw new Error("Not a valid Array"); const member_count = await Member.count({ where: { guild_id } }); @@ -244,15 +246,10 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { }); } -function partition<T>(array: T[], isValid: Function) { - // @ts-ignore - return array.reduce( - // @ts-ignore - ([pass, fail], elem) => { - return isValid(elem) - ? [[...pass, elem], fail] - : [pass, [...fail, elem]]; - }, - [[], []], - ); +/* https://stackoverflow.com/a/50636286 */ +function partition<T>(array: T[], filter: (elem: T) => boolean) { + const pass: T[] = [], + fail: T[] = []; + array.forEach((e) => (filter(e) ? pass : fail).push(e)); + return [pass, fail]; } diff --git a/src/gateway/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts index d669b30e..7822813b 100644 --- a/src/gateway/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts @@ -16,8 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Payload, WebSocket } from "@fosscord/gateway"; +import { WebSocket } from "@fosscord/gateway"; -export function onRequestGuildMembers(this: WebSocket, data: Payload) { +export function onRequestGuildMembers(this: WebSocket) { // return this.close(CLOSECODES.Unknown_error); } diff --git a/src/gateway/opcodes/Resume.ts b/src/gateway/opcodes/Resume.ts index d4bd5320..a8650cc4 100644 --- a/src/gateway/opcodes/Resume.ts +++ b/src/gateway/opcodes/Resume.ts @@ -16,10 +16,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { WebSocket, Payload } from "@fosscord/gateway"; +import { WebSocket } from "@fosscord/gateway"; import { Send } from "../util/Send"; -export async function onResume(this: WebSocket, data: Payload) { +export async function onResume(this: WebSocket) { console.log("Got Resume -> cancel not implemented"); await Send(this, { op: 9, diff --git a/src/gateway/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts index 5ee02e82..d300d7b7 100644 --- a/src/gateway/opcodes/VoiceStateUpdate.ts +++ b/src/gateway/opcodes/VoiceStateUpdate.ts @@ -99,6 +99,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { voiceState.token = genVoiceToken(); voiceState.session_id = this.session_id; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { id, ...newObj } = voiceState; await Promise.all([ diff --git a/src/gateway/opcodes/index.ts b/src/gateway/opcodes/index.ts index 05e8964b..1e32f1e6 100644 --- a/src/gateway/opcodes/index.ts +++ b/src/gateway/opcodes/index.ts @@ -25,7 +25,7 @@ import { onRequestGuildMembers } from "./RequestGuildMembers"; import { onResume } from "./Resume"; import { onVoiceStateUpdate } from "./VoiceStateUpdate"; -export type OPCodeHandler = (this: WebSocket, data: Payload) => any; +export type OPCodeHandler = (this: WebSocket, data: Payload) => unknown; export default { 1: onHeartbeat, @@ -40,4 +40,4 @@ export default { // 10: Hello // 13: Dm_update 14: onLazyRequest, -}; +} as { [key: number]: OPCodeHandler }; diff --git a/src/gateway/opcodes/instanceOf.ts b/src/gateway/opcodes/instanceOf.ts index 17de0a67..6c23cb08 100644 --- a/src/gateway/opcodes/instanceOf.ts +++ b/src/gateway/opcodes/instanceOf.ts @@ -20,7 +20,7 @@ import { instanceOf } from "lambert-server"; import { WebSocket } from "@fosscord/gateway"; import { CLOSECODES } from "../util/Constants"; -export function check(this: WebSocket, schema: any, data: any) { +export function check(this: WebSocket, schema: unknown, data: unknown) { try { const error = instanceOf(schema, data, { path: "body" }); if (error !== true) { diff --git a/src/gateway/start.ts b/src/gateway/start.ts index b86c3eca..79448f91 100644 --- a/src/gateway/start.ts +++ b/src/gateway/start.ts @@ -24,7 +24,7 @@ import { Server } from "./Server"; import { config } from "dotenv"; config(); -var port = Number(process.env.PORT); +let port = Number(process.env.PORT); if (isNaN(port)) port = 3002; const server = new Server({ diff --git a/src/gateway/util/Constants.ts b/src/gateway/util/Constants.ts index cc67ed0f..cb60005c 100644 --- a/src/gateway/util/Constants.ts +++ b/src/gateway/util/Constants.ts @@ -64,6 +64,7 @@ export enum CLOSECODES { export interface Payload { op: OPCODES /* | VoiceOPCodes */; + // eslint-disable-next-line @typescript-eslint/no-explicit-any d?: any; s?: number; t?: string; diff --git a/src/gateway/util/Send.ts b/src/gateway/util/Send.ts index 1ca143b6..a89d92d7 100644 --- a/src/gateway/util/Send.ts +++ b/src/gateway/util/Send.ts @@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -var erlpack: any; +let erlpack: { pack: (data: Payload) => Buffer }; try { erlpack = require("@yukikaze-bot/erlpack"); } catch (error) { @@ -63,7 +63,7 @@ export function Send(socket: WebSocket, data: Payload) { return; } - socket.send(buffer, (err: any) => { + socket.send(buffer, (err) => { if (err) return rej(err); return res(null); }); diff --git a/src/gateway/util/WebSocket.ts b/src/gateway/util/WebSocket.ts index d4a4b8b3..14917f21 100644 --- a/src/gateway/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts @@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Intents, Permissions } from "@fosscord/util"; +import { Intents, ListenEventOpts, Permissions } from "@fosscord/util"; import WS from "ws"; import { Deflate, Inflate } from "fast-zlib"; // import { Client } from "@fosscord/webrtc"; @@ -37,8 +37,8 @@ export interface WebSocket extends WS { intents: Intents; sequence: number; permissions: Record<string, Permissions>; - events: Record<string, Function>; - member_events: Record<string, Function>; - listen_options: any; + events: Record<string, undefined | (() => unknown)>; + member_events: Record<string, () => unknown>; + listen_options: ListenEventOpts; // client?: Client; } diff --git a/src/util/config/types/GifConfiguration.ts b/src/util/config/types/GifConfiguration.ts index a623448c..0e5583fa 100644 --- a/src/util/config/types/GifConfiguration.ts +++ b/src/util/config/types/GifConfiguration.ts @@ -18,6 +18,6 @@ export class GifConfiguration { enabled: boolean = true; - provider: "tenor" = "tenor"; // more coming soon + provider = "tenor" as const; // more coming soon apiKey?: string = "LIVDSRZULELA"; } diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts index 9d4d8dc3..02f7e8f3 100644 --- a/src/util/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts @@ -44,16 +44,14 @@ export class DmChannelDTO { obj.type = channel.type; obj.recipients = ( await Promise.all( - channel - .recipients!.filter( - (r) => !excluded_recipients.includes(r.user_id), - ) + channel.recipients + ?.filter((r) => !excluded_recipients.includes(r.user_id)) .map(async (r) => { return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection, }); - }), + }) || [], ) ).map((u) => new MinimalPublicUserDTO(u)); return obj; diff --git a/src/util/dtos/ReadyGuildDTO.ts b/src/util/dtos/ReadyGuildDTO.ts index 38303aed..97e6931f 100644 --- a/src/util/dtos/ReadyGuildDTO.ts +++ b/src/util/dtos/ReadyGuildDTO.ts @@ -23,7 +23,7 @@ export interface IReadyGuildDTO { channels: Channel[]; data_mode: string; // what is this emojis: Emoji[]; - guild_scheduled_events: any[]; + guild_scheduled_events: unknown[]; // TODO id: string; large: boolean | undefined; lazy: boolean; @@ -57,12 +57,12 @@ export interface IReadyGuildDTO { max_video_channel_users: number | undefined; max_members: number | undefined; nsfw_level: number | undefined; - hub_type?: any | null; // ???? + hub_type?: unknown | null; // ???? }; roles: Role[]; - stage_instances: any[]; + stage_instances: unknown[]; stickers: Sticker[]; - threads: any[]; + threads: unknown[]; version: string; } @@ -71,7 +71,7 @@ export class ReadyGuildDTO implements IReadyGuildDTO { channels: Channel[]; data_mode: string; // what is this emojis: Emoji[]; - guild_scheduled_events: any[]; + guild_scheduled_events: unknown[]; id: string; large: boolean | undefined; lazy: boolean; @@ -105,12 +105,12 @@ export class ReadyGuildDTO implements IReadyGuildDTO { max_video_channel_users: number | undefined; max_members: number | undefined; nsfw_level: number | undefined; - hub_type?: any | null; // ???? + hub_type?: unknown | null; // ???? }; roles: Role[]; - stage_instances: any[]; + stage_instances: unknown[]; stickers: Sticker[]; - threads: any[]; + threads: unknown[]; version: string; constructor(guild: Guild) { diff --git a/src/util/entities/Application.ts b/src/util/entities/Application.ts index 94a015e9..94709320 100644 --- a/src/util/entities/Application.ts +++ b/src/util/entities/Application.ts @@ -16,16 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToOne, - RelationId, -} from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; import { Team } from "./Team"; import { User } from "./User"; @@ -44,7 +36,7 @@ export class Application extends BaseClass { summary: string = ""; @Column({ type: "simple-json", nullable: true }) - type?: any; + type?: object; // TODO: this type is bad @Column() hook: boolean = true; @@ -176,6 +168,6 @@ export interface ApplicationCommandInteractionData { export interface ApplicationCommandInteractionDataOption { name: string; - value?: any; + value?: unknown; options?: ApplicationCommandInteractionDataOption[]; } diff --git a/src/util/entities/AuditLog.ts b/src/util/entities/AuditLog.ts index 68893ea8..0cc2fc04 100644 --- a/src/util/entities/AuditLog.ts +++ b/src/util/entities/AuditLog.ts @@ -173,8 +173,8 @@ export interface AuditLogChangeValue { explicit_content_filter?: number; default_message_notifications?: number; vanity_url_code?: string; - $add?: {}[]; - $remove?: {}[]; + $add?: object[]; // TODO: These types are bad. + $remove?: object[]; prune_delete_days?: number; widget_enabled?: boolean; widget_channel_id?: string; diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts index 1245ecd1..467e1fe3 100644 --- a/src/util/entities/BackupCodes.ts +++ b/src/util/entities/BackupCodes.ts @@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; import crypto from "crypto"; @@ -38,7 +38,7 @@ export class BackupCode extends BaseClass { } export function generateMfaBackupCodes(user_id: string) { - let backup_codes: BackupCode[] = []; + const backup_codes: BackupCode[] = []; for (let i = 0; i < 10; i++) { const code = BackupCode.create({ user: { id: user_id }, diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts index e3df5ad4..445b3fc9 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -29,7 +29,7 @@ import { getDatabase } from "../util/Database"; import { OrmUtils } from "../imports/OrmUtils"; export class BaseClassWithoutId extends BaseEntity { - private get construct(): any { + private get construct() { return this.constructor; } @@ -37,19 +37,24 @@ export class BaseClassWithoutId extends BaseEntity { return getDatabase()?.getMetadata(this.construct); } - assign(props: any) { + assign(props: object) { OrmUtils.mergeDeep(this, props); return this; } + // TODO: fix eslint + // eslint-disable-next-line @typescript-eslint/no-explicit-any toJSON(): any { return Object.fromEntries( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/no-non-null-assertion this.metadata!.columns // @ts-ignore .map((x) => [x.propertyName, this[x.propertyName]]) .concat( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.metadata.relations.map((x) => [ x.propertyName, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this[x.propertyName], ]), diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index 2e5f030c..1f128713 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -35,7 +35,6 @@ import { Snowflake, trimSpecial, InvisibleCharacters, - ChannelTypes, } from "../util"; import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; import { Recipient } from "./Recipient"; @@ -219,7 +218,7 @@ export class Channel extends BaseClass { !guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name ) { - for (var character of InvisibleCharacters) + for (const character of InvisibleCharacters) if (channel.name.includes(character)) throw new HTTPError( "Channel name cannot include invalid characters", @@ -237,7 +236,7 @@ export class Channel extends BaseClass { 403, ); - if (channel.name.match(/\-\-+/g)) + if (channel.name.match(/--+/g)) throw new HTTPError( "Channel name cannot include multiple adjacent dashes.", 403, @@ -344,8 +343,9 @@ export class Channel extends BaseClass { relations: ["channel", "channel.recipients"], }); - for (let ur of userRecipients) { - let re = ur.channel.recipients!.map((r) => r.user_id); + for (const ur of userRecipients) { + if (!ur.channel.recipients) continue; + const re = ur.channel.recipients.map((r) => r.user_id); if (re.length === channelRecipients.length) { if (containsAll(re, channelRecipients)) { if (channel == null) { @@ -380,8 +380,8 @@ export class Channel extends BaseClass { const channel_dto = await DmChannelDTO.from(channel); - if (type === ChannelType.GROUP_DM) { - for (let recipient of channel.recipients!) { + if (type === ChannelType.GROUP_DM && channel.recipients) { + for (const recipient of channel.recipients) { await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto.excludedRecipients([recipient.user_id]), diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index 1d0d0586..9f0ce35e 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -20,8 +20,10 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; -export interface PublicConnectedAccount - extends Pick<ConnectedAccount, "name" | "type" | "verified"> {} +export type PublicConnectedAccount = Pick< + ConnectedAccount, + "name" | "type" | "verified" +>; @Entity("connected_accounts") export class ConnectedAccount extends BaseClass { diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts index 95a641f1..94ce3d54 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts @@ -20,7 +20,6 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { User } from "."; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Role } from "./Role"; @Entity("emojis") export class Emoji extends BaseClass { diff --git a/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts index db9d0983..016b4331 100644 --- a/src/util/entities/Encryption.ts +++ b/src/util/entities/Encryption.ts @@ -16,32 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - RelationId, -} from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; -import { PublicUserProjection, User } from "./User"; -import { HTTPError } from "lambert-server"; -import { - containsAll, - emitEvent, - getPermission, - Snowflake, - trimSpecial, - InvisibleCharacters, -} from "../util"; -import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField"; -import { Recipient } from "./Recipient"; -import { Message } from "./Message"; -import { ReadState } from "./ReadState"; -import { Invite } from "./Invite"; -import { DmChannelDTO } from "../dtos"; @Entity("security_settings") export class SecuritySettings extends BaseClass { diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index 9f300334..c835f5fc 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -20,10 +20,8 @@ import { Column, Entity, JoinColumn, - ManyToMany, ManyToOne, OneToMany, - OneToOne, RelationId, } from "typeorm"; import { Config, handleFile, Snowflake } from ".."; @@ -370,12 +368,12 @@ export class Guild extends BaseClass { } }); - for (const channel of body.channels?.sort((a, b) => + for (const channel of body.channels.sort((a) => a.parent_id ? 1 : -1, )) { - var id = ids.get(channel.id) || Snowflake.generate(); + const id = ids.get(channel.id) || Snowflake.generate(); - var parent_id = ids.get(channel.parent_id); + const parent_id = ids.get(channel.parent_id); await Channel.createChannel( { ...channel, guild_id, id, parent_id }, diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts index 801b5738..c68fe215 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts @@ -33,7 +33,7 @@ import { RelationId, } from "typeorm"; import { Guild } from "./Guild"; -import { Config, emitEvent, FieldErrors } from "../util"; +import { Config, emitEvent } from "../util"; import { GuildCreateEvent, GuildDeleteEvent, @@ -212,12 +212,16 @@ export class Member extends BaseClassWithoutId { } static async addRole(user_id: string, guild_id: string, role_id: string) { - const [member, role] = await Promise.all([ + const [member] = await Promise.all([ Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - //@ts-ignore - select: ["index", "roles.id"], // TODO fix type + select: { + index: true, + roles: { + id: true, + }, + }, }), Role.findOneOrFail({ where: { id: role_id, guild_id }, @@ -249,8 +253,12 @@ export class Member extends BaseClassWithoutId { Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - //@ts-ignore - select: ["roles.id", "index"], // TODO: fix type + select: { + index: true, + roles: { + id: true, + }, + }, }), await Role.findOneOrFail({ where: { id: role_id, guild_id } }), ]); @@ -327,7 +335,7 @@ export class Member extends BaseClassWithoutId { guild_id, user: { sessions: { - status: Not("invisible" as "invisible"), // lol typescript? + status: Not("invisible" as const), // lol typescript? }, }, }, @@ -506,8 +514,7 @@ export const PublicMemberProjection: PublicMemberKeys[] = [ "premium_since", ]; -// @ts-ignore -export type PublicMember = Pick<Member, Omit<PublicMemberKeys, "roles">> & { +export type PublicMember = Omit<Pick<Member, PublicMemberKeys>, "roles"> & { user: PublicUser; roles: string[]; // only role ids not objects }; diff --git a/src/util/entities/ReadState.ts b/src/util/entities/ReadState.ts index a907b701..825beb03 100644 --- a/src/util/entities/ReadState.ts +++ b/src/util/entities/ReadState.ts @@ -26,7 +26,6 @@ import { } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; -import { Message } from "./Message"; import { User } from "./User"; // for read receipts diff --git a/src/util/entities/StickerPack.ts b/src/util/entities/StickerPack.ts index 911d8d05..ce8d5e87 100644 --- a/src/util/entities/StickerPack.ts +++ b/src/util/entities/StickerPack.ts @@ -22,7 +22,6 @@ import { JoinColumn, ManyToOne, OneToMany, - OneToOne, RelationId, } from "typeorm"; import { Sticker } from "."; diff --git a/src/util/entities/Team.ts b/src/util/entities/Team.ts index 730ff75f..82859409 100644 --- a/src/util/entities/Team.ts +++ b/src/util/entities/Team.ts @@ -20,7 +20,6 @@ import { Column, Entity, JoinColumn, - ManyToMany, ManyToOne, OneToMany, RelationId, diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index ed9e3884..658584c3 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -17,8 +17,6 @@ */ import { - BeforeInsert, - BeforeUpdate, Column, Entity, FindOneOptions, @@ -34,6 +32,7 @@ import { Member } from "./Member"; import { UserSettings } from "./UserSettings"; import { Session } from "./Session"; import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from ".."; +import { Request } from "express"; export enum PublicUserEnum { username, @@ -80,7 +79,7 @@ export const PrivateUserProjection = [ // Private user data that should never get sent to the client export type PublicUser = Pick<User, PublicUserKeys>; -export interface UserPublic extends Pick<User, PublicUserKeys> {} +export type UserPublic = Pick<User, PublicUserKeys>; export interface UserPrivate extends Pick<User, PrivateUserKeys> { locale: string; @@ -266,6 +265,7 @@ export class User extends BaseClass { } toPublicUser() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const user: any = {}; PublicUserProjection.forEach((x) => { user[x] = this[x]; @@ -277,6 +277,7 @@ export class User extends BaseClass { return await User.findOneOrFail({ where: { id: user_id }, ...opts, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix }); @@ -328,7 +329,6 @@ export class User extends BaseClass { email, username, password, - date_of_birth, id, req, }: { @@ -337,7 +337,7 @@ export class User extends BaseClass { email?: string; date_of_birth?: Date; // "2000-04-03" id?: string; - req?: any; + req?: Request; }) { // trim special uf8 control characters -> Backspace, Newline, ... username = trimSpecial(username); @@ -348,7 +348,8 @@ export class User extends BaseClass { throw FieldErrors({ username: { code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS"), + message: + req?.t("auth:register.USERNAME_TOO_MANY_USERS") || "", }, }); } @@ -357,7 +358,7 @@ export class User extends BaseClass { // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const language = - req.language === "en" ? "en-US" : req.language || "en-US"; + req?.language === "en" ? "en-US" : req?.language || "en-US"; const settings = UserSettings.create({ locale: language, @@ -386,7 +387,9 @@ export class User extends BaseClass { setImmediate(async () => { if (Config.get().guild.autoJoin.enabled) { for (const guild of Config.get().guild.autoJoin.guilds || []) { - await Member.addToGuild(user.id, guild).catch((e) => {}); + await Member.addToGuild(user.id, guild).catch((e) => + console.error("[Autojoin]", e), + ); } } }); diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts index e6551471..039c81fe 100644 --- a/src/util/imports/OrmUtils.ts +++ b/src/util/imports/OrmUtils.ts @@ -1,25 +1,10 @@ -/* - Fosscord: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Fosscord and Fosscord Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -//source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts +// source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts +// Copyright (c) 2015-2022 TypeORM. http://typeorm.github.io +/* eslint-disable @typescript-eslint/no-explicit-any */ +// @fc-license-skip export class OrmUtils { // Checks if it's an object made by Object.create(null), {} or new Object() - private static isPlainObject(item: any) { + private static isPlainObject(item: unknown) { if (item === null || item === undefined) { return false; } diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts index e902cdf3..c3bfbf9b 100644 --- a/src/util/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts @@ -39,6 +39,7 @@ import { Presence, UserSettings, IReadyGuildDTO, + ReadState, } from "@fosscord/util"; export interface Event { @@ -47,6 +48,7 @@ export interface Event { channel_id?: string; created_at?: Date; event: EVENT; + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; } @@ -103,12 +105,12 @@ export interface ReadyEventData { [number, [[number, [number, number]]]], { b: number; k: bigint[] }[], ][]; - guild_join_requests?: any[]; // ? what is this? this is new + guild_join_requests?: unknown[]; // ? what is this? this is new shard?: [number, number]; user_settings?: UserSettings; relationships?: PublicRelationship[]; // TODO read_state: { - entries: any[]; // TODO + entries: ReadState[]; // TODO partial: boolean; version: number; }; @@ -124,7 +126,7 @@ export interface ReadyEventData { merged_members?: PublicMember[][]; // probably all users who the user is in contact with users?: PublicUser[]; - sessions: any[]; + sessions: unknown[]; } export interface ReadyEvent extends Event { @@ -178,7 +180,7 @@ export interface GuildCreateEvent extends Event { joined_at: Date; // TODO: add them to guild guild_scheduled_events: never[]; - guild_hashes: {}; + guild_hashes: unknown; presences: never[]; stage_instances: never[]; threads: never[]; @@ -408,7 +410,7 @@ export interface TypingStartEvent extends Event { export interface UserUpdateEvent extends Event { event: "USER_UPDATE"; - data: User; + data: Omit<User, "data">; } export interface UserDeleteEvent extends Event { diff --git a/src/util/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts index 8695fca9..4158eda1 100644 --- a/src/util/interfaces/Interaction.ts +++ b/src/util/interfaces/Interaction.ts @@ -21,7 +21,7 @@ import { AllowedMentions, Embed } from "../entities/Message"; export interface Interaction { id: string; type: InteractionType; - data?: {}; + data?: object; // TODO typing guild_id: string; channel_id: string; member_id: string; diff --git a/src/util/schemas/ChannelPermissionOverwriteSchema.ts b/src/util/schemas/ChannelPermissionOverwriteSchema.ts index 97fe9dee..62d0ad14 100644 --- a/src/util/schemas/ChannelPermissionOverwriteSchema.ts +++ b/src/util/schemas/ChannelPermissionOverwriteSchema.ts @@ -18,5 +18,4 @@ import { ChannelPermissionOverwrite } from "@fosscord/util"; -export interface ChannelPermissionOverwriteSchema - extends ChannelPermissionOverwrite {} +export type ChannelPermissionOverwriteSchema = ChannelPermissionOverwrite; diff --git a/src/util/schemas/IdentifySchema.ts b/src/util/schemas/IdentifySchema.ts index 18ab2b49..9bb14ca3 100644 --- a/src/util/schemas/IdentifySchema.ts +++ b/src/util/schemas/IdentifySchema.ts @@ -18,6 +18,8 @@ import { ActivitySchema } from "@fosscord/util"; +// TODO: Need a way to allow camalCase and pascal_case without just duplicating the schema + export const IdentifySchema = { token: String, $intents: BigInt, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt @@ -98,7 +100,7 @@ export interface IdentifySchema { referring_domain_current?: string; release_channel?: "stable" | "dev" | "ptb" | "canary"; client_build_number?: number; - client_event_source?: any; + client_event_source?: string; client_version?: string; system_locale?: string; }; @@ -111,23 +113,23 @@ export interface IdentifySchema { guild_subscriptions?: boolean; capabilities?: number; client_state?: { - guild_hashes?: any; + guild_hashes?: unknown; highest_last_message_id?: string | number; read_state_version?: number; user_guild_settings_version?: number; user_settings_version?: number; useruser_guild_settings_version?: number; private_channels_version?: number; - guild_versions?: any; + guild_versions?: unknown; api_code_version?: number; }; clientState?: { - guildHashes?: any; + guildHashes?: unknown; highestLastMessageId?: string | number; readStateVersion?: number; userGuildSettingsVersion?: number; useruserGuildSettingsVersion?: number; - guildVersions?: any; + guildVersions?: unknown; apiCodeVersion?: number; }; v?: number; diff --git a/src/util/schemas/LazyRequestSchema.ts b/src/util/schemas/LazyRequestSchema.ts index 02fe0d8b..7cf3fd36 100644 --- a/src/util/schemas/LazyRequestSchema.ts +++ b/src/util/schemas/LazyRequestSchema.ts @@ -22,8 +22,8 @@ export interface LazyRequestSchema { activities?: boolean; threads?: boolean; typing?: true; - members?: any[]; - thread_member_lists?: any[]; + members?: unknown[]; + thread_member_lists?: unknown[]; } export const LazyRequestSchema = { @@ -32,6 +32,6 @@ export const LazyRequestSchema = { $channels: Object, $typing: Boolean, $threads: Boolean, - $members: [] as any[], - $thread_member_lists: [] as any[], + $members: [] as unknown[], + $thread_member_lists: [] as unknown[], }; diff --git a/src/util/schemas/MessageCreateSchema.ts b/src/util/schemas/MessageCreateSchema.ts index 1dd5d32c..4ee6e738 100644 --- a/src/util/schemas/MessageCreateSchema.ts +++ b/src/util/schemas/MessageCreateSchema.ts @@ -18,6 +18,11 @@ import { Embed } from "@fosscord/util"; +type Attachment = { + id: string; + filename: string; +}; + export interface MessageCreateSchema { type?: number; content?: string; @@ -41,11 +46,11 @@ export interface MessageCreateSchema { fail_if_not_exists?: boolean; }; payload_json?: string; - file?: any; + file?: { filename: string }; /** TODO: we should create an interface for attachments TODO: OpenWAAO<-->attachment-style metadata conversion **/ - attachments?: any[]; + attachments?: Attachment[]; sticker_ids?: string[]; } diff --git a/src/util/schemas/UserSettingsSchema.ts b/src/util/schemas/UserSettingsSchema.ts index e25588c4..5a590b02 100644 --- a/src/util/schemas/UserSettingsSchema.ts +++ b/src/util/schemas/UserSettingsSchema.ts @@ -18,4 +18,4 @@ import { UserSettings } from "@fosscord/util"; -export interface UserSettingsSchema extends Partial<UserSettings> {} +export type UserSettingsSchema = Partial<UserSettings>; diff --git a/src/util/schemas/Validator.ts b/src/util/schemas/Validator.ts index 26a88ef9..3190dd05 100644 --- a/src/util/schemas/Validator.ts +++ b/src/util/schemas/Validator.ts @@ -45,7 +45,7 @@ export const ajv = new Ajv({ addFormats(ajv); -export function validateSchema<G>(schema: string, data: G): G { +export function validateSchema<G extends object>(schema: string, data: G): G { const valid = ajv.validate(schema, normalizeBody(data)); if (!valid) throw ajv.errors; return data; @@ -55,13 +55,13 @@ export function validateSchema<G>(schema: string, data: G): G { // this removes null values as ajv doesn't treat them as undefined // normalizeBody allows to handle circular structures without issues // taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license) -export const normalizeBody = (body: any = {}) => { +export const normalizeBody = (body: object = {}) => { const normalizedObjectsSet = new WeakSet(); - const normalizeObject = (object: any) => { + const normalizeObject = (object: object) => { if (normalizedObjectsSet.has(object)) return; normalizedObjectsSet.add(object); if (Array.isArray(object)) { - for (const [index, value] of object.entries()) { + for (const [, value] of object.entries()) { if (typeof value === "object") normalizeObject(value); } } else { @@ -75,6 +75,8 @@ export const normalizeBody = (body: any = {}) => { key === "discovery_splash" ) continue; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore delete object[key]; } else if (typeof value === "object") { normalizeObject(value); diff --git a/src/util/util/Array.ts b/src/util/util/Array.ts index 1935fa7a..dbc75b85 100644 --- a/src/util/util/Array.ts +++ b/src/util/util/Array.ts @@ -16,6 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -export function containsAll(arr: any[], target: any[]) { +// TODO: remove this function. + +export function containsAll(arr: unknown[], target: unknown[]) { return target.every((v) => arr.includes(v)); } diff --git a/src/util/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts index 08836ea2..a4a97f3f 100644 --- a/src/util/util/AutoUpdate.ts +++ b/src/util/util/AutoUpdate.ts @@ -36,7 +36,7 @@ export function enableAutoUpdate(opts: { downloadType?: "zip"; }) { if (!opts.checkInterval) return; - var interval = 1000 * 60 * 60 * 24; + const interval = 1000 * 60 * 60 * 24; if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; @@ -70,6 +70,7 @@ export function enableAutoUpdate(opts: { }); } +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function download(url: string, dir: string) { try { // TODO: use file stream instead of buffer (to prevent crash because of high memory usage for big files) @@ -99,7 +100,7 @@ async function getLatestVersion(url: string) { try { const agent = new ProxyAgent(); const response = await fetch(url, { agent }); - const content = (await response.json()) as any; // TODO: types + const content = await response.json(); return content.version; } catch (error) { throw new Error("[Auto update] check failed for " + url); diff --git a/src/util/util/BitField.ts b/src/util/util/BitField.ts index 62bc3c46..d8758327 100644 --- a/src/util/util/BitField.ts +++ b/src/util/util/BitField.ts @@ -6,7 +6,7 @@ export type BitFieldResolvable = | number - | BigInt + | bigint | BitField | string | BitFieldResolvable[]; @@ -135,6 +135,7 @@ export class BitField { * @returns {number} */ static resolve(bit: BitFieldResolvable = BigInt(0)): bigint { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const FLAGS = this.FLAGS || this.constructor?.FLAGS; @@ -152,6 +153,7 @@ export class BitField { if (bit instanceof BitField) return bit.bitfield; if (Array.isArray(bit)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const resolve = this.constructor?.resolve || this.resolve; return bit diff --git a/src/util/util/Config.ts b/src/util/util/Config.ts index 4b2dbb0d..b83f1d7b 100644 --- a/src/util/util/Config.ts +++ b/src/util/util/Config.ts @@ -24,8 +24,8 @@ import { ConfigValue } from "../config"; // TODO: yaml instead of json const overridePath = process.env.CONFIG_PATH ?? ""; -var config: ConfigValue; -var pairs: ConfigEntity[]; +let config: ConfigValue; +let pairs: ConfigEntity[]; // TODO: use events to inform about config updates // Config keys are separated with _ @@ -84,6 +84,8 @@ export const Config = { }; function applyConfig(val: ConfigValue) { + // TODO: typings + // eslint-disable-next-line @typescript-eslint/no-explicit-any async function apply(obj: any, key = ""): Promise<any> { if (typeof obj === "object" && obj !== null) return Promise.all( @@ -107,7 +109,9 @@ function applyConfig(val: ConfigValue) { } function pairsToConfig(pairs: ConfigEntity[]) { - var value: any = {}; + // TODO: typings + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const value: any = {}; pairs.forEach((p) => { const keys = p.key.split("_"); diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts index d65fda58..1afdce49 100644 --- a/src/util/util/Constants.ts +++ b/src/util/util/Constants.ts @@ -1125,7 +1125,7 @@ export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"]; export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"]; function keyMirror(arr: string[]) { - let tmp = Object.create(null); + const tmp = Object.create(null); for (const value of arr) tmp[value] = value; return tmp; } diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts index 38011b9c..e1a4003f 100644 --- a/src/util/util/Database.ts +++ b/src/util/util/Database.ts @@ -25,14 +25,14 @@ import path from "path"; // UUID extension option is only supported with postgres // We want to generate all id's with Snowflakes that's why we have our own BaseEntity class -var dbConnection: DataSource | undefined; +let dbConnection: DataSource | undefined; // For typeorm cli if (!process.env) { config(); } -let dbConnectionString = +const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db"); const DatabaseType = dbConnectionString.includes("://") @@ -41,6 +41,7 @@ const DatabaseType = dbConnectionString.includes("://") const isSqlite = DatabaseType.includes("sqlite"); const DataSourceOptions = new DataSource({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore type 'string' is not 'mysql' | 'sqlite' | 'mariadb' | etc etc type: DatabaseType, charset: "utf8mb4", diff --git a/src/util/util/Email.ts b/src/util/util/Email.ts index f45728fc..48d8cae1 100644 --- a/src/util/util/Email.ts +++ b/src/util/util/Email.ts @@ -17,27 +17,29 @@ */ export const EMAIL_REGEX = - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export function adjustEmail(email?: string): string | undefined { if (!email) return email; // body parser already checked if it is a valid email const parts = <RegExpMatchArray>email.match(EMAIL_REGEX); - // @ts-ignore if (!parts || parts.length < 5) return undefined; - const domain = parts[5]; - const user = parts[1]; - // TODO: check accounts with uncommon email domains - if (domain === "gmail.com" || domain === "googlemail.com") { - // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator - let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; - } + return email; + // // TODO: The below code doesn't actually do anything. + // const domain = parts[5]; + // const user = parts[1]; - if (domain === "google.com") { - // replace .dots and +alternatives -> Google Staff GMail Dot Trick - let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com"; - } + // // TODO: check accounts with uncommon email domains + // if (domain === "gmail.com" || domain === "googlemail.com") { + // // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator + // const v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; + // } - return email; + // if (domain === "google.com") { + // // replace .dots and +alternatives -> Google Staff GMail Dot Trick + // const v = user.replace(/[.]|(\+.*)/g, "") + "@google.com"; + // } + + // return email; } diff --git a/src/util/util/Event.ts b/src/util/util/Event.ts index 5e01e644..79be1a10 100644 --- a/src/util/util/Event.ts +++ b/src/util/util/Event.ts @@ -55,6 +55,7 @@ export async function emitEvent(payload: Omit<Event, "created_at">) { export async function initEvent() { await RabbitMQ.init(); // does nothing if rabbitmq is not setup if (RabbitMQ.connection) { + // empty on purpose? } else { // use event emitter // use process messages @@ -62,9 +63,9 @@ export async function initEvent() { } export interface EventOpts extends Event { - acknowledge?: Function; + acknowledge?: () => unknown; channel?: Channel; - cancel: Function; + cancel: (id?: string) => unknown; } export interface ListenEventOpts { @@ -80,17 +81,18 @@ export interface ProcessEvent { export async function listenEvent( event: string, - callback: (event: EventOpts) => any, + callback: (event: EventOpts) => unknown, opts?: ListenEventOpts, ) { if (RabbitMQ.connection) { - return await rabbitListen( - // @ts-ignore - opts?.channel || RabbitMQ.channel, - event, - callback, - { acknowledge: opts?.acknowledge }, - ); + const channel = opts?.channel || RabbitMQ.channel; + if (!channel) + throw new Error( + "[Events] An event was sent without an associated channel", + ); + return await rabbitListen(channel, event, callback, { + acknowledge: opts?.acknowledge, + }); } else if (process.env.EVENT_TRANSMISSION === "process") { const cancel = async () => { process.removeListener("message", listener); @@ -103,13 +105,13 @@ export async function listenEvent( callback({ ...msg.event, cancel }); }; - //@ts-ignore apparently theres no function addListener with this signature - process.addListener("message", listener); + // TODO: assert the type is correct? + process.addListener("message", (msg) => listener(msg as ProcessEvent)); process.setMaxListeners(process.getMaxListeners() + 1); return cancel; } else { - const listener = (opts: any) => callback({ ...opts, cancel }); + const listener = (opts: EventOpts) => callback({ ...opts, cancel }); const cancel = async () => { events.removeListener(event, listener); events.setMaxListeners(events.getMaxListeners() - 1); @@ -124,7 +126,7 @@ export async function listenEvent( async function rabbitListen( channel: Channel, id: string, - callback: (event: EventOpts) => any, + callback: (event: EventOpts) => unknown, opts?: { acknowledge?: boolean }, ) { await channel.assertExchange(id, "fanout", { durable: false }); diff --git a/src/util/util/FieldError.ts b/src/util/util/FieldError.ts index 69df6f8e..eff793a8 100644 --- a/src/util/util/FieldError.ts +++ b/src/util/util/FieldError.ts @@ -42,7 +42,7 @@ export class FieldError extends Error { constructor( public code: string | number, public message: string, - public errors?: any, + public errors?: object, // TODO: I don't like this typing. ) { super(message); } diff --git a/src/util/util/Permissions.ts b/src/util/util/Permissions.ts index 19614640..996c72ea 100644 --- a/src/util/util/Permissions.ts +++ b/src/util/util/Permissions.ts @@ -12,14 +12,7 @@ import { import { BitField } from "./BitField"; import "missing-native-js-functions"; import { BitFieldResolvable, BitFlag } from "./BitField"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { HTTPError } from "lambert-server"; export type PermissionResolvable = | bigint @@ -31,7 +24,7 @@ export type PermissionResolvable = type PermissionString = keyof typeof Permissions.FLAGS; // BigInt doesn't have a bit limit (https://stackoverflow.com/questions/53335545/whats-the-biggest-bigint-value-in-js-as-per-spec) -const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(64); // 27 permission bits left for discord to add new ones +// const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(64); // 27 permission bits left for discord to add new ones export class Permissions extends BitField { cache: PermissionCache = {}; @@ -114,7 +107,6 @@ export class Permissions extends BitField { */ hasThrow(permission: PermissionResolvable) { if (this.has(permission) && this.has("VIEW_CHANNEL")) return true; - // @ts-ignore throw new HTTPError( `You are missing the following permissions ${permission}`, 403, @@ -177,11 +169,11 @@ export class Permissions extends BitField { }) { if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id - let roles = guild.roles.filter((x) => user.roles.includes(x.id)); + const roles = guild.roles.filter((x) => user.roles.includes(x.id)); let permission = Permissions.rolePermission(roles); if (channel?.overwrites) { - let overwrites = channel.overwrites.filter((x) => { + const overwrites = channel.overwrites.filter((x) => { if (x.type === 0 && user.roles.includes(x.id)) return true; if (x.type === 1 && x.id == user.id) return true; return false; @@ -244,9 +236,9 @@ export async function getPermission( } = {}, ) { if (!user_id) throw new HTTPError("User not found"); - var channel: Channel | undefined; - var member: Member | undefined; - var guild: Guild | undefined; + let channel: Channel | undefined; + let member: Member | undefined; + let guild: Guild | undefined; if (channel_id) { channel = await Channel.findOneOrFail({ @@ -258,7 +250,6 @@ export async function getPermission( "permission_overwrites", "owner_id", "guild_id", - // @ts-ignore ...(opts.channel_select || []), ], }); @@ -268,12 +259,7 @@ export async function getPermission( if (guild_id) { guild = await Guild.findOneOrFail({ where: { id: guild_id }, - select: [ - "id", - "owner_id", - // @ts-ignore - ...(opts.guild_select || []), - ], + select: ["id", "owner_id", ...(opts.guild_select || [])], relations: opts.guild_relations, }); if (guild.owner_id === user_id) @@ -285,17 +271,16 @@ export async function getPermission( // select: [ // "id", // TODO: Bug in typeorm? adding these selects breaks the query. // "roles", - // @ts-ignore // ...(opts.member_select || []), // ], }); } - let recipient_ids: any = channel?.recipients?.map((x) => x.user_id); - if (!recipient_ids?.length) recipient_ids = null; + let recipient_ids = channel?.recipients?.map((x) => x.user_id); + if (!recipient_ids?.length) recipient_ids = undefined; // TODO: remove guild.roles and convert recipient_ids to recipients - var permission = Permissions.finalPermission({ + const permission = Permissions.finalPermission({ user: { id: user_id, roles: member?.roles.map((x) => x.id) || [], diff --git a/src/util/util/Rights.ts b/src/util/util/Rights.ts index 4d437956..b48477ed 100644 --- a/src/util/util/Rights.ts +++ b/src/util/util/Rights.ts @@ -20,14 +20,7 @@ import { BitField } from "./BitField"; import "missing-native-js-functions"; import { BitFieldResolvable, BitFlag } from "./BitField"; import { User } from "../entities"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { HTTPError } from "lambert-server"; export type RightResolvable = | bigint @@ -118,7 +111,6 @@ export class Rights extends BitField { hasThrow(permission: RightResolvable) { if (this.has(permission)) return true; - // @ts-ignore throw new HTTPError( `You are missing the following rights ${permission}`, 403, @@ -137,6 +129,6 @@ export async function getRights( in_behalf?: (keyof User)[]; } = {} **/ ) { - let user = await User.findOneOrFail({ where: { id: user_id } }); + const user = await User.findOneOrFail({ where: { id: user_id } }); return new Rights(user.rights); } diff --git a/src/util/util/Sentry.ts b/src/util/util/Sentry.ts index b0eee169..e1248353 100644 --- a/src/util/util/Sentry.ts +++ b/src/util/util/Sentry.ts @@ -34,7 +34,7 @@ export const Sentry = { Config.get().sentry; if (!enabled) return; - if (!!SentryNode.getCurrentHub().getClient()) return; // we've already initialised sentry + if (SentryNode.getCurrentHub().getClient()) return; // we've already initialised sentry console.log("[Sentry] Enabling sentry..."); @@ -60,7 +60,7 @@ export const Sentry = { environment, }); - SentryNode.addGlobalEventProcessor((event, hint) => { + SentryNode.addGlobalEventProcessor((event) => { if (event.transaction) { // Rewrite things that look like IDs to `:id` for sentry event.transaction = event.transaction @@ -112,6 +112,8 @@ export const Sentry = { errorHandlersUsed = true; app.use(SentryNode.Handlers.errorHandler()); + // The typings for this are broken? + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars app.use(function onError(err: any, req: any, res: any, next: any) { res.statusCode = 500; res.end(res.sentry + "\n"); diff --git a/src/util/util/Snowflake.ts b/src/util/util/Snowflake.ts index 65546958..93898fbb 100644 --- a/src/util/util/Snowflake.ts +++ b/src/util/util/Snowflake.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck import * as cluster from "cluster"; @@ -87,10 +88,10 @@ export class Snowflake { static generateWorkerProcess() { // worker process - returns a number - var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); - var worker = Snowflake.workerId << 17n; - var process = Snowflake.processId << 12n; - var increment = Snowflake.INCREMENT++; + const time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); + const worker = Snowflake.workerId << 17n; + const process = Snowflake.processId << 12n; + const increment = Snowflake.INCREMENT++; return BigInt(time | worker | process | increment); } diff --git a/src/util/util/String.ts b/src/util/util/String.ts index 7addd49a..74fd0295 100644 --- a/src/util/util/String.ts +++ b/src/util/util/String.ts @@ -19,7 +19,6 @@ import { SPECIAL_CHAR } from "./Regex"; export function trimSpecial(str?: string): string { - // @ts-ignore - if (!str) return; + if (!str) return ""; return str.replace(SPECIAL_CHAR, "").trim(); } diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts index c00142bb..ca81eaaa 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts @@ -22,7 +22,15 @@ import { User } from "../entities"; export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; -export function checkToken(token: string, jwtSecret: string): Promise<any> { +export type UserTokenData = { + user: User; + decoded: { id: string; iat: number }; +}; + +export function checkToken( + token: string, + jwtSecret: string, +): Promise<UserTokenData> { return new Promise((res, rej) => { token = token.replace("Bot ", ""); token = token.replace("Bearer ", ""); @@ -31,24 +39,35 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> { as we don't really have separate pathways for bots **/ - jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { + jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded) => { if (err || !decoded) return rej("Invalid Token"); + if ( + typeof decoded == "string" || + !("id" in decoded) || + !decoded.iat + ) + return rej("Invalid Token"); // will never happen, just for typings. const user = await User.findOne({ where: { id: decoded.id }, select: ["data", "bot", "disabled", "deleted", "rights"], }); + if (!user) return rej("Invalid Token"); + // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds if ( decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0) ) return rej("Invalid Token"); + if (user.disabled) return rej("User disabled"); if (user.deleted) return rej("User not found"); - return res({ decoded, user }); + // Using as here because we assert `id` and `iat` are in decoded. + // TS just doesn't want to assume its there, though. + return res({ decoded, user } as UserTokenData); }); }); } diff --git a/src/util/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts index 0cc48d4a..223e3ee0 100644 --- a/src/util/util/TraverseDirectory.ts +++ b/src/util/util/TraverseDirectory.ts @@ -22,7 +22,7 @@ import { Server, traverseDirectory } from "lambert-server"; const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"; -const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!\.d).(" + extension + ")$"); +const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!\\.d).(" + extension + ")$"); export function registerRoutes(server: Server, root: string) { return traverseDirectory( diff --git a/src/util/util/cdn.ts b/src/util/util/cdn.ts index 6d2fd0f1..7f447c53 100644 --- a/src/util/util/cdn.ts +++ b/src/util/util/cdn.ts @@ -24,7 +24,8 @@ import { Config } from "./Config"; export async function uploadFile( path: string, - file?: Express.Multer.File, + // These are the only props we use, don't need to enforce the full type. + file?: Pick<Express.Multer.File, "mimetype" | "originalname" | "buffer">, ): Promise<Attachment> { if (!file?.buffer) throw new HTTPError("Missing file in body"); @@ -60,7 +61,6 @@ export async function handleFile( const mimetype = body.split(":")[1].split(";")[0]; const buffer = Buffer.from(body.split(",")[1], "base64"); - // @ts-ignore const { id } = await uploadFile(path, { buffer, mimetype, |