mirror of
https://github.com/sern-handler/handler
synced 2026-06-16 21:02:16 +00:00
feat!: new module resolution algorithm
This commit is contained in:
@@ -1,35 +1,45 @@
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
import { join, basename } from 'path';
|
||||
import { readdir, stat } from 'fs/promises';
|
||||
import { join, basename, resolve } from 'path';
|
||||
import { type Observable, from, mergeMap } from 'rxjs';
|
||||
import { SernError } from './structures/errors';
|
||||
import { type Result, Err, Ok } from 'ts-results-es';
|
||||
import { ImportPayload } from '../types/handler';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { Processed } from '../types/handler';
|
||||
import { Module } from '../types/module';
|
||||
import * as assert from 'node:assert'
|
||||
import * as util from 'node:util'
|
||||
|
||||
// Courtesy @Townsy45
|
||||
function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
|
||||
try {
|
||||
const files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
if (statSync(dir + '/' + file).isDirectory()) readPath(dir + '/' + file, arrayOfFiles);
|
||||
else arrayOfFiles.push(join(dir, '/', file));
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
async function* readPath(dir: string): AsyncGenerator<string> {
|
||||
try {
|
||||
const files = await readdir(dir);
|
||||
for (const file of files) {
|
||||
const fullPath = join(dir, file);
|
||||
const fileStats = await stat(fullPath);
|
||||
if (fileStats.isDirectory()) {
|
||||
yield* readPath(fullPath);
|
||||
} else {
|
||||
/// #if MODE === 'esm'
|
||||
yield 'file:///'+fullPath;
|
||||
/// #elif MODE === 'cjs'
|
||||
yield fullPath;
|
||||
/// #endif
|
||||
}
|
||||
}
|
||||
|
||||
return arrayOfFiles;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
// export const isLazy = (n: string) => n.indexOf(".lazy.", n.length-9) !== -1;
|
||||
|
||||
export async function defaultModuleLoader<T>(
|
||||
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
|
||||
export async function defaultModuleLoader<T extends Module>(
|
||||
absPath: string,
|
||||
): Promise<Result<ImportPayload<T>, SernError>> {
|
||||
): Promise<Result< Processed<T>, SernError>> {
|
||||
// prettier-ignore
|
||||
let module: T | undefined
|
||||
/// #if MODE === 'esm'
|
||||
= (await import(pathToFileURL(absPath).toString())).default
|
||||
= (await import(absPath)).default
|
||||
/// #elif MODE === 'cjs'
|
||||
= require(absPath).default; // eslint-disable-line
|
||||
/// #endif
|
||||
@@ -39,7 +49,12 @@ export async function defaultModuleLoader<T>(
|
||||
try {
|
||||
module = new (module as unknown as new () => T)();
|
||||
} catch {}
|
||||
return Ok({ module, absPath });
|
||||
checkIsProcessed(module)
|
||||
return Ok(module);
|
||||
}
|
||||
|
||||
function checkIsProcessed<T extends Module>(m: T): asserts m is Processed<T> {
|
||||
assert.ok(m.name !== undefined, `name is not defined for ${util.format(m)}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,15 +63,15 @@ export async function defaultModuleLoader<T>(
|
||||
* @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files
|
||||
* @param commandDir
|
||||
*/
|
||||
export function buildModuleStream<T>(
|
||||
export function buildModuleStream<T extends Module >(
|
||||
commandDir: string,
|
||||
): Observable<Result<ImportPayload<T>, SernError>> {
|
||||
): Observable<Result<Processed<T>, SernError>> {
|
||||
const commands = getCommands(commandDir);
|
||||
return from(commands).pipe(mergeMap(defaultModuleLoader<T>));
|
||||
}
|
||||
|
||||
export function getCommands(dir: string): string[] {
|
||||
return readPath(join(process.cwd(), dir));
|
||||
export function getCommands(dir: string) {
|
||||
return readPath(resolve(dir));
|
||||
}
|
||||
|
||||
export function filename(path: string) {
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import { ApplicationCommandType, ComponentType } from './enums';
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
@@ -9,20 +5,7 @@ import { ApplicationCommandType, ComponentType } from './enums';
|
||||
* This dependency is usually injected into ModuleManager
|
||||
*/
|
||||
export class ModuleStore {
|
||||
readonly BothCommands = new Map<string, Processed<CommandModule>>();
|
||||
readonly ApplicationCommands = {
|
||||
[ApplicationCommandType.User]: new Map<string, Processed<CommandModule>>(),
|
||||
[ApplicationCommandType.Message]: new Map<string, Processed<CommandModule>>(),
|
||||
[ApplicationCommandType.ChatInput]: new Map<string, Processed<CommandModule>>(),
|
||||
};
|
||||
readonly ModalSubmit = new Map<string, Processed<CommandModule>>();
|
||||
readonly TextCommands = new Map<string, Processed<CommandModule>>();
|
||||
readonly InteractionHandlers = {
|
||||
[ComponentType.Button]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.StringSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.ChannelSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.MentionableSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.RoleSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.UserSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
};
|
||||
readonly Commands = new Map<string, string>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Processed } from '../../../types/handler';
|
||||
import type { AutocompleteInteraction } from 'discord.js';
|
||||
import { SernError } from '../../../core/structures';
|
||||
import { treeSearch } from '../../../core/functions';
|
||||
import type { CommandModule, Module } from '../../../types/module';
|
||||
import type { BothCommand, CommandModule, Module } from '../../../types/module';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as assert from 'assert';
|
||||
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
|
||||
@@ -51,7 +51,7 @@ export function eventDispatcher(module: Processed<Module>, source: unknown) {
|
||||
}
|
||||
|
||||
export function dispatchAutocomplete(
|
||||
module: Processed<Module>,
|
||||
module: Processed<BothCommand>,
|
||||
interaction: AutocompleteInteraction,
|
||||
) {
|
||||
const option = treeSearch(interaction, module.options);
|
||||
|
||||
95
src/handler/events/generic.ts
Normal file
95
src/handler/events/generic.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { BaseInteraction, ChatInputCommandInteraction, Interaction, InteractionType } from "discord.js";
|
||||
import { Observable, filter, map, pipe, switchMap} from "rxjs";
|
||||
import { CommandType, ModuleManager, SernEmitter, SernError } from "../../core";
|
||||
import { filterMap, errTap } from '../../core/operators';
|
||||
import { defaultModuleLoader } from "../../core/module-loading";
|
||||
import { Processed } from "../../types/handler";
|
||||
import { AnyModule, BothCommand, CommandModule } from "../../types/module";
|
||||
import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from "./dispatchers";
|
||||
import { isAutocomplete } from "../../core/predicates";
|
||||
import { err } from "../../core/functions";
|
||||
import * as Files from '../../core/module-loading';
|
||||
import { sernMeta } from "../../commands";
|
||||
|
||||
/**
|
||||
* Creates an RxJS observable that filters and maps incoming interactions to their respective modules.
|
||||
* @param i An RxJS observable of interactions.
|
||||
* @param mg The module manager instance used to retrieve the module path for each interaction.
|
||||
* @returns A handler to create a RxJS observable of dispatchers that take incoming interactions and execute their corresponding modules.
|
||||
*/
|
||||
export function createHandler<T extends BaseInteraction>(
|
||||
i: Observable<Interaction>,
|
||||
mg: ModuleManager,
|
||||
) {
|
||||
return (pred: (i: BaseInteraction) => i is T) =>
|
||||
i.pipe(
|
||||
filter(pred),
|
||||
filterMap(event => {
|
||||
const fullPath = mg.get(createId(event as unknown as Interaction))
|
||||
if(!fullPath) return err();
|
||||
return defaultModuleLoader<CommandModule>(fullPath)
|
||||
.then(res => res.map(module => ({ module, event }) ))
|
||||
}),
|
||||
map(createDispatcher)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Creates a unique ID for a given interaction object.
|
||||
* @param event The interaction object for which to create an ID.
|
||||
* @returns A unique string ID based on the type and properties of the interaction object.
|
||||
*/
|
||||
function createId<T extends Interaction>(event: T) {
|
||||
let id: string;
|
||||
switch(event.type) {
|
||||
case InteractionType.MessageComponent: {
|
||||
id = `${event.customId}__C${event.componentType}`;
|
||||
} break;
|
||||
case InteractionType.ApplicationCommand:
|
||||
case InteractionType.ApplicationCommandAutocomplete: {
|
||||
id = `${event.commandName}__A${event.commandType}`;
|
||||
console.log(id)
|
||||
} break;
|
||||
case InteractionType.ModalSubmit: {
|
||||
id = `${event.customId}__C1`;
|
||||
} break;
|
||||
}
|
||||
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>(path: string, sernEmitter: SernEmitter) {
|
||||
return pipe(
|
||||
switchMap(() => Files.buildModuleStream<T>(path)),
|
||||
errTap(error => {
|
||||
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
map(module => ({ module, absPath: module[sernMeta].fullPath }))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,101 +1,22 @@
|
||||
import { ChatInputCommandInteraction, Interaction, InteractionType } from 'discord.js';
|
||||
import { Interaction } from 'discord.js';
|
||||
import {
|
||||
catchError,
|
||||
concatMap,
|
||||
EMPTY,
|
||||
filter,
|
||||
finalize,
|
||||
fromEvent,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
OperatorFunction,
|
||||
pipe,
|
||||
merge,
|
||||
} from 'rxjs';
|
||||
import { CommandType,SernError } from '../../core/structures';
|
||||
import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from './dispatchers';
|
||||
import { SernError } from '../../core/structures';
|
||||
import { executeModule, makeModuleExecutor } from './observableHandling';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import { ErrorHandling, handleError } from '../../core/contracts/errorHandling';
|
||||
import { SernEmitter, WebsocketStrategy } from '../../core';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import { sharedObservable } from '../../core/operators'
|
||||
import { useContainerRaw } from '../../core/dependencies';
|
||||
import type { Logging, ModuleManager } from '../../core/contracts';
|
||||
import type { EventEmitter } from 'node:events';
|
||||
import { ModuleGetter, createModuleGetter } from '../../core/contracts/moduleManager';
|
||||
import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates';
|
||||
import { createHandler } from './generic';
|
||||
|
||||
|
||||
function handleMessageComponents(i: Observable<Interaction>, mg: ModuleGetter) {
|
||||
return i.pipe(
|
||||
filter(e => e.isMessageComponent()),
|
||||
map(event => ({ module: mg('' as any), event }) )
|
||||
)
|
||||
}
|
||||
|
||||
function handleAutocomplete(i: Observable<Interaction>, mg: ModuleGetter) {
|
||||
return i.pipe(
|
||||
filter(e => e.isAutocomplete()),
|
||||
map(event => ({ module: mg('' as any), event }) )
|
||||
)
|
||||
}
|
||||
|
||||
function handleApplicationCommands(i: Observable<Interaction>, mg: ModuleGetter) {
|
||||
return i.pipe(
|
||||
filter(e => e.isCommand()),
|
||||
map(event => ({ module: mg('' as any), event }) )
|
||||
)
|
||||
}
|
||||
|
||||
function handleModal(i: Observable<Interaction>, mg: ModuleGetter) {
|
||||
return i.pipe(
|
||||
filter(e => e.isModalSubmit()),
|
||||
map(event => ({ module: mg('' as any), event }) )
|
||||
)
|
||||
}
|
||||
function makeInteractionProcessor(
|
||||
modules: ModuleManager,
|
||||
): OperatorFunction<Interaction, { module: Processed<CommandModule>; event: Interaction }> {
|
||||
const get = createModuleGetter(modules);
|
||||
return pipe(
|
||||
concatMap(event => {
|
||||
switch(event.type) {
|
||||
case InteractionType.MessageComponent:
|
||||
case InteractionType.ModalSubmit: {
|
||||
const id = `${event.customId}__M${event.componentType}`
|
||||
} break;
|
||||
case InteractionType.ApplicationCommand:
|
||||
case InteractionType.ApplicationCommandAutocomplete: {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if (event.isMessageComponent()) {
|
||||
const customId = event.customId;
|
||||
const module = get(ms => {
|
||||
return ms.InteractionHandlers[event.componentType].get(customId);
|
||||
});
|
||||
return of({ module, event });
|
||||
} else if (event.isCommand() || event.isAutocomplete()) {
|
||||
const commandName = event.commandName;
|
||||
const module = get(
|
||||
ms =>
|
||||
/**
|
||||
* try to fetch from ApplicationCommands, if nothing, try BothCommands
|
||||
* exists on the API but not sern
|
||||
*/
|
||||
ms.ApplicationCommands[event.commandType].get(commandName) ??
|
||||
ms.BothCommands.get(commandName),
|
||||
);
|
||||
return of({ module, event });
|
||||
} else if (event.isModalSubmit()) {
|
||||
const module = get(ms => ms.ModalSubmit.get(event.customId));
|
||||
return of({ module, event });
|
||||
} else return EMPTY;
|
||||
}),
|
||||
filter(m => m.module !== undefined),
|
||||
) as OperatorFunction<Interaction, { module: Processed<CommandModule>; event: Interaction }>;
|
||||
}
|
||||
|
||||
export function makeInteractionCreate([s, err, log, modules, client]: [
|
||||
SernEmitter,
|
||||
ErrorHandling,
|
||||
@@ -105,13 +26,16 @@ export function makeInteractionCreate([s, err, log, modules, client]: [
|
||||
],
|
||||
platform: WebsocketStrategy
|
||||
) {
|
||||
//map. If nothing again,this means a slash command
|
||||
const interactionStream$ = fromEvent(client, platform.eventNames[0]) as Observable<Interaction>;
|
||||
const interactionProcessor = makeInteractionProcessor(modules);
|
||||
return interactionStream$
|
||||
const interactionStream$ = sharedObservable<Interaction>(client, platform.eventNames[0]);
|
||||
const handle = createHandler(interactionStream$, modules);
|
||||
const interactionHandler$ = merge(
|
||||
handle(isMessageComponent),
|
||||
handle(isAutocomplete),
|
||||
handle(isCommand),
|
||||
handle(isModal)
|
||||
);
|
||||
return interactionHandler$
|
||||
.pipe(
|
||||
interactionProcessor,
|
||||
map(createDispatcher),
|
||||
makeModuleExecutor(module => {
|
||||
s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
}),
|
||||
@@ -119,7 +43,7 @@ export function makeInteractionCreate([s, err, log, modules, client]: [
|
||||
catchError(handleError(err, log)),
|
||||
finalize(() => {
|
||||
log?.info({
|
||||
message: 'interactionCreate stream closed or reached end of lifetime',
|
||||
message: 'interaction stream closed or reached end of lifetime',
|
||||
});
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
@@ -128,31 +52,3 @@ export function makeInteractionCreate([s, err, log, modules, client]: [
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
function createDispatcher({
|
||||
module,
|
||||
event,
|
||||
}: {
|
||||
event: Interaction;
|
||||
module: Processed<CommandModule>;
|
||||
}) {
|
||||
switch (module.type) {
|
||||
case CommandType.Text:
|
||||
throw Error(SernError.MismatchEvent);
|
||||
case CommandType.Slash:
|
||||
case CommandType.Both: {
|
||||
if (event.isAutocomplete()) {
|
||||
/**
|
||||
* Autocomplete is a special case that
|
||||
* must be handled separately, since it's
|
||||
* too different from regular command modules
|
||||
*/
|
||||
return dispatchAutocomplete(module, event);
|
||||
} else {
|
||||
return dispatchCommand(module, contextArgs(event as ChatInputCommandInteraction));
|
||||
}
|
||||
}
|
||||
default:
|
||||
return dispatchCommand(module, interactionArg(event));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of, pipe } from 'rxjs';
|
||||
import { type ModuleStore, SernError } from '../../core/structures';
|
||||
import { catchError, concatMap, EMPTY, finalize, map, of, pipe } from 'rxjs';
|
||||
import { SernError } from '../../core/structures';
|
||||
import type { Message } from 'discord.js';
|
||||
import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
@@ -11,7 +11,9 @@ import { useContainerRaw } from '../../core/dependencies';
|
||||
import type { Logging, ModuleManager } from '../../core/contracts';
|
||||
import type { EventEmitter } from 'node:events';
|
||||
import { WebsocketStrategy } from '../../core';
|
||||
import { createModuleGetter } from '../../core/contracts/moduleManager';
|
||||
import { err } from '../../core/functions';
|
||||
import { defaultModuleLoader } from '../../core/module-loading';
|
||||
import { sharedObservable, filterMap } from '../../core/operators';
|
||||
|
||||
/**
|
||||
* Removes the first character(s) _[depending on prefix length]_ of the message
|
||||
@@ -34,25 +36,21 @@ export function fmt(msg: string, prefix: string): string[] {
|
||||
*/
|
||||
const createMessageProcessor = (
|
||||
defaultPrefix: string,
|
||||
get: (
|
||||
cb: (ms: ModuleStore) => Processed<CommandModule> | undefined,
|
||||
) => CommandModule | undefined,
|
||||
moduleManager: ModuleManager
|
||||
) =>
|
||||
pipe(
|
||||
ignoreNonBot(defaultPrefix),
|
||||
//This concatMap checks if module is undefined, and if it is, do not continue.
|
||||
// Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet
|
||||
concatMap(message => {
|
||||
filterMap(message => {
|
||||
const [prefix, ...rest] = fmt(message.content, defaultPrefix);
|
||||
const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix));
|
||||
if (module === undefined) {
|
||||
return EMPTY;
|
||||
const fullPath = moduleManager.get(`${prefix}__A0`);
|
||||
if (fullPath === undefined) {
|
||||
return err();
|
||||
}
|
||||
const payload = {
|
||||
args: contextArgs(message, rest),
|
||||
module,
|
||||
};
|
||||
return of(payload);
|
||||
return defaultModuleLoader<CommandModule>(fullPath).then(
|
||||
result => {
|
||||
const args = contextArgs(message, rest);
|
||||
return result.map(module => ({ module, args }))
|
||||
})
|
||||
}),
|
||||
map(({ args, module }) => dispatchCommand(module as Processed<CommandModule>, args)),
|
||||
);
|
||||
@@ -70,9 +68,8 @@ export function makeMessageCreate(
|
||||
if(!platform.defaultPrefix) {
|
||||
return EMPTY.subscribe()
|
||||
}
|
||||
const get = createModuleGetter(modules);
|
||||
const messageStream$ = fromEvent(client, platform.eventNames[1]) as Observable<Message>;
|
||||
const messageProcessor = createMessageProcessor(platform.defaultPrefix, get);
|
||||
const messageStream$ = sharedObservable<Message>(client, platform.eventNames[1]);
|
||||
const messageProcessor = createMessageProcessor(platform.defaultPrefix, modules);
|
||||
return messageStream$
|
||||
.pipe(
|
||||
messageProcessor,
|
||||
|
||||
@@ -1,50 +1,27 @@
|
||||
import { Subscription, fromEvent, map, of, pipe, switchMap, take } from 'rxjs';
|
||||
import * as Files from '../../core/module-loading';
|
||||
import { Subscription, fromEvent, of, take } from 'rxjs';
|
||||
import { callInitPlugins } from './observableHandling';
|
||||
import { CommandType, type ModuleStore, SernError } from '../../core/structures';
|
||||
import { CommandType, SernError } from '../../core/structures';
|
||||
import { Result } from 'ts-results-es';
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import type { Processed, ServerlessDependencyList, WebsocketDependencyList } from '../../types/handler';
|
||||
import type { ErrorHandling, Logging, ModuleManager } from '../../core/contracts';
|
||||
import { err, ok } from '../../core/functions';
|
||||
import { errTap, fillDefaults } from '../../core/operators';
|
||||
import SernEmitter from '../../core/sernEmitter';
|
||||
import type { EventEmitter } from 'node:events';
|
||||
import { DispatchType, PlatformStrategy, ServerlessStrategy, WebsocketStrategy } from '../../core';
|
||||
|
||||
function buildCommandModules(commandDir: string, sernEmitter: SernEmitter) {
|
||||
return pipe(
|
||||
switchMap(() => Files.buildModuleStream<CommandModule>(commandDir)),
|
||||
errTap(error => {
|
||||
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
map(fillDefaults),
|
||||
);
|
||||
}
|
||||
import { sernMeta } from '../../commands';
|
||||
import { buildModules } from './generic';
|
||||
|
||||
/**
|
||||
* @overload
|
||||
*/
|
||||
export function makeReadyEvent(
|
||||
dependencies: [
|
||||
SernEmitter,
|
||||
ErrorHandling,
|
||||
Logging | undefined,
|
||||
ModuleManager,
|
||||
],
|
||||
dependencies: ServerlessDependencyList,
|
||||
commandDir: string,
|
||||
platform: ServerlessStrategy
|
||||
|
||||
): Subscription
|
||||
export function makeReadyEvent(
|
||||
dependencies: [
|
||||
SernEmitter,
|
||||
ErrorHandling,
|
||||
Logging | undefined,
|
||||
ModuleManager,
|
||||
EventEmitter
|
||||
],
|
||||
dependencies: WebsocketDependencyList,
|
||||
commandDir: string,
|
||||
platform: WebsocketStrategy
|
||||
|
||||
@@ -66,7 +43,7 @@ export function makeReadyEvent(
|
||||
: fromEvent(client!, platform.eventNames[2]).pipe(take(1));
|
||||
return ready$
|
||||
.pipe(
|
||||
buildCommandModules(commandDir, sEmitter),
|
||||
buildModules(commandDir, sEmitter),
|
||||
callInitPlugins({
|
||||
onStop: module => {
|
||||
sEmitter.emit(
|
||||
@@ -90,49 +67,11 @@ export function makeReadyEvent(
|
||||
|
||||
function registerModule<T extends Processed<CommandModule>>(
|
||||
manager: ModuleManager,
|
||||
mod: T,
|
||||
module: T,
|
||||
): Result<void, void> {
|
||||
const name = mod.name;
|
||||
const insert = (cb: (ms: ModuleStore) => void) => {
|
||||
const set = Result.wrap(() => manager.set(cb));
|
||||
return set.ok ? ok() : err();
|
||||
};
|
||||
switch (mod.type) {
|
||||
case CommandType.Text: {
|
||||
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
|
||||
return insert(ms => ms.TextCommands.set(name, mod));
|
||||
}
|
||||
case CommandType.Slash:
|
||||
return insert(ms =>
|
||||
ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod),
|
||||
);
|
||||
case CommandType.Both: {
|
||||
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
|
||||
return insert(ms => ms.BothCommands.set(name, mod));
|
||||
}
|
||||
case CommandType.CtxUser:
|
||||
return insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod));
|
||||
case CommandType.CtxMsg:
|
||||
return insert(ms =>
|
||||
ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod),
|
||||
);
|
||||
case CommandType.Button:
|
||||
return insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod));
|
||||
case CommandType.StringSelect:
|
||||
return insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod));
|
||||
case CommandType.MentionableSelect:
|
||||
return insert(ms =>
|
||||
ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod),
|
||||
);
|
||||
case CommandType.UserSelect:
|
||||
return insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod));
|
||||
case CommandType.ChannelSelect:
|
||||
return insert(ms => ms.InteractionHandlers[ComponentType.ChannelSelect].set(name, mod));
|
||||
case CommandType.RoleSelect:
|
||||
return insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod));
|
||||
case CommandType.Modal:
|
||||
return insert(ms => ms.ModalSubmit.set(name, mod));
|
||||
default:
|
||||
return err();
|
||||
const { id, fullPath } = module[sernMeta];
|
||||
if(module.type === CommandType.Both || module.type === CommandType.Text) {
|
||||
module.alias?.forEach(a => manager.set(`${a}__A0` , fullPath))
|
||||
}
|
||||
return Result.wrap(() => manager.set(id, fullPath))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { catchError, finalize, map, mergeAll } from 'rxjs';
|
||||
import * as Files from '../../core/module-loading';
|
||||
import { catchError, finalize, map, mergeAll, of } from 'rxjs';
|
||||
import type { Processed, WebsocketDependencies } from '../../types/handler';
|
||||
import { callInitPlugins } from './observableHandling';
|
||||
import type { CommandModule, EventModule } from '../../types/module';
|
||||
@@ -9,9 +8,9 @@ import type { ErrorHandling, Logging } from '../../core/contracts';
|
||||
import { SernError, EventType } from '../../core/structures';
|
||||
import { eventDispatcher } from './dispatchers';
|
||||
import { handleError } from '../../core/contracts/errorHandling';
|
||||
import { errTap, fillDefaults } from '../../core/operators';
|
||||
import { useContainerRaw } from '../../core/dependencies';
|
||||
import { AnyWrapper } from '../../core/structures/wrapper';
|
||||
import { buildModules } from './generic';
|
||||
|
||||
export function makeEventsHandler(
|
||||
[s, err, log, client]: [SernEmitter, ErrorHandling, Logging | undefined, EventEmitter],
|
||||
@@ -19,19 +18,6 @@ export function makeEventsHandler(
|
||||
containerGetter: AnyWrapper['containerConfig'],
|
||||
) {
|
||||
const lazy = (k: string) => containerGetter.get(k as keyof WebsocketDependencies)[0];
|
||||
const eventStream$ = eventObservable(eventsPath, s);
|
||||
|
||||
const eventCreation$ = eventStream$.pipe(
|
||||
map(fillDefaults),
|
||||
callInitPlugins({
|
||||
onStop: module =>
|
||||
s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
|
||||
onNext: ({ module }) => {
|
||||
s.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
},
|
||||
}),
|
||||
);
|
||||
const intoDispatcher = (e: Processed<EventModule | CommandModule>) => {
|
||||
switch (e.type) {
|
||||
case EventType.Sern:
|
||||
@@ -46,30 +32,29 @@ export function makeEventsHandler(
|
||||
);
|
||||
}
|
||||
};
|
||||
eventCreation$
|
||||
.pipe(
|
||||
map(intoDispatcher),
|
||||
/**
|
||||
* Where all events are turned on
|
||||
*/
|
||||
mergeAll(),
|
||||
catchError(handleError(err, log)),
|
||||
finalize(() => {
|
||||
log?.info({ message: 'an event module reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => {
|
||||
log?.info({ message: 'Cleaning container and crashing' });
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
function eventObservable(events: string, emitter: SernEmitter) {
|
||||
return Files.buildModuleStream<EventModule>(events).pipe(
|
||||
errTap(reason => {
|
||||
emitter.emit('module.register', SernEmitter.failure(undefined, reason));
|
||||
of(null).pipe(
|
||||
buildModules(eventsPath, s),
|
||||
callInitPlugins({
|
||||
onStop: module =>
|
||||
s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
|
||||
onNext: ({ module }) => {
|
||||
s.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
},
|
||||
}),
|
||||
);
|
||||
map(intoDispatcher),
|
||||
/**
|
||||
* Where all events are turned on
|
||||
*/
|
||||
mergeAll(),
|
||||
catchError(handleError(err, log)),
|
||||
finalize(() => {
|
||||
log?.info({ message: 'an event module reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => {
|
||||
log?.info({ message: 'Cleaning container and crashing' });
|
||||
});
|
||||
}),
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import { discordjs } from '../core';
|
||||
* ```
|
||||
*/
|
||||
export function init(wrapper: DefaultWrapper) {
|
||||
const dependenciesAnd = makeFetcher(wrapper.containerConfig);
|
||||
const startTime = performance.now();
|
||||
const dependenciesAnd = makeFetcher(wrapper.containerConfig);
|
||||
const dependencies = dependenciesAnd(['@sern/modules', '@sern/client']);
|
||||
if (wrapper.events !== undefined) {
|
||||
makeEventsHandler(
|
||||
|
||||
Reference in New Issue
Block a user