Added command categories

This commit is contained in:
WatDuhHekBro 2020-07-25 20:14:11 -05:00
parent 14f78a91dc
commit 4b03912e89
3 changed files with 94 additions and 16 deletions

View file

@ -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.
- `<directory>/`: Specify a directory which'll group commands into a category. For example, a `utility` folder would make all commands inside have the `Utility` category.
- `<file>.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.

View file

@ -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<any>
{
const commands = await loadCommands();
const list: string[] = [];
let output = `Legend: \`<type>\`, \`[list/of/subcommands]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``;
for(const [header, command] of commands)
for(const [category, headers] of categories)
{
output += `\n\n===[ ${category} ]===`;
for(const header of headers)
{
if(header !== "test")
list.push(`- \`${header}\` - ${command.description}`);
{
const command = commands.get(header);
const outList = list.length > 0 ? `\n${list.join('\n')}` : " None";
$.channel.send(`Legend: \`<type>\`, \`[list/of/subcommands]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\`\nCommands:${outList}`, {split: true});
if(!command)
return $.warn(`Command "${header}" of category "${category}" unexpectedly doesn't exist!`);
output += `\n- \`${header}\`: ${command.description}`;
}
}
}
$.channel.send(output, {split: true});
},
any: new Command({
async run($: CommonLibrary): Promise<any>

View file

@ -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<string, Command> | null = null;
let commands: Collection<string, Command>|null = null;
export const categories: Collection<string, string[]> = 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<Collection<string, Command>>
{
if(commands)
@ -110,22 +111,83 @@ export async function loadCommands(): Promise<Collection<string, Command>>
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;
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}`);
$.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';