feat(handler) more plugin work, refactoring rxjs pipes

This commit is contained in:
Jacob Nguyen
2022-04-13 01:34:25 -05:00
parent de7ddc390f
commit f213e88a5d
9 changed files with 139 additions and 133 deletions

View File

@@ -62,7 +62,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => {
},
next(command) {
//log on each command emitted
console.log(command?.name);
console.log(command);
},
});
};

View File

@@ -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<T extends keyof ModuleDefs>(
cmdType : T,
tap: (mod : ModuleDefs[T]) => Awaitable<void>
tap: (mod : ModuleDefs[T], plugins : SernPlugin[]) => Awaitable<void>
) {
return (src : Observable<Module|undefined>) =>
new Observable<Module|undefined>( subscriber => {
return (src : Observable<PluggedModule|undefined>) =>
new Observable<PluggedModule|undefined>( subscriber => {
return src.subscribe({
next(modul) {
if(match(modul, cmdType)) {
const asModT = <ModuleDefs[T]> modul;
tap(asModT);
subscriber.next(asModT);
const asModT = <ModuleDefs[T]> modul!.mod;
tap(asModT, modul!.plugins);
subscriber.next(modul);
} else {
if (modul === undefined) {
return throwError(() => SernError.UndefinedModule);

View File

@@ -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 : <PluggedModule>{ 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 = <T extends ModuleType >(name : string, mod : ModuleStates[T]) =>
(<HandlerCallback<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 <T extends ModuleType> (
name : string,
mod : ModuleStates[T],
plugins : SernPlugin[]
) {
return (<HandlerCallback<T>> 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)
}

View File

@@ -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()
}
}
}

View File

@@ -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<void>,
stop : () => Err<void>
@@ -37,7 +36,9 @@ interface BasePlugin extends Override<BaseModule, executeCmdPlugin>{
export type CommandPlugin = {
type : PluginType.Command
} & Override<BasePlugin, {
execute : (wrapper:Client, controller:Controller) => Result<void,void>
execute : (
wrapper:Client, module : Module, controller:Controller
) => Awaitable<Result<void,void>>
}>;
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
}
}

View File

@@ -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<void>
}
//possible refactoring to interfaces and not types
export type TextCommand = {
type : CommandType.Text;
@@ -38,12 +36,12 @@ export type SelectMenuCommand = {
} & Override<BaseModule, { execute : (ctx : SelectMenuInteraction ) => Awaitable<void> }>;
export type Module =
export type Module = (
TextCommand
| SlashCommand
| BothCommand
| ContextMenuUser
| ContextMenuMsg
| ButtonCommand
| SelectMenuCommand;
| SelectMenuCommand
);

View File

@@ -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<K extends ModuleType> = ( params : ModuleStates[K] ) => unknown;
export type HandlerCallback<K extends ModuleType> =
( mod: ModuleStates[K], plugins : SernPlugin[] ) => unknown;
//An object that acts as the mapped object to handler
export type ModuleHandlers = { [K in ModuleType] : HandlerCallback<K> };

View File

@@ -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
}
}

View File

@@ -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<string, Module>();
export const ContextMenuMsg = new Map<string, Module>();
export const Commands = new Map<string, Module>();
export const Alias = new Map<string, Module>();
export const Buttons = new Map<string, Module>();
export const SelectMenus = new Map<string, Module>();
export const ContextMenuUser = new Map<string, PluggedModule>();
export const ContextMenuMsg = new Map<string, PluggedModule>();
export const Commands = new Map<string, PluggedModule>();
export const Alias = new Map<string, PluggedModule>();
export const Buttons = new Map<string, PluggedModule>();
export const SelectMenus = new Map<string, PluggedModule>();
// 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 = <PluggedModule> (await import(absPath)).module;