mirror of
https://github.com/sern-handler/handler
synced 2026-06-16 12:52:15 +00:00
chore: audit & remove tspattern (#256)
* chore: move import * build: remove ts pattern * fix: forgot to convert to switch * fix workflow * refactor: lift function out of readyHandler * refactor: clean up errTap signature * fix: sern emitter emitting wrong payload * wa * style: space * chore: remove old errTap * chore:bump discord.js * chore: eslint format
This commit is contained in:
2
.github/workflows/npm-publish.yml
vendored
2
.github/workflows/npm-publish.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- recursive: true
|
||||
args: [--strict-peer-dependencies]
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: pnpm build:prod
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
@@ -36,18 +36,17 @@
|
||||
"dependencies": {
|
||||
"iti": "^0.6.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"ts-pattern": "^4.1.4",
|
||||
"ts-results-es": "^3.5.0",
|
||||
"tsup": "^6.6.3"
|
||||
"ts-results-es": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "5.54.0",
|
||||
"@typescript-eslint/parser": "5.54.0",
|
||||
"discord.js": "^14.7.1",
|
||||
"discord.js": "^14.8.0",
|
||||
"esbuild-ifdef": "^0.2.0",
|
||||
"eslint": "8.30.0",
|
||||
"prettier": "2.8.4",
|
||||
"typescript": "4.9.5"
|
||||
"typescript": "4.9.5",
|
||||
"tsup": "^6.6.3"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
4328
pnpm-lock.yaml
generated
4328
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Logging } from './logging';
|
||||
import util from 'util';
|
||||
|
||||
export interface ErrorHandling {
|
||||
/**
|
||||
* Number of times the process should throw an error until crashing and exiting
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
pipe,
|
||||
} from 'rxjs';
|
||||
import { CommandType, type ModuleStore, SernError } from '../structures';
|
||||
import { match, P } from 'ts-pattern';
|
||||
import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from './dispatchers';
|
||||
import { executeModule, makeModuleExecutor } from './observableHandling';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
@@ -38,14 +37,15 @@ function makeInteractionProcessor(
|
||||
);
|
||||
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(event.commandName) ??
|
||||
ms.BothCommands.get(event.commandName),
|
||||
ms.ApplicationCommands[event.commandType].get(commandName) ??
|
||||
ms.BothCommands.get(commandName),
|
||||
);
|
||||
return of({ module, event });
|
||||
} else if (event.isModalSubmit()) {
|
||||
@@ -95,28 +95,21 @@ function createDispatcher({
|
||||
event: Interaction;
|
||||
module: Processed<CommandModule>;
|
||||
}) {
|
||||
return (
|
||||
match(module)
|
||||
.with({ type: CommandType.Text }, () => {
|
||||
throw Error(SernError.MismatchEvent);
|
||||
})
|
||||
//P.union = either CommandType.Slash or CommandType.Both
|
||||
.with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => {
|
||||
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));
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Every other command module takes a one argument parameter, its corresponding interaction
|
||||
* this makes this usage safe
|
||||
*/
|
||||
.otherwise(mod => dispatchCommand(mod, interactionArg(event)))
|
||||
);
|
||||
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));
|
||||
}
|
||||
}
|
||||
default : return dispatchCommand(module, interactionArg(event));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { Awaitable, Message } from 'discord.js';
|
||||
import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs';
|
||||
import type { SernError } from '../structures';
|
||||
import { Result } from 'ts-results-es';
|
||||
import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module';
|
||||
import type { CommandModule, EventModule, Module } from '../../types/module';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import { callPlugin, everyPluginOk, filterMapTo } from './operators';
|
||||
import type { Processed } from '../../types/handler';
|
||||
@@ -30,27 +29,6 @@ export function ignoreNonBot<T extends Message>(prefix: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current value in Result stream is an error, calls callback.
|
||||
* This also extracts the Ok value from Result
|
||||
* @param cb
|
||||
* @returns Observable<{ module: T; absPath: string }>
|
||||
*/
|
||||
export function errTap<T extends AnyModule>(cb: (err: SernError) => void) {
|
||||
return (src: Observable<Result<{ module: T; absPath: string }, SernError>>) =>
|
||||
new Observable<{ module: T; absPath: string }>(subscriber => {
|
||||
return src.subscribe({
|
||||
next(value) {
|
||||
if (value.err) {
|
||||
cb(value.val);
|
||||
} else {
|
||||
subscriber.next(value.val);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the task in a Result as a try / catch.
|
||||
* if the task is ok, an event is emitted and the stream becomes empty
|
||||
@@ -119,7 +97,7 @@ export function createResultResolver<
|
||||
* Calls a module's init plugins and checks for Err. If so, call { onFailure } and
|
||||
* ignore the module
|
||||
*/
|
||||
export function scanModule<
|
||||
export function callInitPlugins<
|
||||
T extends Processed<CommandModule | EventModule>,
|
||||
Args extends { module: T; absPath: string },
|
||||
>(config: { onFailure?: (module: T) => unknown; onSuccess: (module: Args) => T }) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import { nameOrFilename } from '../../utilities/functions';
|
||||
import type { PluginResult, VoidResult } from '../../../types/plugin';
|
||||
import { guayin } from '../../plugins';
|
||||
import { controller } from '../../sern';
|
||||
import { SernError } from '../../structures';
|
||||
import { Result } from 'ts-results-es';
|
||||
|
||||
/**
|
||||
* if {src} is true, mapTo V, else ignore
|
||||
@@ -48,7 +50,7 @@ export function callPlugin(args: unknown): OperatorFunction<
|
||||
* operator function that fill the defaults for a module
|
||||
*/
|
||||
export function defineAllFields<T extends AnyModule>() {
|
||||
const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({
|
||||
const fillFields = ({ module, absPath }: { module: T; absPath: string }) => ({
|
||||
absPath,
|
||||
module: {
|
||||
name: nameOrFilename(module.name, absPath),
|
||||
@@ -59,6 +61,27 @@ export function defineAllFields<T extends AnyModule>() {
|
||||
return pipe(map(fillFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current value in Result stream is an error, calls callback.
|
||||
* This also extracts the Ok value from Result
|
||||
* @param cb
|
||||
* @returns Observable<{ module: T; absPath: string }>
|
||||
*/
|
||||
export function errTap<T extends AnyModule>(
|
||||
cb: (err: SernError) => void
|
||||
): OperatorFunction<Result<{ module: T; absPath: string}, SernError>, { module: T; absPath: string }> {
|
||||
return pipe(
|
||||
concatMap(result => {
|
||||
if(result.ok) {
|
||||
return of(result.val);
|
||||
} else {
|
||||
cb(result.val);
|
||||
return EMPTY;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the stream of results is all ok.
|
||||
*/
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
import { fromEvent, pipe, switchMap, take } from 'rxjs';
|
||||
import * as Files from '../module-loading/readFile';
|
||||
import { errTap, scanModule } from './observableHandling';
|
||||
import { callInitPlugins } from './observableHandling';
|
||||
import { CommandType, type ModuleStore, SernError } from '../structures';
|
||||
import { match } from 'ts-pattern';
|
||||
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 { ErrorHandling, Logging, ModuleManager } from '../contracts';
|
||||
import { err, ok } from '../utilities/functions';
|
||||
import { defineAllFields } from './operators';
|
||||
import { defineAllFields, errTap } from './operators';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import type { EventEmitter } from 'node:events';
|
||||
|
||||
|
||||
function buildCommandModules(
|
||||
commandDir: string,
|
||||
sernEmitter: SernEmitter
|
||||
) {
|
||||
return pipe(
|
||||
switchMap(() => Files.buildData<CommandModule>(commandDir)),
|
||||
errTap(error => {
|
||||
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
defineAllFields(),
|
||||
);
|
||||
}
|
||||
export function makeReadyEvent(
|
||||
[sEmitter, client, errorHandler, , moduleManager]: [
|
||||
SernEmitter,
|
||||
@@ -24,24 +36,17 @@ export function makeReadyEvent(
|
||||
commandDir: string,
|
||||
) {
|
||||
const readyOnce$ = fromEvent(client, 'ready').pipe(take(1));
|
||||
const parseCommandModules = pipe(
|
||||
switchMap(() => Files.buildData<CommandModule>(commandDir)),
|
||||
errTap(error => {
|
||||
sEmitter.emit('module.register', SernEmitter.failure(undefined, error));
|
||||
}),
|
||||
defineAllFields(),
|
||||
);
|
||||
return readyOnce$
|
||||
.pipe(
|
||||
parseCommandModules,
|
||||
scanModule({
|
||||
buildCommandModules(commandDir, sEmitter),
|
||||
callInitPlugins({
|
||||
onFailure: module => {
|
||||
sEmitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
);
|
||||
},
|
||||
onSuccess: ({ module }) => {
|
||||
onSuccess: ({ module }) => {
|
||||
sEmitter.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
},
|
||||
@@ -64,42 +69,35 @@ function registerModule<T extends Processed<CommandModule>>(
|
||||
const set = Result.wrap(() => manager.set(cb));
|
||||
return set.ok ? ok() : err();
|
||||
};
|
||||
return match(mod as Processed<CommandModule>)
|
||||
.with({ type: CommandType.Text }, mod => {
|
||||
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
|
||||
return insert(ms => ms.TextCommands.set(name, mod));
|
||||
})
|
||||
.with({ type: CommandType.Slash }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Both }, mod => {
|
||||
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));
|
||||
})
|
||||
.with({ type: CommandType.CtxUser }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.CtxMsg }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Button }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.StringSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.MentionableSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.ChannelSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.ChannelSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.UserSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.RoleSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Modal }, mod => insert(ms => ms.ModalSubmit.set(name, mod)))
|
||||
.otherwise(err);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { catchError, finalize, map, mergeAll } from 'rxjs';
|
||||
import { buildData } from '../module-loading/readFile';
|
||||
import type { Dependencies, Processed } from '../../types/handler';
|
||||
import { errTap, scanModule } from './observableHandling';
|
||||
import { callInitPlugins } from './observableHandling';
|
||||
import type { CommandModule, EventModule } from '../../types/module';
|
||||
import type { EventEmitter } from 'events';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { ErrorHandling, Logging } from '../contracts';
|
||||
import { SernError, EventType, type Wrapper } from '../structures';
|
||||
import { eventDispatcher } from './dispatchers';
|
||||
import { handleError } from '../contracts/errorHandling';
|
||||
import { defineAllFields } from './operators';
|
||||
import { defineAllFields, errTap } from './operators';
|
||||
import { useContainerRaw } from '../dependencies';
|
||||
|
||||
export function makeEventsHandler(
|
||||
@@ -23,20 +22,22 @@ export function makeEventsHandler(
|
||||
|
||||
const eventCreation$ = eventStream$.pipe(
|
||||
defineAllFields(),
|
||||
scanModule({
|
||||
onFailure: module => s.emit('module.register', SernEmitter.success(module)),
|
||||
callInitPlugins({
|
||||
onFailure: module => s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
|
||||
onSuccess: ({ module }) => {
|
||||
s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure));
|
||||
s.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
},
|
||||
}),
|
||||
);
|
||||
const intoDispatcher = (e: Processed<EventModule | CommandModule>) =>
|
||||
match(e)
|
||||
.with({ type: EventType.Sern }, m => eventDispatcher(m, s))
|
||||
.with({ type: EventType.Discord }, m => eventDispatcher(m, client))
|
||||
.with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter)))
|
||||
.otherwise(() => err.crash(Error(SernError.InvalidModuleType)));
|
||||
const intoDispatcher = (e: Processed<EventModule | CommandModule>) => {
|
||||
switch(e.type) {
|
||||
case EventType.Sern: return eventDispatcher(e, s);
|
||||
case EventType.Discord: return eventDispatcher(e, client);
|
||||
case EventType.External: return eventDispatcher(e, lazy(e.emitter));
|
||||
default: err.crash(Error(SernError.InvalidModuleType + ' while creating event handler'));
|
||||
}
|
||||
};
|
||||
eventCreation$
|
||||
.pipe(
|
||||
map(intoDispatcher),
|
||||
|
||||
@@ -43,7 +43,7 @@ export function buildData<T>(commandDir: string): Observable<
|
||||
/// #if MODE === 'esm'
|
||||
= (await import(`file:///` + absPath)).default
|
||||
/// #elif MODE === 'cjs'
|
||||
= require(absPath).default;
|
||||
= require(absPath).default; // eslint-disable-line
|
||||
/// #endif
|
||||
|
||||
if (module === undefined) {
|
||||
|
||||
@@ -19,13 +19,13 @@ import type {
|
||||
MentionableSelectMenuInteraction,
|
||||
RoleSelectMenuInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
UserSelectMenuInteraction
|
||||
} from 'discord.js';
|
||||
import { CommandType } from '../handler/structures/enums';
|
||||
import type { Args, SlashOptions } from './handler';
|
||||
import type Context from '../handler/structures/context';
|
||||
import type { InitPlugin, ControlPlugin } from './plugin';
|
||||
import { EventType } from '../handler/structures/enums';
|
||||
import type { UserSelectMenuInteraction } from 'discord.js';
|
||||
import type { AnyCommandPlugin, AnyEventPlugin } from './plugin';
|
||||
import type { SernEventsMapping } from './handler';
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
|
||||
Reference in New Issue
Block a user