From 3b15b27803569b22e8053b32cdd7542341d1a92d Mon Sep 17 00:00:00 2001 From: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com> Date: Sat, 12 Feb 2022 22:41:55 +0300 Subject: [PATCH] Format & improve src files --- src/handler/sern.ts | 118 ++++++++++++++++-------- src/handler/utils/messageHelpers.ts | 11 +-- src/handler/utils/preprocessors/args.ts | 29 +++--- src/handler/utils/readFile.ts | 50 +++++----- src/index.ts | 7 +- src/types/handler.ts | 28 ++++-- 6 files changed, 151 insertions(+), 92 deletions(-) diff --git a/src/handler/sern.ts b/src/handler/sern.ts index eedabfb..e556c28 100644 --- a/src/handler/sern.ts +++ b/src/handler/sern.ts @@ -1,15 +1,29 @@ -import type { Arg, Context, Visibility } from "../types/handler"; -import * as Files from "./utils/readFile" -import type { ApplicationCommandOptionData, Awaitable, Client, CommandInteraction, Message } from "discord.js"; -import type { possibleOutput } from "../types/handler" -import { Ok, Result, None, Some } from "ts-results"; -import type * as Utils from "./utils/preprocessors/args"; -import { isBot, hasPrefix, fmt } from "./utils/messageHelpers"; +import * as Files from './utils/readFile' +import type * as Utils from './utils/preprocessors/args'; + +import type { + Arg, + Context, + Visibility, + possibleOutput +} from '../types/handler'; + +import type { + ApplicationCommandOptionData, + Awaitable, + Client, + CommandInteraction, + Message +} from 'discord.js'; + +import { Ok, Result, None, Some } from 'ts-results'; +import { isBot, hasPrefix, fmt } from './utils/messageHelpers'; /** * @class */ + export class Handler { private wrapper: Wrapper; /** @@ -21,64 +35,72 @@ export class Handler { ) { this.wrapper = wrapper; - this.client /** * On ready, builds command data and registers them all * from command directory **/ - .on("ready", async () => { + .on('ready', async () => { Files.buildData(this) - .then( data => this.registerModules(data)) + .then(data => this.registerModules(data)) if (wrapper.init !== undefined) wrapper.init(this); }) - .on("messageCreate", async message => { + .on('messageCreate', async (message: { + channel: { + type: string; send: (arg0: string) => void; + }; + }) => { if (isBot(message) || !hasPrefix(message, this.prefix)) return; - if (message.channel.type === "DM") return; //todo, handle dms + 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") + 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 => { + .on('interactionCreate', async (interaction: { + isCommand: () => boolean; commandName: string; reply: (arg0: any) => any; + }) => { 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 a Discord.js command interaction * @returns {possibleOutput | undefined} takes return value and replies it, if possible input */ + private async interactionResult( module: Files.CommandVal | undefined, interaction: CommandInteraction): Promise { - if (module === undefined) return "Unknown slash command!"; + 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"; + 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(""); + 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 @@ -86,25 +108,33 @@ export class Handler { * @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 { - if (module?.mod === undefined) return "Unknown legacy command"; + 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") { + 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.testOnly) { - return "This private command is a testing command"; + const msg = `This command is only available on test servers.`; // TODO: Customizable private message + + return msg; } } - const context = { message: Some(message), interaction: None } - const parsedArgs = module.mod.parse?.(context, ["text", args]) ?? Ok(""); + 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, absPath: string}} modArr module information */ + private async registerModules( modArr: { name: string, @@ -121,22 +151,22 @@ export class Handler { const options = ((await import(absPath)).options as ApplicationCommandOptionData[]) Files.Commands.set(cmdName, { mod, options: options ?? [], testOnly }); switch (mod.visibility) { - case "private": { - // loading guild slash commands only + case 'private': { + // Loading guild slash commands only await this.reloadSlash(cmdName, mod.desc, options) } - case "public": { - // creating global commands! - await this.client.application!.commands + case 'public': { + // Creating global commands + await this.client.application!.commands .create({ name: cmdName, description: mod.desc, options - }) + }); } } } break; - default: throw Error(`${name} with ${mod.visibility} is not a valid module type.`); + default: throw Error(`SernHandlerError: ${name} with ${mod.visibility} is not a valid module type.`); } if (mod.alias.length > 0) { @@ -146,12 +176,14 @@ export class Handler { } } } + /** * * @param {string} cmdName name of command * @param {string} description description of command * @param {ApplicationCommandOptionData[]} options any options for the slash command */ + private async reloadSlash( cmdName: string, description: string, @@ -167,36 +199,42 @@ export class Handler { }) } } + /** * @readonly - * @returns {string} prefix used for legacy commands + * @returns {string} The prefix used for legacy commands */ + get prefix(): string { return this.wrapper.prefix; } + /** * @readonly - * @returns {string} directory of your commands folder + * @returns {string} Directory of the commands folder */ + get commandDir(): string { return this.wrapper.commands; } + /** * @readonly - * @returns {Client} discord.js client + * @returns {Client} the DiscordJS.Client(); */ + get client(): Client { return this.wrapper.client } + /** * @readonly - * @returns {{test: boolean, id: string}[]} private server id for testing or personal use + * @returns {{test: boolean, id: string}[]} Private server ID for testing or personal use */ + get privateServers(): { test: boolean, id: string }[] { return this.wrapper.privateServers; } - - } /** @@ -215,6 +253,7 @@ export interface Wrapper { init?: (handler: Handler) => void, readonly privateServers: { test: boolean, id: string }[], } + /** * An object to be passed into Sern.Handler constructor. * @typedef {object} Module @@ -224,6 +263,7 @@ export interface Wrapper { * @property {(eventParams : Context, args : Ok) => Awaitable | void>)} delegate * @prop {(ctx: Context, args: Arg) => Utils.ArgType} parse */ + export interface Module { alias: string[], desc: string, @@ -232,9 +272,11 @@ export interface Module { delegate: (eventParams: Context, args: Ok) => Awaitable | void> parse?: (ctx: Context, args: Arg) => Utils.ArgType } + /** * @enum { number }; */ + export enum CommandType { TEXT = 1, SLASH = 2, diff --git a/src/handler/utils/messageHelpers.ts b/src/handler/utils/messageHelpers.ts index 6b1f6f2..cfad6dc 100644 --- a/src/handler/utils/messageHelpers.ts +++ b/src/handler/utils/messageHelpers.ts @@ -1,14 +1,13 @@ -import type { Message } from "discord.js"; - +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) -} \ No newline at end of file + return msg.content.slice(prefix.length).trim().split(/\s+/g); +} diff --git a/src/handler/utils/preprocessors/args.ts b/src/handler/utils/preprocessors/args.ts index 837781a..15cfbd7 100644 --- a/src/handler/utils/preprocessors/args.ts +++ b/src/handler/utils/preprocessors/args.ts @@ -1,22 +1,24 @@ -import { Err, Ok, Result } from "ts-results"; -import type { possibleOutput } from "../../../types/handler"; - - +import { Err, Ok, Result } from 'ts-results'; +import type { possibleOutput } from '../../../types/handler'; /** * Wrapper type taking `Ok(T)` or `Err(possibleOutput)` e.g `Result = Result + +export type ArgType = Result; + /** * * @param {string} arg - command arguments * @param {possibleOutput} onFailure - if `Number.parseInt` returns NaN * @returns {ArgType} Attempts to use `Number.parseInt()` on `arg` */ + export function parseInt(arg: string, onFailure: possibleOutput): ArgType { const val = Number.parseInt(arg); return val === NaN ? Err(onFailure) : Ok(val); } + /** * * @param {string} arg - command arguments @@ -24,22 +26,25 @@ export function parseInt(arg: string, onFailure: possibleOutput): ArgType } attemps to parse `args` as a boolean */ + export function parseBool( arg: string, - onFailure: possibleOutput = `Cannot parse "${arg}" as a boolean`, + onFailure: possibleOutput = `Cannot parse '${arg}' as a boolean`, regexes: { yesRegex: RegExp, noRegex: RegExp } = { yesRegex: /^(?:y(?:es)?|👍)$/i, noRegex: /^(?:n(?:o)?|👎)$/i } ): ArgType { if (arg.match(regexes.yesRegex)) return Ok(true); if (arg.match(regexes.noRegex)) return Ok(false); return Err(onFailure); } + /** * * @param {string} arg - command arguments - * @param {string} sep - default separator = " " + * @param {string} sep - default separator = ' ' * @returns {Ok} */ -export function toArr(arg: string, sep = " "): ArgType { + +export function toArr(arg: string, sep = ' '): ArgType { return Ok(arg.split(sep)); } @@ -49,8 +54,9 @@ export function toArr(arg: string, sep = " "): ArgType { * @param {possibleOutput} onFailure - delegates `Utils.parseInt` * @returns {ArgType} */ + export function toPositiveInt(arg: string, onFailure: possibleOutput): ArgType { - return parseInt(arg, onFailure).andThen(num => Ok(num > 0 ? num : -num)) + return parseInt(arg, onFailure).andThen(num => Ok(num > 0 ? num : -num)); } /** @@ -62,8 +68,3 @@ export function toPositiveInt(arg: string, onFailure: possibleOutput): ArgType { return parseInt(arg, onFailure).andThen(num => Ok(num > 0 ? -num : num)) } - - - - - diff --git a/src/handler/utils/readFile.ts b/src/handler/utils/readFile.ts index 397a055..8eabb80 100644 --- a/src/handler/utils/readFile.ts +++ b/src/handler/utils/readFile.ts @@ -1,40 +1,48 @@ -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, options: ApplicationCommandOptionData[], testOnly: boolean } +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, + options: ApplicationCommandOptionData[], + testOnly: boolean +} export const Commands = new Map(); export const Alias = new Map(); -//courtesy of Townsy#0001 on Discord +// Courtesy of Townsy#0001 on Discord async function readPath(dir: string, arrayOfFiles: string[] = []): Promise { - 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; - } + 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; + return arrayOfFiles; } export const fmtFileName = (n: string) => { - const endsW = n.toLowerCase().endsWith("-test.js") || n.toLowerCase().endsWith("-test.ts"); + const endsW = n.toLowerCase().endsWith('-test.js') || n.toLowerCase().endsWith('-test.ts'); return endsW ? { cmdName: n.substring(0, n.length - 8), testOnly: true } : { cmdName: n.substring(0, n.length - 3), testOnly: false }; }; + /** * * @param {Sern.Handler} handler an instance of Sern.Handler * @returns {Promise<{ name: string; mod: Sern.Module; absPath: string; }[]>} data from command files */ + export async function buildData(handler: Sern.Handler) : Promise<{ name: string; @@ -50,4 +58,4 @@ export async function buildData(handler: Sern.Handler) export async function getCommands(dir: string): Promise { return readPath(join(process.cwd(), dir)) -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index a6f302e..78e789f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ -import * as Sern from "./handler/sern"; -import * as Utils from "./handler/utils/preprocessors/args" -import * as Types from "./types/handler" +import * as Sern from './handler/sern'; +import * as Utils from './handler/utils/preprocessors/args'; +import * as Types from './types/handler'; + module.exports = { Sern, Utils, Types }; export { Sern, Utils, Types }; diff --git a/src/types/handler.ts b/src/types/handler.ts index 644154f..9bfa72f 100644 --- a/src/types/handler.ts +++ b/src/types/handler.ts @@ -1,25 +1,33 @@ -import type { Option } from 'ts-results' -import type { CommandInteraction, CommandInteractionOptionResolver, Message, MessagePayload, MessageOptions } from 'discord.js'; -import type * as Sern from "../handler/sern" +import type { Option } from 'ts-results'; -export type Visibility = "private" | "public" +import type { + 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 `#send` or `#reply` export type possibleOutput = T | MessagePayload & MessageOptions; export type Nullable = T | null; -export type delegate = Sern.Module["delegate"] +export type delegate = Sern.Module['delegate']; // Thanks @cursorsdottsx export type ParseType = { [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; -}[keyof T]; +} [keyof T]; -// A Sern.Module["delegate"] will carry a Context Parameter +// A Sern.Module['delegate'] will carry a Context Parameter export type Context = { - message: Option, - interaction: Option + message: Option, + interaction: Option } export type Arg = ParseType<{text : string, slash : SlashOptions}> // TypeAlias for interaction.options -export type SlashOptions = Omit; +export type SlashOptions = Omit;