From 76000f8fa11dde2f67efdb22a87c4644ea49f9eb Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 30 Jan 2021 19:58:15 +0100 Subject: [PATCH] :sparkles: Util --- .vscode/launch.json | 17 +- package-lock.json | Bin 105284 -> 73445 bytes package.json | 16 +- src/Server.ts | 10 + src/Util.ts | 38 - src/Utils.ts | 38 - src/index.ts | 4 + src/middlewares/index.ts | 4 + src/models/Guild.ts.disabled | 59 -- src/models/Snowflake.ts | 1 - src/routes/api/v8/auth/login.ts | 9 + src/routes/api/v8/auth/register.ts | 4 + .../api/v8/channel/#CHANNELID/followers.ts | 4 + src/routes/api/v8/channel/#CHANNELID/index.ts | 4 + .../api/v8/channel/#CHANNELID/invites.ts | 4 + .../api/v8/channel/#CHANNELID/messages.ts | 4 + .../api/v8/channel/#CHANNELID/permissions.ts | 4 + src/routes/api/v8/channel/#CHANNELID/pins.ts | 4 + .../api/v8/channel/#CHANNELID/recipients.ts | 4 + .../api/v8/channel/#CHANNELID/typing.ts | 4 + src/routes/api/v8/guilds/index.ts | 4 + src/routes/api/v8/guilds/templates/index.ts | 4 + src/routes/api/v8/invite/index.ts | 4 + src/routes/assets/index.ts | 6 +- src/test/db_test.ts | 9 - src/test/jwt.ts | 37 + src/test/jwt2.ts | 8 + src/test/mongo_test.ts | 14 + .../{db_benchmark.ts => rethink_benchmark.ts} | 0 src/test/rethink_test.ts | 34 + src/util/BitField.ts | 146 ++++ src/util/Config.ts | 25 + src/util/Constants.ts | 679 ++++++++++++++++++ src/util/MessageFlags.ts | 14 + src/util/Permissions.ts | 56 ++ src/{Snowflake.js => util/Snowflake.ts} | 27 +- src/util/UserFlags.ts | 22 + src/util/instanceOf.ts | 121 ++++ tsconfig.json | 8 +- 39 files changed, 1280 insertions(+), 170 deletions(-) delete mode 100644 src/Util.ts delete mode 100644 src/Utils.ts create mode 100644 src/middlewares/index.ts delete mode 100644 src/models/Guild.ts.disabled delete mode 100644 src/models/Snowflake.ts delete mode 100644 src/test/db_test.ts create mode 100644 src/test/jwt.ts create mode 100644 src/test/jwt2.ts create mode 100644 src/test/mongo_test.ts rename src/test/{db_benchmark.ts => rethink_benchmark.ts} (100%) create mode 100644 src/test/rethink_test.ts create mode 100644 src/util/BitField.ts create mode 100644 src/util/Config.ts create mode 100644 src/util/Constants.ts create mode 100644 src/util/MessageFlags.ts create mode 100644 src/util/Permissions.ts rename src/{Snowflake.js => util/Snowflake.ts} (83%) create mode 100644 src/util/UserFlags.ts create mode 100644 src/util/instanceOf.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index e4326afa..9d3bb449 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,17 @@ "sourceMaps": true, "type": "node", "request": "launch", - "name": "Launch Program", - "program": "${workspaceFolder}/dist/test/db_test.js", + "name": "Launch Server", + "program": "${workspaceFolder}/dist/index.js", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": ["${workspaceFolder}/dist/**/*.js"] + }, + { + "sourceMaps": true, + "type": "node", + "request": "launch", + "name": "Test", + "program": "${workspaceFolder}/dist/test/mongo_test.js", "preLaunchTask": "tsc: build - tsconfig.json", "outFiles": ["${workspaceFolder}/dist/**/*.js"] }, @@ -18,7 +27,9 @@ "program": "${file}", "request": "launch", "skipFiles": ["/**"], - "type": "node" + "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], + "type": "node", + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"] } ] } diff --git a/package-lock.json b/package-lock.json index 1bdc5d2f126264e76ebf29b1d886a1aacf929c76..5fe539eac19e152eadf85933efa85a138698da80 100644 GIT binary patch delta 11398 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 TeF23ZE5<&Xivop|%_;u_YvzUv diff --git a/package.json b/package.json index ba744d28..5abca2b9 100644 --- a/package.json +++ b/package.json @@ -22,16 +22,24 @@ "@types/node-fetch": "^2.5.7", "express": "^4.17.1", "express-cache-middleware": "^1.0.1", + "express-validator": "^6.9.2", "faker": "^5.1.0", - "lambert-db": "^1.0.3", - "lambert-server": "^1.0.3", - "missing-native-js-functions": "^1.0.8", + "jsonwebtoken": "^8.5.1", + "jwa": "^2.0.0", + "jws": "^4.0.0", + "lambert-db": "^1.1.0", + "lambert-server": "^1.0.8", + "missing-native-js-functions": "^1.1.6", "node-fetch": "^2.6.1", "rethinkdb-ts": "^2.4.5" }, "devDependencies": { "@types/faker": "^5.1.5", - "@types/node": "^14.14.10", + "@types/jsonwebtoken": "^8.5.0", + "@types/jws": "^3.2.3", + "@types/node": "^14.14.22", + "lambert-server": "file:../../Trenite/Lambert-server", + "ts-node": "^9.1.1", "typescript": "^4.1.2" } } diff --git a/src/Server.ts b/src/Server.ts index 631ddeee..ba829eaa 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -1,5 +1,8 @@ import fs from "fs/promises"; import { Server, ServerOptions } from "lambert-server"; +import { Authentication, GlobalRateLimit } from "./middlewares/"; +import Config from "./util/Config"; +import db from "./util/Database"; export interface DiscordServerOptions extends ServerOptions {} @@ -19,6 +22,13 @@ export class DiscordServer extends Server { } async start() { + await db.init(); + console.log("[DB] connected"); + await Promise.all([Config.init()]); + + this.app.use(GlobalRateLimit); + this.app.use(Authentication); + // recursively loads files in routes/ this.routes = await this.registerRoutes(__dirname + "/routes/"); // const indexHTML = await (await fetch("https://discord.com/app")).buffer(); diff --git a/src/Util.ts b/src/Util.ts deleted file mode 100644 index 291372c1..00000000 --- a/src/Util.ts +++ /dev/null @@ -1,38 +0,0 @@ -import fs from "fs/promises"; -import "missing-native-js-functions"; - -export interface traverseDirectoryOptions { - dirname: string; - filter?: RegExp; - excludeDirs?: RegExp; - recursive?: boolean; -} - -const DEFAULT_EXCLUDE_DIR = /^\./; -const DEFAULT_FILTER = /^([^\.].*)\.js$/; - -export async function traverseDirectory( - options: traverseDirectoryOptions, - action: (path: string) => T -): Promise { - if (!options.filter) options.filter = DEFAULT_FILTER; - if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; - - const routes = await fs.readdir(options.dirname); - const promises = []>routes.map(async (file) => { - const path = options.dirname + file; - const stat = await fs.lstat(path); - if (path.match(options.excludeDirs)) return; - - if (stat.isFile() && path.match(options.filter)) { - return action(path); - } else if (options.recursive && stat.isDirectory()) { - return traverseDirectory({ ...options, dirname: path + "/" }, action); - } - }); - const result = await Promise.all(promises); - - const t = <(T | undefined)[]>result.flat(); - - return t.filter((x) => x != undefined); -} diff --git a/src/Utils.ts b/src/Utils.ts deleted file mode 100644 index 291372c1..00000000 --- a/src/Utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import fs from "fs/promises"; -import "missing-native-js-functions"; - -export interface traverseDirectoryOptions { - dirname: string; - filter?: RegExp; - excludeDirs?: RegExp; - recursive?: boolean; -} - -const DEFAULT_EXCLUDE_DIR = /^\./; -const DEFAULT_FILTER = /^([^\.].*)\.js$/; - -export async function traverseDirectory( - options: traverseDirectoryOptions, - action: (path: string) => T -): Promise { - if (!options.filter) options.filter = DEFAULT_FILTER; - if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR; - - const routes = await fs.readdir(options.dirname); - const promises = []>routes.map(async (file) => { - const path = options.dirname + file; - const stat = await fs.lstat(path); - if (path.match(options.excludeDirs)) return; - - if (stat.isFile() && path.match(options.filter)) { - return action(path); - } else if (options.recursive && stat.isDirectory()) { - return traverseDirectory({ ...options, dirname: path + "/" }, action); - } - }); - const result = await Promise.all(promises); - - const t = <(T | undefined)[]>result.flat(); - - return t.filter((x) => x != undefined); -} diff --git a/src/index.ts b/src/index.ts index f5000a61..9299221c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,7 @@ +process.on("uncaughtException", console.error); +process.on("unhandledRejection", console.error); +setTimeout(() => {}, 100000000); + import { DiscordServer } from "./Server"; const server = new DiscordServer({ port: 3000 }); diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts new file mode 100644 index 00000000..e3332f07 --- /dev/null +++ b/src/middlewares/index.ts @@ -0,0 +1,4 @@ +import { Authentication } from "./Authentication"; +import { GlobalRateLimit } from "./GlobalRateLimit"; + +export { Authentication, GlobalRateLimit }; diff --git a/src/models/Guild.ts.disabled b/src/models/Guild.ts.disabled deleted file mode 100644 index 5cffa9b8..00000000 --- a/src/models/Guild.ts.disabled +++ /dev/null @@ -1,59 +0,0 @@ -import { Snowflake } from "./Snowflake"; - -export interface Guild { - afkChannel?: Snowflake; - afkTimeout: number; - onlineCount: number; - available: boolean; - banner: string | null; - channels: GuildChannelManager; - readonly createdTimestamp: number; - defaultMessageNotifications: DefaultMessageNotifications | number; - deleted: boolean; - description: string | null; - discoverySplash: string | null; - embedChannel: GuildChannel | null; - embedChannelID: Snowflake | null; - embedEnabled: boolean; - emojis: GuildEmojiManager; - explicitContentFilter: ExplicitContentFilterLevel; - features: GuildFeatures[]; - icon: string | null; - id: Snowflake; - joinedTimestamp: number; - large: boolean; - maximumMembers: number | null; - maximumPresences: number | null; - memberCount: number; - members: GuildMemberManager; - mfaLevel: number; - name: string; - readonly nameAcronym: string; - readonly owner: Snowflake | null; - ownerID: Snowflake; - readonly partnered: boolean; - preferredLocale: string; - premiumSubscriptionCount: number | null; - premiumTier: PremiumTier; - presences: PresenceManager; - readonly publicUpdatesChannel: TextChannel | null; - publicUpdatesChannelID: Snowflake | null; - region: string; - roles: RoleManager; - readonly rulesChannel: TextChannel | null; - rulesChannelID: Snowflake | null; - readonly shard: WebSocketShard; - shardID: number; - splash: string | null; - readonly systemChannel: TextChannel | null; - systemChannelFlags: Readonly; - systemChannelID: Snowflake | null; - vanityURLCode: string | null; - vanityURLUses: number | null; - verificationLevel: VerificationLevel; - readonly verified: boolean; - readonly voiceStates: VoiceStateManager; - readonly widgetChannel: TextChannel | null; - widgetChannelID: Snowflake | null; - widgetEnabled: boolean | null; -} diff --git a/src/models/Snowflake.ts b/src/models/Snowflake.ts deleted file mode 100644 index 02e6df3a..00000000 --- a/src/models/Snowflake.ts +++ /dev/null @@ -1 +0,0 @@ -export type Snowflake = string; diff --git a/src/routes/api/v8/auth/login.ts b/src/routes/api/v8/auth/login.ts index e69de29b..6d72893b 100644 --- a/src/routes/api/v8/auth/login.ts +++ b/src/routes/api/v8/auth/login.ts @@ -0,0 +1,9 @@ +import { Request, Response, Router } from "express"; +import { check } from "../../../../util/instanceOf"; +const router: Router = Router(); + +router.post("/", check({ test: String, $user: String }), (req: Request, res: Response) => { + res.send("OK"); +}); + +export default router; diff --git a/src/routes/api/v8/auth/register.ts b/src/routes/api/v8/auth/register.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/auth/register.ts +++ b/src/routes/api/v8/auth/register.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/followers.ts b/src/routes/api/v8/channel/#CHANNELID/followers.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/followers.ts +++ b/src/routes/api/v8/channel/#CHANNELID/followers.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/index.ts b/src/routes/api/v8/channel/#CHANNELID/index.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/index.ts +++ b/src/routes/api/v8/channel/#CHANNELID/index.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/invites.ts b/src/routes/api/v8/channel/#CHANNELID/invites.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/invites.ts +++ b/src/routes/api/v8/channel/#CHANNELID/invites.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/messages.ts b/src/routes/api/v8/channel/#CHANNELID/messages.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/messages.ts +++ b/src/routes/api/v8/channel/#CHANNELID/messages.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/permissions.ts b/src/routes/api/v8/channel/#CHANNELID/permissions.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/permissions.ts +++ b/src/routes/api/v8/channel/#CHANNELID/permissions.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/pins.ts b/src/routes/api/v8/channel/#CHANNELID/pins.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/pins.ts +++ b/src/routes/api/v8/channel/#CHANNELID/pins.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/recipients.ts b/src/routes/api/v8/channel/#CHANNELID/recipients.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/recipients.ts +++ b/src/routes/api/v8/channel/#CHANNELID/recipients.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/channel/#CHANNELID/typing.ts b/src/routes/api/v8/channel/#CHANNELID/typing.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/channel/#CHANNELID/typing.ts +++ b/src/routes/api/v8/channel/#CHANNELID/typing.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/guilds/index.ts b/src/routes/api/v8/guilds/index.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/guilds/index.ts +++ b/src/routes/api/v8/guilds/index.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/guilds/templates/index.ts b/src/routes/api/v8/guilds/templates/index.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/guilds/templates/index.ts +++ b/src/routes/api/v8/guilds/templates/index.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/api/v8/invite/index.ts b/src/routes/api/v8/invite/index.ts index e69de29b..9a4e81fa 100644 --- a/src/routes/api/v8/invite/index.ts +++ b/src/routes/api/v8/invite/index.ts @@ -0,0 +1,4 @@ +import { Router } from "express"; +const router: Router = Router(); + +export default router; diff --git a/src/routes/assets/index.ts b/src/routes/assets/index.ts index c2b9f2b0..df30d13e 100644 --- a/src/routes/assets/index.ts +++ b/src/routes/assets/index.ts @@ -3,10 +3,10 @@ * (../../client/index.html) */ import { Router } from "express"; -import fetch from "node-fetch"; +import fetch, { Response } from "node-fetch"; -const router = Router(); -const cache = new Map(); +const router: Router = Router(); +const cache = new Map(); const assetEndpoint = "https://discord.com/assets/"; export async function getCache(key: string): Promise { diff --git a/src/test/db_test.ts b/src/test/db_test.ts deleted file mode 100644 index 9d4b0072..00000000 --- a/src/test/db_test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { r } from "rethinkdb-ts"; - -async function main() { - const connection = await r.connect({ port: 28015, host: "192.168.178.122" }); - - r.db("test"); -} - -main(); diff --git a/src/test/jwt.ts b/src/test/jwt.ts new file mode 100644 index 00000000..bdad513b --- /dev/null +++ b/src/test/jwt.ts @@ -0,0 +1,37 @@ +const jwa = require("jwa"); + +var STR64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".split(""); + +function base64url(string: string, encoding: string) { + // @ts-ignore + return Buffer.from(string, encoding).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +function to64String(input: number, current = ""): string { + if (input < 0 && current.length == 0) { + input = input * -1; + } + var modify = input % 64; + var remain = Math.floor(input / 64); + var result = STR64[modify] + current; + return remain <= 0 ? result : to64String(remain, result); +} + +function to64Parse(input: string) { + var result = 0; + var toProc = input.split(""); + var e; + for (e in toProc) { + result = result * 64 + STR64.indexOf(toProc[e]); + } + return result; +} + +// @ts-ignore +const start = `${base64url("311129357362135041")}.${to64String(Date.now())}`; +const signature = jwa("HS256").sign(start, `test`); +const token = `${start}.${signature}`; +console.log(token); + +// MzExMTI5MzU3MzYyMTM1MDQx.XdQb_rA.907VgF60kocnOTl32MSUWGSSzbAytQ0jbt36KjLaxuY +// MzExMTI5MzU3MzYyMTM1MDQx.XdQbaPy.4vGx4L7IuFJGsRe6IL3BeybLIvbx4Vauvx12pwNsy2U diff --git a/src/test/jwt2.ts b/src/test/jwt2.ts new file mode 100644 index 00000000..ca73a035 --- /dev/null +++ b/src/test/jwt2.ts @@ -0,0 +1,8 @@ +import jwt from "jsonwebtoken"; + +// console.log(jwt.sign("test", "test")); + +jwt.verify(`${"2WmFS_EAdYFCBOFM9pVPo9g4bpuI2I9U_JGTCfrx7Tk".repeat(1000000)}`, "test", (err, decoded) => { + if (err) console.error(err); + console.log(decoded); +}); diff --git a/src/test/mongo_test.ts b/src/test/mongo_test.ts new file mode 100644 index 00000000..c4b3ec37 --- /dev/null +++ b/src/test/mongo_test.ts @@ -0,0 +1,14 @@ +import mongoose from "mongoose"; + +async function main() { + const mongoConnection = await mongoose.createConnection( + "mongodb://localhost:27017/lambert?readPreference=secondaryPreferred", + { + useNewUrlParser: true, + useUnifiedTopology: false, + } + ); + console.log("connected"); +} + +main(); diff --git a/src/test/db_benchmark.ts b/src/test/rethink_benchmark.ts similarity index 100% rename from src/test/db_benchmark.ts rename to src/test/rethink_benchmark.ts diff --git a/src/test/rethink_test.ts b/src/test/rethink_test.ts new file mode 100644 index 00000000..d1470515 --- /dev/null +++ b/src/test/rethink_test.ts @@ -0,0 +1,34 @@ +import { r } from "rethinkdb-ts"; + +async function main() { + const connection = await r.connect({ port: 28015 }); + + const db = r.db("test"); + const cursor = await db + .table("guilds") + .get(0) + .changes({ squash: true }) + .map(function (row) { + return row("old_val") + .keys() + .setUnion(row("new_val").keys()) + .concatMap(function (key) { + return r.branch( + row("old_val")(key).ne(row("new_val")(key)).default(true), + [[key, row("new_val")(key).default(null)]], + [] + ); + }) + .coerceTo("object"); + }) + .run(connection); + + console.log("each"); + cursor.each(function (err, row) { + if (err) throw err; + console.log(row); + }); + console.log("eachend"); +} + +main(); diff --git a/src/util/BitField.ts b/src/util/BitField.ts new file mode 100644 index 00000000..01349a0b --- /dev/null +++ b/src/util/BitField.ts @@ -0,0 +1,146 @@ +"use strict"; + +// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +export type BitFieldResolvable = number | BitField | string | BitFieldResolvable[]; + +/** + * Data structure that makes it easy to interact with a bitfield. + */ +export class BitField { + public bitfield: number; + + /** + * Numeric bitfield flags. + * Defined in extension classes + */ + public static FLAGS: Record; + /** + */ + constructor(bits: BitFieldResolvable = 0) { + /** + * Bitfield of the packed bits + * @type {number} + */ + this.bitfield = BitField.resolve(bits); + } + + /** + * Checks whether the bitfield has a bit, or any of multiple bits. + */ + any(bit: BitFieldResolvable): boolean { + return (this.bitfield & BitField.resolve(bit)) !== 0; + } + + /** + * Checks if this bitfield equals another + */ + equals(bit: BitFieldResolvable): boolean { + return this.bitfield === BitField.resolve(bit); + } + + /** + * Checks whether the bitfield has a bit, or multiple bits. + */ + has(bit: BitFieldResolvable): boolean { + if (Array.isArray(bit)) return bit.every((p) => this.has(p)); + bit = BitField.resolve(bit); + return (this.bitfield & bit) === bit; + } + + /** + * Gets all given bits that are missing from the bitfield. + */ + missing(bits: BitFieldResolvable) { + if (!Array.isArray(bits)) bits = new BitField(bits).toArray(); + return bits.filter((p) => !this.has(p)); + } + + /** + * Freezes these bits, making them immutable. + */ + freeze(): Readonly { + return Object.freeze(this); + } + + /** + * Adds bits to these ones. + * @param {...BitFieldResolvable} [bits] Bits to add + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + add(...bits: BitFieldResolvable[]): BitField { + let total = 0; + for (const bit of bits) { + total |= BitField.resolve(bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield | total); + this.bitfield |= total; + return this; + } + + /** + * Removes bits from these. + * @param {...BitFieldResolvable} [bits] Bits to remove + */ + remove(...bits: BitFieldResolvable[]) { + let total = 0; + for (const bit of bits) { + total |= BitField.resolve(bit); + } + if (Object.isFrozen(this)) return new BitField(this.bitfield & ~total); + this.bitfield &= ~total; + return this; + } + + /** + * Gets an object mapping field names to a {@link boolean} indicating whether the + * bit is available. + * @param {...*} hasParams Additional parameters for the has method, if any + */ + serialize() { + const serialized: Record = {}; + for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit); + return serialized; + } + + /** + * Gets an {@link Array} of bitfield names based on the bits available. + */ + toArray(): string[] { + return Object.keys(BitField.FLAGS).filter((bit) => this.has(bit)); + } + + toJSON() { + return this.bitfield; + } + + valueOf() { + return this.bitfield; + } + + *[Symbol.iterator]() { + yield* this.toArray(); + } + + /** + * Data that can be resolved to give a bitfield. This can be: + * * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS}) + * * An instance of BitField + * * An Array of BitFieldResolvable + * @typedef {number|BitField|BitFieldResolvable[]} BitFieldResolvable + */ + + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit=0] - bit(s) to resolve + * @returns {number} + */ + static resolve(bit: BitFieldResolvable = 0): number { + if (typeof bit === "number" && bit >= 0) return bit; + if (bit instanceof BitField) return bit.bitfield; + if (Array.isArray(bit)) return bit.map((p) => this.resolve(p)).reduce((prev, p) => prev | p, 0); + if (typeof bit === "string" && typeof this.FLAGS[bit] !== "undefined") return this.FLAGS[bit]; + throw new RangeError("BITFIELD_INVALID: " + bit); + } +} diff --git a/src/util/Config.ts b/src/util/Config.ts new file mode 100644 index 00000000..c948d0eb --- /dev/null +++ b/src/util/Config.ts @@ -0,0 +1,25 @@ +import "missing-native-js-functions"; +import db from "./Database"; +import { DefaultOptions } from "./Constants"; +import { ProviderCache } from "lambert-db"; +var Config: ProviderCache; + +async function init() { + Config = db.data.config.cache(); + await Config.init(); + await Config.set(DefaultOptions.merge(Config.cache)); +} + +function get() { + return Config.get(); +} + +function set(val: any) { + return Config.set(val); +} + +export default { + init, + get: get, + set: set, +}; diff --git a/src/util/Constants.ts b/src/util/Constants.ts new file mode 100644 index 00000000..ee2684b8 --- /dev/null +++ b/src/util/Constants.ts @@ -0,0 +1,679 @@ +import crypto from "crypto"; +import { VerifyOptions } from "jsonwebtoken"; + +export interface DefaultOptions { + user: { + maxGuilds: number; + maxUsername: number; + maxFriends: number; + }; + guild: { + maxRoles: number; + maxMembers: number; + maxChannels: number; + maxChannelsInCategory: number; + hideOfflineMember: number; + }; + message: { + characters: number; + ttsCharacters: number; + maxReactions: number; + maxAttachmentSize: number; + }; + channel: { + maxPins: number; + maxTopic: number; + }; + server: { + jwtSecret: string; + ipRateLimit: { + enabled: boolean; + count: number; + timespan: number; + }; + forwadedFor: false | string; + }; +} + +export const DefaultOptions: DefaultOptions = { + user: { + maxGuilds: 100, + maxUsername: 32, + maxFriends: 1000, + }, + guild: { + maxRoles: 250, + maxMembers: 250000, + maxChannels: 500, + maxChannelsInCategory: 50, + hideOfflineMember: 1000, + }, + message: { + characters: 2000, + ttsCharacters: 200, + maxReactions: 20, + maxAttachmentSize: 8388608, + }, + channel: { + maxPins: 50, + maxTopic: 1024, + }, + server: { + jwtSecret: crypto.randomBytes(256).toString("base64"), + ipRateLimit: { + enabled: true, + count: 1000, + timespan: 1000 * 60 * 10, + }, + forwadedFor: false, + // forwadedFor: "X-Forwarded-For" // nginx/reverse proxy + // forwadedFor: "CF-Connecting-IP" // cloudflare: + }, +}; + +export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; + +export const WSCodes = { + 1000: "WS_CLOSE_REQUESTED", + 4004: "TOKEN_INVALID", + 4010: "SHARDING_INVALID", + 4011: "SHARDING_REQUIRED", + 4013: "INVALID_INTENTS", + 4014: "DISALLOWED_INTENTS", +}; + +const AllowedImageFormats = ["webp", "png", "jpg", "jpeg", "gif"]; + +const AllowedImageSizes = Array.from({ length: 9 }, (e, i) => 2 ** (i + 4)); + +function makeImageUrl(root: string, { format = "webp", size = 512 } = {}) { + if (format && !AllowedImageFormats.includes(format)) throw new Error("IMAGE_FORMAT: " + format); + if (size && !AllowedImageSizes.includes(size)) throw new RangeError("IMAGE_SIZE: " + size); + return `${root}.${format}${size ? `?size=${size}` : ""}`; +} +/** + * Options for Image URLs. + * @typedef {Object} ImageURLOptions + * @property {string} [format] One of `webp`, `png`, `jpg`, `jpeg`, `gif`. If no format is provided, + * defaults to `webp`. + * @property {boolean} [dynamic] If true, the format will dynamically change to `gif` for + * animated avatars; the default is false. + * @property {number} [size] One of `16`, `32`, `64`, `128`, `256`, `512`, `1024`, `2048`, `4096` + */ + +export const Endpoints = { + CDN(root: string) { + return { + Emoji: (emojiID: string, format = "png") => `${root}/emojis/${emojiID}.${format}`, + Asset: (name: string) => `${root}/assets/${name}`, + DefaultAvatar: (discriminator: string) => `${root}/embed/avatars/${discriminator}.png`, + Avatar: (userID: string, hash: string, format = "webp", size: number, dynamic = false) => { + if (dynamic) format = hash.startsWith("a_") ? "gif" : format; + return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size }); + }, + Banner: (guildID: string, hash: string, format = "webp", size: number) => + makeImageUrl(`${root}/banners/${guildID}/${hash}`, { format, size }), + Icon: (guildID: string, hash: string, format = "webp", size: number, dynamic = false) => { + if (dynamic) format = hash.startsWith("a_") ? "gif" : format; + return makeImageUrl(`${root}/icons/${guildID}/${hash}`, { format, size }); + }, + AppIcon: ( + clientID: string, + hash: string, + { format = "webp", size }: { format?: string; size?: number } = {} + ) => makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }), + AppAsset: ( + clientID: string, + hash: string, + { format = "webp", size }: { format?: string; size?: number } = {} + ) => makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }), + GDMIcon: (channelID: string, hash: string, format = "webp", size: number) => + makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }), + Splash: (guildID: string, hash: string, format = "webp", size: number) => + makeImageUrl(`${root}/splashes/${guildID}/${hash}`, { size, format }), + DiscoverySplash: (guildID: string, hash: string, format = "webp", size: number) => + makeImageUrl(`${root}/discovery-splashes/${guildID}/${hash}`, { size, format }), + TeamIcon: ( + teamID: string, + hash: string, + { format = "webp", size }: { format?: string; size?: number } = {} + ) => makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }), + }; + }, + invite: (root: string, code: string) => `${root}/${code}`, + botGateway: "/gateway/bot", +}; + +/** + * The current status of the client. Here are the available statuses: + * * READY: 0 + * * CONNECTING: 1 + * * RECONNECTING: 2 + * * IDLE: 3 + * * NEARLY: 4 + * * DISCONNECTED: 5 + * * WAITING_FOR_GUILDS: 6 + * * IDENTIFYING: 7 + * * RESUMING: 8 + * @typedef {number} Status + */ +export const Status = { + READY: 0, + CONNECTING: 1, + RECONNECTING: 2, + IDLE: 3, + NEARLY: 4, + DISCONNECTED: 5, + WAITING_FOR_GUILDS: 6, + IDENTIFYING: 7, + RESUMING: 8, +}; + +/** + * The current status of a voice connection. Here are the available statuses: + * * CONNECTED: 0 + * * CONNECTING: 1 + * * AUTHENTICATING: 2 + * * RECONNECTING: 3 + * * DISCONNECTED: 4 + * @typedef {number} VoiceStatus + */ +export const VoiceStatus = { + CONNECTED: 0, + CONNECTING: 1, + AUTHENTICATING: 2, + RECONNECTING: 3, + DISCONNECTED: 4, +}; + +export const OPCodes = { + DISPATCH: 0, + HEARTBEAT: 1, + IDENTIFY: 2, + STATUS_UPDATE: 3, + VOICE_STATE_UPDATE: 4, + VOICE_GUILD_PING: 5, + RESUME: 6, + RECONNECT: 7, + REQUEST_GUILD_MEMBERS: 8, + INVALID_SESSION: 9, + HELLO: 10, + HEARTBEAT_ACK: 11, +}; + +export const VoiceOPCodes = { + IDENTIFY: 0, + SELECT_PROTOCOL: 1, + READY: 2, + HEARTBEAT: 3, + SESSION_DESCRIPTION: 4, + SPEAKING: 5, + HELLO: 8, + CLIENT_CONNECT: 12, + CLIENT_DISCONNECT: 13, +}; + +export const Events = { + RATE_LIMIT: "rateLimit", + CLIENT_READY: "ready", + GUILD_CREATE: "guildCreate", + GUILD_DELETE: "guildDelete", + GUILD_UPDATE: "guildUpdate", + GUILD_UNAVAILABLE: "guildUnavailable", + GUILD_AVAILABLE: "guildAvailable", + GUILD_MEMBER_ADD: "guildMemberAdd", + GUILD_MEMBER_REMOVE: "guildMemberRemove", + GUILD_MEMBER_UPDATE: "guildMemberUpdate", + GUILD_MEMBER_AVAILABLE: "guildMemberAvailable", + GUILD_MEMBER_SPEAKING: "guildMemberSpeaking", + GUILD_MEMBERS_CHUNK: "guildMembersChunk", + GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate", + GUILD_ROLE_CREATE: "roleCreate", + GUILD_ROLE_DELETE: "roleDelete", + INVITE_CREATE: "inviteCreate", + INVITE_DELETE: "inviteDelete", + GUILD_ROLE_UPDATE: "roleUpdate", + GUILD_EMOJI_CREATE: "emojiCreate", + GUILD_EMOJI_DELETE: "emojiDelete", + GUILD_EMOJI_UPDATE: "emojiUpdate", + GUILD_BAN_ADD: "guildBanAdd", + GUILD_BAN_REMOVE: "guildBanRemove", + CHANNEL_CREATE: "channelCreate", + CHANNEL_DELETE: "channelDelete", + CHANNEL_UPDATE: "channelUpdate", + CHANNEL_PINS_UPDATE: "channelPinsUpdate", + MESSAGE_CREATE: "message", + MESSAGE_DELETE: "messageDelete", + MESSAGE_UPDATE: "messageUpdate", + MESSAGE_BULK_DELETE: "messageDeleteBulk", + MESSAGE_REACTION_ADD: "messageReactionAdd", + MESSAGE_REACTION_REMOVE: "messageReactionRemove", + MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll", + MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji", + USER_UPDATE: "userUpdate", + PRESENCE_UPDATE: "presenceUpdate", + VOICE_SERVER_UPDATE: "voiceServerUpdate", + VOICE_STATE_UPDATE: "voiceStateUpdate", + VOICE_BROADCAST_SUBSCRIBE: "subscribe", + VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe", + TYPING_START: "typingStart", + TYPING_STOP: "typingStop", + WEBHOOKS_UPDATE: "webhookUpdate", + ERROR: "error", + WARN: "warn", + DEBUG: "debug", + SHARD_DISCONNECT: "shardDisconnect", + SHARD_ERROR: "shardError", + SHARD_RECONNECTING: "shardReconnecting", + SHARD_READY: "shardReady", + SHARD_RESUME: "shardResume", + INVALIDATED: "invalidated", + RAW: "raw", +}; + +export const ShardEvents = { + CLOSE: "close", + DESTROYED: "destroyed", + INVALID_SESSION: "invalidSession", + READY: "ready", + RESUMED: "resumed", + ALL_READY: "allReady", +}; + +/** + * The type of Structure allowed to be a partial: + * * USER + * * CHANNEL (only affects DMChannels) + * * GUILD_MEMBER + * * MESSAGE + * * REACTION + * Partials require you to put checks in place when handling data, read the Partials topic listed in the + * sidebar for more information. + * @typedef {string} PartialType + */ +export const PartialTypes = keyMirror(["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"]); + +/** + * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: + * * READY + * * RESUMED + * * GUILD_CREATE + * * GUILD_DELETE + * * GUILD_UPDATE + * * INVITE_CREATE + * * INVITE_DELETE + * * GUILD_MEMBER_ADD + * * GUILD_MEMBER_REMOVE + * * GUILD_MEMBER_UPDATE + * * GUILD_MEMBERS_CHUNK + * * GUILD_INTEGRATIONS_UPDATE + * * GUILD_ROLE_CREATE + * * GUILD_ROLE_DELETE + * * GUILD_ROLE_UPDATE + * * GUILD_BAN_ADD + * * GUILD_BAN_REMOVE + * * GUILD_EMOJIS_UPDATE + * * CHANNEL_CREATE + * * CHANNEL_DELETE + * * CHANNEL_UPDATE + * * CHANNEL_PINS_UPDATE + * * MESSAGE_CREATE + * * MESSAGE_DELETE + * * MESSAGE_UPDATE + * * MESSAGE_DELETE_BULK + * * MESSAGE_REACTION_ADD + * * MESSAGE_REACTION_REMOVE + * * MESSAGE_REACTION_REMOVE_ALL + * * MESSAGE_REACTION_REMOVE_EMOJI + * * USER_UPDATE + * * PRESENCE_UPDATE + * * TYPING_START + * * VOICE_STATE_UPDATE + * * VOICE_SERVER_UPDATE + * * WEBHOOKS_UPDATE + * @typedef {string} WSEventType + */ +export const WSEvents = keyMirror([ + "READY", + "RESUMED", + "GUILD_CREATE", + "GUILD_DELETE", + "GUILD_UPDATE", + "INVITE_CREATE", + "INVITE_DELETE", + "GUILD_MEMBER_ADD", + "GUILD_MEMBER_REMOVE", + "GUILD_MEMBER_UPDATE", + "GUILD_MEMBERS_CHUNK", + "GUILD_INTEGRATIONS_UPDATE", + "GUILD_ROLE_CREATE", + "GUILD_ROLE_DELETE", + "GUILD_ROLE_UPDATE", + "GUILD_BAN_ADD", + "GUILD_BAN_REMOVE", + "GUILD_EMOJIS_UPDATE", + "CHANNEL_CREATE", + "CHANNEL_DELETE", + "CHANNEL_UPDATE", + "CHANNEL_PINS_UPDATE", + "MESSAGE_CREATE", + "MESSAGE_DELETE", + "MESSAGE_UPDATE", + "MESSAGE_DELETE_BULK", + "MESSAGE_REACTION_ADD", + "MESSAGE_REACTION_REMOVE", + "MESSAGE_REACTION_REMOVE_ALL", + "MESSAGE_REACTION_REMOVE_EMOJI", + "USER_UPDATE", + "PRESENCE_UPDATE", + "TYPING_START", + "VOICE_STATE_UPDATE", + "VOICE_SERVER_UPDATE", + "WEBHOOKS_UPDATE", +]); + +/** + * The type of a message, e.g. `DEFAULT`. Here are the available types: + * * DEFAULT + * * RECIPIENT_ADD + * * RECIPIENT_REMOVE + * * CALL + * * CHANNEL_NAME_CHANGE + * * CHANNEL_ICON_CHANGE + * * PINS_ADD + * * GUILD_MEMBER_JOIN + * * USER_PREMIUM_GUILD_SUBSCRIPTION + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1 + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2 + * * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3 + * * CHANNEL_FOLLOW_ADD + * * GUILD_DISCOVERY_DISQUALIFIED + * * GUILD_DISCOVERY_REQUALIFIED + * * REPLY + * @typedef {string} MessageType + */ +export const MessageTypes = [ + "DEFAULT", + "RECIPIENT_ADD", + "RECIPIENT_REMOVE", + "CALL", + "CHANNEL_NAME_CHANGE", + "CHANNEL_ICON_CHANGE", + "PINS_ADD", + "GUILD_MEMBER_JOIN", + "USER_PREMIUM_GUILD_SUBSCRIPTION", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2", + "USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3", + "CHANNEL_FOLLOW_ADD", + null, + "GUILD_DISCOVERY_DISQUALIFIED", + "GUILD_DISCOVERY_REQUALIFIED", + null, + null, + null, + "REPLY", +]; + +/** + * The types of messages that are `System`. The available types are `MessageTypes` excluding: + * * DEFAULT + * * REPLY + * @typedef {string} SystemMessageType + */ +export const SystemMessageTypes = MessageTypes.filter( + (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY" +); + +/** + * Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users + * The type of an activity of a users presence, e.g. `PLAYING`. Here are the available types: + * * PLAYING + * * STREAMING + * * LISTENING + * * WATCHING + * * CUSTOM_STATUS + * * COMPETING + * @typedef {string} ActivityType + */ +export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"]; + +export const ChannelTypes = { + TEXT: 0, + DM: 1, + VOICE: 2, + GROUP: 3, + CATEGORY: 4, + NEWS: 5, + STORE: 6, +}; + +export const ClientApplicationAssetTypes = { + SMALL: 1, + BIG: 2, +}; + +export const Colors = { + DEFAULT: 0x000000, + WHITE: 0xffffff, + AQUA: 0x1abc9c, + GREEN: 0x2ecc71, + BLUE: 0x3498db, + YELLOW: 0xffff00, + PURPLE: 0x9b59b6, + LUMINOUS_VIVID_PINK: 0xe91e63, + GOLD: 0xf1c40f, + ORANGE: 0xe67e22, + RED: 0xe74c3c, + GREY: 0x95a5a6, + NAVY: 0x34495e, + DARK_AQUA: 0x11806a, + DARK_GREEN: 0x1f8b4c, + DARK_BLUE: 0x206694, + DARK_PURPLE: 0x71368a, + DARK_VIVID_PINK: 0xad1457, + DARK_GOLD: 0xc27c0e, + DARK_ORANGE: 0xa84300, + DARK_RED: 0x992d22, + DARK_GREY: 0x979c9f, + DARKER_GREY: 0x7f8c8d, + LIGHT_GREY: 0xbcc0c0, + DARK_NAVY: 0x2c3e50, + BLURPLE: 0x7289da, + GREYPLE: 0x99aab5, + DARK_BUT_NOT_BLACK: 0x2c2f33, + NOT_QUITE_BLACK: 0x23272a, +}; + +/** + * The value set for the explicit content filter levels for a guild: + * * DISABLED + * * MEMBERS_WITHOUT_ROLES + * * ALL_MEMBERS + * @typedef {string} ExplicitContentFilterLevel + */ +export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", "ALL_MEMBERS"]; + +/** + * The value set for the verification levels for a guild: + * * NONE + * * LOW + * * MEDIUM + * * HIGH + * * VERY_HIGH + * @typedef {string} VerificationLevel + */ +export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"]; + +/** + * An error encountered while performing an API request. Here are the potential errors: + * * UNKNOWN_ACCOUNT + * * UNKNOWN_APPLICATION + * * UNKNOWN_CHANNEL + * * UNKNOWN_GUILD + * * UNKNOWN_INTEGRATION + * * UNKNOWN_INVITE + * * UNKNOWN_MEMBER + * * UNKNOWN_MESSAGE + * * UNKNOWN_OVERWRITE + * * UNKNOWN_PROVIDER + * * UNKNOWN_ROLE + * * UNKNOWN_TOKEN + * * UNKNOWN_USER + * * UNKNOWN_EMOJI + * * UNKNOWN_WEBHOOK + * * UNKNOWN_BAN + * * UNKNOWN_GUILD_TEMPLATE + * * BOT_PROHIBITED_ENDPOINT + * * BOT_ONLY_ENDPOINT + * * CHANNEL_HIT_WRITE_RATELIMIT + * * MAXIMUM_GUILDS + * * MAXIMUM_FRIENDS + * * MAXIMUM_PINS + * * MAXIMUM_ROLES + * * MAXIMUM_WEBHOOKS + * * MAXIMUM_REACTIONS + * * MAXIMUM_CHANNELS + * * MAXIMUM_ATTACHMENTS + * * MAXIMUM_INVITES + * * GUILD_ALREADY_HAS_TEMPLATE + * * UNAUTHORIZED + * * ACCOUNT_VERIFICATION_REQUIRED + * * REQUEST_ENTITY_TOO_LARGE + * * FEATURE_TEMPORARILY_DISABLED + * * USER_BANNED + * * ALREADY_CROSSPOSTED + * * MISSING_ACCESS + * * INVALID_ACCOUNT_TYPE + * * CANNOT_EXECUTE_ON_DM + * * EMBED_DISABLED + * * CANNOT_EDIT_MESSAGE_BY_OTHER + * * CANNOT_SEND_EMPTY_MESSAGE + * * CANNOT_MESSAGE_USER + * * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL + * * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH + * * OAUTH2_APPLICATION_BOT_ABSENT + * * MAXIMUM_OAUTH2_APPLICATIONS + * * INVALID_OAUTH_STATE + * * MISSING_PERMISSIONS + * * INVALID_AUTHENTICATION_TOKEN + * * NOTE_TOO_LONG + * * INVALID_BULK_DELETE_QUANTITY + * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL + * * INVALID_OR_TAKEN_INVITE_CODE + * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * * INVALID_OAUTH_TOKEN + * * BULK_DELETE_MESSAGE_TOO_OLD + * * INVALID_FORM_BODY + * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT + * * INVALID_API_VERSION + * * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL + * * REACTION_BLOCKED + * * RESOURCE_OVERLOADED + * @typedef {string} APIError + */ +export const APIErrors = { + UNKNOWN_ACCOUNT: 10001, + UNKNOWN_APPLICATION: 10002, + UNKNOWN_CHANNEL: 10003, + UNKNOWN_GUILD: 10004, + UNKNOWN_INTEGRATION: 10005, + UNKNOWN_INVITE: 10006, + UNKNOWN_MEMBER: 10007, + UNKNOWN_MESSAGE: 10008, + UNKNOWN_OVERWRITE: 10009, + UNKNOWN_PROVIDER: 10010, + UNKNOWN_ROLE: 10011, + UNKNOWN_TOKEN: 10012, + UNKNOWN_USER: 10013, + UNKNOWN_EMOJI: 10014, + UNKNOWN_WEBHOOK: 10015, + UNKNOWN_BAN: 10026, + UNKNOWN_GUILD_TEMPLATE: 10057, + BOT_PROHIBITED_ENDPOINT: 20001, + BOT_ONLY_ENDPOINT: 20002, + CHANNEL_HIT_WRITE_RATELIMIT: 20028, + MAXIMUM_GUILDS: 30001, + MAXIMUM_FRIENDS: 30002, + MAXIMUM_PINS: 30003, + MAXIMUM_ROLES: 30005, + MAXIMUM_WEBHOOKS: 30007, + MAXIMUM_REACTIONS: 30010, + MAXIMUM_CHANNELS: 30013, + MAXIMUM_ATTACHMENTS: 30015, + MAXIMUM_INVITES: 30016, + GUILD_ALREADY_HAS_TEMPLATE: 30031, + UNAUTHORIZED: 40001, + ACCOUNT_VERIFICATION_REQUIRED: 40002, + REQUEST_ENTITY_TOO_LARGE: 40005, + FEATURE_TEMPORARILY_DISABLED: 40006, + USER_BANNED: 40007, + ALREADY_CROSSPOSTED: 40033, + MISSING_ACCESS: 50001, + INVALID_ACCOUNT_TYPE: 50002, + CANNOT_EXECUTE_ON_DM: 50003, + EMBED_DISABLED: 50004, + CANNOT_EDIT_MESSAGE_BY_OTHER: 50005, + CANNOT_SEND_EMPTY_MESSAGE: 50006, + CANNOT_MESSAGE_USER: 50007, + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008, + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009, + OAUTH2_APPLICATION_BOT_ABSENT: 50010, + MAXIMUM_OAUTH2_APPLICATIONS: 50011, + INVALID_OAUTH_STATE: 50012, + MISSING_PERMISSIONS: 50013, + INVALID_AUTHENTICATION_TOKEN: 50014, + NOTE_TOO_LONG: 50015, + INVALID_BULK_DELETE_QUANTITY: 50016, + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019, + INVALID_OR_TAKEN_INVITE_CODE: 50020, + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021, + INVALID_OAUTH_TOKEN: 50025, + BULK_DELETE_MESSAGE_TOO_OLD: 50034, + INVALID_FORM_BODY: 50035, + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, + INVALID_API_VERSION: 50041, + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074, + REACTION_BLOCKED: 90001, + RESOURCE_OVERLOADED: 130000, +}; + +/** + * The value set for a guild's default message notifications, e.g. `ALL`. Here are the available types: + * * ALL + * * MENTIONS + * @typedef {string} DefaultMessageNotifications + */ +export const DefaultMessageNotifications = ["ALL", "MENTIONS"]; + +/** + * The value set for a team members's membership state: + * * INVITED + * * ACCEPTED + * @typedef {string} MembershipStates + */ +export const MembershipStates = [ + // They start at 1 + null, + "INVITED", + "ACCEPTED", +]; + +/** + * The value set for a webhook's type: + * * Incoming + * * Channel Follower + * @typedef {string} WebhookTypes + */ +export const WebhookTypes = [ + // They start at 1 + null, + "Incoming", + "Channel Follower", +]; + +function keyMirror(arr: string[]) { + let tmp = Object.create(null); + for (const value of arr) tmp[value] = value; + return tmp; +} diff --git a/src/util/MessageFlags.ts b/src/util/MessageFlags.ts new file mode 100644 index 00000000..381b460e --- /dev/null +++ b/src/util/MessageFlags.ts @@ -0,0 +1,14 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/MessageFlags.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +import { BitField } from "./BitField"; + +export class MessageFlags extends BitField { + static FLAGS = { + CROSSPOSTED: 1 << 0, + IS_CROSSPOST: 1 << 1, + SUPPRESS_EMBEDS: 1 << 2, + SOURCE_MESSAGE_DELETED: 1 << 3, + URGENT: 1 << 4, + }; +} diff --git a/src/util/Permissions.ts b/src/util/Permissions.ts new file mode 100644 index 00000000..ff4e0f4e --- /dev/null +++ b/src/util/Permissions.ts @@ -0,0 +1,56 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +import { BitField } from "./BitField"; + +export type PermissionResolvable = string | number | Permissions | PermissionResolvable[]; + +export class Permissions extends BitField { + static FLAGS = { + CREATE_INSTANT_INVITE: 1 << 0, + KICK_MEMBERS: 1 << 1, + BAN_MEMBERS: 1 << 2, + ADMINISTRATOR: 1 << 3, + MANAGE_CHANNELS: 1 << 4, + MANAGE_GUILD: 1 << 5, + ADD_REACTIONS: 1 << 6, + VIEW_AUDIT_LOG: 1 << 7, + PRIORITY_SPEAKER: 1 << 8, + STREAM: 1 << 9, + VIEW_CHANNEL: 1 << 10, + SEND_MESSAGES: 1 << 11, + SEND_TTS_MESSAGES: 1 << 12, + MANAGE_MESSAGES: 1 << 13, + EMBED_LINKS: 1 << 14, + ATTACH_FILES: 1 << 15, + READ_MESSAGE_HISTORY: 1 << 16, + MENTION_EVERYONE: 1 << 17, + USE_EXTERNAL_EMOJIS: 1 << 18, + VIEW_GUILD_INSIGHTS: 1 << 19, + CONNECT: 1 << 20, + SPEAK: 1 << 21, + MUTE_MEMBERS: 1 << 22, + DEAFEN_MEMBERS: 1 << 23, + MOVE_MEMBERS: 1 << 24, + USE_VAD: 1 << 25, + CHANGE_NICKNAME: 1 << 26, + MANAGE_NICKNAMES: 1 << 27, + MANAGE_ROLES: 1 << 28, + MANAGE_WEBHOOKS: 1 << 29, + MANAGE_EMOJIS: 1 << 30, + }; + + any(permission: PermissionResolvable, checkAdmin = true) { + return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission); + } + + /** + * Checks whether the bitfield has a permission, or multiple permissions. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {boolean} + */ + has(permission: PermissionResolvable, checkAdmin = true) { + return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission); + } +} diff --git a/src/Snowflake.js b/src/util/Snowflake.ts similarity index 83% rename from src/Snowflake.js rename to src/util/Snowflake.ts index feb5eb41..da6d7b19 100644 --- a/src/Snowflake.js +++ b/src/util/Snowflake.ts @@ -1,16 +1,20 @@ // @ts-nocheck -// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +// https://github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah "use strict"; // Discord epoch (2015-01-01T00:00:00.000Z) -const EPOCH = 1420070400000; -let INCREMENT = 0; /** * A container for useful snowflake-related methods. */ -class SnowflakeUtil { +export class Snowflake { + static EPOCH = 1420070400000; + static INCREMENT = 0; + static processId = 0; + static workerId = 0; + constructor() { throw new Error(`The ${this.constructor.name} class may not be instantiated.`); } @@ -91,11 +95,14 @@ class SnowflakeUtil { `"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})` ); } - if (INCREMENT >= 4095) INCREMENT = 0; - const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++) + if (Snowflake.INCREMENT >= 4095) Snowflake.INCREMENT = 0; + let workerBin = Snowflake.workerId.toString(2).padStart(5, "0"); + let processBin = Snowflake.processId.toString(2).padStart(5, "0"); + + const BINARY = `${(timestamp - EPOCH) .toString(2) - .padStart(12, "0")}`; - return SnowflakeUtil.binaryToID(BINARY); + .padStart(42, "0")}${workerBin}${processBin}${(Snowflake.INCREMENT++).toString(2).padStart(12, "0")}`; + return Snowflake.binaryToID(BINARY); } /** @@ -115,7 +122,7 @@ class SnowflakeUtil { * @returns {DeconstructedSnowflake} Deconstructed snowflake */ static deconstruct(snowflake) { - const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0"); + const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0"); const res = { timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH, workerID: parseInt(BINARY.substring(42, 47), 2), @@ -141,5 +148,3 @@ class SnowflakeUtil { return EPOCH; } } - -module.exports = SnowflakeUtil; diff --git a/src/util/UserFlags.ts b/src/util/UserFlags.ts new file mode 100644 index 00000000..44486cb0 --- /dev/null +++ b/src/util/UserFlags.ts @@ -0,0 +1,22 @@ +// https://github.com/discordjs/discord.js/blob/master/src/util/UserFlags.js +// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah + +import { BitField } from "./BitField"; + +export class UserFlags extends BitField { + static FLAGS = { + DISCORD_EMPLOYEE: 1 << 0, + PARTNERED_SERVER_OWNER: 1 << 1, + HYPESQUAD_EVENTS: 1 << 2, + BUGHUNTER_LEVEL_1: 1 << 3, + HOUSE_BRAVERY: 1 << 6, + HOUSE_BRILLIANCE: 1 << 7, + HOUSE_BALANCE: 1 << 8, + EARLY_SUPPORTER: 1 << 9, + TEAM_USER: 1 << 10, + SYSTEM: 1 << 12, + BUGHUNTER_LEVEL_2: 1 << 14, + VERIFIED_BOT: 1 << 16, + EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17, + }; +} diff --git a/src/util/instanceOf.ts b/src/util/instanceOf.ts new file mode 100644 index 00000000..d83dc39c --- /dev/null +++ b/src/util/instanceOf.ts @@ -0,0 +1,121 @@ +// different version of lambert-server instanceOf with discord error format + +import { NextFunction, Request, Response } from "express"; +import { Tuple } from "lambert-server"; + +const OPTIONAL_PREFIX = "$"; + +export function check(schema: any) { + return (req: Request, res: Response, next: NextFunction) => { + try { + const result = instanceOf(schema, req.body, { path: "body" }); + if (result === true) return next(); + throw result; + } catch (error) { + return res.status(400).json({ code: 50035, message: "Invalid Form Body", success: false, errors: error }); + } + }; +} + +class FieldError extends Error { + constructor(public code: string, public message: string) { + super(message); + } +} + +export function instanceOf( + type: any, + value: any, + { path = "", optional = false, errors = {} }: { path?: string; optional?: boolean; errors?: any } = {} +): Boolean { + try { + if (!type) return true; // no type was specified + + if (value == null) { + if (optional) return true; + throw new FieldError("BASE_TYPE_REQUIRED", `This field is required`); + } + + switch (type) { + case String: + if (typeof value === "string") return true; + throw new FieldError("BASE_TYPE_STRING", `This field must be a string`); + case Number: + value = Number(value); + if (typeof value === "number" && !isNaN(value)) return true; + throw new FieldError("BASE_TYPE_NUMBER", `This field must be a number`); + case BigInt: + try { + value = BigInt(value); + if (typeof value === "bigint") return true; + } catch (error) {} + throw new FieldError("BASE_TYPE_BIGINT", `This field must be a bigint`); + case Boolean: + if (value == "true") value = true; + if (value == "false") value = false; + if (typeof value === "boolean") return true; + throw new FieldError("BASE_TYPE_BOOLEAN", `This field must be a boolean`); + } + + if (typeof type === "object") { + if (type?.constructor?.name != "Object") { + if (type instanceof Tuple) { + if ((type).types.some((x) => instanceOf(x, value, { path, optional, errors }))) return true; + throw new FieldError("BASE_TYPE_CHOICES", `This field must be one of (${type.types})`); + } + if (value instanceof type) return true; + throw new FieldError("BASE_TYPE_CLASS", `This field must be an instance of ${type}`); + } + if (typeof value !== "object") throw new FieldError("BASE_TYPE_OBJECT", `This field must be a object`); + + if (Array.isArray(type)) { + if (!Array.isArray(value)) throw new FieldError("BASE_TYPE_ARRAY", `This field must be an array`); + if (!type.length) return true; // type array didn't specify any type + + return ( + value.every((val, i) => { + errors[i] = {}; + return ( + instanceOf(type[0], val, { path: `${path}[${i}]`, optional, errors: errors[i] }) === true + ); + }) || errors + ); + } + + const diff = Object.keys(value).missing( + Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)) + ); + + if (diff.length) throw new FieldError("UNKOWN_FIELD", `Unkown key ${diff}`); + + return ( + Object.keys(type).every((key) => { + let newKey = key; + const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); + if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); + errors[key] = {}; + + return ( + instanceOf(type[key], value[newKey], { + path: `${path}.${newKey}`, + optional: OPTIONAL, + errors: errors[key], + }) === true + ); + }) || errors + ); + } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { + if (value === type) return true; + throw new FieldError("BASE_TYPE_CONSTANT", `This field must be ${value}`); + } else if (typeof type === "bigint") { + if (BigInt(value) === type) return true; + throw new FieldError("BASE_TYPE_CONSTANT", `This field must be ${value}`); + } + + return type == value; + } catch (error) { + let e = error as FieldError; + errors._errors = [{ message: e.message, code: e.code }]; + return errors; + } +} diff --git a/tsconfig.json b/tsconfig.json index 2f90c121..93f4a144 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,14 +4,14 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - "lib": ["ES2015", "DOM"] /* Specify library files to be included in the compilation. */, + "lib": ["ES2020"] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, "checkJs": true /* Report errors in .js files. */, // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ "declaration": true /* Generates corresponding '.d.ts' file. */, - "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, + "declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, "sourceMap": true /* Generates corresponding '.map' file. */, // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist/" /* Redirect output structure to the directory. */, @@ -27,7 +27,7 @@ /* Strict Type-Checking Options */ "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, - // "strictNullChecks": true, /* Enable strict null checks. */ + "strictNullChecks": true /* Enable strict null checks. */, // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ "strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */,