From e21508ca4f9b777ddcae26122b88585fd8ef9378 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:40:51 -0500 Subject: [PATCH] feat(handler) moving and organizing files, refactor context api --- package-lock.json | 12 +-- src/handler/events/interactionCreate.ts | 16 ++-- src/handler/events/messageEvent.ts | 6 +- src/handler/events/observableHandling.ts | 6 +- src/handler/events/readyEvent.ts | 8 +- src/handler/sern.ts | 3 + src/handler/structures/commands/module.ts | 53 ------------ src/handler/structures/context.ts | 81 ++++++++----------- .../structures/modules/commands/module.ts | 45 +++++++++++ .../{ => modules}/commands/moduleHandler.ts | 2 +- src/handler/structures/modules/module.ts | 13 +++ src/handler/structures/structxports.ts | 2 +- src/handler/structures/wrapper.ts | 1 - src/handler/utilities/readFile.ts | 3 +- 14 files changed, 120 insertions(+), 131 deletions(-) delete mode 100644 src/handler/structures/commands/module.ts create mode 100644 src/handler/structures/modules/commands/module.ts rename src/handler/structures/{ => modules}/commands/moduleHandler.ts (96%) create mode 100644 src/handler/structures/modules/module.ts diff --git a/package-lock.json b/package-lock.json index 7c1e5b3..4c10a54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6879,9 +6879,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minimist-options": { @@ -14100,9 +14100,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minimist-options": { diff --git a/src/handler/events/interactionCreate.ts b/src/handler/events/interactionCreate.ts index 3a55e4b..e44152c 100644 --- a/src/handler/events/interactionCreate.ts +++ b/src/handler/events/interactionCreate.ts @@ -6,12 +6,14 @@ 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'; export const onInteractionCreate = ( wrapper : Wrapper ) => { const { client } = wrapper; (> fromEvent(client, 'interactionCreate')) - .pipe( + .pipe( + filter( i => i.inGuild() ), concatMap ( interaction => { if (interaction.isChatInputCommand()) { return of(Files.Commands.get(interaction.commandName)) @@ -26,8 +28,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => { return of(Files.ContextMenuUser.get(interaction.commandName)) .pipe( filterTap(CommandType.MENU_USER, mod => { - const ctx = Context.wrap(interaction); - mod.execute(ctx); + mod.execute(interaction); }), ); } @@ -35,8 +36,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => { return of(Files.ContextMenuMsg.get(interaction.commandName)) .pipe( filterTap(CommandType.MENU_MSG, mod => { - const ctx = Context.wrap(interaction); - mod.execute(ctx); + mod.execute(interaction); }), ); } @@ -44,8 +44,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => { return of(Files.Buttons.get(interaction.customId)) .pipe( filterTap(CommandType.BUTTON, mod => { - const ctx = Context.wrap(interaction); - mod.execute(ctx); + mod.execute(interaction); }) ); } @@ -53,8 +52,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => { return of(Files.SelectMenus.get(interaction.customId)) .pipe( filterTap(CommandType.MENU_SELECT, mod => { - const ctx = Context.wrap(interaction); - mod.execute(ctx); + mod.execute(interaction); }) ); } diff --git a/src/handler/events/messageEvent.ts b/src/handler/events/messageEvent.ts index b123ff1..d2ee6b6 100644 --- a/src/handler/events/messageEvent.ts +++ b/src/handler/events/messageEvent.ts @@ -1,4 +1,4 @@ -import type { Message } from 'discord.js'; +import type { ChatInputCommandInteraction, Message } from 'discord.js'; import { fromEvent, Observable, of, concatMap } from 'rxjs'; import { CommandType } from '../sern'; import Context from '../structures/context'; @@ -15,14 +15,14 @@ export const onMessageCreate = (wrapper : Wrapper) => { concatMap ( m => { const [ prefix, ...data ] = fmt(m, defaultPrefix); const posMod = Files.Commands.get(prefix) ?? Files.Alias.get(prefix); - const ctx = Context.wrap(m); return of( posMod ) .pipe ( filterTap(CommandType.TEXT, mod => { + const ctx = Context.wrap(m); mod.execute(ctx, ['text', data]); }) - ); + ); }) ).subscribe ({ error(e) { diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index b1e1e4b..d5892b8 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -2,7 +2,7 @@ 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/commands/moduleHandler'; +import type { ModuleDefs } from '../structures/modules/commands/moduleHandler'; import { SernError } from '../structures/errors'; import { isNotFromBot, isNotFromDM } from '../utilities/messageHelpers'; @@ -16,7 +16,7 @@ export function filterTap( return (src : Observable) => new Observable( subscriber => { return src.subscribe({ - next(modul ) { + next(modul) { if(match(modul, cmdType)) { const asModT = modul; tap(asModT); @@ -31,9 +31,9 @@ export function filterTap( error: (e) => subscriber.error(e), complete: () => subscriber.complete() }); - }); } + export function ignoreNonBot(prefix : string) { return (src : Observable) => new Observable(subscriber => { diff --git a/src/handler/events/readyEvent.ts b/src/handler/events/readyEvent.ts index e52de92..b389f94 100644 --- a/src/handler/events/readyEvent.ts +++ b/src/handler/events/readyEvent.ts @@ -1,13 +1,13 @@ -import { concatMap, first, from, fromEvent, pipe, tap } from 'rxjs'; +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/commands/moduleHandler'; +import type { HandlerCallback, ModuleHandlers, ModuleStates, ModuleType } from '../structures/modules/commands/moduleHandler'; import { CommandType } from '../sern'; export const onReady = ( wrapper : Wrapper ) => { - const { client, init, commands, } = wrapper; + const { client, init, commands } = wrapper; fromEvent(client, 'ready') .pipe(first()) .subscribe(() => { @@ -54,7 +54,7 @@ function setCommands ( { mod, absPath } : { mod : Module, absPath : string } ) { registerModules(name, mod); } -async function createCommandCache( +function createCommandCache( arr: {mod: Module, absPath: string}[] ) { from(arr).subscribe ( setCommands ); diff --git a/src/handler/sern.ts b/src/handler/sern.ts index c7144bc..c9ed8ad 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -27,6 +27,8 @@ function eventObserver(client: Client, events: DiscordEvent[] ) { fromEvent(client, event, cb).subscribe(); }); } + + /** * @enum { number }; */ @@ -40,3 +42,4 @@ export enum CommandType { BOTH = 0b000011, ANY = 0b111111 } + diff --git a/src/handler/structures/commands/module.ts b/src/handler/structures/commands/module.ts deleted file mode 100644 index f0683e4..0000000 --- a/src/handler/structures/commands/module.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ChatInputCommandInteraction, ContextMenuCommandInteraction, Interaction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js'; -import type { Args, Override } from '../../../types/handler'; -import type { CommandType } from '../../sern'; -import type Context from '../context'; - -type executeSlash = { execute : (ctx : Context, args: Args) => Awaitable }; - -export interface BaseModule { - name? : string; - description : string; - execute: (ctx: Context, args: Args) => Awaitable; -} -export type TextCommand = { - type : CommandType.TEXT; - alias : string[] | [], -} & BaseModule; - -export type SlashCommand = { - type : CommandType.SLASH; - options : ApplicationCommandOptionData[] | [], -} & Override; - -export type BothCommand = { - type : CommandType.BOTH; - alias : string[] | []; - options : ApplicationCommandOptionData[] | [], -} & Override; - -export type ContextMenuUser = { - type : CommandType.MENU_USER; -} & Override ) => Awaitable }>; - -export type ContextMenuMsg = { - type : CommandType.MENU_MSG; -} & Override ) => Awaitable }>; -export type ButtonCommand = { - type : CommandType.BUTTON; -} & Override ) => Awaitable }>; - -export type SelectMenuCommand = { - type : CommandType.MENU_SELECT; -} & Override ) => Awaitable }>; - - -export type Module = - TextCommand - | SlashCommand - | BothCommand - | ContextMenuUser - | ContextMenuMsg - | ButtonCommand - | SelectMenuCommand; - diff --git a/src/handler/structures/context.ts b/src/handler/structures/context.ts index e34a7a9..b95ae3e 100644 --- a/src/handler/structures/context.ts +++ b/src/handler/structures/context.ts @@ -1,9 +1,8 @@ -import type { APIInteractionGuildMember } from 'discord-api-types/v9'; import type { Awaitable, + ChatInputCommandInteraction, Guild, GuildMember, - Interaction, Message, Snowflake, TextBasedChannel, @@ -18,21 +17,22 @@ function firstSome(...args : Option[]) : Nullish { } return null; } - -export default class Context { +export default class Context { private constructor( private oMsg: Option = None, - private oInterac: Option = None + private oInterac: Option = None ) { this.oMsg = oMsg; this.oInterac = oInterac; } - static wrap(wrappable: I|Message) : Context { + static wrap( + wrappable: ChatInputCommandInteraction|Message + ) : Context { if ( 'token' in wrappable ) { - return new Context( None, Some(wrappable)); + return new Context( None, Some(wrappable)); } - return new Context(Some(wrappable), None); + return new Context(Some(wrappable), None); } public isEmpty() { return this.oMsg.none && this.oInterac.none; @@ -44,18 +44,6 @@ export default class Context { return this.oInterac.unwrap(); } - /** - * maps a general Context to Context - * if interaction is None return Context.empty() - */ - - public mapInteraction( - cb : ( ctx: I ) => Context - ) : Context { - if (this.oInterac.none) return new Context(); - return cb(this.oInterac.val); - } - public get id() : Snowflake { return firstSome( this.oInterac.map( i => i.id), @@ -68,11 +56,11 @@ export default class Context { this.oInterac.map(i => i.channel) ); } - public get user(): Nullish { + public get user(): User { return firstSome( this.oMsg.map(m => m.author), this.oInterac.map(i => i.user) - ); + )!; } public get createdTimestamp() : number { return firstSome( @@ -81,56 +69,51 @@ export default class Context { )!; } - public get guild() : Nullish { + public get guild() : Guild { return firstSome( - this.oMsg.map(m => m.guild), + this.oMsg.map(m => m.guild!), this.oInterac.map(i => i.guild) - ); + )!; } - public get guildId() : Nullish { + public get guildId() : Snowflake { return firstSome( this.oMsg.map(m => m.guildId), this.oInterac.map(i => i.guildId) - ); + )!; } - public get member() : Nullish { + public get member() : Nullish { return firstSome( - this.oMsg.map(m => m.member), - this.oInterac.map(i => i.member) + 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 on ( - onInteraction : ( interaction : I ) => Awaitable, - onMsg? : (message : Message) => Awaitable - ): Context { + public onInteraction( + onInteraction : ( interaction : ChatInputCommandInteraction ) => Awaitable, + ): Context { this.oInterac.map(onInteraction); - this.oMsg.map(m => onMsg?.(m)); return this; } - public extractInteraction( - extract : (interaction : I) => T + 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 extractMessage( - extract : (message: Message) => T - ): Nullish { + public takeMessageValue( + extract : (message: Message) => T + ): Nullish { if(this.oMsg.none) return null; return extract(this.oMsg.val); } - // extract either - public extractEither( - i : (interaction : I) => T, - m : (message : Message ) => V - ) { - const iExtract = this.extractMessage(m); - const mExtract = this.extractInteraction(i); - return iExtract ?? mExtract; - } } diff --git a/src/handler/structures/modules/commands/module.ts b/src/handler/structures/modules/commands/module.ts new file mode 100644 index 0000000..3314784 --- /dev/null +++ b/src/handler/structures/modules/commands/module.ts @@ -0,0 +1,45 @@ +import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ContextMenuCommandInteraction, MessageContextMenuCommandInteraction, 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 +export type TextCommand = { + type : CommandType.TEXT; + alias : string[] | [], +} & BaseModule; + +export type SlashCommand = { + type : CommandType.SLASH; + options : ApplicationCommandOptionData[] | [], +} & BaseModule; + +export type BothCommand = { + type : CommandType.BOTH; + alias : string[] | []; + options : ApplicationCommandOptionData[] | [], +} & BaseModule; + +export type ContextMenuUser = { + type : CommandType.MENU_USER; +} & Override Awaitable }>; +export type ContextMenuMsg = { + type : CommandType.MENU_MSG; +} & Override Awaitable }>; +export type ButtonCommand = { + type : CommandType.BUTTON; +} & Override Awaitable }>; +export type SelectMenuCommand = { + type : CommandType.MENU_SELECT; +} & Override Awaitable }>; + + +export type Module = + TextCommand + | SlashCommand + | BothCommand + | ContextMenuUser + | ContextMenuMsg + | ButtonCommand + | SelectMenuCommand; + diff --git a/src/handler/structures/commands/moduleHandler.ts b/src/handler/structures/modules/commands/moduleHandler.ts similarity index 96% rename from src/handler/structures/commands/moduleHandler.ts rename to src/handler/structures/modules/commands/moduleHandler.ts index 13ced06..caf1e01 100644 --- a/src/handler/structures/commands/moduleHandler.ts +++ b/src/handler/structures/modules/commands/moduleHandler.ts @@ -1,4 +1,4 @@ -import { CommandType } from '../../sern'; +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 diff --git a/src/handler/structures/modules/module.ts b/src/handler/structures/modules/module.ts new file mode 100644 index 0000000..606ef62 --- /dev/null +++ b/src/handler/structures/modules/module.ts @@ -0,0 +1,13 @@ +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; +} + + + + diff --git a/src/handler/structures/structxports.ts b/src/handler/structures/structxports.ts index 57033d3..1ae6997 100644 --- a/src/handler/structures/structxports.ts +++ b/src/handler/structures/structxports.ts @@ -1,5 +1,5 @@ import Context from './context'; -import type { SlashCommand, TextCommand, BothCommand, Module } from './commands/module'; +import type { SlashCommand, TextCommand, BothCommand, Module } from '../structures/modules/commands/module'; import type Wrapper from './wrapper'; export { diff --git a/src/handler/structures/wrapper.ts b/src/handler/structures/wrapper.ts index 34dc3eb..54168d1 100644 --- a/src/handler/structures/wrapper.ts +++ b/src/handler/structures/wrapper.ts @@ -14,7 +14,6 @@ interface Wrapper { readonly client: Client; readonly defaultPrefix: string; readonly commands: string; - readonly components : string; init?: (handler: Wrapper) => void; readonly events? : DiscordEvent[]; } diff --git a/src/handler/utilities/readFile.ts b/src/handler/utilities/readFile.ts index 6054f58..f933feb 100644 --- a/src/handler/utilities/readFile.ts +++ b/src/handler/utilities/readFile.ts @@ -1,6 +1,6 @@ import { readdirSync, statSync } from 'fs'; import { join } from 'path'; -import type { Module } from '../structures/commands/module'; +import type { Module } from '../structures/modules/commands/module'; import { SernError } from '../structures/errors'; //We can look into lazily loading modules once everything is set @@ -11,6 +11,7 @@ export const Alias = new Map(); export const Buttons = new Map(); export const SelectMenus = new Map(); + // Courtesy @Townsy45 function readPath(dir: string, arrayOfFiles: string[] = []): string[] { try {