diff --git a/src/core/create-plugins.ts b/src/core/create-plugins.ts index 1659e17..4bcf269 100644 --- a/src/core/create-plugins.ts +++ b/src/core/create-plugins.ts @@ -1,5 +1,5 @@ import { CommandType, EventType, PluginType } from './structures/enums'; -import type { Plugin, PluginResult, EventArgs, CommandArgs } from '../types/core-plugin'; +import type { Plugin, PluginResult, EventArgs, CommandArgs, InitArgs } from '../types/core-plugin'; import type { ClientEvents } from 'discord.js'; import { err, ok } from './functions'; @@ -12,16 +12,14 @@ export function makePlugin( /** * @since 2.5.0 */ -export function EventInitPlugin( - execute: (...args: EventArgs) => PluginResult, -) { +export function EventInitPlugin(execute: (args: InitArgs) => PluginResult) { return makePlugin(PluginType.Init, execute); } /** * @since 2.5.0 */ export function CommandInitPlugin( - execute: (...args: CommandArgs) => PluginResult, + execute: (args: InitArgs) => PluginResult ) { return makePlugin(PluginType.Init, execute); } @@ -29,7 +27,7 @@ export function CommandInitPlugin( * @since 2.5.0 */ export function CommandControlPlugin( - execute: (...args: CommandArgs) => PluginResult, + execute: (...args: CommandArgs) => PluginResult, ) { return makePlugin(PluginType.Control, execute); } @@ -37,7 +35,7 @@ export function CommandControlPlugin( * @since 2.5.0 */ export function EventControlPlugin( - execute: (...args: EventArgs) => PluginResult, + execute: (...args: EventArgs) => PluginResult, ) { return makePlugin(PluginType.Control, execute); } diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index 2d2b404..88cabd7 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -22,7 +22,6 @@ export const shouldHandle = (pth: string, filenam: string) => { } - /** * Import any module based on the absolute path. * This can accept four types of exported modules @@ -47,7 +46,6 @@ export async function importModule(absPath: string) { const p = path.parse(absPath) commandModule.name ??= p.name; commandModule.description ??= "..."; commandModule.meta = { - //@ts-ignore id: Id.create(commandModule.name, commandModule.type), absPath, }; diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index d0babb4..1a52953 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -26,11 +26,6 @@ import { disposeAll } from '../core/ioc/base'; import { arrayifySource, handleError } from '../core/operators'; import { resultPayload, isAutocomplete, treeSearch } from '../core/functions' -function contextArgs(wrappable: Message | BaseInteraction, prefix?: string) { - const ctx = Context.wrap(wrappable, prefix); - return [ctx] as [Context]; -} - function intoPayload(module: Module) { return pipe(map(arrayifySource), map(args => ({ module, args }))); @@ -39,11 +34,7 @@ const createResult = createResultResolver< Processed, { module: Processed; args: unknown[] }, unknown[] ->({ - //@ts-ignore fix later - callPlugins, - onNext: (p) => p.args, -}); +>({ onNext: (p) => p.args, }); /** * Creates an observable from { source } * @param module @@ -62,9 +53,12 @@ export function eventDispatcher(module: Module, source: unknown) { execute); } -export function createDispatcher( - { module, event }: { module: Processed; event: BaseInteraction; } -) { +interface DispatchPayload { + module: Processed; + event: BaseInteraction; + defaultPrefix?: string +}; +export function createDispatcher({ module, event, defaultPrefix }: DispatchPayload) { assert.ok(CommandType.Text !== module.type, SernError.MismatchEvent + 'Found text command in interaction stream'); if(isAutocomplete(event)) { @@ -79,7 +73,10 @@ export function createDispatcher( switch (module.type) { case CommandType.Slash: case CommandType.Both: { - return { module, args: contextArgs(event) }; + return { + module, + args: [Context.wrap(event, defaultPrefix)] + }; } default: return { module, args: [event] }; } @@ -132,14 +129,18 @@ export function createInteractionHandler( return Err.EMPTY; } const [ path ] = fullPaths; - return Ok(createDispatcher({ module: path as Processed, event })); + return Ok(createDispatcher({ + module: path as Processed, + event, + defaultPrefix + })); }); } export function createMessageHandler( source: Observable, defaultPrefix: string, - mg: any, + mg: Map, ) { return createGenericHandler(source, async event => { const [prefix] = fmt(event.content, defaultPrefix); @@ -156,7 +157,6 @@ export function createMessageHandler( interface ExecutePayload { module: Processed; task: () => Awaitable; - args: unknown[] } /** * Wraps the task in a Result as a try / catch. @@ -171,7 +171,7 @@ export function executeModule( emitter: Emitter, logger: Logging|undefined, errHandler: ErrorHandling, - { module, task, args }: ExecutePayload, + { module, task }: ExecutePayload, ) { return of(module).pipe( //converting the task into a promise so rxjs can resolve the Awaitable properly @@ -200,15 +200,15 @@ export function createResultResolver< Output, >(config: { onStop?: (module: T) => unknown; - onNext: (args: Args) => Output; + onNext: (args: Args, map: Record) => Output; }) { - return async (args: Args) => { + return async (payload: Args) => { //@ts-ignore fix later - const task = await callPlugins(args); + const task = await callPlugins(payload); if(task.isOk()) { - return config.onNext(args) as ExecutePayload; + return config.onNext(payload, task.value) as ExecutePayload; } else { - config.onStop?.(args.module); + config.onStop?.(payload.module); } }; }; @@ -220,8 +220,7 @@ async function callPlugins({ args, module }: { args: unknown[], module: Module } if(result.isErr()) { return result; } - if(typeof result.value === 'object') { - //@ts-ignore TODO + if(typeof result.value === 'object' && result.value !== null) { state = { ...result.value, ...state }; } } @@ -231,12 +230,11 @@ async function callPlugins({ args, module }: { args: unknown[], module: Module } * Creates an executable task ( execute the command ) if all control plugins are successful * @param onStop emits a failure response to the SernEmitter */ -export function makeModuleExecutor< M extends Processed, Args extends { module: M; args: unknown[]; }> +export function makeModuleExecutor (onStop: (m: M) => unknown) { - const onNext = ({ args, module }: Args) => ({ - task: () => module.execute(...args), + const onNext = ({ args, module }: Args, state: Record) => ({ + task: () => module.execute(...args, state), module, - args }); return createResultResolver({ onStop, onNext }) } diff --git a/src/handlers/ready.ts b/src/handlers/ready.ts index e5aa8f6..503301c 100644 --- a/src/handlers/ready.ts +++ b/src/handlers/ready.ts @@ -21,7 +21,7 @@ export default async function(dir: string, deps : UnpackedDependencies) { let { module } = await Files.importModule(path); const validType = module.type >= CommandType.Text && module.type <= CommandType.ChannelSelect; if(!validType) { - throw Error(`Found ${module.name} at ${module.meta.absPath}, which has an incorrect \`type\``); + throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``); } for(const plugin of module.plugins) { const res = await plugin.execute({ @@ -37,8 +37,8 @@ export default async function(dir: string, deps : UnpackedDependencies) { throw Error("Plugin failed with controller.stop()"); } } - Object.freeze(module); // no more writing!! - commands.set(module.meta.id, module); + // FREEZE! no more writing!! + commands.set(module.meta.id, Object.freeze(module)); sEmitter.emit('module.register', resultPayload(PayloadType.Success, module)); } sEmitter.emit('modulesLoaded'); diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts index 20982ae..c58cff4 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -29,9 +29,16 @@ const intoDispatcher = (deps: UnpackedDependencies) => export default async function(deps: UnpackedDependencies, eventDir: string) { const eventModules: EventModule[] = []; for await (const path of Files.readRecursive(eventDir)) { - const { module } = await Files.importModule(path); + let { module } = await Files.importModule(path); for(const plugin of module.plugins) { - const res = await plugin.execute({ module, absPath: module.meta.absPath }); + const res = await plugin.execute({ + module, + absPath: module.meta.absPath , + updateModule: (partial: Partial) => { + module = { ...module, ...partial }; + return module; + } + }); if(res.isErr()) { deps['@sern/emitter'].emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); throw Error("Plugin failed with controller.stop()"); diff --git a/src/index.ts b/src/index.ts index b752b22..624de27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ export type { } from './types/core-plugin'; -export type { Args, SlashOptions, Payload, SernEventsMapping } from './types/utility'; +export type { Payload, SernEventsMapping } from './types/utility'; export type { CoreDependencies } from './types/ioc'; export { diff --git a/src/types/core-modules.ts b/src/types/core-modules.ts index cba3559..e1fbebb 100644 --- a/src/types/core-modules.ts +++ b/src/types/core-modules.ts @@ -18,9 +18,11 @@ import type { import type { CommandType, EventType } from '../core/structures/enums'; import { Context } from '../core/structures/context' import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './core-plugin'; -import { Awaitable, Args, SlashOptions, SernEventsMapping } from './utility'; - +import { Awaitable, SernEventsMapping } from './utility'; +type ToBeDecided = { + result: Record; +} export type Processed = T & { name: string; description: string }; export interface Module { @@ -70,42 +72,41 @@ export interface ContextMenuMsg extends Module { export interface ButtonCommand extends Module { type: CommandType.Button; - execute: (ctx: ButtonInteraction) => Awaitable; + execute: (ctx: ButtonInteraction, tbd: ToBeDecided) => Awaitable; } export interface StringSelectCommand extends Module { type: CommandType.StringSelect; - execute: (ctx: StringSelectMenuInteraction) => Awaitable; + execute: (ctx: StringSelectMenuInteraction, tbd: ToBeDecided) => Awaitable; } export interface ChannelSelectCommand extends Module { type: CommandType.ChannelSelect; - execute: (ctx: ChannelSelectMenuInteraction) => Awaitable; + execute: (ctx: ChannelSelectMenuInteraction, tbd: ToBeDecided) => Awaitable; } export interface RoleSelectCommand extends Module { type: CommandType.RoleSelect; - execute: (ctx: RoleSelectMenuInteraction) => Awaitable; + execute: (ctx: RoleSelectMenuInteraction, tbd: ToBeDecided) => Awaitable; } export interface MentionableSelectCommand extends Module { type: CommandType.MentionableSelect; - execute: (ctx: MentionableSelectMenuInteraction) => Awaitable; + execute: (ctx: MentionableSelectMenuInteraction, tbd: ToBeDecided) => Awaitable; } export interface UserSelectCommand extends Module { type: CommandType.UserSelect; - execute: (ctx: UserSelectMenuInteraction) => Awaitable; + execute: (ctx: UserSelectMenuInteraction, tbd: ToBeDecided) => Awaitable; } export interface ModalSubmitCommand extends Module { type: CommandType.Modal; - execute: (ctx: ModalSubmitInteraction) => Awaitable; + execute: (ctx: ModalSubmitInteraction, tbd: ToBeDecided) => Awaitable; } -export interface AutocompleteCommand - extends Omit { - onEvent: ControlPlugin[]; +export interface AutocompleteCommand { + onEvent?: ControlPlugin[]; execute: (ctx: AutocompleteInteraction) => Awaitable; } diff --git a/src/types/core-plugin.ts b/src/types/core-plugin.ts index 398decb..108d243 100644 --- a/src/types/core-plugin.ts +++ b/src/types/core-plugin.ts @@ -13,27 +13,10 @@ import type { Err, Ok, Result } from 'ts-results-es'; import type { - BothCommand, - ButtonCommand, - ChannelSelectCommand, - CommandModule, - ContextMenuMsg, - ContextMenuUser, - DiscordEventCommand, - EventModule, - ExternalEventCommand, - MentionableSelectCommand, - ModalSubmitCommand, Module, Processed, - RoleSelectCommand, - SernEventCommand, - SlashCommand, - StringSelectCommand, - TextCommand, - UserSelectCommand, } from './core-modules'; -import type { Args, Awaitable, Payload, SlashOptions } from './utility'; +import type { Awaitable, Payload } from './utility'; import type { CommandType, EventType, PluginType } from '../core/structures/enums' import type { Context } from '../core/structures/context' import type { @@ -51,14 +34,14 @@ import type { export type PluginResult = Awaitable>; -export interface InitArgs> { +export interface InitArgs = Processed> { module: T; absPath: string; updateModule: (module: Partial) => T } export interface Controller { - next: () => Ok; - stop: () => Err; + next: () => Ok; + stop: () => Err; } export interface Plugin { type: PluginType; @@ -77,81 +60,27 @@ export interface ControlPlugin { export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; -export type CommandArgs< - I extends CommandType = CommandType, - J extends PluginType = PluginType, -> = CommandArgsMatrix[I][J]; - -export type EventArgs< I extends EventType = EventType, - J extends PluginType = PluginType, -> = EventArgsMatrix[I][J]; +export type CommandArgs = CommandArgsMatrix[I] +export type EventArgs = EventArgsMatrix[I] interface CommandArgsMatrix { - [CommandType.Text]: { - [PluginType.Control]: [Context, ['text', string[]]]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Slash]: { - [PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Both]: { - [PluginType.Control]: [Context, Args]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.CtxMsg]: { - [PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.CtxUser]: { - [PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Button]: { - [PluginType.Control]: [/* library coupled */ ButtonInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.StringSelect]: { - [PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.RoleSelect]: { - [PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.ChannelSelect]: { - [PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.MentionableSelect]: { - [PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.UserSelect]: { - [PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction]; - [PluginType.Init]: [InitArgs>]; - }; - [CommandType.Modal]: { - [PluginType.Control]: [/* library coupled */ ModalSubmitInteraction]; - [PluginType.Init]: [InitArgs>]; - }; + [CommandType.Text]: [Context]; + [CommandType.Slash]: [Context]; + [CommandType.Both]: [Context]; + [CommandType.CtxMsg]: [MessageContextMenuCommandInteraction]; + [CommandType.CtxUser]: [UserContextMenuCommandInteraction]; + [CommandType.Button]: [ButtonInteraction]; + [CommandType.StringSelect]: [StringSelectMenuInteraction]; + [CommandType.RoleSelect]: [RoleSelectMenuInteraction]; + [CommandType.ChannelSelect]: [ChannelSelectMenuInteraction]; + [CommandType.MentionableSelect]: [MentionableSelectMenuInteraction]; + [CommandType.UserSelect]: [UserSelectMenuInteraction]; + [CommandType.Modal]: [ModalSubmitInteraction]; } interface EventArgsMatrix { - [EventType.Discord]: { - [PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents]; - [PluginType.Init]: [InitArgs>]; - }; - [EventType.Sern]: { - [PluginType.Control]: [Payload]; - [PluginType.Init]: [InitArgs>]; - }; - [EventType.External]: { - [PluginType.Control]: unknown[]; - [PluginType.Init]: [InitArgs>]; - }; - [EventType.Cron]: { - [PluginType.Control]: unknown[]; - [PluginType.Init]: [InitArgs>]; - }; + [EventType.Discord]: ClientEvents[keyof ClientEvents]; + [EventType.Sern]: [Payload]; + [EventType.External]: unknown[]; + [EventType.Cron]: unknown[]; } diff --git a/src/types/utility.ts b/src/types/utility.ts index ac959db..3b4c38e 100644 --- a/src/types/utility.ts +++ b/src/types/utility.ts @@ -1,4 +1,4 @@ -import type { CommandInteractionOptionResolver, InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; +import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; import type { PayloadType } from '../core/structures/enums'; import type { Module } from './core-modules'; import type { Result } from 'ts-results-es'; @@ -8,15 +8,6 @@ export type Awaitable = PromiseLike | T; export type VoidResult = Result; export type AnyFunction = (...args: any[]) => unknown; -// Thanks to @kelsny -type ParseType = { - [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; -}[keyof T]; - -export type SlashOptions = Omit; - -export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; - export interface SernEventsMapping { 'module.register': [Payload]; 'module.activate': [Payload];