finished formatting

This commit is contained in:
EvolutionX
2022-02-14 13:18:14 +05:30
parent 5102563869
commit a648f347f1
7 changed files with 289 additions and 288 deletions

View File

@@ -1,9 +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 }]
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals" : true }],
"semi": ["error", "always"]
}
}

View File

@@ -3,40 +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 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 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), {}),
);
}
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

@@ -14,222 +14,222 @@ import Logger from './logger';
*/
export class Handler {
private wrapper: Wrapper;
private wrapper: Wrapper;
/**
* @constructor
* @param {Wrapper} wrapper The data that is required to run sern handler
*/
/**
* @constructor
* @param {Wrapper} wrapper The data that is required to run sern handler
*/
constructor(wrapper: Wrapper) {
this.wrapper = wrapper;
this.client
constructor(wrapper: Wrapper) {
this.wrapper = wrapper;
this.client
/**
* On ready, builds command data and registers them all
* from command directory
**/
/**
* On ready, builds command data and registers them all
* 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('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) => {
if (isBot(message) || !hasPrefix(message, this.prefix)) return;
if (message.channel.type === 'DM') return; // TODO: Handle dms
.on('messageCreate', async (message: Message) => {
if (isBot(message) || !hasPrefix(message, this.prefix)) return;
if (message.channel.type === 'DM') return; // TODO: Handle dms
const tryFmt = fmt(message, this.prefix);
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(' '));
if (cmdResult === undefined) return;
const tryFmt = fmt(message, this.prefix);
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(' '));
if (cmdResult === undefined) return;
message.channel.send(cmdResult);
})
message.channel.send(cmdResult);
})
.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;
const module = Files.Commands.get(interaction.commandName);
const res = await this.interactionResult(module, interaction);
if (res === undefined) return;
await interaction.reply(res);
});
}
/**
*
* @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> {
if (module === undefined) return 'Unknown slash 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
* @returns Takes return value and replies it, if possible input
*/
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 !== module.mod.test) {
const msg = `This command is only available on test servers.`; // TODO: Customizable private message
return msg;
}
.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;
const module = Files.Commands.get(interaction.commandName);
const res = await this.interactionResult(module, interaction);
if (res === undefined) return;
await interaction.reply(res);
});
}
const context = {
message: Some(message),
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
*/
/**
*
* @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 registerModules(
modArr: {
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 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
await this.client.application!.commands.create({
name: cmdName,
description: mod.desc,
options,
});
}
private async interactionResult(
module: Files.CommandVal | undefined,
interaction: CommandInteraction,
): Promise<possibleOutput | undefined> {
if (module === undefined) return 'Unknown slash 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
* @returns Takes return value and replies it, if possible input
*/
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 !== module.mod.test) {
const msg = `This command is only available on test servers.`; // TODO: Customizable private message
return msg;
}
}
break;
default:
throw Error(`SernHandlerError: ${name} with ${mod.visibility} is not a valid module type.`);
}
if (mod.alias.length > 0) {
for (const alias of mod.alias) {
Files.Alias.set(alias, { mod, options: [] });
}
}
const context = {
message: Some(message),
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;
}
}
/**
*
* @param {string} cmdName name of command
* @param {string} description description of command
* @param {ApplicationCommandOptionData[]} options any options for the slash command
*/
/**
* This function chains `Files.buildData`
* @param {{name: string, mod: Module<unknown>, absPath: string}} modArr module information
*/
private async reloadSlash(
cmdName: string,
description: string,
options: ApplicationCommandOptionData[],
): Promise<void> {
for (const { id } of this.privateServers) {
const guild = await this.client.guilds.fetch(id);
private async registerModules(
modArr: {
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 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
await this.client.application!.commands.create({
name: cmdName,
description: mod.desc,
options,
});
}
}
}
break;
default:
throw Error(`SernHandlerError: ${name} with ${mod.visibility} is not a valid module type.`);
}
guild.commands.create({
name: cmdName,
description,
options,
});
if (mod.alias.length > 0) {
for (const alias of mod.alias) {
Files.Alias.set(alias, { mod, options: [] });
}
}
}
}
}
/**
* @readonly
* @returns {string} The prefix used for legacy commands
*/
/**
*
* @param {string} cmdName name of command
* @param {string} description description of command
* @param {ApplicationCommandOptionData[]} options any options for the slash command
*/
get prefix(): string {
return this.wrapper.prefix;
}
private async reloadSlash(
cmdName: string,
description: string,
options: ApplicationCommandOptionData[],
): Promise<void> {
for (const { id } of this.privateServers) {
const guild = await this.client.guilds.fetch(id);
/**
* @readonly
* @returns {string} Directory of the commands folder
*/
guild.commands.create({
name: cmdName,
description,
options,
});
}
}
get commandDir(): string {
return this.wrapper.commands;
}
/**
* @readonly
* @returns {string} The prefix used for legacy commands
*/
/**
* @readonly
* @returns {Client<boolean>} the discord.js client (DiscordJS#Client));
*/
get prefix(): string {
return this.wrapper.prefix;
}
get client(): Client<boolean> {
return this.wrapper.client;
}
/**
* @readonly
* @returns {string} Directory of the commands folder
*/
/**
* @readonly
* @returns {{test: boolean, id: string}[]} Private server ID for testing or personal use
*/
get commandDir(): string {
return this.wrapper.commands;
}
get privateServers(): { test: boolean; id: string }[] {
return this.wrapper.privateServers;
}
/**
* @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 }[] {
return this.wrapper.privateServers;
}
}
/**
@@ -242,11 +242,11 @@ 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 }[];
}
/**
@@ -260,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>;
}
/**
@@ -274,6 +274,6 @@ export interface Module<T = string> {
*/
export enum CommandType {
TEXT = 1,
SLASH = 2,
TEXT = 1,
SLASH = 2,
}

View File

@@ -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);
}

View File

@@ -15,9 +15,9 @@ export type ArgType<T> = Result<T, possibleOutput>;
*/
export function parseInt(arg: string, onFailure: possibleOutput): ArgType<number> {
const val = Number.parseInt(arg);
const val = Number.parseInt(arg);
return val === NaN ? Err(onFailure) : Ok(val);
return val === NaN ? Err(onFailure) : Ok(val);
}
/**
@@ -28,20 +28,20 @@ export function parseInt(arg: string, onFailure: possibleOutput): ArgType<number
*/
export function parseBool(
arg: string,
onFailure: possibleOutput = `Cannot parse '${arg}' as a boolean`,
regexes: {
yesRegex: RegExp;
noRegex: RegExp;
} = {
yesRegex: /^(?:y(?:es)?|👍)$/i,
noRegex: /^(?:n(?:o)?|👎)$/i,
},
arg: string,
onFailure: possibleOutput = `Cannot parse '${arg}' as a boolean`,
regexes: {
yesRegex: RegExp;
noRegex: RegExp;
} = {
yesRegex: /^(?:y(?:es)?|👍)$/i,
noRegex: /^(?:n(?:o)?|👎)$/i,
},
): ArgType<boolean> {
if (arg.match(regexes.yesRegex)) return Ok(true);
if (arg.match(regexes.noRegex)) return Ok(false);
if (arg.match(regexes.yesRegex)) return Ok(true);
if (arg.match(regexes.noRegex)) return Ok(false);
return Err(onFailure);
return Err(onFailure);
}
/**
@@ -52,7 +52,7 @@ export function parseBool(
*/
export function toArr(arg: string, sep = ' '): ArgType<string[]> {
return Ok(arg.split(sep));
return Ok(arg.split(sep));
}
/**
@@ -63,7 +63,7 @@ export function toArr(arg: string, sep = ' '): ArgType<string[]> {
*/
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));
}
/**
@@ -73,5 +73,5 @@ export function toPositiveInt(arg: string, onFailure: possibleOutput): ArgType<n
* @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));
}

View File

@@ -7,8 +7,8 @@ import { basename, join } from 'path';
import type * as Sern from '../sern';
export type CommandVal = {
mod: Sern.Module<unknown>;
options: ApplicationCommandOptionData[];
mod: Sern.Module<unknown>;
options: ApplicationCommandOptionData[];
};
export const Commands = new Map<string, CommandVal>();
@@ -16,17 +16,17 @@ 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));
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;
}
} catch (err) {
throw err;
}
return arrayOfFiles;
return arrayOfFiles;
}
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
@@ -37,20 +37,20 @@ export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
*/
export async function buildData(handler: Sern.Handler): Promise<
{
name: string;
mod: Sern.Module<unknown>;
absPath: string;
}[]
{
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 };
}),
);
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));
return readPath(join(process.cwd(), dir));
}

View File

@@ -1,11 +1,11 @@
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';
@@ -18,14 +18,14 @@ 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;
[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 }>;