mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
chore: cleanup dispatchers subdirectory for single file
This commit is contained in:
118
src/handler/events/dispatchers.ts
Normal file
118
src/handler/events/dispatchers.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { Processed } from '../../types/core';
|
||||
import type { BothCommand, CommandModule, Module } from '../../types/module';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import * as assert from 'node:assert';
|
||||
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
|
||||
import { arrayifySource, callPlugin } from '../../core/operators';
|
||||
import { createResultResolver } from './generic';
|
||||
import { AutocompleteInteraction, BaseInteraction, Message } from 'discord.js';
|
||||
import { treeSearch } from '../../core/functions';
|
||||
import { SernError } from '../../core/structures/errors';
|
||||
import { Args } from '../../types/handler';
|
||||
import { CommandType, Context } from '../../core';
|
||||
import { isAutocomplete } from '../../core/predicates';
|
||||
|
||||
export function dispatchInteraction<
|
||||
T extends CommandModule,
|
||||
V extends BaseInteraction | Message
|
||||
>(
|
||||
payload: { module: Processed<T>; event: V },
|
||||
createArgs: (m: typeof payload.event) => unknown[]
|
||||
) {
|
||||
return {
|
||||
module: payload.module,
|
||||
args: createArgs(payload.event),
|
||||
};
|
||||
}
|
||||
|
||||
export function dispatchMessage(module: Processed<CommandModule>, args: [Context, Args]) {
|
||||
return {
|
||||
module,
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
export function dispatchAutocomplete(payload: { module: Processed<BothCommand>, event: AutocompleteInteraction }) {
|
||||
const option = treeSearch(payload.event, payload.module.options);
|
||||
if (option !== undefined) {
|
||||
return {
|
||||
module: option.command as Processed<Module>, //autocomplete is not a true "module" warning cast!
|
||||
args: [payload.event],
|
||||
};
|
||||
}
|
||||
throw Error(
|
||||
SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export function contextArgs(
|
||||
wrappable: Message | BaseInteraction,
|
||||
messageArgs?: string[],
|
||||
) {
|
||||
const ctx = Context.wrap(wrappable);
|
||||
const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.options];
|
||||
return [ctx, args] as [Context, Args];
|
||||
}
|
||||
|
||||
export function interactionArg<T extends BaseInteraction>(interaction: T) {
|
||||
return [interaction] as [T];
|
||||
}
|
||||
|
||||
function intoPayload(module: Processed<Module>) {
|
||||
return pipe(
|
||||
arrayifySource,
|
||||
map(args => ({ module, args })),
|
||||
);
|
||||
}
|
||||
|
||||
const createResult = createResultResolver<
|
||||
Processed<Module>,
|
||||
{ module: Processed<Module>; args: unknown[] },
|
||||
unknown[]
|
||||
>({
|
||||
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
|
||||
onNext: ({ args }) => args,
|
||||
});
|
||||
/**
|
||||
* Creates an observable from { source }
|
||||
* @param module
|
||||
* @param source
|
||||
*/
|
||||
export function eventDispatcher(module: Processed<Module>, source: unknown) {
|
||||
assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`);
|
||||
|
||||
const execute: OperatorFunction<unknown[], unknown> = concatMap(async args =>
|
||||
module.execute(...args),
|
||||
);
|
||||
return fromEvent(source, module.name).pipe(
|
||||
intoPayload(module),
|
||||
concatMap(createResult),
|
||||
execute,
|
||||
);
|
||||
}
|
||||
|
||||
export function createDispatcher(payload: {
|
||||
module: Processed<CommandModule>;
|
||||
event: BaseInteraction;
|
||||
}) {
|
||||
switch (payload.module.type) {
|
||||
case CommandType.Text:
|
||||
throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream.');
|
||||
case CommandType.Slash:
|
||||
case CommandType.Both: {
|
||||
if (isAutocomplete(payload.event)) {
|
||||
/**
|
||||
* Autocomplete is a special case that
|
||||
* must be handled separately, since it's
|
||||
* too different from regular command modules
|
||||
* cast safety: payload is already guaranteed to be a slash command or both command
|
||||
*/
|
||||
return dispatchAutocomplete(payload as never);
|
||||
}
|
||||
return dispatchInteraction(payload, contextArgs);
|
||||
}
|
||||
default:
|
||||
return dispatchInteraction(payload, interactionArg);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import type { Processed } from '../../../types/core';
|
||||
import type { AutocompleteInteraction } from 'discord.js';
|
||||
import { SernError } from '../../../core/structures/errors';
|
||||
import { treeSearch } from '../../../core/functions';
|
||||
import type { BothCommand, CommandModule, Module } from '../../../types/module';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import * as assert from 'node:assert';
|
||||
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
|
||||
import { arrayifySource, callPlugin } from '../../../core/operators';
|
||||
import { createResultResolver } from '../generic';
|
||||
|
||||
export function dispatchCommand(module: Processed<CommandModule>, createArgs: () => unknown[]) {
|
||||
const args = createArgs();
|
||||
return {
|
||||
module,
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
function intoPayload(module: Processed<Module>) {
|
||||
return pipe(
|
||||
arrayifySource,
|
||||
map(args => ({ module, args })),
|
||||
);
|
||||
}
|
||||
|
||||
const createResult = createResultResolver<
|
||||
Processed<Module>,
|
||||
{ module: Processed<Module>; args: unknown[] },
|
||||
unknown[]
|
||||
>({
|
||||
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
|
||||
onNext: ({ args }) => args,
|
||||
});
|
||||
/**
|
||||
* Creates an observable from { source }
|
||||
* @param module
|
||||
* @param source
|
||||
*/
|
||||
export function eventDispatcher(module: Processed<Module>, source: unknown) {
|
||||
assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`);
|
||||
|
||||
const execute: OperatorFunction<unknown[], unknown> = concatMap(async args =>
|
||||
module.execute(...args),
|
||||
);
|
||||
return fromEvent(source, module.name).pipe(
|
||||
intoPayload(module),
|
||||
concatMap(createResult),
|
||||
execute,
|
||||
);
|
||||
}
|
||||
|
||||
export function dispatchAutocomplete(
|
||||
module: Processed<BothCommand>,
|
||||
interaction: AutocompleteInteraction,
|
||||
) {
|
||||
const option = treeSearch(interaction, module.options);
|
||||
if (option !== undefined) {
|
||||
return {
|
||||
module: option.command as Processed<Module>, //autocomplete is not a true "module" warning cast!
|
||||
args: [interaction],
|
||||
};
|
||||
}
|
||||
throw Error(
|
||||
SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`,
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './dispatchers';
|
||||
export * from './provide-args';
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { Message, ChatInputCommandInteraction } from 'discord.js';
|
||||
import type { Args, SlashOptions } from '../../../types/handler';
|
||||
import { Context } from '../../../classic/context';
|
||||
|
||||
/*
|
||||
* @overload
|
||||
*/
|
||||
export function contextArgs(
|
||||
wrap: Message,
|
||||
messageArgs?: string[],
|
||||
): () => [Context, ['text', string[]]];
|
||||
/*
|
||||
* @overload
|
||||
*/
|
||||
export function contextArgs(
|
||||
wrappable: ChatInputCommandInteraction,
|
||||
): () => [Context, ['slash', SlashOptions]];
|
||||
/**
|
||||
* function overloads to create an arguments list for Context
|
||||
* @param wrap
|
||||
* @param messageArgs
|
||||
*/
|
||||
export function contextArgs(
|
||||
wrappable: Message | ChatInputCommandInteraction,
|
||||
messageArgs?: string[],
|
||||
) {
|
||||
const ctx = Context.wrap(wrappable);
|
||||
const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.options];
|
||||
return () => [ctx, args] as [Context, Args];
|
||||
}
|
||||
|
||||
export function interactionArg<T>(interaction: T) {
|
||||
return () => [interaction] as [T];
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
BaseInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
Interaction,
|
||||
InteractionType,
|
||||
Message,
|
||||
@@ -11,8 +10,8 @@ import { SernError } from '../../core/structures/errors';
|
||||
import { callPlugin, everyPluginOk, filterMap, filterMapTo } from '../../core/operators';
|
||||
import { defaultModuleLoader } from '../../core/module-loading';
|
||||
import { ImportPayload, Processed } from '../../types/core';
|
||||
import { BothCommand, CommandModule, EventModule, Module } from '../../types/module';
|
||||
import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from './dispatchers';
|
||||
import { CommandModule, Module } from '../../types/module';
|
||||
import { contextArgs, createDispatcher, dispatchAutocomplete, dispatchInteraction, dispatchMessage, interactionArg } from './dispatchers';
|
||||
import { isAutocomplete } from '../../core/predicates';
|
||||
import { ObservableInput, pipe, switchMap } from 'rxjs';
|
||||
import { SernEmitter } from '../../core';
|
||||
@@ -66,10 +65,11 @@ export function createMessageHandler(
|
||||
if (fullPath === undefined) {
|
||||
return Err(SernError.UndefinedModule + ' No full path found in module store');
|
||||
}
|
||||
return defaultModuleLoader<CommandModule>(fullPath).then(result => {
|
||||
const args = contextArgs(event, rest);
|
||||
return result.map(module => dispatchCommand(module, args));
|
||||
});
|
||||
return defaultModuleLoader<CommandModule>(fullPath)
|
||||
.then(result => {
|
||||
const args = contextArgs(event, rest);
|
||||
return result.map(module => dispatchMessage(module, args))
|
||||
})
|
||||
});
|
||||
}
|
||||
/**
|
||||
@@ -101,32 +101,7 @@ function createId<T extends Interaction>(event: T) {
|
||||
return id;
|
||||
}
|
||||
|
||||
function createDispatcher({
|
||||
module,
|
||||
event,
|
||||
}: {
|
||||
module: Processed<CommandModule>;
|
||||
event: BaseInteraction;
|
||||
}) {
|
||||
switch (module.type) {
|
||||
case CommandType.Text:
|
||||
throw Error(SernError.MismatchEvent + ' Found a text module in interaction stream.');
|
||||
case CommandType.Slash:
|
||||
case CommandType.Both: {
|
||||
if (isAutocomplete(event)) {
|
||||
/**
|
||||
* Autocomplete is a special case that
|
||||
* must be handled separately, since it's
|
||||
* too different from regular command modules
|
||||
*/
|
||||
return dispatchAutocomplete(module as Processed<BothCommand>, event);
|
||||
}
|
||||
return dispatchCommand(module, contextArgs(event as ChatInputCommandInteraction));
|
||||
}
|
||||
default:
|
||||
return dispatchCommand(module, interactionArg(event));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function buildModules<T extends AnyModule>(
|
||||
input: ObservableInput<string>,
|
||||
@@ -221,7 +196,7 @@ export function createResultResolver<
|
||||
* ignore the module
|
||||
*/
|
||||
export function callInitPlugins<
|
||||
T extends Processed<CommandModule | EventModule>,
|
||||
T extends Processed<Module>,
|
||||
Args extends ImportPayload<T>,
|
||||
>(config: { onStop?: (module: T) => unknown; onNext: (module: Args) => T }) {
|
||||
return concatMap(
|
||||
@@ -249,3 +224,5 @@ export function makeModuleExecutor<
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Interaction } from 'discord.js';
|
||||
import { catchError, concatMap, finalize, merge } from 'rxjs';
|
||||
import { concatMap, merge } from 'rxjs';
|
||||
import { SernError } from '../../core/structures/errors';
|
||||
import { handleError } from '../../core/contracts/error-handling';
|
||||
import { SernEmitter } from '../../core';
|
||||
import { sharedObservable } from '../../core/operators';
|
||||
import { useContainerRaw } from '../../core/dependencies';
|
||||
import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates';
|
||||
import { createInteractionHandler, executeModule, makeModuleExecutor } from './generic';
|
||||
import { DependencyList } from '../../types/core';
|
||||
|
||||
export function makeInteractionCreate([s, err, log, modules, client]: DependencyList ) {
|
||||
export function makeInteractionHandler([emitter, _, _1, modules, client]: DependencyList ) {
|
||||
const interactionStream$ = sharedObservable<Interaction>(client, 'interactionCreate');
|
||||
const handle = createInteractionHandler<Interaction>(interactionStream$, modules);
|
||||
const handle = createInteractionHandler(interactionStream$, modules);
|
||||
const interactionHandler$ = merge(
|
||||
handle(isMessageComponent),
|
||||
handle(isAutocomplete),
|
||||
@@ -21,18 +19,8 @@ export function makeInteractionCreate([s, err, log, modules, client]: Dependency
|
||||
return interactionHandler$
|
||||
.pipe(
|
||||
makeModuleExecutor(module => {
|
||||
s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
concatMap(module => executeModule(s, module)),
|
||||
catchError(handleError(err, log)),
|
||||
finalize(() => {
|
||||
log?.info({
|
||||
message: 'interaction stream closed or reached end of lifetime',
|
||||
});
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => log?.info({ message: 'Cleaning container and crashing' }));
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
concatMap(module => executeModule(emitter, module)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { catchError, concatMap, EMPTY, finalize } from 'rxjs';
|
||||
import { concatMap, EMPTY } from 'rxjs';
|
||||
import { SernError } from '../../core/structures/errors';
|
||||
import type { Message } from 'discord.js';
|
||||
import { ErrorHandling, handleError } from '../../core/contracts/error-handling';
|
||||
import type { Logging, ModuleManager } from '../../core/contracts';
|
||||
import type { EventEmitter } from 'node:events';
|
||||
import { SernEmitter, useContainerRaw } from '../../core';
|
||||
import { SernEmitter } from '../../core';
|
||||
import { sharedObservable } from '../../core/operators';
|
||||
import { createMessageHandler, executeModule, isNonBot, makeModuleExecutor } from './generic';
|
||||
import { DependencyList } from '../../types/core';
|
||||
@@ -23,28 +20,23 @@ export function fmt(msg: string, prefix: string): string[] {
|
||||
return msg.slice(prefix.length).trim().split(/\s+/g);
|
||||
}
|
||||
|
||||
export function makeMessageCreate(
|
||||
[s, err, log, modules, client]: DependencyList,
|
||||
export function makeMessageHandler(
|
||||
[emitter, , log, modules, client]: DependencyList,
|
||||
defaultPrefix: string | undefined,
|
||||
) {
|
||||
if (!defaultPrefix) {
|
||||
log?.debug({ message: 'No prefix found. message handler shut down' });
|
||||
return EMPTY.subscribe();
|
||||
return EMPTY;
|
||||
}
|
||||
const messageStream$ = sharedObservable<Message>(client, 'messageCreate');
|
||||
const handler = createMessageHandler(messageStream$, defaultPrefix, modules);
|
||||
|
||||
const messageHandler = handler(isNonBot(defaultPrefix) as (m: Message) => m is Message);
|
||||
return messageHandler.pipe(
|
||||
makeModuleExecutor(module => {
|
||||
s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
concatMap(payload => executeModule(s, payload)),
|
||||
catchError(handleError(err, log)),
|
||||
finalize(() => {
|
||||
log?.info({ message: 'messageCreate stream closed or reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => log?.info({ message: 'Cleaning container and crashing' }));
|
||||
emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
concatMap(payload => executeModule(emitter, payload)),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { makeEventsHandler } from './events/user-defined';
|
||||
import { makeInteractionCreate } from './events/interactions';
|
||||
import { makeInteractionHandler } from './events/interactions';
|
||||
import { startReadyEvent } from './events/ready';
|
||||
import { makeMessageCreate } from './events/messages';
|
||||
import { makeFetcher, makeDependencies } from '../core/dependencies';
|
||||
import { makeMessageHandler } from './events/messages';
|
||||
import { makeFetcher, makeDependencies, useContainerRaw } from '../core/dependencies';
|
||||
import { err, ok } from '../core/functions';
|
||||
import { Wrapper } from '../types/core';
|
||||
import { getCommands } from '../core/module-loading';
|
||||
import { catchError, finalize, merge } from 'rxjs';
|
||||
import { handleError } from '../core/contracts/error-handling';
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* @param wrapper Options to pass into sern.
|
||||
@@ -25,6 +27,7 @@ export function init(wrapper: Wrapper) {
|
||||
const startTime = performance.now();
|
||||
const dependenciesAnd = makeFetcher(wrapper.containerConfig);
|
||||
const dependencies = dependenciesAnd(['@sern/modules', '@sern/client']);
|
||||
|
||||
if (wrapper.events !== undefined) {
|
||||
makeEventsHandler(
|
||||
dependenciesAnd(['@sern/client']),
|
||||
@@ -32,12 +35,34 @@ export function init(wrapper: Wrapper) {
|
||||
wrapper.containerConfig,
|
||||
);
|
||||
}
|
||||
startReadyEvent(dependencies, getCommands(wrapper.commands));
|
||||
makeMessageCreate(dependencies, wrapper.defaultPrefix);
|
||||
makeInteractionCreate(dependencies);
|
||||
|
||||
startReadyEvent(dependencies, getCommands(wrapper.commands)).add(() => console.log('ready'));
|
||||
|
||||
const logger = dependencies[2];
|
||||
const errorHandler = dependencies[1];
|
||||
|
||||
const messages$ = makeMessageHandler(dependencies, wrapper.defaultPrefix);
|
||||
const interactions$ = makeInteractionHandler(dependencies);
|
||||
|
||||
merge(
|
||||
messages$,
|
||||
interactions$
|
||||
).pipe(
|
||||
catchError(handleError(errorHandler, logger)),
|
||||
finalize(() => {
|
||||
logger?.info({ message: 'a stream closed or reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
|
||||
})
|
||||
).subscribe()
|
||||
|
||||
const endTime = performance.now();
|
||||
dependencies[2]?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated - Please import the function directly:
|
||||
* ```ts
|
||||
|
||||
Reference in New Issue
Block a user