mirror of
https://github.com/sern-handler/handler
synced 2026-06-23 08:12:14 +00:00
feat(handler) moving and organizing files, refactor context api
This commit is contained in:
12
package-lock.json
generated
12
package-lock.json
generated
@@ -6879,9 +6879,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimist-options": {
|
||||
@@ -14100,9 +14100,9 @@
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist-options": {
|
||||
|
||||
@@ -6,12 +6,14 @@ import Context from '../structures/context';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import { filterTap } from './observableHandling';
|
||||
import { filter } from 'rxjs';
|
||||
|
||||
export const onInteractionCreate = ( wrapper : Wrapper ) => {
|
||||
const { client } = wrapper;
|
||||
|
||||
(<Observable<Interaction>> fromEvent(client, 'interactionCreate'))
|
||||
.pipe(
|
||||
.pipe(
|
||||
filter( i => i.inGuild() ),
|
||||
concatMap ( interaction => {
|
||||
if (interaction.isChatInputCommand()) {
|
||||
return of(Files.Commands.get(interaction.commandName))
|
||||
@@ -26,8 +28,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => {
|
||||
return of(Files.ContextMenuUser.get(interaction.commandName))
|
||||
.pipe(
|
||||
filterTap(CommandType.MENU_USER, mod => {
|
||||
const ctx = Context.wrap(interaction);
|
||||
mod.execute(ctx);
|
||||
mod.execute(interaction);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -35,8 +36,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => {
|
||||
return of(Files.ContextMenuMsg.get(interaction.commandName))
|
||||
.pipe(
|
||||
filterTap(CommandType.MENU_MSG, mod => {
|
||||
const ctx = Context.wrap(interaction);
|
||||
mod.execute(ctx);
|
||||
mod.execute(interaction);
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -44,8 +44,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => {
|
||||
return of(Files.Buttons.get(interaction.customId))
|
||||
.pipe(
|
||||
filterTap(CommandType.BUTTON, mod => {
|
||||
const ctx = Context.wrap(interaction);
|
||||
mod.execute(ctx);
|
||||
mod.execute(interaction);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -53,8 +52,7 @@ export const onInteractionCreate = ( wrapper : Wrapper ) => {
|
||||
return of(Files.SelectMenus.get(interaction.customId))
|
||||
.pipe(
|
||||
filterTap(CommandType.MENU_SELECT, mod => {
|
||||
const ctx = Context.wrap(interaction);
|
||||
mod.execute(ctx);
|
||||
mod.execute(interaction);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Message } from 'discord.js';
|
||||
import type { ChatInputCommandInteraction, Message } from 'discord.js';
|
||||
import { fromEvent, Observable, of, concatMap } from 'rxjs';
|
||||
import { CommandType } from '../sern';
|
||||
import Context from '../structures/context';
|
||||
@@ -15,14 +15,14 @@ export const onMessageCreate = (wrapper : Wrapper) => {
|
||||
concatMap ( m => {
|
||||
const [ prefix, ...data ] = fmt(m, defaultPrefix);
|
||||
const posMod = Files.Commands.get(prefix) ?? Files.Alias.get(prefix);
|
||||
const ctx = Context.wrap(m);
|
||||
|
||||
return of( posMod )
|
||||
.pipe (
|
||||
filterTap(CommandType.TEXT, mod => {
|
||||
const ctx = Context.wrap(m);
|
||||
mod.execute(ctx, ['text', data]);
|
||||
})
|
||||
);
|
||||
);
|
||||
})
|
||||
).subscribe ({
|
||||
error(e) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Awaitable, Message } from 'discord.js';
|
||||
import type { CommandType } from '../sern';
|
||||
import type { Module } from '../structures/structxports';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import type { ModuleDefs } from '../structures/commands/moduleHandler';
|
||||
import type { ModuleDefs } from '../structures/modules/commands/moduleHandler';
|
||||
import { SernError } from '../structures/errors';
|
||||
import { isNotFromBot, isNotFromDM } from '../utilities/messageHelpers';
|
||||
|
||||
@@ -16,7 +16,7 @@ export function filterTap<T extends keyof ModuleDefs>(
|
||||
return (src : Observable<Module|undefined>) =>
|
||||
new Observable<Module|undefined>( subscriber => {
|
||||
return src.subscribe({
|
||||
next(modul ) {
|
||||
next(modul) {
|
||||
if(match(modul, cmdType)) {
|
||||
const asModT = <ModuleDefs[T]> modul;
|
||||
tap(asModT);
|
||||
@@ -31,9 +31,9 @@ export function filterTap<T extends keyof ModuleDefs>(
|
||||
error: (e) => subscriber.error(e),
|
||||
complete: () => subscriber.complete()
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export function ignoreNonBot(prefix : string) {
|
||||
return (src : Observable<Message>) =>
|
||||
new Observable<Message>(subscriber => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { concatMap, first, from, fromEvent, pipe, tap } from 'rxjs';
|
||||
import { first, from, fromEvent } from 'rxjs';
|
||||
import { basename } from 'path';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import type { Module } from '../structures/structxports';
|
||||
import type { HandlerCallback, ModuleHandlers, ModuleStates, ModuleType } from '../structures/commands/moduleHandler';
|
||||
import type { HandlerCallback, ModuleHandlers, ModuleStates, ModuleType } from '../structures/modules/commands/moduleHandler';
|
||||
import { CommandType } from '../sern';
|
||||
|
||||
export const onReady = ( wrapper : Wrapper ) => {
|
||||
const { client, init, commands, } = wrapper;
|
||||
const { client, init, commands } = wrapper;
|
||||
fromEvent(client, 'ready')
|
||||
.pipe(first())
|
||||
.subscribe(() => {
|
||||
@@ -54,7 +54,7 @@ function setCommands ( { mod, absPath } : { mod : Module, absPath : string } ) {
|
||||
registerModules(name, mod);
|
||||
}
|
||||
|
||||
async function createCommandCache(
|
||||
function createCommandCache(
|
||||
arr: {mod: Module, absPath: string}[]
|
||||
) {
|
||||
from(arr).subscribe ( setCommands );
|
||||
|
||||
@@ -27,6 +27,8 @@ function eventObserver(client: Client, events: DiscordEvent[] ) {
|
||||
fromEvent(client, event, cb).subscribe();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @enum { number };
|
||||
*/
|
||||
@@ -40,3 +42,4 @@ export enum CommandType {
|
||||
BOTH = 0b000011,
|
||||
ANY = 0b111111
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ChatInputCommandInteraction, ContextMenuCommandInteraction, Interaction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js';
|
||||
import type { Args, Override } from '../../../types/handler';
|
||||
import type { CommandType } from '../../sern';
|
||||
import type Context from '../context';
|
||||
|
||||
type executeSlash = { execute : (ctx : Context<ChatInputCommandInteraction>, args: Args) => Awaitable<void> };
|
||||
|
||||
export interface BaseModule {
|
||||
name? : string;
|
||||
description : string;
|
||||
execute: (ctx: Context<Interaction>, args: Args) => Awaitable<void>;
|
||||
}
|
||||
export type TextCommand = {
|
||||
type : CommandType.TEXT;
|
||||
alias : string[] | [],
|
||||
} & BaseModule;
|
||||
|
||||
export type SlashCommand = {
|
||||
type : CommandType.SLASH;
|
||||
options : ApplicationCommandOptionData[] | [],
|
||||
} & Override<BaseModule, executeSlash>;
|
||||
|
||||
export type BothCommand = {
|
||||
type : CommandType.BOTH;
|
||||
alias : string[] | [];
|
||||
options : ApplicationCommandOptionData[] | [],
|
||||
} & Override<BaseModule, executeSlash>;
|
||||
|
||||
export type ContextMenuUser = {
|
||||
type : CommandType.MENU_USER;
|
||||
} & Override<BaseModule, { execute : ( ctx: Context<ContextMenuCommandInteraction> ) => Awaitable<void> }>;
|
||||
|
||||
export type ContextMenuMsg = {
|
||||
type : CommandType.MENU_MSG;
|
||||
} & Override<BaseModule, { execute : ( ctx: Context<MessageContextMenuCommandInteraction> ) => Awaitable<void> }>;
|
||||
export type ButtonCommand = {
|
||||
type : CommandType.BUTTON;
|
||||
} & Override<BaseModule, { execute : (ctx : Context<ButtonInteraction> ) => Awaitable<void> }>;
|
||||
|
||||
export type SelectMenuCommand = {
|
||||
type : CommandType.MENU_SELECT;
|
||||
} & Override<BaseModule, { execute : (ctx : Context<SelectMenuInteraction> ) => Awaitable<void> }>;
|
||||
|
||||
|
||||
export type Module =
|
||||
TextCommand
|
||||
| SlashCommand
|
||||
| BothCommand
|
||||
| ContextMenuUser
|
||||
| ContextMenuMsg
|
||||
| ButtonCommand
|
||||
| SelectMenuCommand;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { APIInteractionGuildMember } from 'discord-api-types/v9';
|
||||
import type {
|
||||
Awaitable,
|
||||
ChatInputCommandInteraction,
|
||||
Guild,
|
||||
GuildMember,
|
||||
Interaction,
|
||||
Message,
|
||||
Snowflake,
|
||||
TextBasedChannel,
|
||||
@@ -18,21 +17,22 @@ function firstSome<T>(...args : Option<T>[]) : Nullish<T> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default class Context<I extends Interaction = Interaction> {
|
||||
export default class Context {
|
||||
|
||||
private constructor(
|
||||
private oMsg: Option<Message> = None,
|
||||
private oInterac: Option<I> = None
|
||||
private oInterac: Option<ChatInputCommandInteraction> = None
|
||||
) {
|
||||
this.oMsg = oMsg;
|
||||
this.oInterac = oInterac;
|
||||
}
|
||||
static wrap<I extends Interaction = Interaction>(wrappable: I|Message) : Context<I> {
|
||||
static wrap(
|
||||
wrappable: ChatInputCommandInteraction|Message
|
||||
) : Context {
|
||||
if ( 'token' in wrappable ) {
|
||||
return new Context<I>( None, Some(wrappable));
|
||||
return new Context( None, Some(wrappable));
|
||||
}
|
||||
return new Context<I>(Some(wrappable), None);
|
||||
return new Context(Some(wrappable), None);
|
||||
}
|
||||
public isEmpty() {
|
||||
return this.oMsg.none && this.oInterac.none;
|
||||
@@ -44,18 +44,6 @@ export default class Context<I extends Interaction = Interaction> {
|
||||
return this.oInterac.unwrap();
|
||||
}
|
||||
|
||||
/**
|
||||
* maps a general Context<I> to Context<B>
|
||||
* if interaction is None return Context.empty()
|
||||
*/
|
||||
|
||||
public mapInteraction<B extends Interaction = Interaction>(
|
||||
cb : ( ctx: I ) => Context<B>
|
||||
) : Context<B> {
|
||||
if (this.oInterac.none) return new Context();
|
||||
return cb(this.oInterac.val);
|
||||
}
|
||||
|
||||
public get id() : Snowflake {
|
||||
return firstSome(
|
||||
this.oInterac.map( i => i.id),
|
||||
@@ -68,11 +56,11 @@ export default class Context<I extends Interaction = Interaction> {
|
||||
this.oInterac.map(i => i.channel)
|
||||
);
|
||||
}
|
||||
public get user(): Nullish<User> {
|
||||
public get user(): User {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.author),
|
||||
this.oInterac.map(i => i.user)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
public get createdTimestamp() : number {
|
||||
return firstSome(
|
||||
@@ -81,56 +69,51 @@ export default class Context<I extends Interaction = Interaction> {
|
||||
)!;
|
||||
}
|
||||
|
||||
public get guild() : Nullish<Guild> {
|
||||
public get guild() : Guild {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.guild),
|
||||
this.oMsg.map(m => m.guild!),
|
||||
this.oInterac.map(i => i.guild)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
public get guildId() : Nullish<Snowflake> {
|
||||
public get guildId() : Snowflake {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.guildId),
|
||||
this.oInterac.map(i => i.guildId)
|
||||
);
|
||||
)!;
|
||||
}
|
||||
public get member() : Nullish<GuildMember | APIInteractionGuildMember> {
|
||||
public get member() : Nullish<GuildMember> {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.member),
|
||||
this.oInterac.map(i => i.member)
|
||||
this.oMsg.andThen(m => Some(m.member!)),
|
||||
this.oInterac.andThen(i => i.inCachedGuild() ? Some(i.member) : None)
|
||||
);
|
||||
}
|
||||
/*
|
||||
* Returns the underlying Context but allows for doing other operations
|
||||
*/
|
||||
public on (
|
||||
onInteraction : ( interaction : I ) => Awaitable<void>,
|
||||
onMsg? : (message : Message) => Awaitable<void>
|
||||
): Context<I> {
|
||||
public onInteraction(
|
||||
onInteraction : ( interaction : ChatInputCommandInteraction ) => Awaitable<void>,
|
||||
): Context {
|
||||
this.oInterac.map(onInteraction);
|
||||
this.oMsg.map(m => onMsg?.(m));
|
||||
return this;
|
||||
}
|
||||
public extractInteraction<T>(
|
||||
extract : (interaction : I) => T
|
||||
public onMessage(
|
||||
onMessage : ( message : Message ) => Awaitable<void>
|
||||
): Context {
|
||||
this.oMsg.map( onMessage );
|
||||
return this;
|
||||
}
|
||||
public takeInteractionValue<T>(
|
||||
extract : (interaction : ChatInputCommandInteraction) => T
|
||||
): Nullish<T> {
|
||||
if(this.oInterac.none) return null;
|
||||
return extract(this.oInterac.val);
|
||||
}
|
||||
public extractMessage<T>(
|
||||
extract : (message: Message) => T
|
||||
): Nullish<T> {
|
||||
public takeMessageValue<T>(
|
||||
extract : (message: Message) => T
|
||||
): Nullish<T> {
|
||||
if(this.oMsg.none) return null;
|
||||
return extract(this.oMsg.val);
|
||||
}
|
||||
// extract either
|
||||
public extractEither<T,V>(
|
||||
i : (interaction : I) => T,
|
||||
m : (message : Message ) => V
|
||||
) {
|
||||
const iExtract = this.extractMessage(m);
|
||||
const mExtract = this.extractInteraction(i);
|
||||
return iExtract ?? mExtract;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
45
src/handler/structures/modules/commands/module.ts
Normal file
45
src/handler/structures/modules/commands/module.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ContextMenuCommandInteraction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js';
|
||||
import type { Override } from '../../../../types/handler';
|
||||
import type { CommandType } from '../../../sern';
|
||||
import type { BaseModule } from '../module';
|
||||
|
||||
//possible refactoring to interfaces and not types
|
||||
export type TextCommand = {
|
||||
type : CommandType.TEXT;
|
||||
alias : string[] | [],
|
||||
} & BaseModule;
|
||||
|
||||
export type SlashCommand = {
|
||||
type : CommandType.SLASH;
|
||||
options : ApplicationCommandOptionData[] | [],
|
||||
} & BaseModule;
|
||||
|
||||
export type BothCommand = {
|
||||
type : CommandType.BOTH;
|
||||
alias : string[] | [];
|
||||
options : ApplicationCommandOptionData[] | [],
|
||||
} & BaseModule;
|
||||
|
||||
export type ContextMenuUser = {
|
||||
type : CommandType.MENU_USER;
|
||||
} & Override<BaseModule, { execute : ( ctx: ContextMenuCommandInteraction ) => Awaitable<void> }>;
|
||||
export type ContextMenuMsg = {
|
||||
type : CommandType.MENU_MSG;
|
||||
} & Override<BaseModule, { execute : ( ctx: MessageContextMenuCommandInteraction ) => Awaitable<void> }>;
|
||||
export type ButtonCommand = {
|
||||
type : CommandType.BUTTON;
|
||||
} & Override<BaseModule, { execute : (ctx :ButtonInteraction ) => Awaitable<void> }>;
|
||||
export type SelectMenuCommand = {
|
||||
type : CommandType.MENU_SELECT;
|
||||
} & Override<BaseModule, { execute : (ctx : SelectMenuInteraction ) => Awaitable<void> }>;
|
||||
|
||||
|
||||
export type Module =
|
||||
TextCommand
|
||||
| SlashCommand
|
||||
| BothCommand
|
||||
| ContextMenuUser
|
||||
| ContextMenuMsg
|
||||
| ButtonCommand
|
||||
| SelectMenuCommand;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CommandType } from '../../sern';
|
||||
import { CommandType } from '../../../sern';
|
||||
import type { TextCommand, BothCommand, ButtonCommand, SlashCommand, ContextMenuMsg, ContextMenuUser, SelectMenuCommand } from './module';
|
||||
//https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union
|
||||
|
||||
13
src/handler/structures/modules/module.ts
Normal file
13
src/handler/structures/modules/module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Awaitable, ChatInputCommandInteraction, Interaction } from "discord.js";
|
||||
import type { Args } from "../../..";
|
||||
import type Context from "../context";
|
||||
|
||||
export interface BaseModule {
|
||||
name? : string;
|
||||
description : string;
|
||||
execute: (ctx: Context, args: Args) => Awaitable<void>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Context from './context';
|
||||
import type { SlashCommand, TextCommand, BothCommand, Module } from './commands/module';
|
||||
import type { SlashCommand, TextCommand, BothCommand, Module } from '../structures/modules/commands/module';
|
||||
import type Wrapper from './wrapper';
|
||||
|
||||
export {
|
||||
|
||||
@@ -14,7 +14,6 @@ interface Wrapper {
|
||||
readonly client: Client;
|
||||
readonly defaultPrefix: string;
|
||||
readonly commands: string;
|
||||
readonly components : string;
|
||||
init?: (handler: Wrapper) => void;
|
||||
readonly events? : DiscordEvent[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import type { Module } from '../structures/commands/module';
|
||||
import type { Module } from '../structures/modules/commands/module';
|
||||
import { SernError } from '../structures/errors';
|
||||
|
||||
//We can look into lazily loading modules once everything is set
|
||||
@@ -11,6 +11,7 @@ export const Alias = new Map<string, Module>();
|
||||
export const Buttons = new Map<string, Module>();
|
||||
export const SelectMenus = new Map<string, Module>();
|
||||
|
||||
|
||||
// Courtesy @Townsy45
|
||||
function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user