summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-02-05 22:01:01 +0100
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-02-05 22:01:01 +0100
commitea41892fef208b10c1bb322de7683c39ebab3dd5 (patch)
treef26559c73196591ac0338a21908a25c806276a7a /src/util
parentMove Models/Schemas to server-util (diff)
downloadserver-ea41892fef208b10c1bb322de7683c39ebab3dd5.tar.xz
:wrench: build
Diffstat (limited to 'src/util')
-rw-r--r--src/util/BitField.ts138
-rw-r--r--src/util/Config.ts30
-rw-r--r--src/util/Constants.ts3
-rw-r--r--src/util/Database.ts6
-rw-r--r--src/util/Intents.ts21
-rw-r--r--src/util/MessageFlags.ts14
-rw-r--r--src/util/Permissions.ts56
-rw-r--r--src/util/Snowflake.ts126
-rw-r--r--src/util/String.ts6
-rw-r--r--src/util/UserFlags.ts22
-rw-r--r--src/util/checkToken.ts13
11 files changed, 435 insertions, 0 deletions
diff --git a/src/util/BitField.ts b/src/util/BitField.ts
new file mode 100644
index 00000000..17eef796
--- /dev/null
+++ b/src/util/BitField.ts
@@ -0,0 +1,138 @@
+"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 | BigInt | BitField | string | BitFieldResolvable[];
+
+/**
+ * Data structure that makes it easy to interact with a bitfield.
+ */
+export class BitField {
+	public bitfield: bigint = BigInt(0);
+
+	public static FLAGS: Record<string, bigint>;
+
+	constructor(bits: BitFieldResolvable = 0) {
+		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)) !== 0n;
+	}
+
+	/**
+	 * 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));
+		const 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<BitField> {
+		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 = 0n;
+		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 = 0n;
+		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<string, boolean> = {};
+		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 = 0n): bigint {
+		if ((typeof bit === "number" || typeof bit === "bigint") && bit >= 0n) return BigInt(bit);
+		if (bit instanceof BitField) return bit.bitfield;
+		if (Array.isArray(bit))
+			return bit.map((p) => this.resolve(p)).reduce((prev, p) => BigInt(prev) | BigInt(p), 0n);
+		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..b22e88e0
--- /dev/null
+++ b/src/util/Config.ts
@@ -0,0 +1,30 @@
+import "missing-native-js-functions";
+import db from "./Database";
+import { ProviderCache } from "lambert-db";
+var Config: ProviderCache;
+
+export default {
+	init: async function init(opts: DefaultOptions = DefaultOptions) {
+		Config = db.data.config({}).cache();
+		await Config.init();
+		await Config.set(opts.merge(Config.cache || {}));
+	},
+	getAll: function get() {
+		return <DefaultOptions>Config.get();
+	},
+	setAll: function set(val: any) {
+		return Config.set(val);
+	},
+};
+
+export interface DefaultOptions {
+	api?: any;
+	gateway?: any;
+	voice?: any;
+}
+
+export const DefaultOptions: DefaultOptions = {
+	api: {},
+	gateway: {},
+	voice: {},
+};
diff --git a/src/util/Constants.ts b/src/util/Constants.ts
new file mode 100644
index 00000000..808d234b
--- /dev/null
+++ b/src/util/Constants.ts
@@ -0,0 +1,3 @@
+import { VerifyOptions } from "jsonwebtoken";
+
+export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
diff --git a/src/util/Database.ts b/src/util/Database.ts
new file mode 100644
index 00000000..d842ac6b
--- /dev/null
+++ b/src/util/Database.ts
@@ -0,0 +1,6 @@
+import { MongoDatabase } from "lambert-db";
+
+// TODO: load url from config
+const db = new MongoDatabase("mongodb://127.0.0.1:27017/lambert?readPreference=secondaryPreferred");
+
+export default db;
diff --git a/src/util/Intents.ts b/src/util/Intents.ts
new file mode 100644
index 00000000..b96f6af9
--- /dev/null
+++ b/src/util/Intents.ts
@@ -0,0 +1,21 @@
+import { BitField } from "./BitField";
+
+export class Intents extends BitField {
+	static FLAGS = {
+		GUILDS: 1n << 0n,
+		GUILD_MEMBERS: 1n << 1n,
+		GUILD_BANS: 1n << 2n,
+		GUILD_EMOJIS: 1n << 3n,
+		GUILD_INTEGRATIONS: 1n << 4n,
+		GUILD_WEBHOOKS: 1n << 5n,
+		GUILD_INVITES: 1n << 6n,
+		GUILD_VOICE_STATES: 1n << 7n,
+		GUILD_PRESENCES: 1n << 8n,
+		GUILD_MESSAGES: 1n << 9n,
+		GUILD_MESSAGE_REACTIONS: 1n << 10n,
+		GUILD_MESSAGE_TYPING: 1n << 11n,
+		DIRECT_MESSAGES: 1n << 12n,
+		DIRECT_MESSAGE_REACTIONS: 1n << 13n,
+		DIRECT_MESSAGE_TYPING: 1n << 14n,
+	};
+}
diff --git a/src/util/MessageFlags.ts b/src/util/MessageFlags.ts
new file mode 100644
index 00000000..d3e6a07a
--- /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: 1n << 0n,
+		IS_CROSSPOST: 1n << 1n,
+		SUPPRESS_EMBEDS: 1n << 2n,
+		SOURCE_MESSAGE_DELETED: 1n << 3n,
+		URGENT: 1n << 4n,
+	};
+}
diff --git a/src/util/Permissions.ts b/src/util/Permissions.ts
new file mode 100644
index 00000000..f076e0c2
--- /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: 1n << 0n,
+		KICK_MEMBERS: 1n << 1n,
+		BAN_MEMBERS: 1n << 2n,
+		ADMINISTRATOR: 1n << 3n,
+		MANAGE_CHANNELS: 1n << 4n,
+		MANAGE_GUILD: 1n << 5n,
+		ADD_REACTIONS: 1n << 6n,
+		VIEW_AUDIT_LOG: 1n << 7n,
+		PRIORITY_SPEAKER: 1n << 8n,
+		STREAM: 1n << 9n,
+		VIEW_CHANNEL: 1n << 10n,
+		SEND_MESSAGES: 1n << 11n,
+		SEND_TTS_MESSAGES: 1n << 12n,
+		MANAGE_MESSAGES: 1n << 13n,
+		EMBED_LINKS: 1n << 14n,
+		ATTACH_FILES: 1n << 15n,
+		READ_MESSAGE_HISTORY: 1n << 16n,
+		MENTION_EVERYONE: 1n << 17n,
+		USE_EXTERNAL_EMOJIS: 1n << 18n,
+		VIEW_GUILD_INSIGHTS: 1n << 19n,
+		CONNECT: 1n << 20n,
+		SPEAK: 1n << 21n,
+		MUTE_MEMBERS: 1n << 22n,
+		DEAFEN_MEMBERS: 1n << 23n,
+		MOVE_MEMBERS: 1n << 24n,
+		USE_VAD: 1n << 25n,
+		CHANGE_NICKNAME: 1n << 26n,
+		MANAGE_NICKNAMES: 1n << 27n,
+		MANAGE_ROLES: 1n << 28n,
+		MANAGE_WEBHOOKS: 1n << 29n,
+		MANAGE_EMOJIS: 1n << 30n,
+	};
+
+	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/util/Snowflake.ts b/src/util/Snowflake.ts
new file mode 100644
index 00000000..9e94bbd9
--- /dev/null
+++ b/src/util/Snowflake.ts
@@ -0,0 +1,126 @@
+// @ts-nocheck
+
+// 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)
+
+/**
+ * A container for useful snowflake-related methods.
+ */
+export class Snowflake {
+	static readonly EPOCH = 1420070400000;
+	static INCREMENT = 0n; // max 4095
+	static processId = 0n; // max 31
+	static workerId = 0n; // max 31
+
+	constructor() {
+		throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
+	}
+
+	/**
+	 * A Twitter snowflake, except the epoch is 2015-01-01T00:00:00.000Z
+	 * ```
+	 * If we have a snowflake '266241948824764416' we can represent it as binary:
+	 *
+	 * 64                                          22     17     12          0
+	 *  000000111011000111100001101001000101000000  00001  00000  000000000000
+	 *       number of ms since Discord epoch       worker  pid    increment
+	 * ```
+	 * @typedef {string} Snowflake
+	 */
+
+	/**
+	 * Transforms a snowflake from a decimal string to a bit string.
+	 * @param  {Snowflake} num Snowflake to be transformed
+	 * @returns {string}
+	 * @private
+	 */
+	static idToBinary(num) {
+		let bin = "";
+		let high = parseInt(num.slice(0, -10)) || 0;
+		let low = parseInt(num.slice(-10));
+		while (low > 0 || high > 0) {
+			bin = String(low & 1) + bin;
+			low = Math.floor(low / 2);
+			if (high > 0) {
+				low += 5000000000 * (high % 2);
+				high = Math.floor(high / 2);
+			}
+		}
+		return bin;
+	}
+
+	/**
+	 * Transforms a snowflake from a bit string to a decimal string.
+	 * @param  {string} num Bit string to be transformed
+	 * @returns {Snowflake}
+	 * @private
+	 */
+	static binaryToID(num) {
+		let dec = "";
+
+		while (num.length > 50) {
+			const high = parseInt(num.slice(0, -32), 2);
+			const low = parseInt((high % 10).toString(2) + num.slice(-32), 2);
+
+			dec = (low % 10).toString() + dec;
+			num =
+				Math.floor(high / 10).toString(2) +
+				Math.floor(low / 10)
+					.toString(2)
+					.padStart(32, "0");
+		}
+
+		num = parseInt(num, 2);
+		while (num > 0) {
+			dec = (num % 10).toString() + dec;
+			num = Math.floor(num / 10);
+		}
+
+		return dec;
+	}
+
+	static generate() {
+		var time = BigInt(Date.now() - Snowflake.EPOCH) << 22n;
+		var worker = Snowflake.workerId << 17n;
+		var process = Snowflake.processId << 12n;
+		var increment = Snowflake.INCREMENT++;
+		return time | worker | process | increment;
+	}
+
+	/**
+	 * A deconstructed snowflake.
+	 * @typedef {Object} DeconstructedSnowflake
+	 * @property {number} timestamp Timestamp the snowflake was created
+	 * @property {Date} date Date the snowflake was created
+	 * @property {number} workerID Worker ID in the snowflake
+	 * @property {number} processID Process ID in the snowflake
+	 * @property {number} increment Increment in the snowflake
+	 * @property {string} binary Binary representation of the snowflake
+	 */
+
+	/**
+	 * Deconstructs a Discord snowflake.
+	 * @param {Snowflake} snowflake Snowflake to deconstruct
+	 * @returns {DeconstructedSnowflake} Deconstructed snowflake
+	 */
+	static deconstruct(snowflake) {
+		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),
+			processID: parseInt(BINARY.substring(47, 52), 2),
+			increment: parseInt(BINARY.substring(52, 64), 2),
+			binary: BINARY,
+		};
+		Object.defineProperty(res, "date", {
+			get: function get() {
+				return new Date(this.timestamp);
+			},
+			enumerable: true,
+		});
+		return res;
+	}
+}
diff --git a/src/util/String.ts b/src/util/String.ts
new file mode 100644
index 00000000..afbfc1e6
--- /dev/null
+++ b/src/util/String.ts
@@ -0,0 +1,6 @@
+export const DOUBLE_WHITE_SPACE = /\s\s+/g;
+export const SPECIAL_CHAR = /[@#`:\r\n\t\f\v\p{C}]/gu;
+
+export function trimSpecial(str: string) {
+	return str.replace(SPECIAL_CHAR, "").replace(DOUBLE_WHITE_SPACE, " ").trim();
+}
diff --git a/src/util/UserFlags.ts b/src/util/UserFlags.ts
new file mode 100644
index 00000000..6e532f93
--- /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: 1n << 0n,
+		PARTNERED_SERVER_OWNER: 1n << 1n,
+		HYPESQUAD_EVENTS: 1n << 2n,
+		BUGHUNTER_LEVEL_1: 1n << 3n,
+		HOUSE_BRAVERY: 1n << 6n,
+		HOUSE_BRILLIANCE: 1n << 7n,
+		HOUSE_BALANCE: 1n << 8n,
+		EARLY_SUPPORTER: 1n << 9n,
+		TEAM_USER: 1n << 10n,
+		SYSTEM: 1n << 12n,
+		BUGHUNTER_LEVEL_2: 1n << 14n,
+		VERIFIED_BOT: 1n << 16n,
+		EARLY_VERIFIED_BOT_DEVELOPER: 1n << 17n,
+	};
+}
diff --git a/src/util/checkToken.ts b/src/util/checkToken.ts
new file mode 100644
index 00000000..96c7806a
--- /dev/null
+++ b/src/util/checkToken.ts
@@ -0,0 +1,13 @@
+import { JWTOptions } from "./Constants";
+import jwt from "jsonwebtoken";
+import Config from "./Config";
+
+export function checkToken(token: string) {
+	return new Promise((res, rej) => {
+		jwt.verify(token, Config.getAll().api.security.jwtSecret, JWTOptions, (err, decoded: any) => {
+			if (err || !decoded) return rej("Invalid Token");
+
+			return res(decoded);
+		});
+	});
+}