mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
-file,-updateModule,publish?
This commit is contained in:
@@ -44,8 +44,8 @@ const TypeMap = new Map<number, number>([[CommandType.Text, 0],
|
||||
[CommandType.CtxUser, ApplicationCommandType.User],
|
||||
[CommandType.CtxMsg, ApplicationCommandType.Message],
|
||||
[CommandType.Button, ComponentType.Button],
|
||||
[CommandType.Modal, InteractionType.ModalSubmit],
|
||||
[CommandType.StringSelect, ComponentType.StringSelect],
|
||||
[CommandType.Modal, InteractionType.ModalSubmit],
|
||||
[CommandType.UserSelect, ComponentType.UserSelect],
|
||||
[CommandType.MentionableSelect, ComponentType.MentionableSelect],
|
||||
[CommandType.RoleSelect, ComponentType.RoleSelect],
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* This file holds sern's rxjs operators used for processing data.
|
||||
* Each function should be modular and testable, not bound to discord / sern
|
||||
* and independent of each other.
|
||||
*/
|
||||
import {
|
||||
concatMap,
|
||||
EMPTY,
|
||||
fromEvent,
|
||||
Observable,
|
||||
of,
|
||||
OperatorFunction,
|
||||
share,
|
||||
} from 'rxjs';
|
||||
import type { Emitter, ErrorHandling, Logging } from './interfaces';
|
||||
import util from 'node:util';
|
||||
import type { Result } from 'ts-results-es';
|
||||
|
||||
/**
|
||||
* if {src} is true, mapTo V, else ignore
|
||||
* @param item
|
||||
*/
|
||||
export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
|
||||
return concatMap(keep => keep ? of(item()) : EMPTY);
|
||||
}
|
||||
|
||||
export const arrayifySource = <T>(src: T) =>
|
||||
Array.isArray(src) ? src : [src];
|
||||
|
||||
export const sharedEventStream = <T>(e: Emitter, eventName: string) =>
|
||||
(fromEvent(e, eventName) as Observable<T>).pipe(share());
|
||||
|
||||
export function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) {
|
||||
return (pload: unknown, caught: Observable<C>) => {
|
||||
// This is done to fit the ErrorHandling contract
|
||||
if(!emitter.emit('error', pload)) {
|
||||
const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true }));
|
||||
logging?.error({ message: util.inspect(pload) });
|
||||
crashHandler.updateAlive(err);
|
||||
}
|
||||
return caught;
|
||||
};
|
||||
}
|
||||
//// 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;
|
||||
})
|
||||
@@ -61,4 +61,5 @@ export const Presence = {
|
||||
export type PresenceConfig <T extends (keyof Dependencies)[]> = {
|
||||
inject?: [...T]
|
||||
execute: (...v: IntoDependencies<T>) => PresenceResult;
|
||||
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Result, Ok, Err } from 'ts-results-es';
|
||||
import * as assert from 'assert';
|
||||
import type { ReplyOptions } from '../../types/utility';
|
||||
import { fmt } from '../functions'
|
||||
import { SernError } from './enums';
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,7 +22,7 @@ import { fmt } from '../functions'
|
||||
* Message and ChatInputCommandInteraction
|
||||
*/
|
||||
export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
|
||||
|
||||
|
||||
get options() {
|
||||
if(this.isMessage()) {
|
||||
const [, ...rest] = fmt(this.message.content, this.prefix);
|
||||
@@ -30,6 +31,7 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
|
||||
return this.interaction.options;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>,
|
||||
private __prefix?: string) {
|
||||
@@ -94,6 +96,15 @@ export class Context extends CoreContext<Message, ChatInputCommandInteraction> {
|
||||
.mapErr(i => i.member));
|
||||
}
|
||||
|
||||
get message(): Message {
|
||||
return this.ctx.expect(SernError.MismatchEvent);
|
||||
}
|
||||
|
||||
get interaction(): ChatInputCommandInteraction {
|
||||
return this.ctx.expectErr(SernError.MismatchEvent);
|
||||
}
|
||||
|
||||
|
||||
public get client(): Client {
|
||||
return safeUnwrap(this.ctx
|
||||
.map(m => m.client)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Result as Either } from 'ts-results-es';
|
||||
import { SernError } from './enums';
|
||||
import * as assert from 'node:assert';
|
||||
|
||||
/**
|
||||
@@ -9,13 +8,6 @@ export abstract class CoreContext<M, I> {
|
||||
protected constructor(protected ctx: Either<M, I>) {
|
||||
assert.ok(typeof ctx === 'object' && ctx != null, "Context was nonobject or null");
|
||||
}
|
||||
get message(): M {
|
||||
return this.ctx.expect(SernError.MismatchEvent);
|
||||
}
|
||||
get interaction(): I {
|
||||
return this.ctx.expectErr(SernError.MismatchEvent);
|
||||
}
|
||||
|
||||
public isMessage(): this is CoreContext<M, never> {
|
||||
return this.ctx.isOk();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { Interaction, Message, BaseInteraction } from 'discord.js';
|
||||
import util from 'node:util';
|
||||
import {
|
||||
EMPTY, type Observable, concatMap, filter,
|
||||
throwError, fromEvent, map, type OperatorFunction,
|
||||
catchError, finalize, pipe, from, take,
|
||||
catchError, finalize, pipe, from, take, share, of,
|
||||
} from 'rxjs';
|
||||
import * as Id from '../core/id'
|
||||
import type { Emitter } from '../core/interfaces';
|
||||
import type { Emitter, ErrorHandling, Logging } from '../core/interfaces';
|
||||
import { SernError } from '../core/structures/enums'
|
||||
import { Err, Ok, Result } from 'ts-results-es';
|
||||
import type { UnpackedDependencies } from '../types/utility';
|
||||
@@ -15,9 +16,22 @@ import { Context } from '../core/structures/context';
|
||||
import { CommandType } from '../core/structures/enums'
|
||||
import { inspect } from 'node:util'
|
||||
import { disposeAll } from '../core/ioc/base';
|
||||
import { arrayifySource, handleError } from '../core/operators';
|
||||
import { resultPayload, isAutocomplete, treeSearch, fmt } from '../core/functions'
|
||||
|
||||
function handleError<C>(crashHandler: ErrorHandling, emitter: Emitter, logging?: Logging) {
|
||||
return (pload: unknown, caught: Observable<C>) => {
|
||||
// This is done to fit the ErrorHandling contract
|
||||
if(!emitter.emit('error', pload)) {
|
||||
const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true }));
|
||||
logging?.error({ message: util.inspect(pload) });
|
||||
crashHandler.updateAlive(err);
|
||||
}
|
||||
return caught;
|
||||
};
|
||||
}
|
||||
const arrayify= <T>(src: T) =>
|
||||
Array.isArray(src) ? src : [src];
|
||||
|
||||
interface ExecutePayload {
|
||||
module: Module;
|
||||
args: unknown[];
|
||||
@@ -26,8 +40,20 @@ interface ExecutePayload {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
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;
|
||||
})
|
||||
|
||||
export const sharedEventStream = <T>(e: Emitter, eventName: string) =>
|
||||
(fromEvent(e, eventName) as Observable<T>).pipe(share());
|
||||
|
||||
function intoPayload(module: Module, deps: Dependencies) {
|
||||
return pipe(map(arrayifySource),
|
||||
return pipe(map(arrayify),
|
||||
map(args => ({ module, args, deps })),
|
||||
map(p => p.args));
|
||||
}
|
||||
@@ -151,10 +177,7 @@ export function createMessageHandler(
|
||||
* @param module the module that will be executed with task
|
||||
* @param task the deferred execution which will be called
|
||||
*/
|
||||
export function executeModule(
|
||||
emitter: Emitter,
|
||||
{ module, args }: ExecutePayload,
|
||||
) {
|
||||
export function executeModule(emitter: Emitter, { module, args }: ExecutePayload) {
|
||||
return from(Result.wrapAsync(async () => module.execute(...args)))
|
||||
.pipe(concatMap(result => {
|
||||
if (result.isOk()) {
|
||||
@@ -181,6 +204,7 @@ export function createResultResolver<Output>(config: {
|
||||
const { onStop, onNext } = config;
|
||||
return async (payload: ExecutePayload) => {
|
||||
const task = await callPlugins(payload);
|
||||
if (!task) throw Error("Plugin did not return anything.");
|
||||
if(task.isOk()) {
|
||||
return onNext(payload, task.value) as Output;
|
||||
} else {
|
||||
@@ -189,18 +213,16 @@ export function createResultResolver<Output>(config: {
|
||||
};
|
||||
};
|
||||
|
||||
export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean ) {
|
||||
export async function callInitPlugins(module: Module, deps: Dependencies, emit?: boolean) {
|
||||
let _module = module;
|
||||
const emitter = deps['@sern/emitter'];
|
||||
for(const plugin of _module.plugins ?? []) {
|
||||
const res = await plugin.execute({
|
||||
module, absPath: _module.meta.absPath,
|
||||
updateModule: (partial: Partial<Module>) => {
|
||||
_module = { ..._module, ...partial };
|
||||
return _module;
|
||||
},
|
||||
module: _module,
|
||||
absPath: _module.meta.absPath,
|
||||
deps
|
||||
});
|
||||
if (!res) throw Error("Plugin did not return anything.");
|
||||
if(res.isErr()) {
|
||||
if(emit) {
|
||||
emitter?.emit('module.register',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Interaction } from 'discord.js';
|
||||
import { mergeMap, merge, concatMap, EMPTY } from 'rxjs';
|
||||
import { filterTap, sharedEventStream } from '../core/operators'
|
||||
import { createInteractionHandler, executeModule, intoTask } from './event-utils';
|
||||
import { createInteractionHandler, executeModule, intoTask, sharedEventStream, filterTap } from './event-utils';
|
||||
import { SernError } from '../core/structures/enums'
|
||||
import { isAutocomplete, isCommand, isMessageComponent, isModal, resultPayload } from '../core/functions'
|
||||
import { UnpackedDependencies } from '../types/utility';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { EMPTY, mergeMap, concatMap } from 'rxjs';
|
||||
import type { Message } from 'discord.js';
|
||||
import { createMessageHandler, executeModule, intoTask } from './event-utils';
|
||||
import { PayloadType, SernError } from '../core/structures/enums'
|
||||
import { createMessageHandler, executeModule, intoTask, sharedEventStream, filterTap} from './event-utils';
|
||||
import { SernError } from '../core/structures/enums'
|
||||
import { resultPayload } from '../core/functions'
|
||||
import { filterTap, sharedEventStream } from '../core/operators'
|
||||
import { UnpackedDependencies } from '../types/utility';
|
||||
import type { Emitter } from '../core/interfaces';
|
||||
|
||||
@@ -36,7 +35,7 @@ function (deps: UnpackedDependencies, defaultPrefix?: string) {
|
||||
const msgCommands$ = handle(isNonBot(defaultPrefix));
|
||||
|
||||
return msgCommands$.pipe(
|
||||
filterTap(e => emitter.emit('warning', resultPayload(PayloadType.Warning, undefined, e))),
|
||||
filterTap(e => emitter.emit('warning', resultPayload('warning', undefined, e))),
|
||||
concatMap(intoTask(module => {
|
||||
const result = resultPayload('failure', module, SernError.PluginFailure);
|
||||
emitter.emit('module.activate', result);
|
||||
|
||||
14
src/sern.ts
14
src/sern.ts
@@ -10,6 +10,7 @@ import { handleCrash } from './handlers/event-utils';
|
||||
import { useContainerRaw } from './core/ioc/global';
|
||||
import { UnpackedDependencies } from './types/utility';
|
||||
import type { PresenceResult } from './core/presences';
|
||||
import fs from 'fs/promises'
|
||||
|
||||
interface Wrapper {
|
||||
commands: string;
|
||||
@@ -63,3 +64,16 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
|
||||
// listening to the message stream and interaction stream
|
||||
merge(messages$, interactions$).pipe(handleCrash(deps)).subscribe();
|
||||
}
|
||||
|
||||
|
||||
export async function publisher() {
|
||||
const directoryToWatch = "./src/commands";
|
||||
const watcher = fs.watch(directoryToWatch, { recursive: true }, );
|
||||
for await (const { eventType, filename } of watcher) {
|
||||
switch(eventType) {
|
||||
case 'change': {
|
||||
console.log('change', filename)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
StringSelectMenuInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
UserSelectMenuInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
} from 'discord.js';
|
||||
import type { CommandType, EventType } from '../core/structures/enums';
|
||||
import { Context } from '../core/structures/context'
|
||||
@@ -123,14 +124,14 @@ export interface DiscordEventCommand<T extends keyof ClientEvents = keyof Client
|
||||
}
|
||||
export interface TextCommand extends Module {
|
||||
type: CommandType.Text;
|
||||
execute: (ctx: Context, tbd: SDT) => Awaitable<unknown>;
|
||||
execute: (ctx: Context & { get options(): string[] }, tbd: SDT) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface SlashCommand extends Module {
|
||||
type: CommandType.Slash;
|
||||
description: string;
|
||||
options?: SernOptionsData[];
|
||||
execute: (ctx: Context, tbd: SDT) => Awaitable<unknown>;
|
||||
execute: (ctx: Context & { get options(): ChatInputCommandInteraction['options']}, tbd: SDT) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface BothCommand extends Module {
|
||||
|
||||
@@ -56,9 +56,11 @@ vi.mock('discord.js', async (importOriginal) => {
|
||||
});
|
||||
|
||||
function createRandomPlugin (s: 'go', mut?: Partial<Module>) {
|
||||
return CommandInitPlugin(({ module, updateModule }) => {
|
||||
return CommandInitPlugin(({ module }) => {
|
||||
if(mut) {
|
||||
updateModule(mut)
|
||||
for(const [k,v] of Object.entries(mut)) {
|
||||
module[k] = v
|
||||
}
|
||||
}
|
||||
return s == 'go'
|
||||
? controller.next()
|
||||
@@ -105,8 +107,10 @@ test ('mutate with init plugins', async () => {
|
||||
const deps = mockDeps()
|
||||
const plugins = createRandomPlugin('go', { name: "abc" })
|
||||
const mod = createRandomModule([plugins])
|
||||
console.log(mod)
|
||||
const s = await callInitPlugins(mod, deps, false)
|
||||
expect(s.name).not.equal(mod.name)
|
||||
console.log(s)
|
||||
expect("abc").equal(s.name)
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user