From ea41892fef208b10c1bb322de7683c39ebab3dd5 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 5 Feb 2021 22:01:01 +0100 Subject: :wrench: build --- src/util/BitField.ts | 138 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/Config.ts | 30 +++++++++++ src/util/Constants.ts | 3 ++ src/util/Database.ts | 6 +++ src/util/Intents.ts | 21 ++++++++ src/util/MessageFlags.ts | 14 +++++ src/util/Permissions.ts | 56 +++++++++++++++++++ src/util/Snowflake.ts | 126 +++++++++++++++++++++++++++++++++++++++++++ src/util/String.ts | 6 +++ src/util/UserFlags.ts | 22 ++++++++ src/util/checkToken.ts | 13 +++++ 11 files changed, 435 insertions(+) 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/Database.ts create mode 100644 src/util/Intents.ts create mode 100644 src/util/MessageFlags.ts create mode 100644 src/util/Permissions.ts create mode 100644 src/util/Snowflake.ts create mode 100644 src/util/String.ts create mode 100644 src/util/UserFlags.ts create mode 100644 src/util/checkToken.ts (limited to 'src/util') 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; + + 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 { + 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 = {}; + 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 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); + }); + }); +} -- cgit 1.4.1