From 0d82658fc5b09d3fe1d9fe2649786830a85c8e71 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Wed, 15 May 2024 14:59:24 -0500 Subject: [PATCH] up to speed with event modules --- src/core/ioc/base.ts | 4 ++ src/core/module-loading.ts | 2 +- src/core/operators.ts | 10 ++--- src/handlers/event-utils.ts | 12 +++--- src/handlers/interaction.ts | 5 ++- src/handlers/message.ts | 5 ++- src/handlers/ready.ts | 9 +++-- src/handlers/user-defined-events.ts | 58 +++++++++++++++++------------ src/sern.ts | 16 ++++---- 9 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts index cea5577..36ab71f 100644 --- a/src/core/ioc/base.ts +++ b/src/core/ioc/base.ts @@ -4,6 +4,7 @@ import * as __Services from '../structures/default-services'; import { UnpackedDependencies } from '../../types/utility'; import type { Logging } from '../interfaces'; import { __add_container, __init_container, __swap_container, useContainerRaw } from './global'; +import { EventEmitter } from 'node:events'; export function disposeAll(logger: Logging|undefined) { useContainerRaw() @@ -72,6 +73,7 @@ async function composeRoot( __add_container('@sern/errors', new __Services.DefaultErrorHandling()); __add_container('@sern/cron', {}) __add_container('@sern/modules', new Map()) + __add_container('@sern/emitter', new EventEmitter()) //Build the container based on the callback provided by the user conf.build(container as Container); @@ -97,6 +99,8 @@ export async function makeDependencies (conf: ValidDependencyConfig) { } __add_container('@sern/errors', new __Services.DefaultErrorHandling()); __add_container('@sern/cron', {}) + __add_container('@sern/modules', new Map()) + __add_container('@sern/emitter', new EventEmitter()) await useContainerRaw().ready(); } else { await composeRoot(useContainerRaw(), conf); diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index d4c6074..35bd9aa 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -59,7 +59,7 @@ export async function* readRecursive(dir: string): AsyncGenerator { const files = await readdir(dir, { withFileTypes: true }); for (const file of files) { - const fullPath = path.join(dir, file.name); + const fullPath = path.posix.resolve(dir, file.name); if (file.isDirectory()) { if (!file.name.startsWith('!')) { yield* readRecursive(fullPath); diff --git a/src/core/operators.ts b/src/core/operators.ts index 4bb95c7..a02fb24 100644 --- a/src/core/operators.ts +++ b/src/core/operators.ts @@ -73,9 +73,9 @@ export function handleError(crashHandler: ErrorHandling, emitter: Emitter, lo //// Temporary until i get rxjs operators working on ts-results-es export const filterTap = (onErr: (e: R) => void): OperatorFunction, K> => concatMap(result => { - if(result.isOk()) { - return of(result.value) - } - onErr(result.error); - return EMPTY + if(result.isOk()) { + return of(result.value) + } + onErr(result.error); + return EMPTY; }) diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts index 5d9cb51..ba50a58 100644 --- a/src/handlers/event-utils.ts +++ b/src/handlers/event-utils.ts @@ -40,7 +40,7 @@ function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[ return [ctx, args] as [Context, Args]; } -function intoPayload(module: Processed, ) { +function intoPayload(module: Module) { return pipe(map(arrayifySource), map(args => ({ module, args }))); } @@ -58,12 +58,13 @@ const createResult = createResultResolver< * @param module * @param source */ -export function eventDispatcher(module: Processed, source: unknown) { +export function eventDispatcher(module: Module, source: unknown) { assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); const execute: OperatorFunction = concatMap(async args => module.execute(...args)); - return fromEvent(source, module.name) + return fromEvent(source, module.name!) + //@ts-ignore .pipe(intoPayload(module), concatMap(createResult), execute); @@ -190,12 +191,10 @@ export function executeModule( return EMPTY; } return throwError(() => resultPayload(PayloadType.Failure, module, result.error)); - }), ); }; - /** * A higher order function that * - creates a stream of {@link VoidResult} { config.createStream } @@ -221,8 +220,7 @@ export function createResultResolver< result.isErr() && config.onStop?.(args.module); }), everyPluginOk, - filterMapTo(() => config.onNext(args)), - ); + filterMapTo(() => config.onNext(args))); }; }; diff --git a/src/handlers/interaction.ts b/src/handlers/interaction.ts index c055260..9adf3fb 100644 --- a/src/handlers/interaction.ts +++ b/src/handlers/interaction.ts @@ -6,15 +6,16 @@ import { createInteractionHandler, executeModule, makeModuleExecutor } from './e import { SernError } from '../core/structures/enums' import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload, } from '../core/functions' import { UnpackedDependencies } from '../types/utility'; +import { Emitter } from '../core/interfaces'; -export function interactionHandler(deps: UnpackedDependencies) { +export default function interactionHandler(deps: UnpackedDependencies) { //i wish javascript had clojure destructuring const { '@sern/modules': modules, '@sern/client': client, '@sern/logger': log, '@sern/errors': err, '@sern/emitter': emitter } = deps - const interactionStream$ = sharedEventStream(client, 'interactionCreate'); + const interactionStream$ = sharedEventStream(client as unknown as Emitter, 'interactionCreate'); const handle = createInteractionHandler(interactionStream$, modules); const interactionHandler$ = merge(handle(isMessageComponent), diff --git a/src/handlers/message.ts b/src/handlers/message.ts index a1a0556..05a0b1c 100644 --- a/src/handlers/message.ts +++ b/src/handlers/message.ts @@ -5,6 +5,7 @@ import { PayloadType, SernError } from '../core/structures/enums' import { resultPayload } from '../core/functions' import { filterTap, sharedEventStream } from '../core/operators' import { UnpackedDependencies } from '../types/utility'; +import { Emitter } from '..'; /** * Ignores messages from any person / bot except itself @@ -19,7 +20,7 @@ function hasPrefix(prefix: string, content: string) { return (prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0); } -export function messageHandler({"@sern/emitter": emitter, '@sern/errors':err, +export default function message({"@sern/emitter": emitter, '@sern/errors':err, '@sern/logger': log, '@sern/client': client, '@sern/modules': commands}: UnpackedDependencies, defaultPrefix: string | undefined) { @@ -27,7 +28,7 @@ export function messageHandler({"@sern/emitter": emitter, '@sern/errors':err, log?.debug({ message: 'No prefix found. message handler shutting down' }); return EMPTY; } - const messageStream$ = sharedEventStream(client, 'messageCreate'); + const messageStream$ = sharedEventStream(client as unknown as Emitter, 'messageCreate'); const handle = createMessageHandler(messageStream$, defaultPrefix, commands); const msgCommands$ = handle(isNonBot(defaultPrefix)); diff --git a/src/handlers/ready.ts b/src/handlers/ready.ts index 620197c..4d1d859 100644 --- a/src/handlers/ready.ts +++ b/src/handlers/ready.ts @@ -2,7 +2,7 @@ import * as Files from '../core/module-loading' import { once } from 'events'; import { resultPayload } from '../core/functions'; import { PayloadType } from '..'; -import { SernError } from '../core/structures/enums'; +import { CommandType, SernError } from '../core/structures/enums'; import { Module } from '../types/core-modules'; import { UnpackedDependencies } from '../types/utility'; @@ -14,15 +14,18 @@ export default async function(dir: string, deps : UnpackedDependencies) { log?.info({ message: "Waiting on discord client to be ready..." }) await once(client, "ready"); log?.info({ message: "Client signaled ready, registering modules" }); + + // https://observablehq.com/@ehouais/multiple-promises-as-an-async-generator + // possibly optimize to concurrently import modules for await (const path of Files.readRecursive(dir)) { const { module } = await Files.importModule(path); - const validType = module.type >= 0 && module.type <= 1 << 10; + 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\``); } for(const plugin of module.plugins) { const res = await plugin.execute({ module, absPath: module.meta.absPath }); - if(res.isErr()) { + if(res.isErr()) { sEmitter.emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); throw Error("Plugin failed with controller.stop()"); } diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts index 40d1977..627c2ab 100644 --- a/src/handlers/user-defined-events.ts +++ b/src/handlers/user-defined-events.ts @@ -1,35 +1,47 @@ -import { EventType, SernError } from '../core/structures/enums'; -import { eventDispatcher } from './event-utils' -import type { EventModule, Processed } from '../types/core-modules'; +import { EventType, PayloadType, SernError } from '../core/structures/enums'; +import { eventDispatcher, handleCrash } from './event-utils' +import { EventModule, Module, Processed } 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'; -export default function(deps: UnpackedDependencies, eventDir: string) { - //code smell - const intoDispatcher = (e: { module: Processed }) => { - switch (e.module.type) { +const intoDispatcher = (deps: UnpackedDependencies) => + (module : EventModule) => { + switch (module.type) { case EventType.Sern: - return eventDispatcher(e.module, deps['@sern/emitter']); + return eventDispatcher(module, deps['@sern/emitter']); case EventType.Discord: - return eventDispatcher(e.module, deps['@sern/client']); + return eventDispatcher(module, deps['@sern/client']); case EventType.External: - return eventDispatcher(e.module, deps[e.module.emitter]); + return eventDispatcher(module, deps[module.emitter]); case EventType.Cron: //@ts-ignore TODO - return eventDispatcher(e.module, deps['@sern/cron']) + return eventDispatcher(module, deps['@sern/cron']) default: throw Error(SernError.InvalidModuleType + ' while creating event handler'); } - }; - Files.readRecursive(eventDir) - //buildModules(allPaths) -// pipe( -// callInitPlugins(emitter), -// map(intoDispatcher), -// /** -// * Where all events are turned on -// */ -// mergeAll(), -// handleCrash(err, emitter, log)) -// .subscribe(); +}; + +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); + for(const plugin of module.plugins) { + const res = await plugin.execute({ module, absPath: module.meta.absPath }); + if(res.isErr()) { + deps['@sern/emitter'].emit('module.register', resultPayload(PayloadType.Failure, module, SernError.PluginFailure)); + throw Error("Plugin failed with controller.stop()"); + } + } + eventModules.push(module as EventModule); + } + from(eventModules) + .pipe(map(intoDispatcher(deps)), + /** + * Where all events are turned on + */ + mergeAll(), + handleCrash(deps)) + .subscribe(); } diff --git a/src/sern.ts b/src/sern.ts index 49aeb6f..b48da0b 100644 --- a/src/sern.ts +++ b/src/sern.ts @@ -1,13 +1,11 @@ import callsites from 'callsites'; import * as Files from './core/module-loading'; import { merge } from 'rxjs'; -import { Services } from './core/ioc'; import eventsHandler from './handlers/user-defined-events'; import ready from './handlers/ready'; -import { messageHandler } from './handlers/message'; -import { interactionHandler } from './handlers/interaction'; +import messageHandler from './handlers/message'; +import interactionHandler from './handlers/interaction'; import { presenceHandler } from './handlers/presence'; -import { Client } from 'discord.js'; import { handleCrash } from './handlers/event-utils'; import { useContainerRaw } from './core/ioc/global'; import { UnpackedDependencies } from './types/utility'; @@ -35,7 +33,12 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) { const deps = useContainerRaw().deps(); if (maybeWrapper.events !== undefined) { - eventsHandler(deps, maybeWrapper.events); + eventsHandler(deps, maybeWrapper.events) + .then(() => { + deps['@sern/logger']?.info({ message: "Events registered" }); + }); + } else { + deps['@sern/logger']?.info({ message: "No events registered" }); } const initCallsite = callsites()[1].getFileName(); @@ -47,8 +50,7 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) { deps['@sern/logger']?.info({ message: `sern: registered in ${time} s` }); if(presencePath.exists) { const setPresence = async (p: any) => { - //@ts-ignore - return (dependencies[3] as Client).user?.setPresence(p); + return deps['@sern/client'].user?.setPresence(p); } presenceHandler(presencePath.path, setPresence).subscribe(); }