diff --git a/src/handler/events/interactionCreate.ts b/src/handler/events/interactionCreate.ts index e1489cc..93edc67 100644 --- a/src/handler/events/interactionCreate.ts +++ b/src/handler/events/interactionCreate.ts @@ -8,37 +8,39 @@ import type { import { concatMap, fromEvent, Observable, of, throwError } from 'rxjs'; import type Wrapper from '../structures/wrapper'; import * as Files from '../utilities/readFile'; -import { isEventPlugin } from './readyEvent'; import { match, P } from 'ts-pattern'; import { SernError } from '../structures/errors'; import Context from '../structures/context'; import type { Result } from 'ts-results'; -import type { PluggedModule } from '../structures/modules/module'; import { CommandType, controller } from '../sern'; import type { Args } from '../../types/handler'; import type { MessageComponentInteraction } from 'discord.js'; import { ComponentType } from 'discord.js'; import type { UnionToTuple } from '../utilities/resolveParameters'; +import type { Module } from '../structures/modules/commands/module'; +import type { EventPlugin } from '../plugins/plugin'; + function isChatInputCommand(i : CommandInteraction) : i is ChatInputCommandInteraction { return i.isChatInputCommand(); } -function applicationCommandHandler(plugged: PluggedModule| undefined, interaction: CommandInteraction) { - if (plugged === undefined) { +function applicationCommandHandler(mod: Module | undefined, interaction: CommandInteraction) { + if (mod === undefined) { return throwError(() => SernError.UndefinedModule); } - const eventPlugins = plugged.plugins.filter(isEventPlugin); + const eventPlugins = mod.onEvent; + return match(interaction) .when(isChatInputCommand, i => { const ctx = Context.wrap(i); - const res = eventPlugins.map(e => { - return e.execute( + const res = eventPlugins.map(e => { + return (>e).execute( [ctx, ['slash', i.options]] , controller); }) as Awaited>[]; //Possible unsafe cast // could result in the promises not being resolved - return of({ type : plugged.mod.type, res, plugged, ctx }); + return of({ type : mod.type, res, plugged: mod, ctx }); }, ) .when( @@ -50,20 +52,20 @@ function applicationCommandHandler(plugged: PluggedModule| undefined, interactio [ctx] as UnionToTuple , controller); }) as Awaited>[]; - return of({ type : plugged.mod.type, res, plugged, ctx }); + return of({ type : mod.type, res, plugged: mod, ctx }); }, ) .run(); } function messageComponentInteractionHandler( - plugged: PluggedModule | undefined, + mod: Module | undefined, interaction: MessageComponentInteraction, ) { - if (plugged === undefined) { + if (mod === undefined) { return throwError(() => SernError.UndefinedModule); } - const eventPlugins = plugged.plugins.filter(isEventPlugin); + const eventPlugins = mod.onEvent; return match(interaction) .with({ componentType : P.union(ComponentType.Button, ComponentType.SelectMenu) @@ -71,7 +73,7 @@ function messageComponentInteractionHandler( const res = eventPlugins.map(e => { return e.execute([ctx] as UnionToTuple, controller); }) as Awaited>[]; - return of({ type : plugged.mod.type, res, plugged, ctx }); + return of({ type : mod.type, res, plugged: mod, ctx }); }) .otherwise(() => throwError( () => SernError.NotSupportedInteraction) ); } diff --git a/src/handler/events/messageEvent.ts b/src/handler/events/messageEvent.ts index 96ba638..abe60d5 100644 --- a/src/handler/events/messageEvent.ts +++ b/src/handler/events/messageEvent.ts @@ -8,7 +8,6 @@ import type Wrapper from '../structures/wrapper'; import { fmt } from '../utilities/messageHelpers'; import * as Files from '../utilities/readFile'; import { filterCorrectModule, ignoreNonBot } from './observableHandling'; -import { isEventPlugin } from './readyEvent'; export const onMessageCreate = (wrapper: Wrapper) => { const { client, defaultPrefix } = wrapper; @@ -40,25 +39,24 @@ export const onMessageCreate = (wrapper: Wrapper) => { ); const processEventPlugins$ = ensureModuleType$.pipe( - concatMap(({ ctx, args, mod: plugged }) => { - const eventPlugins = plugged.plugins.filter(isEventPlugin); + concatMap(({ ctx, args, mod }) => { const res = Promise.all( - eventPlugins.map(ePlug => { - if ((ePlug.modType & plugged.mod.type) === 0) { + mod.onEvent.map(ePlug => { + if ((ePlug.modType & mod.type) === 0) { return Err.EMPTY; } return ePlug.execute([ctx, args], controller); }), ); - return from(res).pipe(map(res => ({ plugged, ctx, args, res }))); + return from(res).pipe(map(res => ({ mod, ctx, args, res }))); }), ); - processEventPlugins$.subscribe(({ plugged, ctx, args, res }) => { + processEventPlugins$.subscribe(({ mod, ctx, args, res }) => { if (res.every(pl => pl.ok)) { - Promise.resolve(plugged.mod.execute(ctx, args)).then(() => console.log(plugged)); + Promise.resolve(mod.execute(ctx, args)).then(() => console.log(mod)); } else { - console.log(plugged, 'failed'); + console.log(mod, 'failed'); } }); }; diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index f0bc9e5..6dbc0d1 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -3,25 +3,24 @@ import { Observable, throwError } from 'rxjs'; import type { ModuleDefs } from '../structures/modules/commands/moduleHandler'; import { SernError } from '../structures/errors'; import { isNotFromBot } from '../utilities/messageHelpers'; -import type { PluggedModule } from '../structures/modules/module'; -import type { SernPlugin } from '../plugins/plugin'; +import type { Module } from '../structures/modules/commands/module'; export function correctModuleType( - plug: PluggedModule | undefined, + plug: Module | undefined, type: T, -): plug is { mod: ModuleDefs[T]; plugins: SernPlugin[] } { - return plug !== undefined && plug.mod.type === type; +): plug is ModuleDefs[T] { + return plug !== undefined && plug.type === type; } export function filterCorrectModule(cmdType: T) { - return (src: Observable) => - new Observable<{ mod: ModuleDefs[T]; plugins: SernPlugin[] }>(subscriber => { + return (src: Observable) => + new Observable(subscriber => { return src.subscribe({ - next(plug) { - if (correctModuleType(plug, cmdType)) { - subscriber.next({ mod: plug.mod, plugins: plug.plugins }); + next(mod) { + if (correctModuleType(mod, cmdType)) { + subscriber.next(mod); } else { - if (plug === undefined) { + if (mod === undefined) { return throwError(() => SernError.UndefinedModule); } return throwError(() => SernError.MismatchModule); diff --git a/src/handler/events/readyEvent.ts b/src/handler/events/readyEvent.ts index d6e7083..1cc3f7e 100644 --- a/src/handler/events/readyEvent.ts +++ b/src/handler/events/readyEvent.ts @@ -9,28 +9,26 @@ import type { ModuleType, } from '../structures/modules/commands/moduleHandler'; import { CommandType } from '../sern'; -import { CommandPlugin, EventPlugin, PluginType, SernPlugin } from '../plugins/plugin'; -import { partition } from '../utilities/partition'; +import type { PluginType } from '../plugins/plugin'; import { Err, Ok, Result } from 'ts-results'; -import type { PluggedModule } from '../structures/modules/module'; import type { Awaitable } from 'discord.js'; +import type { Module } from '../structures/modules/commands/module'; export const onReady = (wrapper: Wrapper) => { const { client, commands } = wrapper; const ready$ = fromEvent(client, 'ready').pipe(take(1), skip(1)); const processCommandFiles$ = Files.buildData(commands).pipe( - map(({ plugged, absPath }) => { - const name = plugged.mod?.name ?? Files.fmtFileName(basename(absPath)); - if (plugged.mod?.name === undefined) { - return { mod: { name, ...plugged.mod }, plugins: plugged.plugins }; + map(({ mod, absPath }) => { + const name = mod?.name ?? Files.fmtFileName(basename(absPath)); + if (mod?.name === undefined) { + return { name, ...mod } ; } - return plugged; + return mod; }), ); const processPlugins$ = processCommandFiles$.pipe( - concatMap(({ mod, plugins: allPlugins }) => { - const [cmdPlugins, eventPlugins] = partition(isCmdPlugin, allPlugins); - const cmdPluginsRes = cmdPlugins.map(plug => { + concatMap(( mod ) => { + const cmdPluginsRes = mod.plugins.map(plug => { return { ...plug, name: plug?.name ?? 'Unnamed Plugin', @@ -40,13 +38,13 @@ export const onReady = (wrapper: Wrapper) => { }), }; }); - return of({ plugged: { mod, plugins: eventPlugins }, cmdPluginsRes }); + return of({ mod , cmdPluginsRes }); }), ); ( concat(ready$, processPlugins$) as Observable<{ - plugged: PluggedModule; + mod: Module; cmdPluginsRes: { execute: Awaitable>; type: PluginType.Command; @@ -62,11 +60,10 @@ export const onReady = (wrapper: Wrapper) => { ), ), ) - .subscribe(({ plugged, cmdPluginsRes }) => { + .subscribe(({ mod, cmdPluginsRes }) => { const loadedPluginsCorrectly = cmdPluginsRes.every(res => res.execute.ok); - const { mod, plugins } = plugged; if (loadedPluginsCorrectly) { - registerModule(mod.name!, mod, plugins); + registerModule(mod.name!, mod); } else { console.log(`Failed to load command ${mod.name!}`); console.log(mod); @@ -76,40 +73,32 @@ export const onReady = (wrapper: Wrapper) => { function handler(name: string): ModuleHandlers { return { - [CommandType.Text]: (mod, plugins) => { - mod.alias.forEach(a => Files.TextCommandStore.aliases.set(a, { mod, plugins })); - Files.TextCommandStore.text.set(name, { mod, plugins }); + [CommandType.Text]: (mod) => { + mod.alias.forEach(a => Files.TextCommandStore.aliases.set(a, mod)); + Files.TextCommandStore.text.set(name, mod); }, - [CommandType.Slash]: (mod, plugins) => { - Files.ApplicationCommandStore[1].set(name, { mod, plugins }); + [CommandType.Slash]: (mod) => { + Files.ApplicationCommandStore[1].set(name, mod); }, - [CommandType.Both]: (mod, plugins) => { - Files.BothCommand.set(name, { mod, plugins }); - mod.alias.forEach(a => Files.TextCommandStore.aliases.set(a, { mod, plugins })); + [CommandType.Both]: (mod) => { + Files.BothCommand.set(name, mod); + mod.alias.forEach(a => Files.TextCommandStore.aliases.set(a, mod)); }, - [CommandType.MenuUser]: (mod, plugins) => { - Files.ApplicationCommandStore[2].set(name, { mod, plugins }); + [CommandType.MenuUser]: (mod) => { + Files.ApplicationCommandStore[2].set(name, mod); }, - [CommandType.MenuMsg]: (mod, plugins) => { - Files.ApplicationCommandStore[3].set(name, { mod, plugins }); + [CommandType.MenuMsg]: (mod) => { + Files.ApplicationCommandStore[3].set(name, mod); }, - [CommandType.Button]: (mod, plugins) => { - Files.MessageCompCommandStore[2].set(name, { mod, plugins }); + [CommandType.Button]: (mod) => { + Files.MessageCompCommandStore[2].set(name, mod); }, - [CommandType.MenuSelect]: (mod, plugins) => { - Files.MessageCompCommandStore[2].set(name, { mod, plugins }); + [CommandType.MenuSelect]: (mod) => { + Files.MessageCompCommandStore[2].set(name, mod); }, }; } -function isCmdPlugin(p: SernPlugin): p is CommandPlugin { - return (p.type & PluginType.Command) !== 0; -} - -export function isEventPlugin(p: SernPlugin): p is EventPlugin { - return (p.type & PluginType.Event) !== 0; -} - -function registerModule(name: string, mod: ModuleStates[T], plugins: SernPlugin[]) { - return (>handler(name)[mod.type])(mod, plugins); +function registerModule(name: string, mod: ModuleStates[T]) { + return (>handler(name)[mod.type])(mod); } diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index fecfeb3..765e06d 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -7,8 +7,8 @@ // The goal of plugins is to organize commands and // provide extensions to repetitive patterns // examples include refreshing modules, -// categorizing commands, cooldowns, permissions, etc -// Plugins are reminisce of middleware in express. +// categorizing commands, cooldowns, permissions, etc. +// Plugins are reminiscent of middleware in express. // import type { Awaitable, Client } from 'discord.js'; @@ -16,7 +16,7 @@ import type { Err, Ok, Result } from 'ts-results'; import type { Module, Override, Wrapper } from '../..'; import type { CommandType } from '../sern'; import type { ModuleDefs } from '../structures/modules/commands/moduleHandler'; -import type { BaseModule, PluggedModule } from '../structures/modules/module'; +import type { BaseModule } from '../structures/modules/module'; export enum PluginType { Command = 0b01, @@ -45,7 +45,7 @@ export type CommandPlugin = { //TODO: rn adding the modType check a little hackish. Find better way to determine the // module type of the event plugin -export type EventPlugin = { +export type EventPlugin = { type: PluginType.Event; modType: T; } & Override< @@ -54,16 +54,13 @@ export type EventPlugin = { execute: (event: Parameters, controller: Controller) => Awaitable>; } >; +export function plugins(...plug: CommandPlugin[]) : CommandPlugin[]; +export function plugins(...plug: EventPlugin[]): EventPlugin[]; -export type SernPlugin = CommandPlugin | EventPlugin; - -export function plugins(...plug: SernPlugin[]) { +export function plugins(...plug : CommandPlugin[] | EventPlugin[]) { return plug; } -export function sernModule(plugins: SernPlugin[] , mod: Module): PluggedModule { - return { - mod, - plugins, - }; +export function sernModule(mod: Module): Module { + return mod; } diff --git a/src/handler/structures/modules/commands/module.ts b/src/handler/structures/modules/commands/module.ts index 9ee8528..6518ce0 100644 --- a/src/handler/structures/modules/commands/module.ts +++ b/src/handler/structures/modules/commands/module.ts @@ -9,37 +9,53 @@ import type { Override } from '../../../../types/handler'; import type { CommandType } from '../../../sern'; import type { BaseModule } from '../module'; import type { UserContextMenuCommandInteraction } from 'discord.js'; +import type { CommandPlugin, EventPlugin } from '../../../plugins/plugin'; -//possible refactoring to interfaces and not types +//possible refactoring types into interfaces and not types export type TextCommand = { type: CommandType.Text; + onEvent : EventPlugin[]; + plugins : CommandPlugin[]; alias: string[] | []; } & BaseModule; export type SlashCommand = { type: CommandType.Slash; + onEvent : EventPlugin[]; + plugins : CommandPlugin[]; options: ApplicationCommandOptionData[] | []; } & BaseModule; export type BothCommand = { type: CommandType.Both; + onEvent : EventPlugin[] + plugins : CommandPlugin[] alias: string[] | []; options: ApplicationCommandOptionData[] | []; } & BaseModule; export type ContextMenuUser = { type: CommandType.MenuUser; + onEvent : EventPlugin[]; + plugins : CommandPlugin[]; } & Override Awaitable }>; export type ContextMenuMsg = { type: CommandType.MenuMsg; + onEvent : EventPlugin[]; + plugins : CommandPlugin[]; } & Override Awaitable }>; export type ButtonCommand = { type: CommandType.Button; + onEvent : EventPlugin[]; + plugins : CommandPlugin[]; } & Override Awaitable }>; + export type SelectMenuCommand = { type: CommandType.MenuSelect; + onEvent : EventPlugin[]; + plugins :CommandPlugin[]; } & Override Awaitable }>; export type Module = diff --git a/src/handler/structures/modules/commands/moduleHandler.ts b/src/handler/structures/modules/commands/moduleHandler.ts index a8ecfbb..71be268 100644 --- a/src/handler/structures/modules/commands/moduleHandler.ts +++ b/src/handler/structures/modules/commands/moduleHandler.ts @@ -1,4 +1,3 @@ -import type { SernPlugin } from '../../../plugins/plugin'; import { CommandType } from '../../../sern'; import type { BothCommand, @@ -28,6 +27,6 @@ export type ModuleStates = { [K in ModuleType]: { type: K } & ModuleDefs[K]; }; // A handler callback that is called on each ModuleDef -export type HandlerCallback = (mod: ModuleStates[K], plugins: SernPlugin[]) => unknown; +export type HandlerCallback = (mod: ModuleStates[K]) => unknown; //An object that acts as the mapped object to handler export type ModuleHandlers = { [K in ModuleType]: HandlerCallback }; diff --git a/src/handler/structures/modules/module.ts b/src/handler/structures/modules/module.ts index 80e04d7..9cac8e6 100644 --- a/src/handler/structures/modules/module.ts +++ b/src/handler/structures/modules/module.ts @@ -1,6 +1,5 @@ import type { Awaitable } from 'discord.js'; -import type { Args, Module } from '../../..'; -import type { SernPlugin } from '../../plugins/plugin'; +import type { Args } from '../../..'; import type Context from '../context'; export interface BaseModule { @@ -8,8 +7,3 @@ export interface BaseModule { description: string; execute: (ctx: Context, args: Args) => Awaitable; } - -export interface PluggedModule { - mod: Module; - plugins: SernPlugin[]; -} diff --git a/src/handler/utilities/readFile.ts b/src/handler/utilities/readFile.ts index 676ca73..559c326 100644 --- a/src/handler/utilities/readFile.ts +++ b/src/handler/utilities/readFile.ts @@ -2,23 +2,23 @@ import { ApplicationCommandType, ComponentType } from 'discord.js'; import { readdirSync, statSync } from 'fs'; import { join } from 'path'; import { from, Observable } from 'rxjs'; -import type { PluggedModule } from '../structures/modules/module'; +import type { Module } from '../structures/modules/commands/module'; -export const BothCommand = new Map(); +export const BothCommand = new Map(); export const ApplicationCommandStore = { - [ApplicationCommandType.User]: new Map(), - [ApplicationCommandType.Message]: new Map(), - [ApplicationCommandType.ChatInput]: new Map(), -} as { [K in ApplicationCommandType]: Map }; + [ApplicationCommandType.User]: new Map(), + [ApplicationCommandType.Message]: new Map(), + [ApplicationCommandType.ChatInput]: new Map(), +} as { [K in ApplicationCommandType]: Map }; export const MessageCompCommandStore = { - [ComponentType.Button]: new Map(), - [ComponentType.SelectMenu]: new Map(), - [ComponentType.TextInput] : new Map() + [ComponentType.Button]: new Map(), + [ComponentType.SelectMenu]: new Map(), + [ComponentType.TextInput] : new Map() }; export const TextCommandStore = { - text: new Map(), - aliases: new Map(), + text: new Map(), + aliases: new Map(), }; // Courtesy @Townsy45 @@ -40,19 +40,19 @@ export const fmtFileName = (n: string) => n.substring(0, n.length - 3); /** * - * @returns {Observable<{ mod: PluggedModule; absPath: string; }[]>} data from command files + * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files * @param commandDir */ export function buildData(commandDir: string): Observable<{ - plugged: PluggedModule; + mod: Module; absPath: string; }> { return from( getCommands(commandDir).map(absPath => { // eslint-disable-next-line @typescript-eslint/no-var-requires - const plugged = require(absPath).module; - return { plugged, absPath }; + const mod = require(absPath).module; + return { mod, absPath }; }), ); }