diff --git a/.prettierrc b/.prettierrc index 2f31668..0c67073 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,5 @@ "trailingComma": "all", "singleQuote": true, "printWidth": 120, - "tabWidth": 4 + "tabWidth": 2 } diff --git a/package-lock.json b/package-lock.json index 4c10a54..fe6d367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "cz-conventional-changelog": "^3.0.1", "jest": "^27.5.1", "standard-version": "^9.3.2", - "typedoc": "^0.22.11", + "typedoc": "^0.22.14", "typescript": "^4.5.5" } }, @@ -8494,16 +8494,16 @@ } }, "node_modules/typedoc": { - "version": "0.22.11", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz", - "integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==", + "version": "0.22.14", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.14.tgz", + "integrity": "sha512-tlf9wIcsrnQSjetStrnRutuy2RjZkG5PK2umwveZLTkuC2K9VywOZTdu2G19BdOPzGrhZjf9WK7pthXqnFQejg==", "dev": true, "dependencies": { "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^4.0.10", - "minimatch": "^3.0.4", - "shiki": "^0.10.0" + "marked": "^4.0.12", + "minimatch": "^5.0.1", + "shiki": "^0.10.1" }, "bin": { "typedoc": "bin/typedoc" @@ -8512,7 +8512,28 @@ "node": ">= 12.10.0" }, "peerDependencies": { - "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x" + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/typescript": { @@ -15296,16 +15317,36 @@ } }, "typedoc": { - "version": "0.22.11", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz", - "integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==", + "version": "0.22.14", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.14.tgz", + "integrity": "sha512-tlf9wIcsrnQSjetStrnRutuy2RjZkG5PK2umwveZLTkuC2K9VywOZTdu2G19BdOPzGrhZjf9WK7pthXqnFQejg==", "dev": true, "requires": { "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^4.0.10", - "minimatch": "^3.0.4", - "shiki": "^0.10.0" + "marked": "^4.0.12", + "minimatch": "^5.0.1", + "shiki": "^0.10.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "typescript": { diff --git a/package.json b/package.json index ba3c002..fb2f5b0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "description": "", "main": "dist/index.js", - "scripts": { + "scripts": { "compile": "tsc", "watch": "tsc -w", "lint": "eslint src/**/*.ts", @@ -34,7 +34,7 @@ "cz-conventional-changelog": "^3.0.1", "jest": "^27.5.1", "standard-version": "^9.3.2", - "typedoc": "^0.22.11", + "typedoc": "^0.22.14", "typescript": "^4.5.5" }, "config": { diff --git a/src/handler/events/interactionCreate.ts b/src/handler/events/interactionCreate.ts index e44152c..e463ce7 100644 --- a/src/handler/events/interactionCreate.ts +++ b/src/handler/events/interactionCreate.ts @@ -1,10 +1,11 @@ - import type { Interaction } from 'discord.js'; +import type Wrapper from '../structures/wrapper'; + +import * as Files from '../utilities/readFile'; +import Context from '../structures/context'; + import { fromEvent, Observable, of, concatMap } from 'rxjs'; import { CommandType } from '../sern'; -import Context from '../structures/context'; -import type Wrapper from '../structures/wrapper'; -import * as Files from '../utilities/readFile'; import { filterTap } from './observableHandling'; import { filter } from 'rxjs'; diff --git a/src/handler/events/messageEvent.ts b/src/handler/events/messageEvent.ts index d2ee6b6..925b3f4 100644 --- a/src/handler/events/messageEvent.ts +++ b/src/handler/events/messageEvent.ts @@ -1,39 +1,43 @@ -import type { ChatInputCommandInteraction, Message } from 'discord.js'; -import { fromEvent, Observable, of, concatMap } from 'rxjs'; -import { CommandType } from '../sern'; -import Context from '../structures/context'; +import type { Message } from 'discord.js'; import type Wrapper from '../structures/wrapper'; -import { fmt } from '../utilities/messageHelpers'; + +import { fromEvent, Observable, of, concatMap } from 'rxjs'; + +import Context from '../structures/context'; import * as Files from '../utilities/readFile'; + +import { fmt } from '../utilities/messageHelpers'; +import { CommandType } from '../sern'; import { filterTap, ignoreNonBot } from './observableHandling'; -export const onMessageCreate = (wrapper : Wrapper) => { - const { client, defaultPrefix } = wrapper; - (> fromEvent( client, 'messageCreate')) - .pipe ( - ignoreNonBot(defaultPrefix), - concatMap ( m => { - const [ prefix, ...data ] = fmt(m, defaultPrefix); +export const onMessageCreate = (wrapper: Wrapper) => { + const { client, defaultPrefix } = wrapper; + + (>fromEvent(client, 'messageCreate')) + .pipe( + ignoreNonBot(defaultPrefix), + concatMap(m => { + const [prefix, ...data] = fmt({ msg: m, prefix: defaultPrefix }); const posMod = Files.Commands.get(prefix) ?? Files.Alias.get(prefix); - return of( posMod ) - .pipe ( - filterTap(CommandType.TEXT, mod => { - const ctx = Context.wrap(m); - mod.execute(ctx, ['text', data]); - }) - ); - }) - ).subscribe ({ - error(e) { - //log things - throw e; - }, - next(command) { - //log on each command emitted - console.log(command); - }, - }); - - + return of(posMod) + .pipe( + filterTap(CommandType.TEXT, mod => { + const ctx = Context.wrap(m); + mod.execute(ctx, ['text', data]); + }) + ); + }) + ).subscribe({ + error(e) { + // Log the errors + + throw e; + }, + next(command) { + // Log on each command emitted + + console.log(command); + }, + }); }; diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index d5892b8..93d3381 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -1,63 +1,65 @@ import type { Awaitable, Message } from 'discord.js'; + import type { CommandType } from '../sern'; import type { Module } from '../structures/structxports'; -import { Observable, throwError } from 'rxjs'; import type { ModuleDefs } from '../structures/modules/commands/moduleHandler'; -import { SernError } from '../structures/errors'; -import { isNotFromBot, isNotFromDM } from '../utilities/messageHelpers'; -export function match(mod: Module | undefined, type : CommandType) : boolean { +import { Observable, throwError } from 'rxjs'; +import { SernError } from '../structures/errors'; +import { isFromBot, isFromDM } from '../utilities/messageHelpers'; + +export function match(mod: Module | undefined, type: CommandType): boolean { return mod !== undefined && (mod.type & type) != 0; } export function filterTap( - cmdType : T, - tap: (mod : ModuleDefs[T]) => Awaitable + cmdType: T, + tap: (mod: ModuleDefs[T]) => Awaitable ) { - return (src : Observable) => - new Observable( subscriber => { - return src.subscribe({ + return (src: Observable) => + new Observable(subscriber => { + return src.subscribe({ next(modul) { - if(match(modul, cmdType)) { - const asModT = modul; - tap(asModT); - subscriber.next(asModT); + if (match(modul, cmdType)) { + const asModT = modul; + tap(asModT); + subscriber.next(asModT); } else { - if (modul === undefined) { - return throwError(() => SernError.UNDEFINED_MODULE); - } - return throwError(() => SernError.MISMATCH_MODULE_TYPE); + if (modul === undefined) { + return throwError(() => SernError.UNDEFINED_MODULE); + } + return throwError(() => SernError.MISMATCH_MODULE_TYPE); } }, - error: (e) => subscriber.error(e), + error: (e) => subscriber.error(e), complete: () => subscriber.complete() }); }); - } +} -export function ignoreNonBot(prefix : string) { - return (src : Observable) => +export function ignoreNonBot(prefix: string) { + return (src: Observable) => new Observable(subscriber => { - return src.subscribe({ - next(m) { - const passAll = [ - isNotFromDM, - isNotFromBot, - (m : Message) => - m.content - .slice(0,prefix.length) - .localeCompare(prefix, - undefined, { sensitivity : 'accent' } - ) === 0 - ].every( fn => fn(m)); + return src.subscribe({ + next(m) { + const passAll = [ + !isFromDM, + !isFromBot, + (m: Message) => + m.content + .slice(0, prefix.length) + .localeCompare(prefix, + undefined, { sensitivity: 'accent' } + ) === 0 + ].every(fn => fn(m)); - if (passAll) { - subscriber.next(m); - } - }, - error: (e) => subscriber.error(e), - complete: () => subscriber.complete() + if (passAll) { + subscriber.next(m); + } + }, + error: (e) => subscriber.error(e), + complete: () => subscriber.complete() }); - }); + }); } diff --git a/src/handler/events/readyEvent.ts b/src/handler/events/readyEvent.ts index b389f94..2593cbe 100644 --- a/src/handler/events/readyEvent.ts +++ b/src/handler/events/readyEvent.ts @@ -1,61 +1,62 @@ -import { first, from, fromEvent } from 'rxjs'; -import { basename } from 'path'; -import * as Files from '../utilities/readFile'; import type Wrapper from '../structures/wrapper'; import type { Module } from '../structures/structxports'; import type { HandlerCallback, ModuleHandlers, ModuleStates, ModuleType } from '../structures/modules/commands/moduleHandler'; + +import * as Files from '../utilities/readFile'; +import { first, from, fromEvent } from 'rxjs'; +import { basename } from 'path'; import { CommandType } from '../sern'; -export const onReady = ( wrapper : Wrapper ) => { +export const onReady = (wrapper: Wrapper) => { const { client, init, commands } = wrapper; fromEvent(client, 'ready') - .pipe(first()) - .subscribe(() => { - init?.( wrapper ); - Files.buildData( commands ) - .then( createCommandCache ); - }) + .pipe(first()) + .subscribe(() => { + init?.(wrapper); + Files.buildData(commands) + .then(createCommandCache); + }) }; // Refactor : ? Possibly repetitive and verbose. -const handler = ( name : string ) => - ({ - [CommandType.TEXT] : mod => { - mod.alias.forEach ( a => Files.Alias.set(a,mod)); - Files.Commands.set( name, mod ); - }, - [CommandType.SLASH]: mod => { - Files.Commands.set( name , mod); - }, - [CommandType.BOTH] : mod => { - Files.Commands.set ( name, mod); - mod.alias.forEach (a => Files.Alias.set(a, mod)); - }, - [CommandType.MENU_USER] : mod => { - Files.ContextMenuUser.set ( name, mod ); - }, - [CommandType.MENU_MSG] : mod => { - Files.ContextMenuMsg.set (name, mod ); - }, - [CommandType.BUTTON] : mod => { - Files.Buttons.set(name, mod); - }, - [CommandType.MENU_SELECT] : mod => { - Files.SelectMenus.set(name, mod); - } +const handler = (name: string) => +({ + [CommandType.TEXT]: mod => { + mod.alias.forEach(a => Files.Alias.set(a, mod)); + Files.Commands.set(name, mod); + }, + [CommandType.SLASH]: mod => { + Files.Commands.set(name, mod); + }, + [CommandType.BOTH]: mod => { + Files.Commands.set(name, mod); + mod.alias.forEach(a => Files.Alias.set(a, mod)); + }, + [CommandType.MENU_USER]: mod => { + Files.ContextMenuUser.set(name, mod); + }, + [CommandType.MENU_MSG]: mod => { + Files.ContextMenuMsg.set(name, mod); + }, + [CommandType.BUTTON]: mod => { + Files.Buttons.set(name, mod); + }, + [CommandType.MENU_SELECT]: mod => { + Files.SelectMenus.set(name, mod); + } - } as ModuleHandlers); +} as ModuleHandlers); -const registerModules = (name : string, mod : ModuleStates[T]) => - (> handler(name)[mod.type])(mod); +const registerModules = (name: string, mod: ModuleStates[T]) => + (>handler(name)[mod.type])(mod); -function setCommands ( { mod, absPath } : { mod : Module, absPath : string } ) { - const name = mod.name ?? Files.fmtFileName(basename(absPath)); - registerModules(name, mod); +function setCommands({ mod, absPath }: { mod: Module, absPath: string }) { + const name = mod.name ?? Files.fmtFileName(basename(absPath)); + registerModules(name, mod); } -function createCommandCache( - arr: {mod: Module, absPath: string}[] - ) { - from(arr).subscribe ( setCommands ); +function createCommandCache( + arr: { mod: Module, absPath: string }[] +) { + from(arr).subscribe(setCommands); } diff --git a/src/handler/logger.ts b/src/handler/logger.ts index aff768d..25901d7 100644 --- a/src/handler/logger.ts +++ b/src/handler/logger.ts @@ -1,34 +1,34 @@ - export enum sEvent { - GLOBAL_SLASH, - LOCAL_SLASH, - MISUSE_CMD, - DM, - CRASH, - TEXT_CMD, + GLOBAL_SLASH, + LOCAL_SLASH, + MISUSE_CMD, + CRASH, + TEXT_CMD, + DM, } export default class Logger { - public clear() { - console.clear(); - } + public clear() { + console.clear(); + } - public log(e: T, guildId: string, message: string) { - // add colored logging? - console.log(`[${new Date().toISOString()}] [${sEvent[e]}] @ ${guildId} :: ${message}`); - } + public log(e: T, guildId: string, message: string) { + // TODO: Add colored logging + console.log(`[${new Date().toISOString()}] [${sEvent[e]}] @ ${guildId} :: ${message}`); + } - /** - * Utilizes console.table() to print out memory usage of current process. - * Optional at startup. - */ - public tableRam() { - console.table( - Object.entries(process.memoryUsage()) - .map(([k, v]: [string, number]) => { - return { [k]: `${(((Math.round(v) / 1024 / 1024) * 100) / 100).toFixed(2)} MBs` }; - }) - .reduce((r, c) => Object.assign(r, c), {}), - ); - } + /** + * Utilizes console.table() to print out memory usage of current process. + * Optional at startup. + */ + + public tableRam() { + console.table( + Object.entries(process.memoryUsage()) + .map(([k, v]: [string, number]) => { + return { [k]: `${(((Math.round(v) / 1024 / 1024) * 100) / 100).toFixed(2)} MBs` }; + }) + .reduce((r, c) => Object.assign(r, c), {}), + ); + } } diff --git a/src/handler/sern.ts b/src/handler/sern.ts index c9ed8ad..1d7e560 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -1,45 +1,41 @@ -import type { - DiscordEvent, -} from '../types/handler'; - -import type { - Client, -} from 'discord.js'; - +import type { DiscordEvent, } from '../types/handler'; +import type { Client } from 'discord.js'; import type Wrapper from './structures/wrapper'; + import { fromEvent } from 'rxjs'; import { SernError } from './structures/errors'; import { onReady } from './events/readyEvent'; import { onMessageCreate } from './events/messageEvent'; import { onInteractionCreate } from './events/interactionCreate'; -export function init( wrapper : Wrapper ) { - const { events, client } = wrapper; - if (events !== undefined) eventObserver(client, events); - onReady( wrapper ); - onMessageCreate( wrapper ); - onInteractionCreate ( wrapper ); +export function init(wrapper: Wrapper) { + const { events, client } = wrapper; + if (events !== undefined) eventObserver(client, events); + + onReady(wrapper); + onMessageCreate(wrapper); + onInteractionCreate(wrapper); } -function eventObserver(client: Client, events: DiscordEvent[] ) { - events.forEach( ( [event, cb] ) => { - if (event === 'ready') throw Error(SernError.RESERVED_EVENT); - fromEvent(client, event, cb).subscribe(); +function eventObserver(client: Client, events: DiscordEvent[]) { + events.forEach(([event, cb]) => { + if (event === 'ready') throw Error(SernError.RESERVED_EVENT); + fromEvent(client, event, cb).subscribe(); }); } - /** * @enum { number }; */ + export enum CommandType { - TEXT = 0b000001, - SLASH = 0b000010, - MENU_USER = 0b000100, - MENU_MSG = 0b001000, - BUTTON = 0b010000, - MENU_SELECT= 0b100000, - BOTH = 0b000011, - ANY = 0b111111 + TEXT = 0b000001, + SLASH = 0b000010, + MENU_USER = 0b000100, + MENU_MSG = 0b001000, + BUTTON = 0b010000, + MENU_SELECT = 0b100000, + BOTH = 0b000011, + ANY = 0b111111, } diff --git a/src/handler/structures/context.ts b/src/handler/structures/context.ts index b95ae3e..fdbe705 100644 --- a/src/handler/structures/context.ts +++ b/src/handler/structures/context.ts @@ -1,121 +1,108 @@ import type { - Awaitable, - ChatInputCommandInteraction, - Guild, - GuildMember, - Message, - Snowflake, - TextBasedChannel, - User + Awaitable, + ChatInputCommandInteraction, + Guild, + GuildMember, + Message, + Snowflake, + TextBasedChannel, + User, } from 'discord.js'; import { None, Option, Some } from 'ts-results'; import type { Nullish } from '../../types/handler'; -function firstSome(...args : Option[]) : Nullish { - for ( const op of args ) { - if (op.some) return op.val; - } - return null; + +function firstSome(...args: Option[]): Nullish { + for (const op of args) { + if (op.some) return op.val; + } + return null; } + export default class Context { + private constructor( + private oMsg: Option = None, + private oInterac: Option = None, + ) { + this.oMsg = oMsg; + this.oInterac = oInterac; + } + static wrap(wrappable: ChatInputCommandInteraction | Message): Context { + if ('token' in wrappable) { + return new Context(None, Some(wrappable)); + } + return new Context(Some(wrappable), None); + } + public isEmpty() { + return this.oMsg.none && this.oInterac.none; + } + public get message() { + return this.oMsg.unwrap(); + } + public get interaction() { + return this.oInterac.unwrap(); + } - private constructor( - private oMsg: Option = None, - private oInterac: Option = None - ) { - this.oMsg = oMsg; - this.oInterac = oInterac; - } - static wrap( - wrappable: ChatInputCommandInteraction|Message - ) : Context { - if ( 'token' in wrappable ) { - return new Context( None, Some(wrappable)); - } - return new Context(Some(wrappable), None); - } - public isEmpty() { - return this.oMsg.none && this.oInterac.none; - } - public get message() { - return this.oMsg.unwrap(); - } - public get interaction() { - return this.oInterac.unwrap(); - } + public get id(): Snowflake { + return firstSome( + this.oInterac.map((i) => i.id), + this.oMsg.map((m) => m.id), + )!; + } + public get channel(): Nullish { + return firstSome( + this.oMsg.map((m) => m.channel), + this.oInterac.map((i) => i.channel), + ); + } + public get user(): User { + return firstSome( + this.oMsg.map((m) => m.author), + this.oInterac.map((i) => i.user), + )!; + } + public get createdTimestamp(): number { + return firstSome( + this.oMsg.map((m) => m.createdTimestamp), + this.oInterac.map((i) => i.createdTimestamp), + )!; + } - public get id() : Snowflake { - return firstSome( - this.oInterac.map( i => i.id), - this.oMsg.map(m => m.id) - )!; - } - public get channel() : Nullish { - return firstSome( - this.oMsg.map(m => m.channel), - this.oInterac.map(i => i.channel) - ); - } - public get user(): User { - return firstSome( - this.oMsg.map(m => m.author), - this.oInterac.map(i => i.user) - )!; - } - public get createdTimestamp() : number { - return firstSome( - this.oMsg.map(m => m.createdTimestamp), - this.oInterac.map(i => i.createdTimestamp) - )!; - } - - public get guild() : Guild { - return firstSome( - this.oMsg.map(m => m.guild!), - this.oInterac.map(i => i.guild) - )!; - } - public get guildId() : Snowflake { - return firstSome( - this.oMsg.map(m => m.guildId), - this.oInterac.map(i => i.guildId) - )!; - } - public get member() : Nullish { - return firstSome( - this.oMsg.andThen(m => Some(m.member!)), - this.oInterac.andThen(i => i.inCachedGuild() ? Some(i.member) : None) - ); - } - /* - * Returns the underlying Context but allows for doing other operations - */ - public onInteraction( - onInteraction : ( interaction : ChatInputCommandInteraction ) => Awaitable, - ): Context { - this.oInterac.map(onInteraction); - return this; - } - public onMessage( - onMessage : ( message : Message ) => Awaitable - ): Context { - this.oMsg.map( onMessage ); - return this; - } - public takeInteractionValue( - extract : (interaction : ChatInputCommandInteraction) => T - ): Nullish { - if(this.oInterac.none) return null; - return extract(this.oInterac.val); - } - public takeMessageValue( - extract : (message: Message) => T - ): Nullish { - if(this.oMsg.none) return null; - return extract(this.oMsg.val); - } - + public get guild(): Guild { + return firstSome( + this.oMsg.map((m) => m.guild!), + this.oInterac.map((i) => i.guild), + )!; + } + public get guildId(): Snowflake { + return firstSome( + this.oMsg.map((m) => m.guildId), + this.oInterac.map((i) => i.guildId), + )!; + } + public get member(): Nullish { + return firstSome( + this.oMsg.andThen((m) => Some(m.member!)), + this.oInterac.andThen((i) => (i.inCachedGuild() ? Some(i.member) : None)), + ); + } + /* + * Returns the underlying Context but allows for doing other operations + */ + public onInteraction(onInteraction: (interaction: ChatInputCommandInteraction) => Awaitable): Context { + this.oInterac.map(onInteraction); + return this; + } + public onMessage(onMessage: (message: Message) => Awaitable): Context { + this.oMsg.map(onMessage); + return this; + } + public takeInteractionValue(extract: (interaction: ChatInputCommandInteraction) => T): Nullish { + if (this.oInterac.none) return null; + return extract(this.oInterac.val); + } + public takeMessageValue(extract: (message: Message) => T): Nullish { + if (this.oMsg.none) return null; + return extract(this.oMsg.val); + } } - - - diff --git a/src/handler/structures/errors.ts b/src/handler/structures/errors.ts index c29128e..3a6c255 100644 --- a/src/handler/structures/errors.ts +++ b/src/handler/structures/errors.ts @@ -1,7 +1,7 @@ export enum SernError { - RESERVED_EVENT = 'Cannot register the reserved ready event. Please use the init property.', - NO_ALIAS = 'You cannot provide an array with elements to a slash command.', - NOT_VALID_MOD_TYPE = 'Detected an unknown module type', - UNDEFINED_MODULE = `A module could not be detected at`, - MISMATCH_MODULE_TYPE = `A module type mismatched with event emitted!` + RESERVED_EVENT = 'Cannot register the reserved ready event. Please use the init property.', + NO_ALIAS = 'You cannot provide an array with elements to a slash command.', + NOT_VALID_MOD_TYPE = 'Detected an unknown module type', + UNDEFINED_MODULE = `A module could not be detected at`, + MISMATCH_MODULE_TYPE = `A module type mismatched with event emitted!`, } diff --git a/src/handler/structures/modules/commands/module.ts b/src/handler/structures/modules/commands/module.ts index 3314784..fe68227 100644 --- a/src/handler/structures/modules/commands/module.ts +++ b/src/handler/structures/modules/commands/module.ts @@ -1,45 +1,51 @@ -import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ContextMenuCommandInteraction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js'; +import type { + ApplicationCommandOptionData, + Awaitable, + ButtonInteraction, + MessageContextMenuInteraction, + ContextMenuInteraction, + SelectMenuInteraction, +} from 'discord.js'; + import type { Override } from '../../../../types/handler'; import type { CommandType } from '../../../sern'; import type { BaseModule } from '../module'; -//possible refactoring to interfaces and not types +// Possible refactoring to interfaces and not types export type TextCommand = { - type : CommandType.TEXT; - alias : string[] | [], + type: CommandType.TEXT; + alias: string[] | []; } & BaseModule; export type SlashCommand = { - type : CommandType.SLASH; - options : ApplicationCommandOptionData[] | [], -} & BaseModule; + type: CommandType.SLASH; + options: ApplicationCommandOptionData[] | []; +} & BaseModule; export type BothCommand = { - type : CommandType.BOTH; - alias : string[] | []; - options : ApplicationCommandOptionData[] | [], + type: CommandType.BOTH; + alias: string[] | []; + options: ApplicationCommandOptionData[] | []; } & BaseModule; export type ContextMenuUser = { - type : CommandType.MENU_USER; -} & Override Awaitable }>; + type: CommandType.MENU_USER; +} & Override Awaitable }>; export type ContextMenuMsg = { - type : CommandType.MENU_MSG; -} & Override Awaitable }>; + type: CommandType.MENU_MSG; +} & Override Awaitable }>; export type ButtonCommand = { - type : CommandType.BUTTON; -} & Override Awaitable }>; + type: CommandType.BUTTON; +} & Override Awaitable }>; export type SelectMenuCommand = { - type : CommandType.MENU_SELECT; -} & Override Awaitable }>; + type: CommandType.MENU_SELECT; +} & Override Awaitable }>; - -export type Module = - TextCommand - | SlashCommand - | BothCommand - | ContextMenuUser - | ContextMenuMsg - | ButtonCommand - | SelectMenuCommand; - +export type Module = + | TextCommand + | SlashCommand + | BothCommand + | ContextMenuUser + | ContextMenuMsg + | ButtonCommand + | SelectMenuCommand; diff --git a/src/handler/structures/modules/commands/moduleHandler.ts b/src/handler/structures/modules/commands/moduleHandler.ts index caf1e01..5814d7c 100644 --- a/src/handler/structures/modules/commands/moduleHandler.ts +++ b/src/handler/structures/modules/commands/moduleHandler.ts @@ -1,24 +1,31 @@ import { CommandType } from '../../../sern'; -import type { TextCommand, BothCommand, ButtonCommand, SlashCommand, ContextMenuMsg, ContextMenuUser, SelectMenuCommand } from './module'; -//https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union +import type { + TextCommand, + BothCommand, + ButtonCommand, + SlashCommand, + ContextMenuMsg, + ContextMenuUser, + SelectMenuCommand, +} from './module'; +// https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union // Explicit Module Definitions for mapping export type ModuleDefs = { - [CommandType.TEXT] : TextCommand, - [CommandType.SLASH] : SlashCommand, - [CommandType.BOTH] : BothCommand, - [CommandType.MENU_MSG] : ContextMenuMsg, - [CommandType.MENU_USER] : ContextMenuUser, - [CommandType.BUTTON] : ButtonCommand, - [CommandType.MENU_SELECT] : SelectMenuCommand -} + [CommandType.TEXT]: TextCommand; + [CommandType.SLASH]: SlashCommand; + [CommandType.BOTH]: BothCommand; + [CommandType.MENU_MSG]: ContextMenuMsg; + [CommandType.MENU_USER]: ContextMenuUser; + [CommandType.BUTTON]: ButtonCommand; + [CommandType.MENU_SELECT]: SelectMenuCommand; +}; //Keys of ModuleDefs export type ModuleType = keyof ModuleDefs; // The keys mapped to a constructed union with its type -export type ModuleStates = { [ K in ModuleType ] : { type : K } & ModuleDefs[K] }; -// A handler callback that is called on each ModuleDef -export type HandlerCallback = ( params : ModuleStates[K] ) => unknown; +export type ModuleStates = { [K in ModuleType]: { type: K } & ModuleDefs[K] }; +// A handler callback that is called on each ModuleDef +export type HandlerCallback = (params: ModuleStates[K]) => unknown; //An object that acts as the mapped object to handler -export type ModuleHandlers = { [K in ModuleType] : HandlerCallback }; - +export type ModuleHandlers = { [K in ModuleType]: HandlerCallback }; diff --git a/src/handler/structures/modules/module.ts b/src/handler/structures/modules/module.ts index 606ef62..f127114 100644 --- a/src/handler/structures/modules/module.ts +++ b/src/handler/structures/modules/module.ts @@ -1,13 +1,9 @@ -import type { Awaitable, ChatInputCommandInteraction, Interaction } from "discord.js"; -import type { Args } from "../../.."; -import type Context from "../context"; +import type { Awaitable, ChatInputCommandInteraction, Interaction } from 'discord.js'; +import type { Args } from '../../..'; +import type Context from '../context'; export interface BaseModule { - name? : string; - description : string; - execute: (ctx: Context, args: Args) => Awaitable; + name?: string; + description: string; + execute: (ctx: Context, args: Args) => Awaitable; } - - - - diff --git a/src/handler/structures/structxports.ts b/src/handler/structures/structxports.ts index 1ae6997..46a6f3c 100644 --- a/src/handler/structures/structxports.ts +++ b/src/handler/structures/structxports.ts @@ -2,11 +2,4 @@ import Context from './context'; import type { SlashCommand, TextCommand, BothCommand, Module } from '../structures/modules/commands/module'; import type Wrapper from './wrapper'; -export { - Context, - SlashCommand, - TextCommand, - BothCommand, - Module, - Wrapper -}; +export { Context, SlashCommand, TextCommand, BothCommand, Module, Wrapper }; diff --git a/src/handler/structures/wrapper.ts b/src/handler/structures/wrapper.ts index 54168d1..b0ad93b 100644 --- a/src/handler/structures/wrapper.ts +++ b/src/handler/structures/wrapper.ts @@ -4,18 +4,19 @@ import type { DiscordEvent } from '../../types/handler'; /** * An object to be passed into Sern.Handler constructor. * @typedef {object} Wrapper - * @property {readonly Client} client - * @property {readonly string} defaultPrefix - * @property {readonly string} commands - * @prop {(handler : Handler) => void)} init - * @prop { readonly DiscordEvent[] } events + * @property {readonly Client} Client + * @property {readonly string} The default prefix + * @property {readonly string} Commands + * @prop {(handler: Handler) => void)} init + * @prop { readonly DiscordEvent[] } Events */ + interface Wrapper { - readonly client: Client; - readonly defaultPrefix: string; - readonly commands: string; - init?: (handler: Wrapper) => void; - readonly events? : DiscordEvent[]; + readonly client: Client; + readonly defaultPrefix: string; + readonly commands: string; + init?: (handler: Wrapper) => void; + readonly events?: DiscordEvent[]; } export default Wrapper; diff --git a/src/handler/utilities/markup.ts b/src/handler/utilities/markup.ts index 4dba827..89ebe2a 100644 --- a/src/handler/utilities/markup.ts +++ b/src/handler/utilities/markup.ts @@ -1,730 +1,796 @@ - /** - * An enumeration of all the valid Discord timestamp styles. - */ - export enum TimestampStyles { - BOTH_LONG = 'F', - BOTH_SHORT = 'f', - DATE_LONG = 'D', - DATE_SHORT = 'd', - RELATIVE = 'R', - TIME_LONG = 'T', - TIME_SHORT = 't' +/** + * An enumeration of all the valid Discord timestamp styles. + */ + +export enum TimestampStyles { + BOTH_LONG = 'F', + BOTH_SHORT = 'f', + DATE_LONG = 'D', + DATE_SHORT = 'd', + RELATIVE = 'R', + TIME_LONG = 'T', + TIME_SHORT = 't' +} + +/** + * Utility to cut messages by bytes and not characters + */ + +export function trueSlice(text: string, limit?: number): string { + if (limit) { + return new TextDecoder().decode( + new TextEncoder().encode(text).slice(0, limit) + ); } - /** - * Utility to cut messages by bytes and not characters - */ - export function trueSlice(text: string, limit?: number): string { - if (limit) { - return new TextDecoder().decode( - new TextEncoder().encode(text).slice(0, limit) - ); + return text; +} + +/** + * Object that holds all the Discord Markup identifiers. + */ + +export const Strings = { + BOLD: '**', + CODEBLOCK: '```', + CODESTRING: '`', + CODESTRING_DOUBLE: '``', + ESCAPE: '\\', + ITALICS: '_', + SPOILER: '||', + STRIKE: '~~', + UNDERLINE: '__' +}; + +/** + * Object that maps all the Discord Markup identifiers to their respective RegExp matchers. + */ + +const Regexes = { + [Strings.BOLD]: /\*\*/g, + [Strings.CODEBLOCK]: new RegExp(Strings.CODEBLOCK, 'g'), + [Strings.CODESTRING]: new RegExp(Strings.CODESTRING, 'g'), + [Strings.ESCAPE]: /\\/g, + [Strings.ITALICS]: /(_|\*)/g, + [Strings.SPOILER]: /\|\|/g, + [Strings.STRIKE]: new RegExp(Strings.STRIKE, 'g'), + [Strings.UNDERLINE]: new RegExp(Strings.UNDERLINE, 'g'), + + EVERYONE: /@(everyone|here)/g, + LINK: /\]\(/g, + MENTION: /<@([!&]?[0-9]{16,21})>/g, + MENTION_HARDCORE: /@/g, + URL: /\)/g +}; + +/** + * Object to replace Discord Markup identifiers with when escaping strings. + */ + +const Replacements = { + [Strings.BOLD]: '\\*\\*', + [Strings.CODEBLOCK]: '``\u200b`', + [Strings.CODESTRING]: '\\`', + [Strings.ESCAPE]: '\\\\', + [Strings.ITALICS]: '\\$1', + [Strings.SPOILER]: '\\|\\|', + [Strings.STRIKE]: '\\~\\~', + [Strings.UNDERLINE]: '\\_\\_', + MENTION: '\u200b' +}; + +/** + * Utility to escape some Discord Markup Identifier + */ + +function EscapeBasic(raw: string, key: keyof typeof Strings) { + return raw.replace(Regexes[key], Replacements[key]); +} + +/** + * Object of all the Escape functions used to apply mixed markup + */ + +export const Escape: Record< + keyof typeof Strings, + typeof EscapeBasic +> = (Object.keys(Strings) as Array).reduce( + (p, v) => Object.assign(p, { [v]: (raw: string) => EscapeBasic(raw, v) }), + {} as Record +); + +/** + * String formatting for freezing Discord timestamps that have the Relative (R) flag + */ + +const FrozenTimestampStyles: Record = { + [TimestampStyles.BOTH_LONG]: + '{day}, {month} {date}, {year} {hour}:{minute} {meridian}', + [TimestampStyles.BOTH_SHORT]: + '{month} {date}, {year} {hour}:{minute} {meridian}', + [TimestampStyles.DATE_LONG]: '{month} {date}, {year}', + [TimestampStyles.DATE_SHORT]: '{month_short}/{date}/{year}', + [TimestampStyles.RELATIVE]: '{relative}', + [TimestampStyles.TIME_LONG]: '{hour}:{minute}:{second} {meridian}', + [TimestampStyles.TIME_SHORT]: '{hour}:{minute} {meridian}' +}; + +/** + * Holds metadata and string conversions of a UNIX Timestamp + */ + +interface Timestamp { + raw: number; + month: string; + month_short: string; + date: string; + year: string; + second: string; + meridian: 'AM' | 'PM'; + hour: string; + minute: string; + day: string; + relative: string; +} + +/** + * Converter for number to Days of the Week + */ + +const Days: Record = { + 0: 'Sunday', + 1: 'Monday', + 2: 'Tuesday', + 3: 'Wednesday', + 4: 'Thursday', + 5: 'Friday', + 6: 'Saturday' +}; + +/** + * Converter for number to Months of the Year + */ + +const Months: Record = { + 0: 'January', + 1: 'February', + 2: 'March', + 3: 'April', + 4: 'May', + 5: 'June', + 6: 'July', + 7: 'August', + 8: 'September', + 9: 'October', + 10: 'November', + 11: 'December' +}; + +/** + * Converts a Date object to a Timestamp object + */ + +function formatDate(date: Date): Timestamp { + return { + relative: toTimeString(date.getTime(), TimestampUnits), + raw: date.getTime(), + date: date + .getDate() + .toString() + .padStart(2, '0'), + day: Days[date.getDay()], + hour: date + .getHours() + .toString() + .padStart(2, '0'), + meridian: date.getHours() > 12 ? 'PM' : 'AM', + minute: date + .getMinutes() + .toString() + .padStart(2, '0'), + month: Months[date.getMonth()], + month_short: (date.getMonth() + 1).toString().padStart(2, '0'), + second: date + .getSeconds() + .toString() + .padStart(2, '0'), + year: date.getFullYear().toString() + }; +} + +/** + * Collectively multiplies bigints together + */ + +function multiplyLarge(...nums: Array): bigint { + return nums.map(BigInt).reduce((p, v) => (p *= v), 1n); +} + +/** + * Get the absolute value of a bigint + */ + +function bigintAbs(int: bigint) { + if (int < 0) return -int; + return int; +} + +/** + * Object of Units matched with their string representations. + */ + +const TimestampUnits = { + myriad: multiplyLarge(10, 10, 10, 10, 12, 4, 7, 24, 60, 1000), + millenium: multiplyLarge(10, 10, 10, 12, 4, 7, 24, 60, 1000), + century: multiplyLarge(10, 10, 12, 4, 7, 24, 60, 1000), + decade: multiplyLarge(10, 12, 4, 7, 24, 60, 60, 1000), + year: multiplyLarge(12, 4, 7, 24, 60, 60, 1000), + month: multiplyLarge(4, 7, 24, 60, 60, 1000), + week: multiplyLarge(7, 24, 60, 60, 1000), + day: multiplyLarge(24, 60, 60, 1000), + hour: multiplyLarge(60, 60, 1000), + minute: multiplyLarge(60, 1000), + second: multiplyLarge(1000), + millisecond: multiplyLarge(1) +}; + +/** + * Utility type. Used to force Object.entries to allow non-strings. + */ + +type ObjectEntries = Array<[K, V]>; + +/** + * Converts a UNIX timestamp to a Relative String + */ + +function toTimeString( + unix: bigint | number, + units: Record, + isFromNow = false, + limit?: number +) { + if (typeof unix === 'number') unix = BigInt(unix); + + if (isFromNow) unix = bigintAbs(unix - BigInt(Date.now())); + if (unix === 0n) return '0 milliseconds'; + + const formatted: Map = new Map(); + const unitList: ObjectEntries = Object.entries(units) as any; + let run = unix; + + for (const [unit, value] of unitList) { + if (run < value) continue; + const runs = run / value + 1n; + + for (let loop = 0; loop <= runs; loop++) { + if (run < value) break; + const item = formatted.get(unit); + + if (item) formatted.set(unit, item + 1); + else formatted.set(unit, 1); + + run -= value; } - return text; } - /** - * Object that holds all the Discord Markup identifiers. - */ - export const Strings = { - BOLD: '**', - CODEBLOCK: '```', - CODESTRING: '`', - CODESTRING_DOUBLE: '``', - ESCAPE: '\\', - ITALICS: '_', - SPOILER: '||', - STRIKE: '~~', - UNDERLINE: '__' - }; - /** - * Object that maps all the Discord Markup identifiers to their respective RegExp matchers. - */ - const Regexes = { - [Strings.BOLD]: /\*\*/g, - [Strings.CODEBLOCK]: new RegExp(Strings.CODEBLOCK, 'g'), - [Strings.CODESTRING]: new RegExp(Strings.CODESTRING, 'g'), - [Strings.ESCAPE]: /\\/g, - [Strings.ITALICS]: /(_|\*)/g, - [Strings.SPOILER]: /\|\|/g, - [Strings.STRIKE]: new RegExp(Strings.STRIKE, 'g'), - [Strings.UNDERLINE]: new RegExp(Strings.UNDERLINE, 'g'), - EVERYONE: /@(everyone|here)/g, - LINK: /\]\(/g, - MENTION: /<@([!&]?[0-9]{16,21})>/g, - MENTION_HARDCORE: /@/g, - URL: /\)/g - }; - /** - * Object to replace Discord Markup identifiers with when escaping strings. - */ - const Replacements = { - [Strings.BOLD]: '\\*\\*', - [Strings.CODEBLOCK]: '``\u200b`', - [Strings.CODESTRING]: '\\`', - [Strings.ESCAPE]: '\\\\', - [Strings.ITALICS]: '\\$1', - [Strings.SPOILER]: '\\|\\|', - [Strings.STRIKE]: '\\~\\~', - [Strings.UNDERLINE]: '\\_\\_', - MENTION: '\u200b' - }; - /** - * Utility to escape some Discord Markup Identifier - */ - function EscapeBasic(raw: string, key: keyof typeof Strings) { - return raw.replace(Regexes[key], Replacements[key]); + let returned: Array = []; + for (const [key, value] of formatted) { + const unit = key + (value === 1 ? '' : 's'); + returned.push(`${value} ${unit}`); } - /** - * Object of all the Escape functions used to apply mixed markup - */ - export const Escape: Record< - keyof typeof Strings, - typeof EscapeBasic - > = (Object.keys(Strings) as Array).reduce( - (p, v) => Object.assign(p, { [v]: (raw: string) => EscapeBasic(raw, v) }), - {} as Record - ); - /** - * String formatting for freezing Discord timestamps that have the Relative (R) flag - */ - const FrozenTimestampStyles: Record = { - [TimestampStyles.BOTH_LONG]: - '{day}, {month} {date}, {year} {hour}:{minute} {meridian}', - [TimestampStyles.BOTH_SHORT]: - '{month} {date}, {year} {hour}:{minute} {meridian}', - [TimestampStyles.DATE_LONG]: '{month} {date}, {year}', - [TimestampStyles.DATE_SHORT]: '{month_short}/{date}/{year}', - [TimestampStyles.RELATIVE]: '{relative}', - [TimestampStyles.TIME_LONG]: '{hour}:{minute}:{second} {meridian}', - [TimestampStyles.TIME_SHORT]: '{hour}:{minute} {meridian}' - }; - /** - * Holds metadata and string conversions of a UNIX Timestamp - */ - interface Timestamp { - raw: number; - month: string; - month_short: string; - date: string; - year: string; - second: string; - meridian: 'AM' | 'PM'; - hour: string; - minute: string; - day: string; - relative: string; + if (limit !== undefined) { + returned = returned.slice(0, limit); } - /** - * Converter for number to Days of the Week - */ - const Days: Record = { - 0: 'Sunday', - 1: 'Monday', - 2: 'Tuesday', - 3: 'Wednesday', - 4: 'Thursday', - 5: 'Friday', - 6: 'Saturday' - }; - /** - * Converter for number to Months of the Year - */ - const Months: Record = { - 0: 'January', - 1: 'February', - 2: 'March', - 3: 'April', - 4: 'May', - 5: 'June', - 6: 'July', - 7: 'August', - 8: 'September', - 9: 'October', - 10: 'November', - 11: 'December' - }; - /** - * Converts a Date object to a Timestamp object - */ - function formatDate(date: Date): Timestamp { - return { - relative: toTimeString(date.getTime(), TimestampUnits), - raw: date.getTime(), - date: date - .getDate() - .toString() - .padStart(2, '0'), - day: Days[date.getDay()], - hour: date - .getHours() - .toString() - .padStart(2, '0'), - meridian: date.getHours() > 12 ? 'PM' : 'AM', - minute: date - .getMinutes() - .toString() - .padStart(2, '0'), - month: Months[date.getMonth()], - month_short: (date.getMonth() + 1).toString().padStart(2, '0'), - second: date - .getSeconds() - .toString() - .padStart(2, '0'), - year: date.getFullYear().toString() - }; + return returned.join(', '); +} + +/** + * Freezes a UNIT timestamp into some time string based on the Timestamp Style + */ + +function freezeUnix({ unix, style }:{ unix: number; style: TimestampStyles; }): string { + const date = new Date(unix); + const timestamp = formatDate(date); + let ret = FrozenTimestampStyles[style]; + for (const [key, value] of Object.entries(timestamp)) { + ret = ret.split(`{${key}}`).join(value); } - /** - * Collectively multiplies bigints together - */ - function multiplyLarge(...nums: Array): bigint { - return nums.map(BigInt).reduce((p, v) => (p *= v), 1n); + return ret; +} + +/** + * Instanced Class for formatting strings into their Markup variants + */ + +class FormatInner { + public raw: string; + public static: typeof FormatInner = FormatInner; + constructor(raw: string | FormatInner) { + if (raw instanceof FormatInner) { + raw = raw.raw; + } + this.raw = raw; } - /** - * Get the absolute value of a bigint - */ - function bigintAbs(int: bigint) { - if (int < 0) return -int; - return int; + toString() { + return this.raw; } - /** - * Object of Units matched with their string representations. - */ - const TimestampUnits = { - myriad: multiplyLarge(10, 10, 10, 10, 12, 4, 7, 24, 60, 1000), - millenium: multiplyLarge(10, 10, 10, 12, 4, 7, 24, 60, 1000), - century: multiplyLarge(10, 10, 12, 4, 7, 24, 60, 1000), - decade: multiplyLarge(10, 12, 4, 7, 24, 60, 60, 1000), - year: multiplyLarge(12, 4, 7, 24, 60, 60, 1000), - month: multiplyLarge(4, 7, 24, 60, 60, 1000), - week: multiplyLarge(7, 24, 60, 60, 1000), - day: multiplyLarge(24, 60, 60, 1000), - hour: multiplyLarge(60, 60, 1000), - minute: multiplyLarge(60, 1000), - second: multiplyLarge(1000), - millisecond: multiplyLarge(1) - }; - /** - * Utility type. Used to force Object.entries to allow non-strings. - */ - type ObjectEntries = Array<[K, V]>; - /** - * Converts a UNIX timestamp to a Relative String - */ - function toTimeString( - unix: bigint | number, - units: Record, - isFromNow = false, - limit?: number + valueOf() { + return this.raw; + } + italics() { + return this.build('ITALICS', this.raw); + } + bold() { + return this.build('BOLD', this.raw); + } + codestring() { + const useDouble = this.raw.includes(Strings.CODESTRING); + if (useDouble) { + return this.codestringDouble(); + } + return this.codestringSingle(); + } + codestringDouble() { + return this.build('CODESTRING_DOUBLE', this.raw); + } + codestringSingle() { + return this.build('CODESTRING', this.raw); + } + codeblock(language?: string) { + let full = ''; + if (language) { + full += language + '\n'; + } + full += this.raw; + return this.build('CODEBLOCK', full); + } + spoiler() { + return this.build('SPOILER', this.raw); + } + strike() { + return this.build('STRIKE', this.raw); + } + underline() { + return this.build('UNDERLINE', this.raw); + } + + build(key: keyof typeof Strings, w: string) { + const escaped = Escape[key](w, key); + const ret = this.static.wrap(escaped, Strings[key]); + return new this.static(ret); + } + static wrap(raw: string, what: string) { + return `${what}${raw}${what}`; + } +} + +/** + * Formats strings into their Markup Variants + */ + +export class Format extends FormatInner { + static bold(text: string) { + return new this(text).bold(); + } + static build(text: string, key: keyof typeof Strings) { + return new this(text).build(key, text); + } + static codeblock(text: string, language?: string) { + return new this(text).codeblock(language); + } + static codestring(text: string) { + return new this(text).codestring(); + } + static codestringSingle(text: string) { + return new this(text).codestringSingle(); + } + static codestringDouble(text: string) { + return new this(text).codestringDouble(); + } + static italics(text: string) { + return new this(text).italics(); + } + static spoiler(text: string) { + return new this(text).spoiler(); + } + static strike(text: string) { + return new this(text).strike(); + } + static underline(text: string) { + return new this(text).underline(); + } + static timestamp( + unix: number | Date | string, + format: TimestampStyles = TimestampStyles.BOTH_SHORT, + isSeconds = false ) { - if (typeof unix === 'number') unix = BigInt(unix); + if (typeof unix === 'string') unix = Number(unix); + if (unix instanceof Date) unix = unix.getTime(); - if (isFromNow) unix = bigintAbs(unix - BigInt(Date.now())); - if (unix === 0n) return '0 milliseconds'; - - const formatted: Map = new Map(); - const unitList: ObjectEntries = Object.entries(units) as any; - let run = unix; - - for (const [unit, value] of unitList) { - if (run < value) continue; - const runs = run / value + 1n; - - for (let loop = 0; loop <= runs; loop++) { - if (run < value) break; - const item = formatted.get(unit); - - if (item) formatted.set(unit, item + 1); - else formatted.set(unit, 1); - - run -= value; - } + if (!isSeconds) { + unix /= 1000; } - let returned: Array = []; - for (const [key, value] of formatted) { - const unit = key + (value === 1 ? '' : 's'); - returned.push(`${value} ${unit}`); - } - if (limit !== undefined) { - returned = returned.slice(0, limit); - } - return returned.join(', '); + unix = Math.floor(unix); + return new this(``); } - /** - * Freezes a UNIT timestamp into some time string based on the Timestamp Style - */ - function freezeUnix(unix: number, style: TimestampStyles) { - const date = new Date(unix); - const timestamp = formatDate(date); - let ret = FrozenTimestampStyles[style]; - for (const [key, value] of Object.entries(timestamp)) { - ret = ret.split(`{${key}}`).join(value); - } - return ret; - } - /** - * Instanced Class for formatting strings into their Markup variants - */ - class FormatInner { - public raw: string; - public static: typeof FormatInner = FormatInner; - constructor(raw: string | FormatInner) { - if (raw instanceof FormatInner) { - raw = raw.raw; - } - this.raw = raw; - } - toString() { - return this.raw; - } - valueOf() { - return this.raw; - } - italics() { - return this.build('ITALICS', this.raw); - } - bold() { - return this.build('BOLD', this.raw); - } - codestring() { - const useDouble = this.raw.includes(Strings.CODESTRING); - if (useDouble) { - return this.codestringDouble(); - } - return this.codestringSingle(); - } - codestringDouble() { - return this.build('CODESTRING_DOUBLE', this.raw); - } - codestringSingle() { - return this.build('CODESTRING', this.raw); - } - codeblock(language?: string) { - let full = ''; - if (language) { - full += language + '\n'; - } - full += this.raw; - return this.build('CODEBLOCK', full); - } - spoiler() { - return this.build('SPOILER', this.raw); - } - strike() { - return this.build('STRIKE', this.raw); - } - underline() { - return this.build('UNDERLINE', this.raw); - } + static date( + unix: number | Date | string, + format: TimestampStyles = TimestampStyles.BOTH_SHORT, + isSeconds = false + ) { + if (typeof unix === 'string') unix = Number(unix); + if (unix instanceof Date) unix = unix.getTime(); - build(key: keyof typeof Strings, w: string) { - const escaped = Escape[key](w, key); - const ret = this.static.wrap(escaped, Strings[key]); - return new this.static(ret); - } - static wrap(raw: string, what: string) { - return `${what}${raw}${what}`; + if (isSeconds) { + unix *= 1000; } + return new this(freezeUnix({ unix, style: format })); } - /** - * Formats strings into their Markup Variants - */ - export class Format extends FormatInner { - static bold(text: string) { - return new this(text).bold(); - } - static build(text: string, key: keyof typeof Strings) { - return new this(text).build(key, text); - } - static codeblock(text: string, language?: string) { - return new this(text).codeblock(language); - } - static codestring(text: string) { - return new this(text).codestring(); - } - static codestringSingle(text: string) { - return new this(text).codestringSingle(); - } - static codestringDouble(text: string) { - return new this(text).codestringDouble(); - } - static italics(text: string) { - return new this(text).italics(); - } - static spoiler(text: string) { - return new this(text).spoiler(); - } - static strike(text: string) { - return new this(text).strike(); - } - static underline(text: string) { - return new this(text).underline(); - } - static timestamp( - unix: number | Date | string, - format: TimestampStyles = TimestampStyles.BOTH_SHORT, - isSeconds = false - ) { - if (typeof unix === 'string') unix = Number(unix); - if (unix instanceof Date) unix = unix.getTime(); + static link(text: string, url: string | URL) { + if (url instanceof URL) url = url.href; + return new this(`[${text}](${url})`); + } +} - if (!isSeconds) { - unix /= 1000; - } - unix = Math.floor(unix); - return new this(``); - } - static date( - unix: number | Date | string, - format: TimestampStyles = TimestampStyles.BOTH_SHORT, - isSeconds = false - ) { - if (typeof unix === 'string') unix = Number(unix); - if (unix instanceof Date) unix = unix.getTime(); +/** + * Enumeration of names used in the Matching process + */ - if (isSeconds) { - unix *= 1000; - } - return new this(freezeUnix(unix, format)); - } - static link(text: string, url: string | URL) { - if (url instanceof URL) url = url.href; - return new this(`[${text}](${url})`); - } - } - /** - * Enumeration of names used in the Matching process - */ - enum DiscordRegexNames { - EMOJI = 'EMOJI', - JUMP_CHANNEL = 'JUMP_CHANNEL', - JUMP_CHANNEL_MESSAGE = 'JUMP_CHANNEL_MESSAGE', - MENTION_CHANNEL = 'MENTION_CHANNEL', - MENTION_ROLE = 'MENTION_ROLE', - MENTION_USER = 'MENTION_USER', - TEXT_BOLD = 'TEXT_BOLD', - TEXT_CODEBLOCK = 'TEXT_CODEBLOCK', - TEXT_CODESTRING = 'TEXT_CODESTRING', - TEXT_ITALICS = 'TEXT_ITALICS', - TEXT_SNOWFLAKE = 'TEXT_SNOWFLAKE', - TEXT_SPOILER = 'TEXT_SPOILER', - TEXT_STRIKE = 'TEXT_STRIKE', - TEXT_UNDERLINE = 'TEXT_UNDERLINE', - TEXT_URL = 'TEXT_URL' - } - /** - * Mapping of Matching Names to their respective Regular Expressions - */ - export const DiscordRegex = { - [DiscordRegexNames.EMOJI]: //g, - [DiscordRegexNames.JUMP_CHANNEL]: /^(?:https?):\/\/(?:(?:(?:canary|ptb)\.)?(?:discord|discordapp)\.com\/channels\/)(\@me|\d+)\/(\d+)$/g, - [DiscordRegexNames.JUMP_CHANNEL_MESSAGE]: /^(?:https?):\/\/(?:(?:(?:canary|ptb)\.)?(?:discord|discordapp)\.com\/channels\/)(\@me|\d+)\/(\d+)\/(\d+)$/g, - [DiscordRegexNames.MENTION_CHANNEL]: /<#(\d+)>/g, - [DiscordRegexNames.MENTION_ROLE]: /<@&(\d+)>/g, - [DiscordRegexNames.MENTION_USER]: /<@(!?)(\d+)>/g, - [DiscordRegexNames.TEXT_BOLD]: /\*\*([\s\S]+?)\*\*/g, - [DiscordRegexNames.TEXT_CODEBLOCK]: /```(([a-z0-9-]+?)\n+)?\n*([^]+?)\n*```/gi, - [DiscordRegexNames.TEXT_CODESTRING]: /`([\s\S]+?)`/g, - [DiscordRegexNames.TEXT_ITALICS]: /_([\s\S]+?)_|\*([\s\S]+?)\*/g, - [DiscordRegexNames.TEXT_SNOWFLAKE]: /(\d+)/g, - [DiscordRegexNames.TEXT_SPOILER]: /\|\|([\s\S]+?)\|\|/g, - [DiscordRegexNames.TEXT_STRIKE]: /~~([\s\S]+?)~~(?!_)/g, - [DiscordRegexNames.TEXT_UNDERLINE]: /__([\s\S]+?)__/g, - [DiscordRegexNames.TEXT_URL]: /((?:https?):\/\/[^\s<]+[^<.,:;"'\]\s])/g +enum DiscordRegexNames { + EMOJI = 'EMOJI', + JUMP_CHANNEL = 'JUMP_CHANNEL', + JUMP_CHANNEL_MESSAGE = 'JUMP_CHANNEL_MESSAGE', + MENTION_CHANNEL = 'MENTION_CHANNEL', + MENTION_ROLE = 'MENTION_ROLE', + MENTION_USER = 'MENTION_USER', + TEXT_BOLD = 'TEXT_BOLD', + TEXT_CODEBLOCK = 'TEXT_CODEBLOCK', + TEXT_CODESTRING = 'TEXT_CODESTRING', + TEXT_ITALICS = 'TEXT_ITALICS', + TEXT_SNOWFLAKE = 'TEXT_SNOWFLAKE', + TEXT_SPOILER = 'TEXT_SPOILER', + TEXT_STRIKE = 'TEXT_STRIKE', + TEXT_UNDERLINE = 'TEXT_UNDERLINE', + TEXT_URL = 'TEXT_URL' +} + +/** + * Mapping of Matching Names to their respective Regular Expressions + */ + +export const DiscordRegex = { + [DiscordRegexNames.EMOJI]: //g, + [DiscordRegexNames.JUMP_CHANNEL]: /^(?:https?):\/\/(?:(?:(?:canary|ptb)\.)?(?:discord|discordapp)\.com\/channels\/)(\@me|\d+)\/(\d+)$/g, + [DiscordRegexNames.JUMP_CHANNEL_MESSAGE]: /^(?:https?):\/\/(?:(?:(?:canary|ptb)\.)?(?:discord|discordapp)\.com\/channels\/)(\@me|\d+)\/(\d+)\/(\d+)$/g, + [DiscordRegexNames.MENTION_CHANNEL]: /<#(\d+)>/g, + [DiscordRegexNames.MENTION_ROLE]: /<@&(\d+)>/g, + [DiscordRegexNames.MENTION_USER]: /<@(!?)(\d+)>/g, + [DiscordRegexNames.TEXT_BOLD]: /\*\*([\s\S]+?)\*\*/g, + [DiscordRegexNames.TEXT_CODEBLOCK]: /```(([a-z0-9-]+?)\n+)?\n*([^]+?)\n*```/gi, + [DiscordRegexNames.TEXT_CODESTRING]: /`([\s\S]+?)`/g, + [DiscordRegexNames.TEXT_ITALICS]: /_([\s\S]+?)_|\*([\s\S]+?)\*/g, + [DiscordRegexNames.TEXT_SNOWFLAKE]: /(\d+)/g, + [DiscordRegexNames.TEXT_SPOILER]: /\|\|([\s\S]+?)\|\|/g, + [DiscordRegexNames.TEXT_STRIKE]: /~~([\s\S]+?)~~(?!_)/g, + [DiscordRegexNames.TEXT_UNDERLINE]: /__([\s\S]+?)__/g, + [DiscordRegexNames.TEXT_URL]: /((?:https?):\/\/[^\s<]+[^<.,:;''\]\s])/g +}; + +/** + * Object containing all the data from some Matching sequence + */ + +export interface DiscordRegexMatch { + animated?: boolean; + channelId?: string; + guildId?: string; + id?: string; + language?: string; + matched: string; + mentionType?: string; + messageId?: string; + name?: string; + text?: string; + species: DiscordRegexNames; +} + +/** + * The result of a matched string. + */ + +export interface DiscordRegexPayload { + match: { + regex: RegExp; + type: string; }; - /** - * Object containing all the data from some Matching sequence - */ - export interface DiscordRegexMatch { - animated?: boolean; - channelId?: string; - guildId?: string; - id?: string; - language?: string; - matched: string; - mentionType?: string; - messageId?: string; - name?: string; - text?: string; - species: DiscordRegexNames; + matches: Array; +} + +export interface EmojiMatch extends DiscordRegexMatch { + name: string; + id: string; + animated: boolean; + species: DiscordRegexNames.EMOJI; +} + +export interface JumpMatch extends DiscordRegexMatch { + guildId: string; + species: + | DiscordRegexNames.JUMP_CHANNEL + | DiscordRegexNames.JUMP_CHANNEL_MESSAGE; +} + +export interface JumpChannelMatch extends JumpMatch { + channelId: string; + species: DiscordRegexNames.JUMP_CHANNEL; +} + +export interface JumpChannelMessageMatch extends JumpMatch { + channelId: string; + messageId: string; + species: DiscordRegexNames.JUMP_CHANNEL_MESSAGE; +} + +export interface MentionableMatch extends DiscordRegexMatch { + id: string; + species: + | DiscordRegexNames.MENTION_CHANNEL + | DiscordRegexNames.MENTION_ROLE + | DiscordRegexNames.MENTION_USER; +} + +export interface MentionChannelMatch extends MentionableMatch { + species: DiscordRegexNames.MENTION_CHANNEL; +} + +export interface MentionRoleMatch extends MentionableMatch { + species: DiscordRegexNames.MENTION_ROLE; +} + +export interface MentionUserMatch extends MentionableMatch { + mentionType: string; + species: DiscordRegexNames.MENTION_USER; +} + +export interface TextMatch extends DiscordRegexMatch { + text: string; + species: + | DiscordRegexNames.TEXT_BOLD + | DiscordRegexNames.TEXT_CODEBLOCK + | DiscordRegexNames.TEXT_CODESTRING + | DiscordRegexNames.TEXT_ITALICS + | DiscordRegexNames.TEXT_SNOWFLAKE + | DiscordRegexNames.TEXT_SPOILER + | DiscordRegexNames.TEXT_STRIKE + | DiscordRegexNames.TEXT_UNDERLINE + | DiscordRegexNames.TEXT_URL; +} + +export interface TextCodeblockMatch extends TextMatch { + language: string; + species: DiscordRegexNames.TEXT_CODEBLOCK; +} + +export interface TextBoldMatch extends TextMatch { + species: DiscordRegexNames.TEXT_BOLD; +} + +export interface TextCodestringMatch extends TextMatch { + species: DiscordRegexNames.TEXT_CODESTRING; +} + +export interface TextItalicsMatch extends TextMatch { + species: DiscordRegexNames.TEXT_ITALICS; +} + +export interface TextSnowflakeMatch extends TextMatch { + species: DiscordRegexNames.TEXT_SNOWFLAKE; +} + +export interface TextSpoilerMatch extends TextMatch { + species: DiscordRegexNames.TEXT_SPOILER; +} + +export interface TextStrikeMatch extends TextMatch { + species: DiscordRegexNames.TEXT_STRIKE; +} + +export interface TextUnderlineMatch extends TextMatch { + species: DiscordRegexNames.TEXT_UNDERLINE; +} + +export interface TextUrlMatch extends TextMatch { + species: DiscordRegexNames.TEXT_URL; +} + +class MatchInner { + public raw: string; + public static: typeof MatchInner = MatchInner; + + constructor(raw: string) { + this.raw = raw; } - /** - * The result of a matched string. - */ - export interface DiscordRegexPayload { - match: { - regex: RegExp; - type: string; + + emoji(): DiscordRegexPayload { + return this.match(DiscordRegexNames.EMOJI); + } + jumpChannel(): DiscordRegexPayload { + return this.match(DiscordRegexNames.JUMP_CHANNEL); + } + jumpChannelMessage(): DiscordRegexPayload { + return this.match(DiscordRegexNames.JUMP_CHANNEL_MESSAGE); + } + mentionChannel(): DiscordRegexPayload { + return this.match(DiscordRegexNames.MENTION_CHANNEL); + } + mentionRole(): DiscordRegexPayload { + return this.match(DiscordRegexNames.MENTION_ROLE); + } + mentionUser(): DiscordRegexPayload { + return this.match(DiscordRegexNames.MENTION_USER); + } + codeblock(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_CODEBLOCK); + } + bold(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_BOLD); + } + codestring(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_CODESTRING); + } + italics(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_ITALICS); + } + snowflake(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_SNOWFLAKE); + } + spoiler(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_SPOILER); + } + strike(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_STRIKE); + } + underline(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_UNDERLINE); + } + url(): DiscordRegexPayload { + return this.match(DiscordRegexNames.TEXT_URL); + } + + match( + type: DiscordRegexNames, + onlyFirst = false + ): DiscordRegexPayload { + const regex = DiscordRegex[type]; + if (regex === undefined) { + throw new global.Error(`Unknown regex type: ${type}`); + } + regex.lastIndex = 0; + + const payload: DiscordRegexPayload = { + match: { regex, type }, + matches: [] }; - matches: Array; - } - export interface EmojiMatch extends DiscordRegexMatch { - name: string; - id: string; - animated: boolean; - species: DiscordRegexNames.EMOJI; - } - export interface JumpMatch extends DiscordRegexMatch { - guildId: string; - species: - | DiscordRegexNames.JUMP_CHANNEL - | DiscordRegexNames.JUMP_CHANNEL_MESSAGE; - } - export interface JumpChannelMatch extends JumpMatch { - channelId: string; - species: DiscordRegexNames.JUMP_CHANNEL; - } - export interface JumpChannelMessageMatch extends JumpMatch { - channelId: string; - messageId: string; - species: DiscordRegexNames.JUMP_CHANNEL_MESSAGE; - } - export interface MentionableMatch extends DiscordRegexMatch { - id: string; - species: - | DiscordRegexNames.MENTION_CHANNEL - | DiscordRegexNames.MENTION_ROLE - | DiscordRegexNames.MENTION_USER; - } - export interface MentionChannelMatch extends MentionableMatch { - species: DiscordRegexNames.MENTION_CHANNEL; - } - export interface MentionRoleMatch extends MentionableMatch { - species: DiscordRegexNames.MENTION_ROLE; - } - export interface MentionUserMatch extends MentionableMatch { - mentionType: string; - species: DiscordRegexNames.MENTION_USER; - } - export interface TextMatch extends DiscordRegexMatch { - text: string; - species: - | DiscordRegexNames.TEXT_BOLD - | DiscordRegexNames.TEXT_CODEBLOCK - | DiscordRegexNames.TEXT_CODESTRING - | DiscordRegexNames.TEXT_ITALICS - | DiscordRegexNames.TEXT_SNOWFLAKE - | DiscordRegexNames.TEXT_SPOILER - | DiscordRegexNames.TEXT_STRIKE - | DiscordRegexNames.TEXT_UNDERLINE - | DiscordRegexNames.TEXT_URL; - } - export interface TextCodeblockMatch extends TextMatch { - language: string; - species: DiscordRegexNames.TEXT_CODEBLOCK; - } - export interface TextBoldMatch extends TextMatch { - species: DiscordRegexNames.TEXT_BOLD; - } - export interface TextCodestringMatch extends TextMatch { - species: DiscordRegexNames.TEXT_CODESTRING; - } - export interface TextItalicsMatch extends TextMatch { - species: DiscordRegexNames.TEXT_ITALICS; - } - export interface TextSnowflakeMatch extends TextMatch { - species: DiscordRegexNames.TEXT_SNOWFLAKE; - } - export interface TextSpoilerMatch extends TextMatch { - species: DiscordRegexNames.TEXT_SPOILER; - } - export interface TextStrikeMatch extends TextMatch { - species: DiscordRegexNames.TEXT_STRIKE; - } - export interface TextUnderlineMatch extends TextMatch { - species: DiscordRegexNames.TEXT_UNDERLINE; - } - export interface TextUrlMatch extends TextMatch { - species: DiscordRegexNames.TEXT_URL; - } - class MatchInner { - public raw: string; - public static: typeof MatchInner = MatchInner; - - constructor(raw: string) { - this.raw = raw; - } - - emoji(): DiscordRegexPayload { - return this.match(DiscordRegexNames.EMOJI); - } - jumpChannel(): DiscordRegexPayload { - return this.match(DiscordRegexNames.JUMP_CHANNEL); - } - jumpChannelMessage(): DiscordRegexPayload { - return this.match(DiscordRegexNames.JUMP_CHANNEL_MESSAGE); - } - mentionChannel(): DiscordRegexPayload { - return this.match(DiscordRegexNames.MENTION_CHANNEL); - } - mentionRole(): DiscordRegexPayload { - return this.match(DiscordRegexNames.MENTION_ROLE); - } - mentionUser(): DiscordRegexPayload { - return this.match(DiscordRegexNames.MENTION_USER); - } - codeblock(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_CODEBLOCK); - } - bold(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_BOLD); - } - codestring(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_CODESTRING); - } - italics(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_ITALICS); - } - snowflake(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_SNOWFLAKE); - } - spoiler(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_SPOILER); - } - strike(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_STRIKE); - } - underline(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_UNDERLINE); - } - url(): DiscordRegexPayload { - return this.match(DiscordRegexNames.TEXT_URL); - } - - match( - type: DiscordRegexNames, - onlyFirst = false - ): DiscordRegexPayload { - const regex = DiscordRegex[type]; - if (regex === undefined) { - throw new global.Error(`Unknown regex type: ${type}`); - } - regex.lastIndex = 0; - - const payload: DiscordRegexPayload = { - match: { regex, type }, - matches: [] - }; - - let match: RegExpExecArray | null = null; - while ((match = regex.exec(this.raw))) { - const result: DiscordRegexMatch = { matched: match[0], species: type }; - switch (type) { - case DiscordRegexNames.EMOJI: - { - result.name = match[1] as string; - result.id = match[2] as string; - result.animated = this.raw.startsWith('(); export const ContextMenuMsg = new Map(); export const Commands = new Map(); @@ -11,8 +12,7 @@ export const Alias = new Map(); export const Buttons = new Map(); export const SelectMenus = new Map(); - -// Courtesy @Townsy45 +// Thanks to @Townsy45 function readPath(dir: string, arrayOfFiles: string[] = []): string[] { try { const files = readdirSync(dir); @@ -30,20 +30,20 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] { export const fmtFileName = (n: string) => n.substring(0, n.length - 3); /** - * + * * @param {commandsDir} Relative path to commands directory * @returns {Promise<{ mod: Command; absPath: string; }[]>} data from command files */ -export async function buildData(commandDir: string ): Promise< +export async function buildData(commandDir: string): Promise< { mod: Module; absPath: string; }[] > { return Promise.all( - getCommands(commandDir).map( async (absPath) => { - const mod = (await import(absPath)).module; + getCommands(commandDir).map(async (absPath) => { + const mod = (await import(absPath)).module; if (mod === undefined) throw Error(`${SernError.UNDEFINED_MODULE} ${absPath}`); return { mod, absPath }; }), diff --git a/tests/functions.test.ts b/tests/functions.test.ts index e142bd2..5ac007f 100644 --- a/tests/functions.test.ts +++ b/tests/functions.test.ts @@ -1,13 +1,13 @@ -import { hasPrefix, fmt, isNotFromBot } from '../src/handler/utilities/messageHelpers'; -describe('FUNCTIONS', () => { - test('If hasPrefix is a function', () => { - expect(typeof hasPrefix).toBe('function'); - }); - test('if fmt is a function', () => { - expect(typeof fmt).toBe('function'); - }); - test('if isBot is a function', () => { - expect(typeof isNotFromBot).toBe('function'); - }); +import { hasPrefix, fmt, isFromBot } from '../src/handler/utilities/messageHelpers'; +describe('FUNCTIONS', () => { + test('If hasPrefix is a function', () => { + expect(typeof hasPrefix).toBe('function'); + }); + test('if fmt is a function', () => { + expect(typeof fmt).toBe('function'); + }); + test('if isBot is a function', () => { + expect(typeof isFromBot).toBe('function'); + }); });