mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
simplify plugin args and prepare for reduction among plugins
This commit is contained in:
@@ -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<V extends unknown[]>(
|
||||
/**
|
||||
* @since 2.5.0
|
||||
*/
|
||||
export function EventInitPlugin<I extends EventType>(
|
||||
execute: (...args: EventArgs<I, PluginType.Init>) => PluginResult,
|
||||
) {
|
||||
export function EventInitPlugin(execute: (args: InitArgs) => PluginResult) {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
/**
|
||||
* @since 2.5.0
|
||||
*/
|
||||
export function CommandInitPlugin<I extends CommandType>(
|
||||
execute: (...args: CommandArgs<I, PluginType.Init>) => PluginResult,
|
||||
execute: (args: InitArgs) => PluginResult
|
||||
) {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
@@ -29,7 +27,7 @@ export function CommandInitPlugin<I extends CommandType>(
|
||||
* @since 2.5.0
|
||||
*/
|
||||
export function CommandControlPlugin<I extends CommandType>(
|
||||
execute: (...args: CommandArgs<I, PluginType.Control>) => PluginResult,
|
||||
execute: (...args: CommandArgs<I>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
@@ -37,7 +35,7 @@ export function CommandControlPlugin<I extends CommandType>(
|
||||
* @since 2.5.0
|
||||
*/
|
||||
export function EventControlPlugin<I extends EventType>(
|
||||
execute: (...args: EventArgs<I, PluginType.Control>) => PluginResult,
|
||||
execute: (...args: EventArgs<I>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
|
||||
@@ -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<T>(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,
|
||||
};
|
||||
|
||||
@@ -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>,
|
||||
{ module: Processed<Module>; 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<CommandModule>; event: BaseInteraction; }
|
||||
) {
|
||||
interface DispatchPayload {
|
||||
module: Processed<CommandModule>;
|
||||
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<T extends Interaction>(
|
||||
return Err.EMPTY;
|
||||
}
|
||||
const [ path ] = fullPaths;
|
||||
return Ok(createDispatcher({ module: path as Processed<CommandModule>, event }));
|
||||
return Ok(createDispatcher({
|
||||
module: path as Processed<CommandModule>,
|
||||
event,
|
||||
defaultPrefix
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export function createMessageHandler(
|
||||
source: Observable<Message>,
|
||||
defaultPrefix: string,
|
||||
mg: any,
|
||||
mg: Map<string, Module>,
|
||||
) {
|
||||
return createGenericHandler(source, async event => {
|
||||
const [prefix] = fmt(event.content, defaultPrefix);
|
||||
@@ -156,7 +157,6 @@ export function createMessageHandler(
|
||||
interface ExecutePayload {
|
||||
module: Processed<Module>;
|
||||
task: () => Awaitable<unknown>;
|
||||
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<string, unknown>) => 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<Module>, Args extends { module: M; args: unknown[]; }>
|
||||
export function makeModuleExecutor<M extends Module, Args extends { module: M; args: unknown[]; }>
|
||||
(onStop: (m: M) => unknown) {
|
||||
const onNext = ({ args, module }: Args) => ({
|
||||
task: () => module.execute(...args),
|
||||
const onNext = ({ args, module }: Args, state: Record<string, unknown>) => ({
|
||||
task: () => module.execute(...args, state),
|
||||
module,
|
||||
args
|
||||
});
|
||||
return createResultResolver({ onStop, onNext })
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function(dir: string, deps : UnpackedDependencies) {
|
||||
let { module } = await Files.importModule<Module>(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');
|
||||
|
||||
@@ -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<Module>(path);
|
||||
let { module } = await Files.importModule<Module>(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 = { ...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()");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<string,unknown>;
|
||||
}
|
||||
export type Processed<T> = 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<unknown>;
|
||||
execute: (ctx: ButtonInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface StringSelectCommand extends Module {
|
||||
type: CommandType.StringSelect;
|
||||
execute: (ctx: StringSelectMenuInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: StringSelectMenuInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ChannelSelectCommand extends Module {
|
||||
type: CommandType.ChannelSelect;
|
||||
execute: (ctx: ChannelSelectMenuInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: ChannelSelectMenuInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface RoleSelectCommand extends Module {
|
||||
type: CommandType.RoleSelect;
|
||||
execute: (ctx: RoleSelectMenuInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: RoleSelectMenuInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface MentionableSelectCommand extends Module {
|
||||
type: CommandType.MentionableSelect;
|
||||
execute: (ctx: MentionableSelectMenuInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: MentionableSelectMenuInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface UserSelectCommand extends Module {
|
||||
type: CommandType.UserSelect;
|
||||
execute: (ctx: UserSelectMenuInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: UserSelectMenuInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ModalSubmitCommand extends Module {
|
||||
type: CommandType.Modal;
|
||||
execute: (ctx: ModalSubmitInteraction) => Awaitable<unknown>;
|
||||
execute: (ctx: ModalSubmitInteraction, tbd: ToBeDecided) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface AutocompleteCommand
|
||||
extends Omit<Module, 'name' | 'type' | 'plugins' | 'description' | 'meta'> {
|
||||
onEvent: ControlPlugin[];
|
||||
export interface AutocompleteCommand {
|
||||
onEvent?: ControlPlugin[];
|
||||
execute: (ctx: AutocompleteInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Result<unknown, unknown>>;
|
||||
|
||||
export interface InitArgs<T extends Processed<Module>> {
|
||||
export interface InitArgs<T extends Processed<Module> = Processed<Module>> {
|
||||
module: T;
|
||||
absPath: string;
|
||||
updateModule: (module: Partial<T>) => T
|
||||
}
|
||||
export interface Controller {
|
||||
next: () => Ok<void>;
|
||||
stop: () => Err<void>;
|
||||
next: () => Ok<unknown>;
|
||||
stop: () => Err<string|undefined>;
|
||||
}
|
||||
export interface Plugin<Args extends any[] = any[]> {
|
||||
type: PluginType;
|
||||
@@ -77,81 +60,27 @@ export interface ControlPlugin<Args extends any[] = any[]> {
|
||||
export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<Module>>]>;
|
||||
export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<Module>>]>;
|
||||
|
||||
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<I extends CommandType = CommandType > = CommandArgsMatrix[I]
|
||||
export type EventArgs<I extends EventType = EventType> = EventArgsMatrix[I]
|
||||
|
||||
interface CommandArgsMatrix {
|
||||
[CommandType.Text]: {
|
||||
[PluginType.Control]: [Context, ['text', string[]]];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.Slash]: {
|
||||
[PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.Both]: {
|
||||
[PluginType.Control]: [Context, Args];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.CtxMsg]: {
|
||||
[PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.CtxUser]: {
|
||||
[PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.Button]: {
|
||||
[PluginType.Control]: [/* library coupled */ ButtonInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.StringSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.RoleSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.ChannelSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.MentionableSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.UserSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[CommandType.Modal]: {
|
||||
[PluginType.Control]: [/* library coupled */ ModalSubmitInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<Module>>];
|
||||
};
|
||||
[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<Processed<DiscordEventCommand>>];
|
||||
};
|
||||
[EventType.Sern]: {
|
||||
[PluginType.Control]: [Payload];
|
||||
[PluginType.Init]: [InitArgs<Processed<SernEventCommand>>];
|
||||
};
|
||||
[EventType.External]: {
|
||||
[PluginType.Control]: unknown[];
|
||||
[PluginType.Init]: [InitArgs<Processed<ExternalEventCommand>>];
|
||||
};
|
||||
[EventType.Cron]: {
|
||||
[PluginType.Control]: unknown[];
|
||||
[PluginType.Init]: [InitArgs<Processed<ExternalEventCommand>>];
|
||||
};
|
||||
[EventType.Discord]: ClientEvents[keyof ClientEvents];
|
||||
[EventType.Sern]: [Payload];
|
||||
[EventType.External]: unknown[];
|
||||
[EventType.Cron]: unknown[];
|
||||
}
|
||||
|
||||
@@ -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<T> = PromiseLike<T> | T;
|
||||
export type VoidResult = Result<void, void>;
|
||||
export type AnyFunction = (...args: any[]) => unknown;
|
||||
|
||||
// Thanks to @kelsny
|
||||
type ParseType<T> = {
|
||||
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
|
||||
}[keyof T];
|
||||
|
||||
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;
|
||||
|
||||
export type Args = ParseType<{ text: string[]; slash: SlashOptions }>;
|
||||
|
||||
export interface SernEventsMapping {
|
||||
'module.register': [Payload];
|
||||
'module.activate': [Payload];
|
||||
|
||||
Reference in New Issue
Block a user