From 4b03912e89c73bedacea472d7100af9693cf2778 Mon Sep 17 00:00:00 2001 From: WatDuhHekBro <44940783+WatDuhHekBro@users.noreply.github.com> Date: Sat, 25 Jul 2020 20:14:11 -0500 Subject: [PATCH] Added command categories --- docs/Specifications.md | 3 ++ src/commands/help.ts | 27 ++++++++++---- src/core/command.ts | 80 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/docs/Specifications.md b/docs/Specifications.md index 9a64ccf..cc2b08e 100644 --- a/docs/Specifications.md +++ b/docs/Specifications.md @@ -4,6 +4,9 @@ The top-level directory is reserved for files that have to be there for it to wo - `core`: This is where core structures and critical functions for the bot go. - `modules`: This is where modules go that accomplish one specific purpose but isn't so necessary for the bot to function. The goal is to be able to safely remove these without too much trouble. - `commands`: Here's the place to store commands. The file name determines the command name. + - `subcommands/`: All commands here are ignored by the category loader. Here is where you can split commands into different files. Also works per directory, for example, `utility/subcommands/` is ignored. + - `/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category. + - `.ts`: All commands at this level will have the `Miscellaneous` category. - `events`: Here's the place to store events. The file name determines the event type. - `dist`: This is where the runnable code in `src` compiles to. (The directory structure mirrors `src`.) - `data`: Holds all the dynamic data used by the bot. This is what you modify if you want to change stuff for just your instance of the bot. diff --git a/src/commands/help.ts b/src/commands/help.ts index 51c5eba..9c3e33a 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,6 +1,6 @@ import Command from "../core/command"; import {CommonLibrary} from "../core/lib"; -import {loadCommands} from "../core/command"; +import {loadCommands, categories} from "../core/command"; const types = ["user", "number", "any"]; @@ -10,14 +10,27 @@ export default new Command({ async run($: CommonLibrary): Promise { const commands = await loadCommands(); - const list: string[] = []; + let output = `Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\``; - for(const [header, command] of commands) - if(header !== "test") - list.push(`- \`${header}\` - ${command.description}`); + for(const [category, headers] of categories) + { + output += `\n\n===[ ${category} ]===`; + + for(const header of headers) + { + if(header !== "test") + { + const command = commands.get(header); + + if(!command) + return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`); + + output += `\n- \`${header}\`: ${command.description}`; + } + } + } - const outList = list.length > 0 ? `\n${list.join('\n')}` : " None"; - $.channel.send(`Legend: \`\`, \`[list/of/subcommands]\`, \`(optional)\`, \`()\`, \`([optional/list/...])\`\nCommands:${outList}`, {split: true}); + $.channel.send(output, {split: true}); }, any: new Command({ async run($: CommonLibrary): Promise diff --git a/src/core/command.ts b/src/core/command.ts index fb844a9..5618ca9 100644 --- a/src/core/command.ts +++ b/src/core/command.ts @@ -1,7 +1,8 @@ import $, {isType, parseVars, CommonLibrary} from "./lib"; import {Collection} from "discord.js"; -import Storage, {generateHandler} from "./storage"; +import {generateHandler} from "./storage"; import {existsSync, writeFile} from "fs"; +import {promises as ffs} from "fs"; // Permission levels starting from zero then increasing, allowing for numerical comparisons. // Note: For my bot, there really isn't much purpose to doing so, as it's just one command. And plus, if you're doing stuff like moderation commands, it's probably better to make a permissions system that allows for you to separate permissions into different trees. After all, it'd be a really bad idea to allow a bot mechanic to ban users. @@ -97,10 +98,10 @@ export default class Command return true; }*/ -let commands: Collection | null = null; +let commands: Collection|null = null; +export const categories: Collection = new Collection(); /** Returns the cache of the commands if it exists and searches the directory if not. */ -// Fun, Miscellaneous (default), Music, System, Utility export async function loadCommands(): Promise> { if(commands) @@ -110,22 +111,83 @@ export async function loadCommands(): Promise> writeFile("src/commands/test.ts", template, generateHandler('"test.ts" (testing/template command) successfully generated.')); commands = new Collection(); + const dir = await ffs.opendir("dist/commands"); + const listMisc: string[] = []; + let selected; - for(const file of Storage.open("dist/commands", (filename: string) => filename.endsWith(".js"))) + // There will only be one level of directory searching (per category). + while(selected = await dir.read()) { - const header = file.substring(0, file.indexOf(".js")); - const command = (await import(`../commands/${header}`)).default; - commands.set(header, command); - $.log(`Loading Command: ${header}`); + if(selected.isDirectory()) + { + if(selected.name === "subcommands") + continue; + + const subdir = await ffs.opendir(`dist/commands/${selected.name}`); + const category = getTitleCase(selected.name); + const list: string[] = []; + let cmd; + + while(cmd = await subdir.read()) + { + if(cmd.isDirectory()) + { + if(cmd.name === "subcommands") + continue; + else + $.warn(`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`); + } + else + { + const header = cmd.name.substring(0, cmd.name.indexOf(".js")); + const command = (await import(`../commands/${selected.name}/${header}`)).default; + list.push(header); + + if(commands.has(header)) + $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`); + else + commands.set(header, command); + + $.log(`Loading Command: ${header} (${category})`); + } + } + + subdir.close(); + categories.set(category, list); + } + else + { + const header = selected.name.substring(0, selected.name.indexOf(".js")); + const command = (await import(`../commands/${header}`)).default; + listMisc.push(header); + + if(commands.has(header)) + $.warn(`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories.`); + else + commands.set(header, command); + + $.log(`Loading Command: ${header} (Miscellaneous)`); + } } + dir.close(); + categories.set("Miscellaneous", listMisc); + return commands; } +function getTitleCase(name: string): string +{ + if(name.length < 1) + return name; + const first = name[0].toUpperCase(); + return first + name.substring(1); +} + // The template should be built with a reductionist mentality. // Provide everything the user needs and then let them remove whatever they want. // That way, they aren't focusing on what's missing, but rather what they need for their command. -export const template = +const template = `import Command from '../core/command'; import {CommonLibrary} from '../core/lib';