From 84baae279e40e0a7370748d15c55131545526bda Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 21 Feb 2021 18:44:14 +0530 Subject: [PATCH 1/2] use bigint for bitfield (perms) --- src/types/permissionFlags.ts | 65 ++++++++++++++++++------------------ src/utils/bitfield.ts | 61 ++++++++++++++++++--------------- src/utils/permissions.ts | 19 +++++------ 3 files changed, 75 insertions(+), 70 deletions(-) diff --git a/src/types/permissionFlags.ts b/src/types/permissionFlags.ts index 9cb3040..928a16f 100644 --- a/src/types/permissionFlags.ts +++ b/src/types/permissionFlags.ts @@ -1,35 +1,36 @@ // https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags -export const PermissionFlags: { [key: string]: number } = { - 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 +export const PermissionFlags: { [key: string]: bigint } = { + 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, + USE_SLASH_COMMANDS: 1n << 31n } diff --git a/src/utils/bitfield.ts b/src/utils/bitfield.ts index eb7a67c..8631f1f 100644 --- a/src/utils/bitfield.ts +++ b/src/utils/bitfield.ts @@ -6,34 +6,36 @@ export type BitFieldResolvable = | string | string[] | BitField[] + | bigint + | Array /** Bit Field utility to work with Bits and Flags */ export class BitField { - flags: { [name: string]: number } = {} - bitfield: number + #flags: { [name: string]: number | bigint } = {} + bitfield: bigint - constructor(flags: { [name: string]: number }, bits: any) { - this.flags = flags - this.bitfield = BitField.resolve(this.flags, bits) + constructor(flags: { [name: string]: number | bigint }, bits: any) { + this.#flags = flags + this.bitfield = BitField.resolve(this.#flags, bits) } any(bit: BitFieldResolvable): boolean { - return (this.bitfield & BitField.resolve(this.flags, bit)) !== 0 + return (this.bitfield & BitField.resolve(this.#flags, bit)) !== 0n } equals(bit: BitFieldResolvable): boolean { - return this.bitfield === BitField.resolve(this.flags, bit) + return this.bitfield === BitField.resolve(this.#flags, bit) } has(bit: BitFieldResolvable, ...args: any[]): boolean { if (Array.isArray(bit)) return (bit.every as any)((p: any) => this.has(p)) - bit = BitField.resolve(this.flags, bit) + bit = BitField.resolve(this.#flags, bit) return (this.bitfield & bit) === bit } missing(bits: any, ...hasParams: any[]): string[] { if (!Array.isArray(bits)) - bits = new BitField(this.flags, bits).toArray(false) + bits = new BitField(this.#flags, bits).toArray(false) return bits.filter((p: any) => !this.has(p, ...hasParams)) } @@ -42,48 +44,52 @@ export class BitField { } add(...bits: BitFieldResolvable[]): BitField { - let total = 0 + let total = 0n for (const bit of bits) { - total |= BitField.resolve(this.flags, bit) + total |= BitField.resolve(this.#flags, bit) } if (Object.isFrozen(this)) - return new BitField(this.flags, this.bitfield | total) + return new BitField(this.#flags, this.bitfield | total) this.bitfield |= total return this } remove(...bits: BitFieldResolvable[]): BitField { - let total = 0 + let total = 0n for (const bit of bits) { - total |= BitField.resolve(this.flags, bit) + total |= BitField.resolve(this.#flags, bit) } if (Object.isFrozen(this)) - return new BitField(this.flags, this.bitfield & ~total) + return new BitField(this.#flags, this.bitfield & ~total) this.bitfield &= ~total return this } - serialize(...hasParams: any[]): { [key: string]: any } { - const serialized: { [key: string]: any } = {} - for (const [flag, bit] of Object.entries(this.flags)) + flags(): { [name: string]: bigint | number } { + return this.#flags + } + + serialize(...hasParams: any[]): { [key: string]: boolean } { + const serialized: { [key: string]: boolean } = {} + for (const [flag, bit] of Object.entries(this.#flags)) serialized[flag] = this.has( - BitField.resolve(this.flags, bit), + BitField.resolve(this.#flags, bit), ...hasParams ) return serialized } toArray(...hasParams: any[]): string[] { - return Object.keys(this.flags).filter((bit) => - this.has(BitField.resolve(this.flags, bit), ...hasParams) + return Object.keys(this.#flags).filter((bit) => + this.has(BitField.resolve(this.#flags, bit), ...hasParams) ) } - toJSON(): number { - return this.bitfield + toJSON(): string { + return this.bitfield.toString() } - valueOf(): number { + valueOf(): bigint { return this.bitfield } @@ -91,9 +97,10 @@ export class BitField { yield* this.toArray() } - static resolve(flags: any, bit: BitFieldResolvable = 0): number { - if (typeof bit === 'string' && !isNaN(parseInt(bit))) return parseInt(bit) - if (typeof bit === 'number' && bit >= 0) return bit + static resolve(flags: any, bit: BitFieldResolvable = 0n): bigint { + if (typeof bit === 'bigint') return bit + if (typeof bit === 'string' && !isNaN(parseInt(bit))) return BigInt(bit) + if (typeof bit === 'number' && bit >= 0) return BigInt(bit) if (bit instanceof BitField) return this.resolve(flags, bit.bitfield) if (Array.isArray(bit)) return (bit.map as any)((p: any) => this.resolve(flags, p)).reduce( diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 03b4b82..8d7a488 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -2,18 +2,15 @@ import { PermissionFlags } from '../types/permissionFlags.ts' import { BitField, BitFieldResolvable } from './bitfield.ts' -export type PermissionResolvable = - | string - | string[] - | number - | number[] - | Permissions - | PermissionResolvable[] +export type PermissionResolvable = BitFieldResolvable /** Manages Discord's Bit-based Permissions */ export class Permissions extends BitField { - static DEFAULT = 104324673 - static ALL = Object.values(PermissionFlags).reduce((all, p) => all | p, 0) + static DEFAULT = 104324673n + static ALL = Object.values(PermissionFlags).reduce( + (all, p) => BigInt(all) | BigInt(p), + 0n + ) constructor(bits: BitFieldResolvable) { super(PermissionFlags, bits) @@ -21,14 +18,14 @@ export class Permissions extends BitField { any(permission: PermissionResolvable, checkAdmin = true): boolean { return ( - (checkAdmin && super.has(this.flags.ADMINISTRATOR)) || + (checkAdmin && super.has(this.flags().ADMINISTRATOR)) || super.any(permission as any) ) } has(permission: PermissionResolvable, checkAdmin = true): boolean { return ( - (checkAdmin && super.has(this.flags.ADMINISTRATOR)) || + (checkAdmin && super.has(this.flags().ADMINISTRATOR)) || super.has(permission as any) ) } From 2d6f3d46f3af21f9cf881174eaa51e02a3e05e81 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 21 Feb 2021 18:48:18 +0530 Subject: [PATCH 2/2] add commands loader --- deps.ts | 2 + src/models/command.ts | 92 ++++++++++++++++++++++++++++++++++++- src/models/commandClient.ts | 27 ++++------- src/utils/mixedPromise.ts | 3 -- 4 files changed, 103 insertions(+), 21 deletions(-) delete mode 100644 src/utils/mixedPromise.ts diff --git a/deps.ts b/deps.ts index 7a04e52..6656d75 100644 --- a/deps.ts +++ b/deps.ts @@ -7,3 +7,5 @@ export type { Redis, RedisConnectOptions } from 'https://deno.land/x/redis@v0.14.1/mod.ts' +export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts' +export { join } from 'https://deno.land/std@0.86.0/path/mod.ts' diff --git a/src/models/command.ts b/src/models/command.ts index cca21c1..05e59ff 100644 --- a/src/models/command.ts +++ b/src/models/command.ts @@ -5,7 +5,7 @@ import { User } from '../structures/user.ts' import { Collection } from '../utils/collection.ts' import { CommandClient } from './commandClient.ts' import { Extension } from './extensions.ts' -import { parse } from '../../deps.ts' +import { join, parse, walk } from '../../deps.ts' export interface CommandContext { /** The Client object */ @@ -284,13 +284,103 @@ export class CommandBuilder extends Command { } } +export class CommandsLoader { + client: CommandClient + #importSeq: { [name: string]: number } = {} + + constructor(client: CommandClient) { + this.client = client + } + + /** + * Load a Command from file. + * + * @param filePath Path of Command file. + * @param exportName Export name. Default is the "default" export. + */ + async load( + filePath: string, + exportName: string = 'default', + onlyRead?: boolean + ): Promise { + const stat = await Deno.stat(filePath).catch(() => undefined) + if (stat === undefined || stat.isFile !== true) + throw new Error(`File not found on path ${filePath}`) + + let seq: number | undefined + + if (this.#importSeq[filePath] !== undefined) seq = this.#importSeq[filePath] + const mod = await import( + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + 'file:///' + + join(Deno.cwd(), filePath) + + (seq === undefined ? '' : `#${seq}`) + ) + if (this.#importSeq[filePath] === undefined) this.#importSeq[filePath] = 0 + else this.#importSeq[filePath]++ + + const Cmd = mod[exportName] + if (Cmd === undefined) + throw new Error(`Command not exported as ${exportName} from ${filePath}`) + + let cmd: Command + try { + if (Cmd instanceof Command) cmd = Cmd + else cmd = new Cmd() + if (!(cmd instanceof Command)) throw new Error('failed') + } catch (e) { + throw new Error(`Failed to load Command from ${filePath}`) + } + + if (onlyRead !== true) this.client.commands.add(cmd) + return cmd + } + + /** + * Load commands from a Directory. + * + * @param path Path of the directory. + * @param options Options to configure loading. + */ + async loadDirectory( + path: string, + options?: { + recursive?: boolean + exportName?: string + maxDepth?: number + exts?: string[] + onlyRead?: boolean + } + ): Promise { + const commands: Command[] = [] + + for await (const entry of walk(path, { + maxDepth: options?.maxDepth, + exts: options?.exts, + includeDirs: false + })) { + if (entry.isFile !== true) continue + const cmd = await this.load( + entry.path, + options?.exportName, + options?.onlyRead + ) + commands.push(cmd) + } + + return commands + } +} + export class CommandsManager { client: CommandClient list: Collection = new Collection() disabled: Set = new Set() + loader: CommandsLoader constructor(client: CommandClient) { this.client = client + this.loader = new CommandsLoader(client) } /** Number of loaded Commands */ diff --git a/src/models/commandClient.ts b/src/models/commandClient.ts index 94d01ba..67d121d 100644 --- a/src/models/commandClient.ts +++ b/src/models/commandClient.ts @@ -1,6 +1,5 @@ import { Message } from '../structures/message.ts' import { GuildTextChannel } from '../structures/textChannel.ts' -import { awaitSync } from '../utils/mixedPromise.ts' import { Client, ClientOptions } from './client.ts' import { CategoriesManager, @@ -129,35 +128,29 @@ export class CommandClient extends Client implements CommandClientOptions { async processMessage(msg: Message): Promise { if (!this.allowBots && msg.author.bot === true) return - const isUserBlacklisted = await awaitSync( - this.isUserBlacklisted(msg.author.id) - ) - if (isUserBlacklisted === true) return + const isUserBlacklisted = await this.isUserBlacklisted(msg.author.id) + if (isUserBlacklisted) return - const isChannelBlacklisted = await awaitSync( - this.isChannelBlacklisted(msg.channel.id) - ) - if (isChannelBlacklisted === true) return + const isChannelBlacklisted = await this.isChannelBlacklisted(msg.channel.id) + if (isChannelBlacklisted) return if (msg.guild !== undefined) { - const isGuildBlacklisted = await awaitSync( - this.isGuildBlacklisted(msg.guild.id) - ) - if (isGuildBlacklisted === true) return + const isGuildBlacklisted = await this.isGuildBlacklisted(msg.guild.id) + if (isGuildBlacklisted) return } let prefix: string | string[] = [] if (typeof this.prefix === 'string') prefix = [...prefix, this.prefix] else prefix = [...prefix, ...this.prefix] - const userPrefix = await awaitSync(this.getUserPrefix(msg.author.id)) + const userPrefix = await this.getUserPrefix(msg.author.id) if (userPrefix !== undefined) { if (typeof userPrefix === 'string') prefix = [...prefix, userPrefix] else prefix = [...prefix, ...userPrefix] } if (msg.guild !== undefined) { - const guildPrefix = await awaitSync(this.getGuildPrefix(msg.guild.id)) + const guildPrefix = await this.getGuildPrefix(msg.guild.id) if (guildPrefix !== undefined) { if (typeof guildPrefix === 'string') prefix = [...prefix, guildPrefix] else prefix = [...prefix, ...guildPrefix] @@ -361,10 +354,10 @@ export class CommandClient extends Client implements CommandClientOptions { try { this.emit('commandUsed', ctx) - const beforeExecute = await awaitSync(command.beforeExecute(ctx)) + const beforeExecute = await command.beforeExecute(ctx) if (beforeExecute === false) return - const result = await awaitSync(command.execute(ctx)) + const result = await command.execute(ctx) command.afterExecute(ctx, result) } catch (e) { this.emit('commandError', ctx, e) diff --git a/src/utils/mixedPromise.ts b/src/utils/mixedPromise.ts deleted file mode 100644 index 4f1b297..0000000 --- a/src/utils/mixedPromise.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const awaitSync = async (val: any | Promise): Promise => { - return val instanceof Promise ? await val : val -}