From f213e88a5d450ed366d772c53afbcdabb802139f Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 13 Apr 2022 01:34:25 -0500 Subject: [PATCH] feat(handler) more plugin work, refactoring rxjs pipes --- src/handler/events/interactionCreate.ts | 2 +- src/handler/events/observableHandling.ts | 18 +-- src/handler/events/readyEvent.ts | 128 ++++++++---------- src/handler/plugins/defaultPlugins.ts | 23 ++++ src/handler/plugins/plugin.ts | 33 ++++- .../structures/modules/commands/module.ts | 12 +- .../modules/commands/moduleHandler.ts | 22 +-- src/handler/structures/modules/module.ts | 18 --- src/handler/utilities/readFile.ts | 16 +-- 9 files changed, 139 insertions(+), 133 deletions(-) create mode 100644 src/handler/plugins/defaultPlugins.ts diff --git a/src/handler/events/interactionCreate.ts b/src/handler/events/interactionCreate.ts index cae3387..3768bed 100644 --- a/src/handler/events/interactionCreate.ts +++ b/src/handler/events/interactionCreate.ts @@ -62,7 +62,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => { }, next(command) { //log on each command emitted - console.log(command?.name); + console.log(command); }, }); }; diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts index ca650b8..ec49434 100644 --- a/src/handler/events/observableHandling.ts +++ b/src/handler/events/observableHandling.ts @@ -5,22 +5,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'; -export function match(mod: Module | undefined, type : CommandType) : boolean { - return mod !== undefined && (mod.type & type) != 0; +export function match(plug: PluggedModule | undefined, type : CommandType) : boolean { + return plug !== undefined && (plug.mod.type & type) != 0; } export function filterTap( cmdType : T, - tap: (mod : ModuleDefs[T]) => Awaitable + tap: (mod : ModuleDefs[T], plugins : SernPlugin[]) => Awaitable ) { - return (src : Observable) => - new Observable( subscriber => { + return (src : Observable) => + new Observable( subscriber => { return src.subscribe({ next(modul) { if(match(modul, cmdType)) { - const asModT = modul; - tap(asModT); - subscriber.next(asModT); + const asModT = modul!.mod; + tap(asModT, modul!.plugins); + subscriber.next(modul); } else { if (modul === undefined) { return throwError(() => SernError.UndefinedModule); diff --git a/src/handler/events/readyEvent.ts b/src/handler/events/readyEvent.ts index c94231c..4d27ebe 100644 --- a/src/handler/events/readyEvent.ts +++ b/src/handler/events/readyEvent.ts @@ -1,102 +1,84 @@ -import { concatMap, first, from, fromEvent, map, tap } from 'rxjs'; -import { basename } from 'path'; +import { from, fromEvent, map, take,concat, concatAll, mergeMap, skip} from 'rxjs'; import { basename } from 'path'; import * as Files from '../utilities/readFile'; import type Wrapper from '../structures/wrapper'; import type { HandlerCallback, ModuleHandlers, ModuleStates, ModuleType } from '../structures/modules/commands/moduleHandler'; import { CommandType } from '../sern'; -import type { PluggedModule } from '../structures/modules/module'; import type { CommandPlugin, SernPlugin } from '../plugins/plugin'; import { partition } from './observableHandling'; -import type { Client } from 'discord.js'; import { Err, Ok } from 'ts-results'; +import type { PluggedModule } from '../structures/modules/module'; export const onReady = ( wrapper : Wrapper ) => { const { client, commands } = wrapper; - fromEvent(client, 'ready') - .pipe( - take(1), - concatMap ( _ => { - - - - }) - ) - .subscribe(() => { - Files.buildData( commands ) - .then( deployCommands(client) ); - }) -}; + const ready$ = fromEvent(client, 'ready').pipe(take(1)); + const processCommandFiles$ = from(Files.buildData(commands)).pipe( + concatAll(), + 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 }; + } + return plugged; + }), + mergeMap(({ mod, plugins: allPlugins }) => { + const [ cmdPlugins, plugins ] = partition(allPlugins, isCmdPlugin); + return cmdPlugins.map(pl => { + const res = pl.execute(client, mod, { + next: () => Ok.EMPTY, + stop: () => Err.EMPTY + }) + return { res, plugged : { mod, plugins } } + }) + }), + ); + concat(ready$.pipe(skip(1)),processCommandFiles$) + .subscribe( _ => { + if(a.ok) { + registerModule(mod.name!, mod, plugins) + } else { + + } + }) +} // 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.Text] : (mod, plugins) => { + mod.alias.forEach ( a => Files.Alias.set(a,{ mod, plugins})); + Files.Commands.set( name, { mod, plugins } ); }, - [CommandType.Slash]: mod => { - Files.Commands.set( name , mod); + [CommandType.Slash]: (mod, plugins) => { + Files.Commands.set( name , { mod, plugins }); }, - [CommandType.Both] : mod => { - Files.Commands.set ( name, mod); - mod.alias.forEach (a => Files.Alias.set(a, mod)); + [CommandType.Both] :( mod, plugins )=> { + Files.Commands.set ( name,{ mod, plugins}); + mod.alias.forEach (a => Files.Alias.set(a, {mod,plugins})); }, - [CommandType.MenuUser] : mod => { - Files.ContextMenuUser.set ( name, mod ); + [CommandType.MenuUser] : (mod, plugins) => { + Files.ContextMenuUser.set ( name, {mod, plugins} ); }, - [CommandType.MenuMsg] : mod => { - Files.ContextMenuMsg.set (name, mod ); + [CommandType.MenuMsg] : (mod,plugins) => { + Files.ContextMenuMsg.set (name, {mod, plugins} ); }, - [CommandType.Button] : mod => { - Files.Buttons.set(name, mod); + [CommandType.Button] : (mod,plugins) => { + Files.Buttons.set(name, {mod, plugins}); }, - [CommandType.MenuSelect] : mod => { - Files.SelectMenus.set(name, mod); + [CommandType.MenuSelect] : ( mod, plugins ) => { + Files.SelectMenus.set(name, { mod, plugins }); }, } as ModuleHandlers); -const registerModules = (name : string, mod : ModuleStates[T]) => - (> handler(name)[mod.type])(mod); - -function setCommands ( plugged : PluggedModule ) { - registerModules(plugged.mod.name!, plugged.mod); -} - -function deployCommands (wrapper : Client) { - - return function (arr : { plugged : PluggedModule, absPath : string}[]) { - from(arr) - .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 }; - } - return plugged; - }), - concatMap( ({ plugins, mod} ) => { - const [ cmdPlugins, eventPlugins ] = partition(plugins, isCmdPlugin); - - return from(cmdPlugins) - .pipe( - - - ) - }), - tap (plug => deployPlugins(plug, wrapper)), - tap ( setCommands ), - ).subscribe ( ); - } +function registerModule ( + name : string, + mod : ModuleStates[T], + plugins : SernPlugin[] +) { + return (> handler(name)[mod.type])(mod, plugins); } function isCmdPlugin ( p : SernPlugin) : p is CommandPlugin { return (p.type & 0) !== 0; } -// 0b0 -// 0b0 -function deployPlugins(plugged: PluggedModule, client : Client) { - const { plugins, mod } = plugged; - const [ cmdPlugins, eventPlugins ] = partition(plugins, isCmdPlugin) - -} + diff --git a/src/handler/plugins/defaultPlugins.ts b/src/handler/plugins/defaultPlugins.ts new file mode 100644 index 0000000..4ce781f --- /dev/null +++ b/src/handler/plugins/defaultPlugins.ts @@ -0,0 +1,23 @@ +import type { Client } from "discord.js"; +import type { Module } from "../.."; +import { CommandPlugin, Controller, PluginType } from "./plugin"; + +export function reload( + data : { guildId: string, applicationId: string } +) : CommandPlugin { + + return { + type : PluginType.Command, + name : 'Refresh', + description : 'Will reload the command this plugin is applied to', + async execute(client : Client, module: Module, controller : Controller ) { + const curGuild = await client.guilds.fetch(data.guildId); + await curGuild.commands.edit(data.applicationId, { + name : module.name!, + description: module.description, + }) + return controller.next() + } + } +} + diff --git a/src/handler/plugins/plugin.ts b/src/handler/plugins/plugin.ts index bc94b55..df9b672 100644 --- a/src/handler/plugins/plugin.ts +++ b/src/handler/plugins/plugin.ts @@ -10,19 +10,18 @@ // categorizing commands, cooldowns, permissions, etc // Plugins are reminisce of middleware in express. // -// -import type { Client } from "discord.js"; +import type { Awaitable, Client } from "discord.js"; import type { Err, Ok, Result } from "ts-results"; -import type { Override, Wrapper } from "../.."; -import type { BaseModule } from "../structures/modules/module"; +import type { Module, Override, Wrapper } from "../.."; +import type { BaseModule, PluggedModule } from "../structures/modules/module"; export enum PluginType { Command = 0b00, Event = 0b01 } -interface Controller { +export interface Controller { next : () => Ok, stop : () => Err @@ -37,7 +36,9 @@ interface BasePlugin extends Override{ export type CommandPlugin = { type : PluginType.Command } & Override Result + execute : ( + wrapper:Client, module : Module, controller:Controller + ) => Awaitable> }>; export type EventPlugin = { @@ -48,7 +49,25 @@ export type SernPlugin = CommandPlugin | EventPlugin; - +export function commmand(plug : CommandPlugin) { + return plug; +} + +export function event(plug : EventPlugin) { + return plug; +} + +export function apply(...plugins: SernPlugin[]) { + return plugins; +} + +export function sernModule + (plugins : SernPlugin[], mod : Module ) : PluggedModule { + return { + mod, + plugins + } +} diff --git a/src/handler/structures/modules/commands/module.ts b/src/handler/structures/modules/commands/module.ts index 1443ec2..006a614 100644 --- a/src/handler/structures/modules/commands/module.ts +++ b/src/handler/structures/modules/commands/module.ts @@ -1,12 +1,10 @@ -import type { ApplicationCommandOptionData, AutocompleteInteraction, Awaitable, ButtonInteraction, ContextMenuCommandInteraction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js'; +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'; -type AutoComp = { - update : (ctx : AutocompleteInteraction) => Awaitable -} + //possible refactoring to interfaces and not types export type TextCommand = { type : CommandType.Text; @@ -38,12 +36,12 @@ export type SelectMenuCommand = { } & Override Awaitable }>; -export type Module = +export type Module = ( TextCommand | SlashCommand | BothCommand | ContextMenuUser | ContextMenuMsg | ButtonCommand - | SelectMenuCommand; - + | SelectMenuCommand +); diff --git a/src/handler/structures/modules/commands/moduleHandler.ts b/src/handler/structures/modules/commands/moduleHandler.ts index 07480e1..837de72 100644 --- a/src/handler/structures/modules/commands/moduleHandler.ts +++ b/src/handler/structures/modules/commands/moduleHandler.ts @@ -1,3 +1,4 @@ +import type { EventPlugin, SernPlugin } from '../../../plugins/plugin'; import { CommandType } from '../../../sern'; import type { TextCommand, @@ -9,24 +10,25 @@ import type { 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.MenuMsg] : ContextMenuMsg, - [CommandType.MenuUser] : ContextMenuUser, - [CommandType.Button] : ButtonCommand, - [CommandType.MenuSelect] : SelectMenuCommand, + [CommandType.Text] : TextCommand; + [CommandType.Slash] : SlashCommand; + [CommandType.Both] : BothCommand; + [CommandType.MenuMsg] : ContextMenuMsg; + [CommandType.MenuUser] : ContextMenuUser; + [CommandType.Button] : ButtonCommand; + [CommandType.MenuSelect] : 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] }; +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 HandlerCallback = + ( mod: ModuleStates[K], plugins : SernPlugin[] ) => 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 cf05e3a..74c91ca 100644 --- a/src/handler/structures/modules/module.ts +++ b/src/handler/structures/modules/module.ts @@ -14,22 +14,4 @@ export interface PluggedModule { plugins : SernPlugin[]; } -export function commmand ( plug : CommandPlugin ) { - return plug; -} -export function event( plug : EventPlugin ) { - return plug; -} - -export function apply(...plugins: SernPlugin[]) { - return plugins; -} - -export function sernModule - (plugins : SernPlugin[], mod : Module, ) : PluggedModule { - return { - mod, - plugins - } -} diff --git a/src/handler/utilities/readFile.ts b/src/handler/utilities/readFile.ts index 8ebebc1..bbcede0 100644 --- a/src/handler/utilities/readFile.ts +++ b/src/handler/utilities/readFile.ts @@ -1,16 +1,15 @@ import { readdirSync, statSync } from 'fs'; import { join } from 'path'; -import type { Module } from '../structures/modules/commands/module'; import { SernError } from '../structures/errors'; import type { PluggedModule } from '../structures/modules/module'; //We can look into lazily loading modules once everything is set -export const ContextMenuUser = new Map(); -export const ContextMenuMsg = new Map(); -export const Commands = new Map(); -export const Alias = new Map(); -export const Buttons = new Map(); -export const SelectMenus = new Map(); +export const ContextMenuUser = new Map(); +export const ContextMenuMsg = new Map(); +export const Commands = new Map(); +export const Alias = new Map(); +export const Buttons = new Map(); +export const SelectMenus = new Map(); // Courtesy @Townsy45 @@ -40,8 +39,7 @@ export async function buildData(commandDir: string ): Promise< { plugged: PluggedModule; absPath: string; - }[] -> { + }[]> { return Promise.all( getCommands(commandDir).map( async (absPath) => { const plugged = (await import(absPath)).module;