mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
up to speed with event modules
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function* readRecursive(dir: string): AsyncGenerator<string> {
|
||||
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);
|
||||
|
||||
@@ -73,9 +73,9 @@ export function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, lo
|
||||
//// Temporary until i get rxjs operators working on ts-results-es
|
||||
export const filterTap = <K, R>(onErr: (e: R) => void): OperatorFunction<Result<K, R>, 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;
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[
|
||||
return [ctx, args] as [Context, Args];
|
||||
}
|
||||
|
||||
function intoPayload(module: Processed<Module>, ) {
|
||||
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<Module>, source: unknown) {
|
||||
export function eventDispatcher(module: 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)
|
||||
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)));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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<Interaction>(client, 'interactionCreate');
|
||||
const interactionStream$ = sharedEventStream<Interaction>(client as unknown as Emitter, 'interactionCreate');
|
||||
const handle = createInteractionHandler(interactionStream$, modules);
|
||||
|
||||
const interactionHandler$ = merge(handle(isMessageComponent),
|
||||
|
||||
@@ -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<Message>(client, 'messageCreate');
|
||||
const messageStream$ = sharedEventStream<Message>(client as unknown as Emitter, 'messageCreate');
|
||||
const handle = createMessageHandler(messageStream$, defaultPrefix, commands);
|
||||
|
||||
const msgCommands$ = handle(isNonBot(defaultPrefix));
|
||||
|
||||
@@ -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<Module>(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()");
|
||||
}
|
||||
|
||||
@@ -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<EventModule> }) => {
|
||||
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<EventModule>(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<Module>(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();
|
||||
}
|
||||
|
||||
16
src/sern.ts
16
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<UnpackedDependencies>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user