style: Formatted 17 files & changes messageHelper util

This commit is contained in:
xxDeveloper
2022-04-07 22:37:40 +03:00
parent e21508ca4f
commit 988d7fa6d2
20 changed files with 1223 additions and 1116 deletions

View File

@@ -3,5 +3,5 @@
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4
"tabWidth": 2
}

69
package-lock.json generated
View File

@@ -26,7 +26,7 @@
"cz-conventional-changelog": "^3.0.1",
"jest": "^27.5.1",
"standard-version": "^9.3.2",
"typedoc": "^0.22.11",
"typedoc": "^0.22.14",
"typescript": "^4.5.5"
}
},
@@ -8494,16 +8494,16 @@
}
},
"node_modules/typedoc": {
"version": "0.22.11",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz",
"integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==",
"version": "0.22.14",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.14.tgz",
"integrity": "sha512-tlf9wIcsrnQSjetStrnRutuy2RjZkG5PK2umwveZLTkuC2K9VywOZTdu2G19BdOPzGrhZjf9WK7pthXqnFQejg==",
"dev": true,
"dependencies": {
"glob": "^7.2.0",
"lunr": "^2.3.9",
"marked": "^4.0.10",
"minimatch": "^3.0.4",
"shiki": "^0.10.0"
"marked": "^4.0.12",
"minimatch": "^5.0.1",
"shiki": "^0.10.1"
},
"bin": {
"typedoc": "bin/typedoc"
@@ -8512,7 +8512,28 @@
"node": ">= 12.10.0"
},
"peerDependencies": {
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x"
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x"
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/typedoc/node_modules/minimatch": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/typescript": {
@@ -15296,16 +15317,36 @@
}
},
"typedoc": {
"version": "0.22.11",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.11.tgz",
"integrity": "sha512-pVr3hh6dkS3lPPaZz1fNpvcrqLdtEvXmXayN55czlamSgvEjh+57GUqfhAI1Xsuu/hNHUT1KNSx8LH2wBP/7SA==",
"version": "0.22.14",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.14.tgz",
"integrity": "sha512-tlf9wIcsrnQSjetStrnRutuy2RjZkG5PK2umwveZLTkuC2K9VywOZTdu2G19BdOPzGrhZjf9WK7pthXqnFQejg==",
"dev": true,
"requires": {
"glob": "^7.2.0",
"lunr": "^2.3.9",
"marked": "^4.0.10",
"minimatch": "^3.0.4",
"shiki": "^0.10.0"
"marked": "^4.0.12",
"minimatch": "^5.0.1",
"shiki": "^0.10.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"minimatch": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"typescript": {

View File

@@ -3,7 +3,7 @@
"version": "0.1.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"scripts": {
"compile": "tsc",
"watch": "tsc -w",
"lint": "eslint src/**/*.ts",
@@ -34,7 +34,7 @@
"cz-conventional-changelog": "^3.0.1",
"jest": "^27.5.1",
"standard-version": "^9.3.2",
"typedoc": "^0.22.11",
"typedoc": "^0.22.14",
"typescript": "^4.5.5"
},
"config": {

View File

@@ -1,10 +1,11 @@
import type { Interaction } from 'discord.js';
import type Wrapper from '../structures/wrapper';
import * as Files from '../utilities/readFile';
import Context from '../structures/context';
import { fromEvent, Observable, of, concatMap } from 'rxjs';
import { CommandType } from '../sern';
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';

View File

@@ -1,39 +1,43 @@
import type { ChatInputCommandInteraction, Message } from 'discord.js';
import { fromEvent, Observable, of, concatMap } from 'rxjs';
import { CommandType } from '../sern';
import Context from '../structures/context';
import type { Message } from 'discord.js';
import type Wrapper from '../structures/wrapper';
import { fmt } from '../utilities/messageHelpers';
import { fromEvent, Observable, of, concatMap } from 'rxjs';
import Context from '../structures/context';
import * as Files from '../utilities/readFile';
import { fmt } from '../utilities/messageHelpers';
import { CommandType } from '../sern';
import { filterTap, ignoreNonBot } from './observableHandling';
export const onMessageCreate = (wrapper : Wrapper) => {
const { client, defaultPrefix } = wrapper;
(<Observable<Message>> fromEvent( client, 'messageCreate'))
.pipe (
ignoreNonBot(defaultPrefix),
concatMap ( m => {
const [ prefix, ...data ] = fmt(m, defaultPrefix);
export const onMessageCreate = (wrapper: Wrapper) => {
const { client, defaultPrefix } = wrapper;
(<Observable<Message>>fromEvent(client, 'messageCreate'))
.pipe(
ignoreNonBot(defaultPrefix),
concatMap(m => {
const [prefix, ...data] = fmt({ msg: m, prefix: defaultPrefix });
const posMod = Files.Commands.get(prefix) ?? Files.Alias.get(prefix);
return of( posMod )
.pipe (
filterTap(CommandType.TEXT, mod => {
const ctx = Context.wrap(m);
mod.execute(ctx, ['text', data]);
})
);
})
).subscribe ({
error(e) {
//log things
throw e;
},
next(command) {
//log on each command emitted
console.log(command);
},
});
return of(posMod)
.pipe(
filterTap(CommandType.TEXT, mod => {
const ctx = Context.wrap(m);
mod.execute(ctx, ['text', data]);
})
);
})
).subscribe({
error(e) {
// Log the errors
throw e;
},
next(command) {
// Log on each command emitted
console.log(command);
},
});
};

View File

@@ -1,63 +1,65 @@
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/modules/commands/moduleHandler';
import { SernError } from '../structures/errors';
import { isNotFromBot, isNotFromDM } from '../utilities/messageHelpers';
export function match(mod: Module | undefined, type : CommandType) : boolean {
import { Observable, throwError } from 'rxjs';
import { SernError } from '../structures/errors';
import { isFromBot, isFromDM } from '../utilities/messageHelpers';
export function match(mod: Module | undefined, type: CommandType): boolean {
return mod !== undefined && (mod.type & type) != 0;
}
export function filterTap<T extends keyof ModuleDefs>(
cmdType : T,
tap: (mod : ModuleDefs[T]) => Awaitable<void>
cmdType: T,
tap: (mod: ModuleDefs[T]) => Awaitable<void>
) {
return (src : Observable<Module|undefined>) =>
new Observable<Module|undefined>( subscriber => {
return src.subscribe({
return (src: Observable<Module | undefined>) =>
new Observable<Module | undefined>(subscriber => {
return src.subscribe({
next(modul) {
if(match(modul, cmdType)) {
const asModT = <ModuleDefs[T]> modul;
tap(asModT);
subscriber.next(asModT);
if (match(modul, cmdType)) {
const asModT = <ModuleDefs[T]>modul;
tap(asModT);
subscriber.next(asModT);
} else {
if (modul === undefined) {
return throwError(() => SernError.UNDEFINED_MODULE);
}
return throwError(() => SernError.MISMATCH_MODULE_TYPE);
if (modul === undefined) {
return throwError(() => SernError.UNDEFINED_MODULE);
}
return throwError(() => SernError.MISMATCH_MODULE_TYPE);
}
},
error: (e) => subscriber.error(e),
error: (e) => subscriber.error(e),
complete: () => subscriber.complete()
});
});
}
}
export function ignoreNonBot(prefix : string) {
return (src : Observable<Message>) =>
export function ignoreNonBot(prefix: string) {
return (src: Observable<Message>) =>
new Observable<Message>(subscriber => {
return src.subscribe({
next(m) {
const passAll = [
isNotFromDM,
isNotFromBot,
(m : Message) =>
m.content
.slice(0,prefix.length)
.localeCompare(prefix,
undefined, { sensitivity : 'accent' }
) === 0
].every( fn => fn(m));
return src.subscribe({
next(m) {
const passAll = [
!isFromDM,
!isFromBot,
(m: Message) =>
m.content
.slice(0, prefix.length)
.localeCompare(prefix,
undefined, { sensitivity: 'accent' }
) === 0
].every(fn => fn(m));
if (passAll) {
subscriber.next(m);
}
},
error: (e) => subscriber.error(e),
complete: () => subscriber.complete()
if (passAll) {
subscriber.next(m);
}
},
error: (e) => subscriber.error(e),
complete: () => subscriber.complete()
});
});
});
}

View File

@@ -1,61 +1,62 @@
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/modules/commands/moduleHandler';
import * as Files from '../utilities/readFile';
import { first, from, fromEvent } from 'rxjs';
import { basename } from 'path';
import { CommandType } from '../sern';
export const onReady = ( wrapper : Wrapper ) => {
export const onReady = (wrapper: Wrapper) => {
const { client, init, commands } = wrapper;
fromEvent(client, 'ready')
.pipe(first())
.subscribe(() => {
init?.( wrapper );
Files.buildData( commands )
.then( createCommandCache );
})
.pipe(first())
.subscribe(() => {
init?.(wrapper);
Files.buildData(commands)
.then(createCommandCache);
})
};
// Refactor : ? Possibly repetitive and verbose.
const handler = ( name : string ) =>
({
[CommandType.TEXT] : mod => {
mod.alias.forEach ( a => Files.Alias.set(a,mod));
Files.Commands.set( name, mod );
},
[CommandType.SLASH]: mod => {
Files.Commands.set( name , mod);
},
[CommandType.BOTH] : mod => {
Files.Commands.set ( name, mod);
mod.alias.forEach (a => Files.Alias.set(a, mod));
},
[CommandType.MENU_USER] : mod => {
Files.ContextMenuUser.set ( name, mod );
},
[CommandType.MENU_MSG] : mod => {
Files.ContextMenuMsg.set (name, mod );
},
[CommandType.BUTTON] : mod => {
Files.Buttons.set(name, mod);
},
[CommandType.MENU_SELECT] : mod => {
Files.SelectMenus.set(name, mod);
}
const handler = (name: string) =>
({
[CommandType.TEXT]: mod => {
mod.alias.forEach(a => Files.Alias.set(a, mod));
Files.Commands.set(name, mod);
},
[CommandType.SLASH]: mod => {
Files.Commands.set(name, mod);
},
[CommandType.BOTH]: mod => {
Files.Commands.set(name, mod);
mod.alias.forEach(a => Files.Alias.set(a, mod));
},
[CommandType.MENU_USER]: mod => {
Files.ContextMenuUser.set(name, mod);
},
[CommandType.MENU_MSG]: mod => {
Files.ContextMenuMsg.set(name, mod);
},
[CommandType.BUTTON]: mod => {
Files.Buttons.set(name, mod);
},
[CommandType.MENU_SELECT]: mod => {
Files.SelectMenus.set(name, mod);
}
} as ModuleHandlers);
} as ModuleHandlers);
const registerModules = <T extends ModuleType >(name : string, mod : ModuleStates[T]) =>
(<HandlerCallback<T>> handler(name)[mod.type])(mod);
const registerModules = <T extends ModuleType>(name: string, mod: ModuleStates[T]) =>
(<HandlerCallback<T>>handler(name)[mod.type])(mod);
function setCommands ( { mod, absPath } : { mod : Module, absPath : string } ) {
const name = mod.name ?? Files.fmtFileName(basename(absPath));
registerModules(name, mod);
function setCommands({ mod, absPath }: { mod: Module, absPath: string }) {
const name = mod.name ?? Files.fmtFileName(basename(absPath));
registerModules(name, mod);
}
function createCommandCache(
arr: {mod: Module, absPath: string}[]
) {
from(arr).subscribe ( setCommands );
function createCommandCache(
arr: { mod: Module, absPath: string }[]
) {
from(arr).subscribe(setCommands);
}

View File

@@ -1,34 +1,34 @@
export enum sEvent {
GLOBAL_SLASH,
LOCAL_SLASH,
MISUSE_CMD,
DM,
CRASH,
TEXT_CMD,
GLOBAL_SLASH,
LOCAL_SLASH,
MISUSE_CMD,
CRASH,
TEXT_CMD,
DM,
}
export default class Logger {
public clear() {
console.clear();
}
public clear() {
console.clear();
}
public log<T extends sEvent>(e: T, guildId: string, message: string) {
// add colored logging?
console.log(`[${new Date().toISOString()}] [${sEvent[e]}] @ ${guildId} :: ${message}`);
}
public log<T extends sEvent>(e: T, guildId: string, message: string) {
// TODO: Add colored logging
console.log(`[${new Date().toISOString()}] [${sEvent[e]}] @ ${guildId} :: ${message}`);
}
/**
* Utilizes console.table() to print out memory usage of current process.
* Optional at startup.
*/
public tableRam() {
console.table(
Object.entries(process.memoryUsage())
.map(([k, v]: [string, number]) => {
return { [k]: `${(((Math.round(v) / 1024 / 1024) * 100) / 100).toFixed(2)} MBs` };
})
.reduce((r, c) => Object.assign(r, c), {}),
);
}
/**
* Utilizes console.table() to print out memory usage of current process.
* Optional at startup.
*/
public tableRam() {
console.table(
Object.entries(process.memoryUsage())
.map(([k, v]: [string, number]) => {
return { [k]: `${(((Math.round(v) / 1024 / 1024) * 100) / 100).toFixed(2)} MBs` };
})
.reduce((r, c) => Object.assign(r, c), {}),
);
}
}

View File

@@ -1,45 +1,41 @@
import type {
DiscordEvent,
} from '../types/handler';
import type {
Client,
} from 'discord.js';
import type { DiscordEvent, } from '../types/handler';
import type { Client } from 'discord.js';
import type Wrapper from './structures/wrapper';
import { fromEvent } from 'rxjs';
import { SernError } from './structures/errors';
import { onReady } from './events/readyEvent';
import { onMessageCreate } from './events/messageEvent';
import { onInteractionCreate } from './events/interactionCreate';
export function init( wrapper : Wrapper ) {
const { events, client } = wrapper;
if (events !== undefined) eventObserver(client, events);
onReady( wrapper );
onMessageCreate( wrapper );
onInteractionCreate ( wrapper );
export function init(wrapper: Wrapper) {
const { events, client } = wrapper;
if (events !== undefined) eventObserver(client, events);
onReady(wrapper);
onMessageCreate(wrapper);
onInteractionCreate(wrapper);
}
function eventObserver(client: Client, events: DiscordEvent[] ) {
events.forEach( ( [event, cb] ) => {
if (event === 'ready') throw Error(SernError.RESERVED_EVENT);
fromEvent(client, event, cb).subscribe();
function eventObserver(client: Client, events: DiscordEvent[]) {
events.forEach(([event, cb]) => {
if (event === 'ready') throw Error(SernError.RESERVED_EVENT);
fromEvent(client, event, cb).subscribe();
});
}
/**
* @enum { number };
*/
export enum CommandType {
TEXT = 0b000001,
SLASH = 0b000010,
MENU_USER = 0b000100,
MENU_MSG = 0b001000,
BUTTON = 0b010000,
MENU_SELECT= 0b100000,
BOTH = 0b000011,
ANY = 0b111111
TEXT = 0b000001,
SLASH = 0b000010,
MENU_USER = 0b000100,
MENU_MSG = 0b001000,
BUTTON = 0b010000,
MENU_SELECT = 0b100000,
BOTH = 0b000011,
ANY = 0b111111,
}

View File

@@ -1,121 +1,108 @@
import type {
Awaitable,
ChatInputCommandInteraction,
Guild,
GuildMember,
Message,
Snowflake,
TextBasedChannel,
User
Awaitable,
ChatInputCommandInteraction,
Guild,
GuildMember,
Message,
Snowflake,
TextBasedChannel,
User,
} from 'discord.js';
import { None, Option, Some } from 'ts-results';
import type { Nullish } from '../../types/handler';
function firstSome<T>(...args : Option<T>[]) : Nullish<T> {
for ( const op of args ) {
if (op.some) return op.val;
}
return null;
function firstSome<T>(...args: Option<T>[]): Nullish<T> {
for (const op of args) {
if (op.some) return op.val;
}
return null;
}
export default class Context {
private constructor(
private oMsg: Option<Message> = None,
private oInterac: Option<ChatInputCommandInteraction> = None,
) {
this.oMsg = oMsg;
this.oInterac = oInterac;
}
static wrap(wrappable: ChatInputCommandInteraction | Message): Context {
if ('token' in wrappable) {
return new Context(None, Some(wrappable));
}
return new Context(Some(wrappable), None);
}
public isEmpty() {
return this.oMsg.none && this.oInterac.none;
}
public get message() {
return this.oMsg.unwrap();
}
public get interaction() {
return this.oInterac.unwrap();
}
private constructor(
private oMsg: Option<Message> = None,
private oInterac: Option<ChatInputCommandInteraction> = None
) {
this.oMsg = oMsg;
this.oInterac = oInterac;
}
static wrap(
wrappable: ChatInputCommandInteraction|Message
) : Context {
if ( 'token' in wrappable ) {
return new Context( None, Some(wrappable));
}
return new Context(Some(wrappable), None);
}
public isEmpty() {
return this.oMsg.none && this.oInterac.none;
}
public get message() {
return this.oMsg.unwrap();
}
public get interaction() {
return this.oInterac.unwrap();
}
public get id(): Snowflake {
return firstSome(
this.oInterac.map((i) => i.id),
this.oMsg.map((m) => m.id),
)!;
}
public get channel(): Nullish<TextBasedChannel> {
return firstSome(
this.oMsg.map((m) => m.channel),
this.oInterac.map((i) => i.channel),
);
}
public get user(): User {
return firstSome(
this.oMsg.map((m) => m.author),
this.oInterac.map((i) => i.user),
)!;
}
public get createdTimestamp(): number {
return firstSome(
this.oMsg.map((m) => m.createdTimestamp),
this.oInterac.map((i) => i.createdTimestamp),
)!;
}
public get id() : Snowflake {
return firstSome(
this.oInterac.map( i => i.id),
this.oMsg.map(m => m.id)
)!;
}
public get channel() : Nullish<TextBasedChannel> {
return firstSome(
this.oMsg.map(m => m.channel),
this.oInterac.map(i => i.channel)
);
}
public get user(): User {
return firstSome(
this.oMsg.map(m => m.author),
this.oInterac.map(i => i.user)
)!;
}
public get createdTimestamp() : number {
return firstSome(
this.oMsg.map(m => m.createdTimestamp),
this.oInterac.map(i => i.createdTimestamp)
)!;
}
public get guild() : Guild {
return firstSome(
this.oMsg.map(m => m.guild!),
this.oInterac.map(i => i.guild)
)!;
}
public get guildId() : Snowflake {
return firstSome(
this.oMsg.map(m => m.guildId),
this.oInterac.map(i => i.guildId)
)!;
}
public get member() : Nullish<GuildMember> {
return firstSome(
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 onInteraction(
onInteraction : ( interaction : ChatInputCommandInteraction ) => Awaitable<void>,
): Context {
this.oInterac.map(onInteraction);
return this;
}
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 takeMessageValue<T>(
extract : (message: Message) => T
): Nullish<T> {
if(this.oMsg.none) return null;
return extract(this.oMsg.val);
}
public get guild(): Guild {
return firstSome(
this.oMsg.map((m) => m.guild!),
this.oInterac.map((i) => i.guild),
)!;
}
public get guildId(): Snowflake {
return firstSome(
this.oMsg.map((m) => m.guildId),
this.oInterac.map((i) => i.guildId),
)!;
}
public get member(): Nullish<GuildMember> {
return firstSome(
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 onInteraction(onInteraction: (interaction: ChatInputCommandInteraction) => Awaitable<void>): Context {
this.oInterac.map(onInteraction);
return this;
}
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 takeMessageValue<T>(extract: (message: Message) => T): Nullish<T> {
if (this.oMsg.none) return null;
return extract(this.oMsg.val);
}
}

View File

@@ -1,7 +1,7 @@
export enum SernError {
RESERVED_EVENT = 'Cannot register the reserved ready event. Please use the init property.',
NO_ALIAS = 'You cannot provide an array with elements to a slash command.',
NOT_VALID_MOD_TYPE = 'Detected an unknown module type',
UNDEFINED_MODULE = `A module could not be detected at`,
MISMATCH_MODULE_TYPE = `A module type mismatched with event emitted!`
RESERVED_EVENT = 'Cannot register the reserved ready event. Please use the init property.',
NO_ALIAS = 'You cannot provide an array with elements to a slash command.',
NOT_VALID_MOD_TYPE = 'Detected an unknown module type',
UNDEFINED_MODULE = `A module could not be detected at`,
MISMATCH_MODULE_TYPE = `A module type mismatched with event emitted!`,
}

View File

@@ -1,45 +1,51 @@
import type { ApplicationCommandOptionData, Awaitable, ButtonInteraction, ContextMenuCommandInteraction, MessageContextMenuCommandInteraction, SelectMenuInteraction } from 'discord.js';
import type {
ApplicationCommandOptionData,
Awaitable,
ButtonInteraction,
MessageContextMenuInteraction,
ContextMenuInteraction,
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
// Possible refactoring to interfaces and not types
export type TextCommand = {
type : CommandType.TEXT;
alias : string[] | [],
type: CommandType.TEXT;
alias: string[] | [];
} & BaseModule;
export type SlashCommand = {
type : CommandType.SLASH;
options : ApplicationCommandOptionData[] | [],
} & BaseModule;
type: CommandType.SLASH;
options: ApplicationCommandOptionData[] | [];
} & BaseModule;
export type BothCommand = {
type : CommandType.BOTH;
alias : string[] | [];
options : ApplicationCommandOptionData[] | [],
type: CommandType.BOTH;
alias: string[] | [];
options: ApplicationCommandOptionData[] | [];
} & BaseModule;
export type ContextMenuUser = {
type : CommandType.MENU_USER;
} & Override<BaseModule, { execute : ( ctx: ContextMenuCommandInteraction ) => Awaitable<void> }>;
type: CommandType.MENU_USER;
} & Override<BaseModule, { execute: (ctx: ContextMenuInteraction) => Awaitable<void> }>;
export type ContextMenuMsg = {
type : CommandType.MENU_MSG;
} & Override<BaseModule, { execute : ( ctx: MessageContextMenuCommandInteraction ) => Awaitable<void> }>;
type: CommandType.MENU_MSG;
} & Override<BaseModule, { execute: (ctx: MessageContextMenuInteraction) => Awaitable<void> }>;
export type ButtonCommand = {
type : CommandType.BUTTON;
} & Override<BaseModule, { execute : (ctx :ButtonInteraction ) => Awaitable<void> }>;
type: CommandType.BUTTON;
} & Override<BaseModule, { execute: (ctx: ButtonInteraction) => Awaitable<void> }>;
export type SelectMenuCommand = {
type : CommandType.MENU_SELECT;
} & Override<BaseModule, { execute : (ctx : SelectMenuInteraction ) => Awaitable<void> }>;
type: CommandType.MENU_SELECT;
} & Override<BaseModule, { execute: (ctx: SelectMenuInteraction) => Awaitable<void> }>;
export type Module =
TextCommand
| SlashCommand
| BothCommand
| ContextMenuUser
| ContextMenuMsg
| ButtonCommand
| SelectMenuCommand;
export type Module =
| TextCommand
| SlashCommand
| BothCommand
| ContextMenuUser
| ContextMenuMsg
| ButtonCommand
| SelectMenuCommand;

View File

@@ -1,24 +1,31 @@
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
import type {
TextCommand,
BothCommand,
ButtonCommand,
SlashCommand,
ContextMenuMsg,
ContextMenuUser,
SelectMenuCommand,
} from './module';
// https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union
// Explicit Module Definitions for mapping
export type ModuleDefs = {
[CommandType.TEXT] : TextCommand,
[CommandType.SLASH] : SlashCommand,
[CommandType.BOTH] : BothCommand,
[CommandType.MENU_MSG] : ContextMenuMsg,
[CommandType.MENU_USER] : ContextMenuUser,
[CommandType.BUTTON] : ButtonCommand,
[CommandType.MENU_SELECT] : SelectMenuCommand
}
[CommandType.TEXT]: TextCommand;
[CommandType.SLASH]: SlashCommand;
[CommandType.BOTH]: BothCommand;
[CommandType.MENU_MSG]: ContextMenuMsg;
[CommandType.MENU_USER]: ContextMenuUser;
[CommandType.BUTTON]: ButtonCommand;
[CommandType.MENU_SELECT]: SelectMenuCommand;
};
//Keys of ModuleDefs
export type ModuleType = keyof ModuleDefs;
// The keys mapped to a constructed union with its type
export type ModuleStates = { [ K in ModuleType ] : { type : K } & ModuleDefs[K] };
// A handler callback that is called on each ModuleDef
export type HandlerCallback<K extends ModuleType> = ( params : ModuleStates[K] ) => unknown;
export type ModuleStates = { [K in ModuleType]: { type: K } & ModuleDefs[K] };
// A handler callback that is called on each ModuleDef
export type HandlerCallback<K extends ModuleType> = (params: ModuleStates[K]) => unknown;
//An object that acts as the mapped object to handler
export type ModuleHandlers = { [K in ModuleType] : HandlerCallback<K> };
export type ModuleHandlers = { [K in ModuleType]: HandlerCallback<K> };

View File

@@ -1,13 +1,9 @@
import type { Awaitable, ChatInputCommandInteraction, Interaction } from "discord.js";
import type { Args } from "../../..";
import type Context from "../context";
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>;
name?: string;
description: string;
execute: (ctx: Context, args: Args) => Awaitable<void>;
}

View File

@@ -2,11 +2,4 @@ import Context from './context';
import type { SlashCommand, TextCommand, BothCommand, Module } from '../structures/modules/commands/module';
import type Wrapper from './wrapper';
export {
Context,
SlashCommand,
TextCommand,
BothCommand,
Module,
Wrapper
};
export { Context, SlashCommand, TextCommand, BothCommand, Module, Wrapper };

View File

@@ -4,18 +4,19 @@ import type { DiscordEvent } from '../../types/handler';
/**
* An object to be passed into Sern.Handler constructor.
* @typedef {object} Wrapper
* @property {readonly Client} client
* @property {readonly string} defaultPrefix
* @property {readonly string} commands
* @prop {(handler : Handler) => void)} init
* @prop { readonly DiscordEvent[] } events
* @property {readonly Client} Client
* @property {readonly string} The default prefix
* @property {readonly string} Commands
* @prop {(handler: Handler) => void)} init
* @prop { readonly DiscordEvent[] } Events
*/
interface Wrapper {
readonly client: Client;
readonly defaultPrefix: string;
readonly commands: string;
init?: (handler: Wrapper) => void;
readonly events? : DiscordEvent[];
readonly client: Client;
readonly defaultPrefix: string;
readonly commands: string;
init?: (handler: Wrapper) => void;
readonly events?: DiscordEvent[];
}
export default Wrapper;

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,15 @@
import { ChannelType, Message } from 'discord.js';
import type { Message } from 'discord.js';
/**
* Checks if the message sent in DMs.
* @param message The message object
* @returns `true` if message comes from DM, `false` otherwise
* @example isNotFromDM(message) ? 'Not From DM' : 'from DM'
*
*/
export function isNotFromDM ( message: Message ) {
return message.channel.type !== ChannelType.DM;
export function isFromDM(message: Message): boolean {
return message.channel.type == 'DM';
}
/**
@@ -16,25 +17,29 @@ export function isNotFromDM ( message: Message ) {
* @param message The message to check
* @returns `true` if the author of the message is a bot, `false` otherwise
* @example
* isBot(message) ? 'yes it is a bot' : 'no it is not a bot';
* isFromBot(message) ? 'Sent by a bot' : 'Sent by a person'';
*/
export function isNotFromBot(message: Message) {
return !message.author.bot;
export function isFromBot(message: Message): boolean {
return !!message.author.bot;
}
/**
* Checks if the message **starts** with the prefix
* Checks if the message starts with the prefix
* @param message The message to check
* @param prefix The prefix to check for
* @returns `true` if the message starts with the prefix, `false` otherwise
* @example
* hasPrefix(message, '!') ? 'yes it does' : 'no it does not';
* hasPrefix(message, '!') ? 'Starts with prefix' : 'Not starts with prefix';
*/
export function hasPrefix(message: Message, prefix?: string) {
return message.content.startsWith(prefix!);
export function hasPrefix(message: Message, prefix?: string): boolean {
return message.content.startsWith(prefix!);
}
/**
* Removes the first character(s) _[depending on prefix length]_ of the message
* @param message The message to remove the prefix from
* Removes the first character(s) _-depending on prefix length-_ of the message
* @param msg The message to remove the prefix from
* @param prefix The prefix to remove
* @returns The message without the prefix
* @example
@@ -42,6 +47,7 @@ export function hasPrefix(message: Message, prefix?: string) {
* console.log(fmt(message, '!'));
* // [ 'ping' ]
*/
export function fmt(msg: Message, prefix: string): string[] {
return msg.content.slice(prefix.length).trim().split(/\s+/g);
export function fmt({ msg, prefix }: { msg: Message; prefix: string; }): string[] {
return msg.content.slice(prefix.length).trim().split(/\s+/g);
}

View File

@@ -1,9 +1,10 @@
import type { Module } from '../structures/modules/commands/module';
import { readdirSync, statSync } from 'fs';
import { join } from 'path';
import type { Module } from '../structures/modules/commands/module';
import { SernError } from '../structures/errors';
//We can look into lazily loading modules once everything is set
// We can look into lazily loading modules once everything is set
export const ContextMenuUser = new Map<string, Module>();
export const ContextMenuMsg = new Map<string, Module>();
export const Commands = new Map<string, Module>();
@@ -11,8 +12,7 @@ export const Alias = new Map<string, Module>();
export const Buttons = new Map<string, Module>();
export const SelectMenus = new Map<string, Module>();
// Courtesy @Townsy45
// Thanks to @Townsy45
function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
try {
const files = readdirSync(dir);
@@ -30,20 +30,20 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
/**
*
*
* @param {commandsDir} Relative path to commands directory
* @returns {Promise<{ mod: Command; absPath: string; }[]>} data from command files
*/
export async function buildData(commandDir: string ): Promise<
export async function buildData(commandDir: string): Promise<
{
mod: Module;
absPath: string;
}[]
> {
return Promise.all(
getCommands(commandDir).map( async (absPath) => {
const mod = <Module> (await import(absPath)).module;
getCommands(commandDir).map(async (absPath) => {
const mod = <Module>(await import(absPath)).module;
if (mod === undefined) throw Error(`${SernError.UNDEFINED_MODULE} ${absPath}`);
return { mod, absPath };
}),

View File

@@ -1,13 +1,13 @@
import { hasPrefix, fmt, isNotFromBot } from '../src/handler/utilities/messageHelpers';
describe('FUNCTIONS', () => {
test('If hasPrefix is a function', () => {
expect(typeof hasPrefix).toBe('function');
});
test('if fmt is a function', () => {
expect(typeof fmt).toBe('function');
});
test('if isBot is a function', () => {
expect(typeof isNotFromBot).toBe('function');
});
import { hasPrefix, fmt, isFromBot } from '../src/handler/utilities/messageHelpers';
describe('FUNCTIONS', () => {
test('If hasPrefix is a function', () => {
expect(typeof hasPrefix).toBe('function');
});
test('if fmt is a function', () => {
expect(typeof fmt).toBe('function');
});
test('if isBot is a function', () => {
expect(typeof isFromBot).toBe('function');
});
});