mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
Merge branch 'main' of https://github.com/EvolutionX-10/Sern into EvolutionX-10-main
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"parserOptions": { "ecmaVersion": "esnext", "sourceType": "" },
|
||||
"parserOptions": { "ecmaVersion": "latest", "sourceType": "script" },
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals" : true }],
|
||||
"semi" : ["error", "always"]
|
||||
"semi": ["error", "always"]
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2
|
||||
}
|
||||
"tabWidth": 4
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import type {
|
||||
ApplicationCommandOptionData
|
||||
} from 'discord.js';
|
||||
|
||||
import {
|
||||
readdirSync,
|
||||
statSync
|
||||
} from 'fs';
|
||||
|
||||
import {
|
||||
basename,
|
||||
join
|
||||
} from 'path';
|
||||
|
||||
import type * as Sern from '../sern';
|
||||
|
||||
export type CommandVal = {
|
||||
mod: Sern.Module<unknown>,
|
||||
options: ApplicationCommandOptionData[],
|
||||
}
|
||||
|
||||
export const Commands = new Map<string, CommandVal>();
|
||||
export const Alias = new Map<string, CommandVal>();
|
||||
|
||||
// Courtesy of Townsy#0001 on Discord
|
||||
async function readPath(dir: string, arrayOfFiles: string[] = []): Promise<string[]> {
|
||||
try {
|
||||
const files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
if (statSync(dir + '/' + file).isDirectory())
|
||||
await readPath(dir + '/' + file, arrayOfFiles);
|
||||
else
|
||||
arrayOfFiles.push(join(dir, '/', file));
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return arrayOfFiles;
|
||||
}
|
||||
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
|
||||
/**
|
||||
* @param {Sern.Handler} handler an instance of Sern.Handler
|
||||
* @returns {Promise<{ name: string; mod: Sern.Module<unknown>; absPath: string; }[]>} data from command files
|
||||
*/
|
||||
|
||||
export async function buildData(handler: Sern.Handler)
|
||||
: Promise<{
|
||||
name: string;
|
||||
mod: Sern.Module<unknown>;
|
||||
absPath: string;
|
||||
}[]> {
|
||||
const commandDir = handler.commandDir;
|
||||
return Promise.all((await getCommands(commandDir))
|
||||
.map(async absPath => {
|
||||
return { name: basename(absPath), mod: (await import(absPath)).default as Sern.Module<unknown>, absPath };
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getCommands(dir: string): Promise<string[]> {
|
||||
return readPath(join(process.cwd(), dir));
|
||||
}
|
||||
@@ -3,39 +3,40 @@ import Timezone from 'dayjs/plugin/timezone';
|
||||
import UTC from 'dayjs/plugin/timezone';
|
||||
|
||||
enum sEvent {
|
||||
GLOBAL_SLASH,
|
||||
LOCAL_SLASH,
|
||||
DM,
|
||||
CRASH,
|
||||
TEXT_CMD,
|
||||
GLOBAL_SLASH,
|
||||
LOCAL_SLASH,
|
||||
DM,
|
||||
CRASH,
|
||||
TEXT_CMD,
|
||||
}
|
||||
|
||||
export default class Logger {
|
||||
|
||||
public clear() { console.clear(); }
|
||||
|
||||
public log<T extends sEvent>(e : T, message: string) {
|
||||
dayJS.extend(UTC);
|
||||
dayJS.extend(Timezone);
|
||||
dayJS.tz.guess();
|
||||
// add colored logging?
|
||||
const tz = dayJS().format();
|
||||
console.log(`[${`${tz}`}][${sEvent[e]}] :: ${message}`);
|
||||
}
|
||||
public clear() {
|
||||
console.clear();
|
||||
}
|
||||
|
||||
public log<T extends sEvent>(e: T, message: string) {
|
||||
dayJS.extend(UTC);
|
||||
dayJS.extend(Timezone);
|
||||
dayJS.tz.guess();
|
||||
// add colored logging?
|
||||
const tz = dayJS().format();
|
||||
console.log(`[${`${tz}`}][${sEvent[e]}] :: ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilizes console.table() to print out memory usage of current process.
|
||||
* Optional at startup.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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)), {})
|
||||
);
|
||||
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), {}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import * as Files from './utilities/readFile';
|
||||
import type * as Utils from './utilities/preprocessors/args';
|
||||
|
||||
import type {
|
||||
Arg,
|
||||
Context,
|
||||
Visibility,
|
||||
possibleOutput
|
||||
} from '../types/handler';
|
||||
import type { Arg, Context, Visibility, possibleOutput } from '../types/handler';
|
||||
|
||||
import type {
|
||||
ApplicationCommandOptionData,
|
||||
Awaitable,
|
||||
Client,
|
||||
CommandInteraction,
|
||||
Message
|
||||
} from 'discord.js';
|
||||
import type { ApplicationCommandOptionData, Awaitable, Client, CommandInteraction, Message } from 'discord.js';
|
||||
|
||||
import { Ok, Result, None, Some } from 'ts-results';
|
||||
import { isBot, hasPrefix, fmt } from './utilities/messageHelpers';
|
||||
import Logger from './logger';
|
||||
|
||||
/**
|
||||
* @class
|
||||
@@ -28,24 +18,23 @@ export class Handler {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Wrapper} wrapper The data that is required to run sern handler
|
||||
* @param {Wrapper} wrapper The data that is required to run sern handler
|
||||
*/
|
||||
|
||||
constructor(
|
||||
wrapper: Wrapper,
|
||||
) {
|
||||
|
||||
constructor(wrapper: Wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
this.client
|
||||
|
||||
|
||||
/**
|
||||
* On ready, builds command data and registers them all
|
||||
* from command directory
|
||||
* from command directory
|
||||
**/
|
||||
|
||||
|
||||
.on('ready', async () => {
|
||||
Files.buildData(this)
|
||||
.then(data => this.registerModules(data));
|
||||
if (wrapper.init !== undefined) wrapper.init(this);
|
||||
new Logger().tableRam();
|
||||
})
|
||||
|
||||
.on('messageCreate', async (message: Message) => {
|
||||
@@ -53,16 +42,16 @@ export class Handler {
|
||||
if (message.channel.type === 'DM') return; // TODO: Handle dms
|
||||
|
||||
const tryFmt = fmt(message, this.prefix);
|
||||
const module = this.findCommand(tryFmt.shift()!);
|
||||
const commandName = tryFmt.shift()!;
|
||||
const module = Files.Commands.get(commandName) ?? Files.Alias.get(commandName);
|
||||
if (module === undefined) {
|
||||
message.channel.send('Unknown legacy command');
|
||||
return;
|
||||
}
|
||||
const cmdResult = (await this.commandResult(module, message, tryFmt.join(' ')));
|
||||
const cmdResult = await this.commandResult(module, message, tryFmt.join(' '));
|
||||
if (cmdResult === undefined) return;
|
||||
|
||||
message.channel.send(cmdResult);
|
||||
|
||||
})
|
||||
|
||||
.on('interactionCreate', async (interaction) => {
|
||||
@@ -75,58 +64,63 @@ export class Handler {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Files.CommandVal | undefined} module Command file information
|
||||
* @param {CommandInteraction} interaction The Discord.js command interaction (DiscordJS#CommandInteraction))
|
||||
*
|
||||
* @param {Files.CommandVal | undefined} module Command file information
|
||||
* @param {CommandInteraction} interaction The Discord.js command interaction (DiscordJS#CommandInteraction))
|
||||
* @returns {possibleOutput | undefined} Takes return value and replies it, if possible input
|
||||
*/
|
||||
|
||||
|
||||
private async interactionResult(
|
||||
module: Files.CommandVal | undefined,
|
||||
interaction: CommandInteraction): Promise<possibleOutput | undefined> {
|
||||
|
||||
interaction: CommandInteraction,
|
||||
): Promise<possibleOutput | undefined> {
|
||||
if (module === undefined) return 'Unknown slash command!';
|
||||
const name = this.findCommand(interaction.commandName);
|
||||
if (name === undefined) `${interaction.commandName} is not a valid command!`;
|
||||
const name = Array.from(Files.Commands.keys()).find((it) => it === interaction.commandName);
|
||||
if (name === undefined) return `Could not find ${interaction.commandName} command!`;
|
||||
|
||||
if (module.mod.type < CommandType.SLASH) return 'This is not a slash command';
|
||||
|
||||
const context = { message: None, interaction: Some(interaction) };
|
||||
const parsedArgs = module.mod.parse?.(context, ['slash', interaction.options]) ?? Ok('');
|
||||
|
||||
|
||||
if (parsedArgs.err) return parsedArgs.val;
|
||||
|
||||
|
||||
return (await module.mod.delegate(context, parsedArgs))?.val;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {Files.CommandVal | undefined} module Command file information
|
||||
* @param {Message} message The message object
|
||||
* @param {string} args Anything after the command
|
||||
* @param {string} args Anything after the command
|
||||
* @returns Takes return value and replies it, if possible input
|
||||
*/
|
||||
|
||||
private async commandResult(module: Files.CommandVal | undefined, message: Message, args: string): Promise<possibleOutput | undefined> {
|
||||
|
||||
private async commandResult(
|
||||
module: Files.CommandVal | undefined,
|
||||
message: Message,
|
||||
args: string,
|
||||
): Promise<possibleOutput | undefined> {
|
||||
if (module?.mod === undefined) return 'Unknown legacy command';
|
||||
if (module.mod.type === CommandType.SLASH) return `This may be a slash command and not a legacy command`;
|
||||
if (module.mod.visibility === 'private') {
|
||||
const checkIsTestServer = this.privateServers.find(({ id }) => id === message.guildId!)?.test;
|
||||
if (checkIsTestServer === undefined) return 'This command has the private modifier but is not registered under Handler#privateServers';
|
||||
if (checkIsTestServer === undefined)
|
||||
return 'This command has the private modifier but is not registered under Handler#privateServers';
|
||||
if (checkIsTestServer !== module.mod.test) {
|
||||
const msg = `This command is only available on test servers.`; // TODO: Customizable private message
|
||||
|
||||
return msg;
|
||||
const msg = `This command is only available on test servers.`; // TODO: Customizable private message
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
const context = {
|
||||
message: Some(message),
|
||||
interaction: None
|
||||
interaction: None,
|
||||
};
|
||||
const parsedArgs = module.mod.parse?.(context, ['text', args]) ?? Ok('');
|
||||
if (parsedArgs.err) return parsedArgs.val;
|
||||
return (await module.mod.delegate(context, parsedArgs))?.val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function chains `Files.buildData`
|
||||
* @param {{name: string, mod: Module<unknown>, absPath: string}} modArr module information
|
||||
@@ -134,37 +128,40 @@ export class Handler {
|
||||
|
||||
private async registerModules(
|
||||
modArr: {
|
||||
name: string,
|
||||
mod: Module<unknown>,
|
||||
absPath: string
|
||||
}[]
|
||||
name: string;
|
||||
mod: Module<unknown>;
|
||||
absPath: string;
|
||||
}[],
|
||||
) {
|
||||
for await (const { name, mod, absPath } of modArr) {
|
||||
const cmdName = Files.fmtFileName(name);
|
||||
switch (mod.type) {
|
||||
case 1: Files.Commands.set(cmdName, { mod, options: [] }); break;
|
||||
case 1:
|
||||
Files.Commands.set(cmdName, { mod, options: [] });
|
||||
break;
|
||||
case 2:
|
||||
case (1 | 2): {
|
||||
const options = ((await import(absPath)).options as ApplicationCommandOptionData[]);
|
||||
Files.Commands.set(cmdName, { mod, options: options ?? [] });
|
||||
switch (mod.visibility) {
|
||||
case 'private': {
|
||||
// Reloading guild slash commands
|
||||
await this.reloadSlash(cmdName, mod.desc, options);
|
||||
}
|
||||
case 'public': {
|
||||
// Creating global commands
|
||||
// TODO : warn user they will be creating a public command
|
||||
await this.client.application!.commands
|
||||
.create({
|
||||
case 1 | 2:
|
||||
{
|
||||
const options = (await import(absPath)).options as ApplicationCommandOptionData[];
|
||||
Files.Commands.set(cmdName, { mod, options: options ?? [] });
|
||||
switch (mod.visibility) {
|
||||
case 'private': {
|
||||
// Reloading guild slash commands
|
||||
await this.reloadSlash(cmdName, mod.desc, options);
|
||||
}
|
||||
case 'public': {
|
||||
// Creating global commands
|
||||
await this.client.application!.commands.create({
|
||||
name: cmdName,
|
||||
description: mod.desc,
|
||||
options
|
||||
options,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default: throw Error(`SernHandlerError: ${name} with ${mod.visibility} is not a valid module type.`);
|
||||
break;
|
||||
default:
|
||||
throw Error(`SernHandlerError: ${name} with ${mod.visibility} is not a valid module type.`);
|
||||
}
|
||||
|
||||
if (mod.alias.length > 0) {
|
||||
@@ -174,34 +171,26 @@ export class Handler {
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} name name of possible command
|
||||
* @returns {Files.CommandVal | undefined}
|
||||
*/
|
||||
private findCommand(name : string) : Files.CommandVal | undefined {
|
||||
return Files.Commands.get(name) ?? Files.Alias.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {string} cmdName name of command
|
||||
* @param {string} description description of command
|
||||
* @param {ApplicationCommandOptionData[]} options any options for the slash command
|
||||
* @param {string} description description of command
|
||||
* @param {ApplicationCommandOptionData[]} options any options for the slash command
|
||||
*/
|
||||
|
||||
|
||||
private async reloadSlash(
|
||||
cmdName: string,
|
||||
description: string,
|
||||
options: ApplicationCommandOptionData[]
|
||||
) : Promise<void> {
|
||||
options: ApplicationCommandOptionData[],
|
||||
): Promise<void> {
|
||||
for (const { id } of this.privateServers) {
|
||||
const guild = (await this.client.guilds.fetch(id));
|
||||
const guild = await this.client.guilds.fetch(id);
|
||||
|
||||
guild.commands.create({
|
||||
name: cmdName,
|
||||
description,
|
||||
options
|
||||
options,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -210,16 +199,16 @@ export class Handler {
|
||||
* @readonly
|
||||
* @returns {string} The prefix used for legacy commands
|
||||
*/
|
||||
|
||||
|
||||
get prefix(): string {
|
||||
return this.wrapper.prefix;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @returns {string} Directory of the commands folder
|
||||
*/
|
||||
|
||||
* @readonly
|
||||
* @returns {string} Directory of the commands folder
|
||||
*/
|
||||
|
||||
get commandDir(): string {
|
||||
return this.wrapper.commands;
|
||||
}
|
||||
@@ -228,23 +217,23 @@ export class Handler {
|
||||
* @readonly
|
||||
* @returns {Client<boolean>} the discord.js client (DiscordJS#Client));
|
||||
*/
|
||||
|
||||
|
||||
get client(): Client<boolean> {
|
||||
return this.wrapper.client;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @returns {{test: boolean, id: string}[]} Private server ID for testing or personal use
|
||||
*/
|
||||
|
||||
get privateServers(): { test: boolean, id: string }[] {
|
||||
|
||||
get privateServers(): { test: boolean; id: string }[] {
|
||||
return this.wrapper.privateServers;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to be passed into Sern.Handler constructor.
|
||||
* An object to be passed into Sern.Handler constructor.
|
||||
* @typedef {object} Wrapper
|
||||
* @property {readonly Client} client
|
||||
* @property {readonly string} prefix
|
||||
@@ -253,15 +242,15 @@ export class Handler {
|
||||
* @property {readonly {test: boolean, id: string}[]} privateServers
|
||||
*/
|
||||
export interface Wrapper {
|
||||
readonly client: Client,
|
||||
readonly prefix: string,
|
||||
readonly commands: string
|
||||
init?: (handler: Handler) => void,
|
||||
readonly privateServers: { test: boolean, id: string }[],
|
||||
readonly client: Client;
|
||||
readonly prefix: string;
|
||||
readonly commands: string;
|
||||
init?: (handler: Handler) => void;
|
||||
readonly privateServers: { test: boolean; id: string }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that gets imported and acts as a command.
|
||||
* An object that gets imported and acts as a command.
|
||||
* @typedef {object} Module<T=string>
|
||||
* @property {string} desc
|
||||
* @property {Visibility} visibility
|
||||
@@ -271,13 +260,13 @@ export interface Wrapper {
|
||||
*/
|
||||
|
||||
export interface Module<T = string> {
|
||||
alias: string[],
|
||||
desc: string,
|
||||
visibility: Visibility,
|
||||
type: CommandType,
|
||||
test : boolean,
|
||||
delegate: (eventParams: Context, args: Ok<T>) => Awaitable<Result<possibleOutput, string> | void>
|
||||
parse?: (ctx: Context, args: Arg) => Utils.ArgType<T>
|
||||
alias: string[];
|
||||
desc: string;
|
||||
visibility: Visibility;
|
||||
type: CommandType;
|
||||
test: boolean;
|
||||
delegate: (eventParams: Context, args: Ok<T>) => Awaitable<Result<possibleOutput, string> | void>;
|
||||
parse?: (ctx: Context, args: Arg) => Utils.ArgType<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,6 +274,6 @@ export interface Module<T = string> {
|
||||
*/
|
||||
|
||||
export enum CommandType {
|
||||
TEXT = 1,
|
||||
SLASH = 2,
|
||||
TEXT = 1,
|
||||
SLASH = 2,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Message } from 'discord.js';
|
||||
|
||||
export function isBot(message: Message) {
|
||||
return message.author.bot;
|
||||
return message.author.bot;
|
||||
}
|
||||
|
||||
export function hasPrefix(message: Message, prefix: string) {
|
||||
return (message.content.slice(0, prefix.length).toLowerCase().trim()) === prefix;
|
||||
return message.content.slice(0, prefix.length).toLowerCase().trim() === prefix;
|
||||
}
|
||||
|
||||
export function fmt(msg: Message, prefix: string): string[] {
|
||||
return msg.content.slice(prefix.length).trim().split(/\s+/g);
|
||||
return msg.content.slice(prefix.length).trim().split(/\s+/g);
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import type { possibleOutput } from '../../../types/handler';
|
||||
export type ArgType<T> = Result<T, possibleOutput>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - if `Number.parseInt` returns NaN
|
||||
* @returns {ArgType<number>} Attempts to use `Number.parseInt()` on `arg`
|
||||
*/
|
||||
@@ -21,9 +21,9 @@ export function parseInt(arg: string, onFailure: possibleOutput): ArgType<number
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - If cannot parse `arg` into boolean.
|
||||
* @param { {yesRegex: RegExp, noRegex: RegExp} } regexes - default regexes: yes : `/^(?:y(?:es)?|👍)$/i`, no : /^(?:n(?:o)?|👎)$/i
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - If cannot parse `arg` into boolean.
|
||||
* @param { {yesRegex: RegExp, noRegex: RegExp} } regexes - default regexes: yes : `/^(?:y(?:es)?|👍)$/i`, no : /^(?:n(?:o)?|👎)$/i
|
||||
* @returns { ArgType<boolean> } attemps to parse `args` as a boolean
|
||||
*/
|
||||
|
||||
@@ -31,12 +31,12 @@ export function parseBool(
|
||||
arg: string,
|
||||
onFailure: possibleOutput = `Cannot parse '${arg}' as a boolean`,
|
||||
regexes: {
|
||||
yesRegex: RegExp,
|
||||
noRegex: RegExp
|
||||
yesRegex: RegExp;
|
||||
noRegex: RegExp;
|
||||
} = {
|
||||
yesRegex: /^(?:y(?:es)?|👍)$/i,
|
||||
noRegex: /^(?:n(?:o)?|👎)$/i
|
||||
}
|
||||
noRegex: /^(?:n(?:o)?|👎)$/i,
|
||||
},
|
||||
): ArgType<boolean> {
|
||||
if (arg.match(regexes.yesRegex)) return Ok(true);
|
||||
if (arg.match(regexes.noRegex)) return Ok(false);
|
||||
@@ -45,8 +45,8 @@ export function parseBool(
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {string} sep - default separator = ' '
|
||||
* @returns {Ok<string[]>}
|
||||
*/
|
||||
@@ -56,22 +56,22 @@ export function toArr(arg: string, sep = ' '): ArgType<string[]> {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - delegates `Utils.parseInt`
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - delegates `Utils.parseInt`
|
||||
* @returns {ArgType<number>}
|
||||
*/
|
||||
|
||||
export function toPositiveInt(arg: string, onFailure: possibleOutput): ArgType<number> {
|
||||
return parseInt(arg, onFailure).andThen(num => Ok(num > 0 ? num : -num));
|
||||
return parseInt(arg, onFailure).andThen((num) => Ok(num > 0 ? num : -num));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - delegates `parseInt`
|
||||
*
|
||||
* @param {string} arg - command arguments
|
||||
* @param {possibleOutput} onFailure - delegates `parseInt`
|
||||
* @returns {ArgType<number>}
|
||||
*/
|
||||
export function toNegativeInt(arg: string, onFailure: possibleOutput): ArgType<number> {
|
||||
return parseInt(arg, onFailure).andThen(num => Ok(num > 0 ? -num : num));
|
||||
return parseInt(arg, onFailure).andThen((num) => Ok(num > 0 ? -num : num));
|
||||
}
|
||||
56
src/handler/utilities/readFile.ts
Normal file
56
src/handler/utilities/readFile.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { ApplicationCommandOptionData } from 'discord.js';
|
||||
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
|
||||
import { basename, join } from 'path';
|
||||
|
||||
import type * as Sern from '../sern';
|
||||
|
||||
export type CommandVal = {
|
||||
mod: Sern.Module<unknown>;
|
||||
options: ApplicationCommandOptionData[];
|
||||
};
|
||||
|
||||
export const Commands = new Map<string, CommandVal>();
|
||||
export const Alias = new Map<string, CommandVal>();
|
||||
|
||||
// Courtesy of Townsy#0001 on Discord
|
||||
async function readPath(dir: string, arrayOfFiles: string[] = []): Promise<string[]> {
|
||||
try {
|
||||
const files = readdirSync(dir);
|
||||
for (const file of files) {
|
||||
if (statSync(dir + '/' + file).isDirectory()) await readPath(dir + '/' + file, arrayOfFiles);
|
||||
else arrayOfFiles.push(join(dir, '/', file));
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return arrayOfFiles;
|
||||
}
|
||||
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
|
||||
/**
|
||||
* @param {Sern.Handler} handler an instance of Sern.Handler
|
||||
* @returns {Promise<{ name: string; mod: Sern.Module<unknown>; absPath: string; }[]>} data from command files
|
||||
*/
|
||||
|
||||
export async function buildData(handler: Sern.Handler): Promise<
|
||||
{
|
||||
name: string;
|
||||
mod: Sern.Module<unknown>;
|
||||
absPath: string;
|
||||
}[]
|
||||
> {
|
||||
const commandDir = handler.commandDir;
|
||||
return Promise.all(
|
||||
(await getCommands(commandDir)).map(async (absPath) => {
|
||||
return { name: basename(absPath), mod: (await import(absPath)).default as Sern.Module<unknown>, absPath };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getCommands(dir: string): Promise<string[]> {
|
||||
return readPath(join(process.cwd(), dir));
|
||||
}
|
||||
@@ -1,34 +1,34 @@
|
||||
import type { Option } from 'ts-results';
|
||||
|
||||
import type {
|
||||
CommandInteraction,
|
||||
CommandInteractionOptionResolver,
|
||||
Message,
|
||||
MessagePayload,
|
||||
MessageOptions
|
||||
CommandInteraction,
|
||||
CommandInteractionOptionResolver,
|
||||
Message,
|
||||
MessagePayload,
|
||||
MessageOptions,
|
||||
} from 'discord.js';
|
||||
|
||||
import type * as Sern from '../handler/sern';
|
||||
|
||||
export type Visibility = 'private' | 'public';
|
||||
// Anything that can be sent in a `<TextChannel>#send` or `<CommandInteraction>#reply`
|
||||
export type possibleOutput<T = string> = T | MessagePayload & MessageOptions;
|
||||
export type possibleOutput<T = string> = T | (MessagePayload & MessageOptions);
|
||||
export type Nullable<T> = T | null;
|
||||
export type delegate = Sern.Module<unknown>['delegate'];
|
||||
|
||||
// Thanks @cursorsdottsx
|
||||
export type ParseType<T> = {
|
||||
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
|
||||
} [keyof T];
|
||||
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
|
||||
}[keyof T];
|
||||
|
||||
// A Sern.Module['delegate'] will carry a Context Parameter
|
||||
|
||||
export type Context = {
|
||||
message: Option<Message>,
|
||||
interaction: Option<CommandInteraction>
|
||||
}
|
||||
message: Option<Message>;
|
||||
interaction: Option<CommandInteraction>;
|
||||
};
|
||||
|
||||
export type Arg = ParseType<{ text: string; slash: SlashOptions }>;
|
||||
|
||||
export type Arg = ParseType<{ text: string, slash: SlashOptions }>;
|
||||
|
||||
// TypeAlias for interaction.options
|
||||
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;
|
||||
|
||||
Reference in New Issue
Block a user