initplugins inject deps, inconspicuos

This commit is contained in:
Jacob Nguyen
2024-05-20 12:21:18 -05:00
parent 15511a4868
commit e0f6a4cd16
11 changed files with 68 additions and 129 deletions

View File

@@ -1,6 +1,5 @@
import { CommandType, EventType, PluginType } from './structures/enums';
import type { Plugin, PluginResult, EventArgs, CommandArgs, InitArgs } from '../types/core-plugin';
import type { ClientEvents } from 'discord.js';
import { CommandType, PluginType } from './structures/enums';
import type { Plugin, PluginResult, CommandArgs, InitArgs } from '../types/core-plugin';
import { err, ok } from './functions';
export function makePlugin<V extends unknown[]>(
@@ -31,27 +30,7 @@ export function CommandControlPlugin<I extends CommandType>(
) {
return makePlugin(PluginType.Control, execute);
}
/**
* @since 2.5.0
*/
export function EventControlPlugin<I extends EventType>(
execute: (...args: EventArgs<I>) => PluginResult,
) {
return makePlugin(PluginType.Control, execute);
}
/**
* @since 2.5.0
* @Experimental
* A specialized function for creating control plugins with discord.js ClientEvents.
* Will probably be moved one day!
*/
export function DiscordEventControlPlugin<T extends keyof ClientEvents>(
name: T,
execute: (...args: ClientEvents[T]) => PluginResult,
) {
return makePlugin(PluginType.Control, execute);
}
/**
* @since 1.0.0

View File

@@ -46,37 +46,31 @@ export function treeSearch(
while (_options.length > 0) {
const cur = _options.pop()!;
switch (cur.type) {
case ApplicationCommandOptionType.Subcommand:
{
case ApplicationCommandOptionType.Subcommand: {
subcommands.add(cur.name);
for (const option of cur.options ?? []) _options.push(option);
}
break;
case ApplicationCommandOptionType.SubcommandGroup:
{
} break;
case ApplicationCommandOptionType.SubcommandGroup: {
for (const command of cur.options ?? []) _options.push(command);
}
break;
default:
{
if ('autocomplete' in cur && cur.autocomplete) {
const choice = iAutocomplete.options.getFocused(true);
assert( 'command' in cur, 'No `command` property found for option ' + cur.name);
if (subcommands.size > 0) {
const parent = iAutocomplete.options.getSubcommand();
const parentAndOptionMatches =
subcommands.has(parent) && cur.name === choice.name;
if (parentAndOptionMatches) {
return { ...cur, parent };
}
} else {
if (cur.name === choice.name) {
return { ...cur, parent: undefined };
}
} break;
default: {
if ('autocomplete' in cur && cur.autocomplete) {
const choice = iAutocomplete.options.getFocused(true);
assert( 'command' in cur, 'No `command` property found for option ' + cur.name);
if (subcommands.size > 0) {
const parent = iAutocomplete.options.getSubcommand();
const parentAndOptionMatches =
subcommands.has(parent) && cur.name === choice.name;
if (parentAndOptionMatches) {
return { ...cur, parent };
}
} else {
if (cur.name === choice.name) {
return { ...cur, parent: undefined };
}
}
}
break;
} break;
}
}
}

View File

@@ -1,6 +1,5 @@
import type { ClientEvents } from 'discord.js';
import { EventType } from '../core/structures/enums';
import type { AnyEventPlugin, } from '../types/core-plugin';
import type {
InputCommand,
InputEvent,
@@ -21,18 +20,14 @@ export function commandModule(mod: InputCommand): Module {
plugins,
} as Module;
}
/**
* @since 1.0.0
* The wrapper function to define event modules for sern
* @param mod
*/
export function eventModule(mod: InputEvent): Module {
const [onEvent, plugins] = partitionPlugins(mod.plugins);
return {
...mod,
plugins,
onEvent,
} as Module;
return mod as Module;
}
/** Create event modules from discord.js client events,
@@ -43,7 +38,6 @@ export function eventModule(mod: InputEvent): Module {
*/
export function discordEvent<T extends keyof ClientEvents>(mod: {
name: T;
plugins?: AnyEventPlugin[];
execute: (...args: ClientEvents[T]) => Awaitable<unknown>;
}) {
return eventModule({ type: EventType.Discord, ...mod, });

View File

@@ -24,25 +24,26 @@ function fmt(msg: string, prefix?: string): string[] {
* Message and ChatInputCommandInteraction
*/
export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
prefix: string|undefined;
get options() {
return this.interaction.options;
}
args() {
return {
message: <T = string[]>() => {
args(type: 'message'|'interaction', parser?: Function) {
switch(type) {
case 'message': {
const [, ...rest] = fmt(this.message.content, this.prefix);
return rest as T;
},
interaction: () => this.interaction.options
return rest;
};
case 'interaction': {
return this.interaction.options;
};
}
}
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>, prefix?: string) {
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>,
public prefix?: string) {
super(ctx);
this.prefix = prefix
}
public get id(): Snowflake {
@@ -52,9 +53,7 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
}
public get channel() {
return safeUnwrap(this.ctx
.map(m => m.channel)
.mapErr(i => i.channel));
return safeUnwrap(this.ctx.map(m => m.channel).mapErr(i => i.channel));
}
public get channelId(): Snowflake {

View File

@@ -17,7 +17,7 @@ import * as Id from '../core/id'
import type { Emitter } from '../core/interfaces';
import { PayloadType, SernError } from '../core/structures/enums'
import { Err, Ok, Result } from 'ts-results-es';
import type { UnpackedDependencies, VoidResult } from '../types/utility';
import type { UnpackedDependencies } from '../types/utility';
import type { CommandModule, Module, Processed } from '../types/core-modules';
import * as assert from 'node:assert';
import { Context } from '../core/structures/context';
@@ -36,15 +36,9 @@ interface ExecutePayload {
function intoPayload(module: Module, deps: Dependencies) {
return pipe(map(arrayifySource),
map(args => ({ module, args, deps })));
map(args => ({ module, args, deps })),
map(p => p.args));
}
const createResult = (deps: Dependencies) =>
createResultResolver<unknown[]>({
onNext: (p) => p.args,
onStop: (module) => {
//maybe do something when plugins fail?
}
});
/**
* Creates an observable from { source }
* @param module
@@ -60,7 +54,6 @@ export function eventDispatcher(deps: Dependencies, module: Module, source: unkn
//@ts-ignore
return fromEvent(source, module.name!)
.pipe(intoPayload(module, deps),
concatMap(createResult(deps)),
execute);
}
@@ -218,7 +211,23 @@ export function createResultResolver<Output>(config: {
}
};
};
export async function callInitPlugins(module: Module, deps: Dependencies, sEmitter?: Emitter) {
for(const plugin of module.plugins) {
const res = await plugin.execute({
module,
absPath: module.meta.absPath ,
updateModule: (partial: Partial<Module>) => {
module = { ...module, ...partial };
return module;
},
deps
});
if(res.isErr()) {
sEmitter?.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
throw Error("Plugin failed with controller.stop()");
}
}
}
async function callPlugins({ args, module, deps }: ExecutePayload) {
let state = {};
for(const plugin of module.onEvent) {

View File

@@ -17,7 +17,7 @@ function isNonBot(prefix: string) {
function hasPrefix(prefix: string, content: string) {
const prefixInContent = content.slice(0, prefix.length);
return (prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0);
return prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0;
}
export default function (

View File

@@ -5,6 +5,7 @@ import { PayloadType } from '..';
import { CommandType, SernError } from '../core/structures/enums';
import { Module } from '../types/core-modules';
import { UnpackedDependencies } from '../types/utility';
import { callInitPlugins } from './event-utils';
export default async function(dir: string, deps : UnpackedDependencies) {
const { '@sern/client': client,
@@ -23,20 +24,7 @@ export default async function(dir: string, deps : UnpackedDependencies) {
if(!validType) {
throw Error(`Found ${module.name} at ${module.meta.absPath}, which has incorrect \`type\``);
}
for(const plugin of module.plugins) {
const res = await plugin.execute({
module,
absPath: module.meta.absPath ,
updateModule: (partial: Partial<Module>) => {
module = { ...module, ...partial };
return module;
}
});
if(res.isErr()) {
sEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure));
throw Error("Plugin failed with controller.stop()");
}
}
await callInitPlugins(module, deps, sEmitter);
// FREEZE! no more writing!!
commands.set(module.meta.id, Object.freeze(module));
sEmitter.emit('module.register', resultPayload(PayloadType.Success, module));

View File

@@ -1,9 +1,8 @@
import { EventType, PayloadType, SernError } from '../core/structures/enums';
import { eventDispatcher, handleCrash } from './event-utils'
import { EventType, SernError } from '../core/structures/enums';
import { callInitPlugins, eventDispatcher, handleCrash } from './event-utils'
import { EventModule, Module } from '../types/core-modules';
import * as Files from '../core/module-loading'
import type { UnpackedDependencies } from '../types/utility';
import { resultPayload } from '../core/functions';
import { from, map, mergeAll } from 'rxjs';
const intoDispatcher = (deps: UnpackedDependencies) =>
@@ -29,20 +28,7 @@ export default async function(deps: UnpackedDependencies, eventDir: string) {
const eventModules: EventModule[] = [];
for await (const path of Files.readRecursive(eventDir)) {
let { module } = await Files.importModule<Module>(path);
for(const plugin of module.plugins) {
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()");
}
}
await callInitPlugins(module, deps)
eventModules.push(module as EventModule);
}
from(eventModules)

View File

@@ -32,8 +32,7 @@ export type {
InitPlugin,
ControlPlugin,
Plugin,
AnyEventPlugin,
AnyCommandPlugin,
AnyPlugin,
} from './types/core-plugin';

View File

@@ -17,7 +17,7 @@ import type {
} from 'discord.js';
import type { CommandType, EventType } from '../core/structures/enums';
import { Context } from '../core/structures/context'
import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './core-plugin';
import { AnyPlugin, ControlPlugin, InitPlugin } from './core-plugin';
import { Awaitable, SernEventsMapping } from './utility';
type ToBeDecided = {
@@ -193,12 +193,12 @@ type EventModulesNoPlugins = {
};
export type InputEvent = {
[T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] };
[T in EventType]: EventModulesNoPlugins[T];
}[EventType];
export type InputCommand = {
[T in CommandType]: CommandModuleNoPlugins[T] & {
plugins?: AnyCommandPlugin[];
plugins?: AnyPlugin[];
};
}[CommandType];

View File

@@ -16,13 +16,12 @@ import type {
Module,
Processed,
} from './core-modules';
import type { Awaitable, Payload } from './utility';
import type { CommandType, EventType, PluginType } from '../core/structures/enums'
import type { Awaitable } from './utility';
import type { CommandType, PluginType } from '../core/structures/enums'
import type { Context } from '../core/structures/context'
import type {
ButtonInteraction,
ChannelSelectMenuInteraction,
ClientEvents,
MentionableSelectMenuInteraction,
MessageContextMenuCommandInteraction,
ModalSubmitInteraction,
@@ -37,6 +36,7 @@ export type PluginResult = Awaitable<Result<unknown, unknown>>;
export interface InitArgs<T extends Processed<Module> = Processed<Module>> {
module: T;
absPath: string;
deps: Dependencies
updateModule: (module: Partial<T>) => T
}
export interface Controller {
@@ -57,11 +57,9 @@ export interface ControlPlugin<Args extends any[] = any[]> {
execute: (...args: Args) => PluginResult;
}
export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<Module>>]>;
export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<Module>>]>;
export type AnyPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<Module>>]>;
export type CommandArgs<I extends CommandType = CommandType > = CommandArgsMatrix[I]
export type EventArgs<I extends EventType = EventType> = EventArgsMatrix[I]
interface CommandArgsMatrix {
[CommandType.Text]: [Context];
@@ -77,10 +75,3 @@ interface CommandArgsMatrix {
[CommandType.UserSelect]: [UserSelectMenuInteraction];
[CommandType.Modal]: [ModalSubmitInteraction];
}
interface EventArgsMatrix {
[EventType.Discord]: ClientEvents[keyof ClientEvents];
[EventType.Sern]: [Payload];
[EventType.External]: unknown[];
[EventType.Cron]: unknown[];
}