mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
feat: Separating events from command modules, leads separation of responsibility
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import type { Message } from 'discord.js';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { SernError } from '../structures/errors';
|
||||
import type { Module, ModuleDefs } from '../structures/module';
|
||||
import type { Module, CommandModuleDefs } from '../structures/module';
|
||||
import { correctModuleType } from '../utilities/predicates';
|
||||
import type { Result } from 'ts-results';
|
||||
export function filterCorrectModule<T extends keyof ModuleDefs>(cmdType: T) {
|
||||
export function filterCorrectModule<T extends keyof CommandModuleDefs>(cmdType: T) {
|
||||
return (src: Observable<Module | undefined>) =>
|
||||
new Observable<ModuleDefs[T]>(subscriber => {
|
||||
new Observable<CommandModuleDefs[T]>(subscriber => {
|
||||
return src.subscribe({
|
||||
next(mod) {
|
||||
if (mod === undefined) {
|
||||
|
||||
@@ -6,13 +6,13 @@ import type { Result } from 'ts-results';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import type { Awaitable } from 'discord.js';
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import type { CommandModule, Module } from '../structures/module';
|
||||
import type { CommandModule } from '../structures/module';
|
||||
import { match } from 'ts-pattern';
|
||||
import { SernError } from '../structures/errors';
|
||||
import type { DefinedCommandModule, DefinedModule } from '../../types/handler';
|
||||
import type { DefinedCommandModule } from '../../types/handler';
|
||||
import { CommandType, PluginType } from '../structures/enums';
|
||||
import { errTap } from './observableHandling';
|
||||
import { processCommandPlugins$ } from './userDefinedEventsHandling';
|
||||
import { processCommandPlugins } from './userDefinedEventsHandling';
|
||||
|
||||
export function onReady(wrapper: Wrapper) {
|
||||
const { client, commands } = wrapper;
|
||||
@@ -37,11 +37,8 @@ export function onReady(wrapper: Wrapper) {
|
||||
);
|
||||
const processPlugins$ = processCommandFiles$.pipe(
|
||||
concatMap(mod => {
|
||||
const cmdPluginRes = processCommandPlugins$(wrapper, mod);
|
||||
if (cmdPluginRes.err) {
|
||||
return cmdPluginRes.val;
|
||||
}
|
||||
return of({ mod, cmdPluginRes: cmdPluginRes.val });
|
||||
const cmdPluginRes = processCommandPlugins(wrapper, mod);
|
||||
return of({ mod, cmdPluginRes });
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -84,9 +81,9 @@ export function onReady(wrapper: Wrapper) {
|
||||
});
|
||||
}
|
||||
|
||||
function registerModule(mod: DefinedModule): Result<void, void> {
|
||||
function registerModule(mod: DefinedCommandModule): Result<void, void> {
|
||||
const name = mod.name;
|
||||
return match<Module>(mod)
|
||||
return match<DefinedCommandModule>(mod)
|
||||
.with({ type: CommandType.Text }, mod => {
|
||||
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
|
||||
Files.TextCommands.text.set(name, mod);
|
||||
|
||||
@@ -1,76 +1,27 @@
|
||||
import { CommandType } from '../structures/enums';
|
||||
import { from, fromEvent, map, throwError } from 'rxjs';
|
||||
import { SernError } from '../structures/errors';
|
||||
import { from, fromEvent, map } from 'rxjs';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import { buildData, ExternalEventEmitters } from '../utilities/readFile';
|
||||
import { controller } from '../sern';
|
||||
import type { DefinedEventModule, DefinedModule, SpreadParams } from '../../types/handler';
|
||||
import type { DefinedCommandModule, DefinedEventModule, SpreadParams } from '../../types/handler';
|
||||
import type { EventModule } from '../structures/module';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { basename } from 'path';
|
||||
import { match, P } from 'ts-pattern';
|
||||
import { isDiscordEvent, isSernEvent } from '../utilities/predicates';
|
||||
import type { CommandPlugin } from '../plugins/plugin';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import { errTap } from './observableHandling';
|
||||
|
||||
/**
|
||||
* Utility function to process command plugins for all Modules
|
||||
* @param client
|
||||
* @param sernEmitter
|
||||
* @param mod
|
||||
* @param absPath
|
||||
*/
|
||||
export function processCommandPlugins$<T extends DefinedModule>(
|
||||
{ client, sernEmitter }: Wrapper,
|
||||
mod: T,
|
||||
) {
|
||||
return match(mod as DefinedModule)
|
||||
.with({ type: CommandType.External }, m =>
|
||||
Ok(
|
||||
m.plugins.map(plug => ({
|
||||
...plug,
|
||||
name: plug?.name ?? 'Unnamed Plugin',
|
||||
description: plug?.description ?? '...',
|
||||
execute: plug.execute(ExternalEventEmitters.get(m.emitter)!, m, controller),
|
||||
})),
|
||||
),
|
||||
)
|
||||
.with({ type: CommandType.Sern }, m =>
|
||||
Ok(
|
||||
m.plugins.map(plug => ({
|
||||
...plug,
|
||||
name: plug?.name ?? 'Unnamed Plugin',
|
||||
description: plug?.description ?? '...',
|
||||
execute: plug.execute(sernEmitter!, m, controller),
|
||||
})),
|
||||
),
|
||||
)
|
||||
.with(
|
||||
{
|
||||
type: P.not(CommandType.Autocomplete),
|
||||
plugins: P.array({} as P.infer<CommandPlugin>),
|
||||
},
|
||||
m => {
|
||||
return Ok(
|
||||
m.plugins.map(plug => ({
|
||||
...plug,
|
||||
name: plug?.name ?? 'Unnamed Plugin',
|
||||
description: plug?.description ?? '...',
|
||||
execute: plug.execute(client, m, controller),
|
||||
})),
|
||||
);
|
||||
},
|
||||
)
|
||||
.otherwise(() =>
|
||||
Err(
|
||||
throwError(
|
||||
() =>
|
||||
SernError.NonValidModuleType +
|
||||
`. You cannot use command plugins and Autocomplete.`,
|
||||
),
|
||||
),
|
||||
);
|
||||
export function processCommandPlugins<T extends DefinedCommandModule>({ client }: Wrapper, mod: T) {
|
||||
return mod.plugins.map(plug => ({
|
||||
...plug,
|
||||
name: plug?.name ?? 'Unnamed Plugin',
|
||||
description: plug?.description ?? '...',
|
||||
execute: plug.execute(client, mod, controller),
|
||||
}));
|
||||
}
|
||||
|
||||
export function processEvents(
|
||||
|
||||
@@ -14,12 +14,18 @@
|
||||
import type { Awaitable, Client } from 'discord.js';
|
||||
import type { Err, Ok, Result } from 'ts-results';
|
||||
import type { DefinitelyDefined, Module, Override } from '../..';
|
||||
import { CommandType } from '../..';
|
||||
import type { AutocompleteCommand, BaseModule, ModuleDefs } from '../structures/module';
|
||||
import type { CommandType } from '../..';
|
||||
import type {
|
||||
BaseModule,
|
||||
EventModule,
|
||||
CommandModuleDefs,
|
||||
CommandModule,
|
||||
} from '../structures/module';
|
||||
import { PluginType } from '../structures/enums';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type { ExternalEventCommand, SernEventCommand } from '../structures/events';
|
||||
import type SernEmitter from '../sernEmitter';
|
||||
import type { AutocompleteInteraction } from 'discord.js';
|
||||
|
||||
export interface Controller {
|
||||
next: () => Ok<void>;
|
||||
@@ -33,14 +39,14 @@ type BasePlugin = Override<
|
||||
}
|
||||
>;
|
||||
|
||||
export type CommandPlugin<T extends keyof ModuleDefs = keyof ModuleDefs> = {
|
||||
export type CommandPlugin<T extends keyof CommandModuleDefs = keyof CommandModuleDefs> = {
|
||||
[K in T]: Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
wrapper: Client,
|
||||
module: DefinitelyDefined<ModuleDefs[T], 'name' | 'description'>,
|
||||
module: DefinitelyDefined<CommandModuleDefs[T], 'name' | 'description'>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
@@ -71,13 +77,24 @@ export type SernEmitterPlugin = Override<
|
||||
}
|
||||
>;
|
||||
|
||||
export type EventPlugin<T extends keyof ModuleDefs = keyof ModuleDefs> = {
|
||||
export type AutocompletePlugin = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: PluginType.Event;
|
||||
execute: (
|
||||
autocmp: AutocompleteInteraction,
|
||||
controlller: Controller,
|
||||
) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type EventPlugin<T extends keyof CommandModuleDefs = keyof CommandModuleDefs> = {
|
||||
[K in T]: Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Event;
|
||||
execute: (
|
||||
event: Parameters<ModuleDefs[K]['execute']>,
|
||||
event: Parameters<CommandModuleDefs[K]['execute']>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
@@ -92,7 +109,7 @@ export type EventPlugin<T extends keyof ModuleDefs = keyof ModuleDefs> = {
|
||||
// }
|
||||
|
||||
export type ModuleNoPlugins = {
|
||||
[T in keyof ModuleDefs]: Omit<ModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
[T in keyof CommandModuleDefs]: Omit<CommandModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
|
||||
function isEventPlugin<T extends CommandType>(
|
||||
@@ -107,31 +124,54 @@ function isCommandPlugin<T extends CommandType>(
|
||||
}
|
||||
//TODO: I WANT BETTER TYPINGS AHHHHHHHHHHHHHHH
|
||||
// Maybe add overlaods
|
||||
export function sernModule<T extends keyof ModuleDefs>(
|
||||
|
||||
export function sernModule<T extends CommandType.Slash = CommandType.Slash>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.Slash],
|
||||
): Module;
|
||||
export function sernModule<T extends CommandType.Text = CommandType.Text>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.Text],
|
||||
): Module;
|
||||
export function sernModule<T extends CommandType.Button = CommandType.Button>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.Button],
|
||||
): Module;
|
||||
export function sernModule<T extends CommandType.Both = CommandType.Both>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.Both],
|
||||
): Module;
|
||||
export function sernModule<T extends CommandType.MenuUser = CommandType.MenuUser>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.MenuMsg],
|
||||
): Module;
|
||||
export function sernModule<T extends CommandType.MenuSelect = CommandType.MenuSelect>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.MenuSelect],
|
||||
): Module;
|
||||
|
||||
export function sernModule<T extends CommandType.Modal = CommandType.Modal>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.Modal],
|
||||
): Module;
|
||||
|
||||
export function sernModule<T extends CommandType.MenuUser = CommandType.MenuUser>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[CommandType.MenuUser],
|
||||
): Module;
|
||||
export function sernModule<T extends keyof CommandModuleDefs = keyof CommandModuleDefs>(
|
||||
plugin: (CommandPlugin<T> | EventPlugin<T>)[],
|
||||
mod: ModuleNoPlugins[T],
|
||||
): Module {
|
||||
): CommandModule {
|
||||
const onEvent = plugin.filter(isEventPlugin);
|
||||
const plugins = plugin.filter(isCommandPlugin);
|
||||
if (mod.type === CommandType.Autocomplete) {
|
||||
throw new Error(
|
||||
'You cannot use this function declaration for Autocomplete Interactions! use the raw object for options or' +
|
||||
'sernAutoComplete function',
|
||||
);
|
||||
} else
|
||||
return {
|
||||
onEvent,
|
||||
plugins,
|
||||
...mod,
|
||||
} as Module;
|
||||
}
|
||||
|
||||
export function sernAutocomplete(
|
||||
onEvent: EventPlugin<CommandType.Autocomplete>[],
|
||||
mod: Omit<AutocompleteCommand, 'type' | 'name' | 'description' | 'onEvent'>,
|
||||
): Omit<AutocompleteCommand, 'type' | 'name' | 'description'> {
|
||||
return {
|
||||
onEvent,
|
||||
plugins,
|
||||
...mod,
|
||||
};
|
||||
} as CommandModule;
|
||||
}
|
||||
|
||||
export function eventModule<T extends keyof EventModule>(): EventModule {
|
||||
return {} as EventModule;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import type {
|
||||
UserContextMenuCommandInteraction,
|
||||
} from 'discord.js';
|
||||
import type { Args, Override, SlashOptions } from '../../types/handler';
|
||||
import type { CommandPlugin, EventPlugin } from '../plugins/plugin';
|
||||
import type { AutocompletePlugin, CommandPlugin, EventPlugin } from '../plugins/plugin';
|
||||
import type Context from './context';
|
||||
import { CommandType, PluginType } from './enums';
|
||||
import { CommandType, EventType, PluginType } from './enums';
|
||||
import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from './events';
|
||||
|
||||
export interface BaseModule {
|
||||
@@ -122,9 +122,10 @@ export type ModalSubmitCommand = Override<
|
||||
export type AutocompleteCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Autocomplete;
|
||||
name: string;
|
||||
onEvent: EventPlugin<CommandType.Autocomplete>[];
|
||||
name?: never;
|
||||
description?: never;
|
||||
type?: never;
|
||||
onEvent: AutocompletePlugin[];
|
||||
execute: (ctx: AutocompleteInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
@@ -137,14 +138,13 @@ export type CommandModule =
|
||||
| ContextMenuMsg
|
||||
| ButtonCommand
|
||||
| SelectMenuCommand
|
||||
| ModalSubmitCommand
|
||||
| AutocompleteCommand;
|
||||
| ModalSubmitCommand;
|
||||
|
||||
export type Module = CommandModule | EventModule;
|
||||
|
||||
//https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union
|
||||
// Explicit Module Definitions for mapping
|
||||
export type ModuleDefs = {
|
||||
export type CommandModuleDefs = {
|
||||
[CommandType.Text]: TextCommand;
|
||||
[CommandType.Slash]: SlashCommand;
|
||||
[CommandType.Both]: BothCommand;
|
||||
@@ -153,10 +153,12 @@ export type ModuleDefs = {
|
||||
[CommandType.Button]: ButtonCommand;
|
||||
[CommandType.MenuSelect]: SelectMenuCommand;
|
||||
[CommandType.Modal]: ModalSubmitCommand;
|
||||
[CommandType.Autocomplete]: AutocompleteCommand;
|
||||
[CommandType.Sern]: SernEventCommand;
|
||||
[CommandType.Discord]: DiscordEventCommand;
|
||||
[CommandType.External]: ExternalEventCommand;
|
||||
};
|
||||
|
||||
export type EventModuleDefs = {
|
||||
[EventType.Sern]: SernEventCommand;
|
||||
[EventType.Discord]: DiscordEventCommand;
|
||||
[EventType.External]: ExternalEventCommand;
|
||||
};
|
||||
|
||||
//TODO: support deeply nested Autocomplete
|
||||
@@ -171,7 +173,7 @@ export type SernAutocompleteData = Override<
|
||||
| ApplicationCommandOptionType.String
|
||||
| ApplicationCommandOptionType.Number
|
||||
| ApplicationCommandOptionType.Integer;
|
||||
command: Omit<AutocompleteCommand, 'type' | 'name' | 'description'>;
|
||||
command: AutocompleteCommand;
|
||||
}
|
||||
>;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EventModule, Module, ModuleDefs } from '../structures/module';
|
||||
import type { CommandModuleDefs, EventModule, Module } from '../structures/module';
|
||||
import type {
|
||||
Awaitable,
|
||||
ButtonInteraction,
|
||||
@@ -9,23 +9,23 @@ import type {
|
||||
SelectMenuInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
} from 'discord.js';
|
||||
import type {
|
||||
DiscordEventCommand,
|
||||
ExternalEventCommand,
|
||||
SernEventCommand,
|
||||
} from '../structures/events';
|
||||
import { CommandType } from '../..';
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
Interaction,
|
||||
InteractionType,
|
||||
ModalSubmitInteraction,
|
||||
} from 'discord.js';
|
||||
import type {
|
||||
DiscordEventCommand,
|
||||
ExternalEventCommand,
|
||||
SernEventCommand,
|
||||
} from '../structures/events';
|
||||
import { EventType } from '../..';
|
||||
|
||||
export function correctModuleType<T extends keyof ModuleDefs>(
|
||||
export function correctModuleType<T extends keyof CommandModuleDefs>(
|
||||
plug: Module | undefined,
|
||||
type: T,
|
||||
): plug is ModuleDefs[T] {
|
||||
): plug is CommandModuleDefs[T] {
|
||||
// Another way to check if type is equivalent,
|
||||
// It will check based on flag system instead
|
||||
return plug !== undefined && (plug.type & type) !== 0;
|
||||
@@ -78,7 +78,7 @@ export function isPromise<T>(promiseLike: Awaitable<T>): promiseLike is PromiseL
|
||||
}
|
||||
|
||||
export function isDiscordEvent(el: EventModule): el is DiscordEventCommand {
|
||||
return el.type === CommandType.Discord;
|
||||
return el.type === EventType.Discord;
|
||||
}
|
||||
export function isSernEvent(el: EventModule): el is SernEventCommand {
|
||||
return !isDiscordEvent(el);
|
||||
@@ -89,5 +89,5 @@ export function isExternalEvent(el: EventModule): el is ExternalEventCommand {
|
||||
}
|
||||
|
||||
export function isEventModule(module: Module): module is EventModule {
|
||||
return [CommandType.Discord, CommandType.Sern, CommandType.External].includes(module.type);
|
||||
return [EventType.Sern, EventType.Discord, EventType.External].includes(module.type);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user