refactor: add createGenericHandler

This commit is contained in:
Jacob Nguyen
2023-05-06 01:01:54 -05:00
parent ff478123a8
commit 1fea6fa136
4 changed files with 65 additions and 52 deletions

View File

@@ -1,21 +1,35 @@
import { BaseInteraction, ChatInputCommandInteraction, Interaction, InteractionType } from "discord.js";
import { BaseInteraction, ChatInputCommandInteraction, Interaction, InteractionType, Message } from "discord.js";
import { Observable, filter, map } from "rxjs";
import { CommandType, ModuleManager } from "../../core";
import { SernError } from '../../core/structures/errors'
import { filterMap } from '../../core/operators';
import { defaultModuleLoader } from "../../core/module-loading";
import { Processed } from "../../types/core";
import { BothCommand, CommandModule } from "../../types/module";
import { BothCommand, CommandModule, Module } from "../../types/module";
import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from "./dispatchers";
import { isAutocomplete } from "../../core/predicates";
import { err } from "../../core/functions";
import { ObservableInput, pipe, switchMap} from "rxjs";
import { SernEmitter } from "../../core";
import { errTap } from '../../core/operators';
import * as Files from '../../core/module-loading';
import { sernMeta } from "../../commands";
import { AnyModule } from "../../types/module";
import { Err, Result } from "ts-results-es";
import { Awaitable } from "../../types/handler";
import { fmt } from "./messages";
function createGenericHandler<Source, Narrowed extends Source, Output>(
source: Observable<Source>,
makeModule: (event: Narrowed) => Awaitable<Result<Output, unknown>>
) {
return (pred: (i: Source) => i is Narrowed) =>
source.pipe(
filter(pred),
filterMap(makeModule)
)
}
/**
*
* Creates an RxJS observable that filters and maps incoming interactions to their respective modules.
@@ -23,21 +37,45 @@ import { AnyModule } from "../../types/module";
* @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>,
export function createInteractionHandler<T extends Interaction>(
source: 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)
)
return createGenericHandler<Interaction, T, ReturnType<typeof createDispatcher>>(
source,
( event ) => {
const fullPath = mg.get(createId(event as unknown as Interaction))
if(!fullPath) return Err(SernError.UndefinedModule + " No full path found in module store");
return defaultModuleLoader<CommandModule>(fullPath)
.then(res =>
res.map(module => createDispatcher({ module, event }))
)
}
)
}
export function createMessageHandler(
source: Observable<Message>,
defaultPrefix: string,
mg: ModuleManager
) {
return createGenericHandler(
source,
( event ) => {
const [prefix, ...rest] = fmt(event.content, defaultPrefix);
const fullPath = mg.get(`${prefix}__A0`);
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))
})
}
)
}
/**
* Creates a unique ID for a given interaction object.

View File

@@ -1,4 +1,4 @@
import { Interaction } from 'discord.js';
import { BaseInteraction, Interaction } from 'discord.js';
import {
catchError,
concatMap,
@@ -14,7 +14,7 @@ import { useContainerRaw } from '../../core/dependencies';
import type { Logging, ModuleManager } from '../../core/contracts';
import type { EventEmitter } from 'node:events';
import { isAutocomplete, isCommand, isMessageComponent, isModal } from '../../core/predicates';
import { createHandler } from './generic';
import { createInteractionHandler } from './generic';
export function makeInteractionCreate([s, err, log, modules, client]: [
@@ -27,7 +27,7 @@ export function makeInteractionCreate([s, err, log, modules, client]: [
platform: WebsocketStrategy
) {
const interactionStream$ = sharedObservable<Interaction>(client, platform.eventNames[0]);
const handle = createHandler(interactionStream$, modules);
const handle = createInteractionHandler<Interaction>(interactionStream$, modules);
const interactionHandler$ = merge(
handle(isMessageComponent),
handle(isAutocomplete),

View File

@@ -13,6 +13,7 @@ import { WebsocketStrategy, SernEmitter } from '../../core';
import { err } from '../../core/functions';
import { defaultModuleLoader } from '../../core/module-loading';
import { sharedObservable, filterMap } from '../../core/operators';
import { createMessageHandler } from './generic';
/**
* Removes the first character(s) _[depending on prefix length]_ of the message
@@ -28,32 +29,6 @@ export function fmt(msg: string, prefix: string): string[] {
return msg.slice(prefix.length).trim().split(/\s+/g);
}
/**
* An operator function that processes a message to fetch a command module and prepares context payload.
* @param defaultPrefix
* @param get
*/
const createMessageProcessor = (
defaultPrefix: string,
moduleManager: ModuleManager
) =>
pipe(
ignoreNonBot(defaultPrefix),
filterMap(message => {
const [prefix, ...rest] = fmt(message.content, defaultPrefix);
const fullPath = moduleManager.get(`${prefix}__A0`);
if (fullPath === undefined) {
return err();
}
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)),
);
export function makeMessageCreate(
[s, err, log, modules, client]: [
SernEmitter,
@@ -68,10 +43,12 @@ export function makeMessageCreate(
return EMPTY.subscribe()
}
const messageStream$ = sharedObservable<Message>(client, platform.eventNames[1]);
const messageProcessor = createMessageProcessor(platform.defaultPrefix, modules);
return messageStream$
const handler = createMessageHandler(messageStream$, platform.defaultPrefix, modules);
const messageHandler = handler(
ignoreNonBot(platform.defaultPrefix) as (m: Message) => m is Message
)
return messageHandler
.pipe(
messageProcessor,
makeModuleExecutor(module => {
s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure));
}),
@@ -84,5 +61,4 @@ export function makeMessageCreate(
.then(() => log?.info({ message: 'Cleaning container and crashing' }));
}),
)
.subscribe();
}

View File

@@ -1,4 +1,4 @@
import { concatMap, EMPTY, filter, from, Observable, of, tap, throwError } from 'rxjs';
import { concatMap, EMPTY, from, Observable, of, tap, throwError } from 'rxjs';
import { Result } from 'ts-results-es';
import type { CommandModule, EventModule, Module } from '../../types/module';
import { SernEmitter } from '../../core';
@@ -18,9 +18,8 @@ function hasPrefix(prefix: string, content: string) {
* @param prefix
*/
export function ignoreNonBot(prefix: string) {
const messageFromHumanAndHasPrefix = ({ author, content }: Message) =>
return ({ author, content }: Message) =>
!author.bot && hasPrefix(prefix, content);
return filter(messageFromHumanAndHasPrefix);
}
/**