Reorganized code dealing with the command class
This commit is contained in:
parent
9adc5eea6e
commit
f650faee89
|
@ -1,6 +1,6 @@
|
|||
import Command from "../../core/command";
|
||||
import {toTitleCase} from "../../core/lib";
|
||||
import {loadableCommands, categories} from "../../core/command";
|
||||
import {loadableCommands, categories} from "../../core/loader";
|
||||
import {getPermissionName} from "../../core/permissions";
|
||||
|
||||
export default new Command({
|
||||
|
@ -32,69 +32,7 @@ export default new Command({
|
|||
},
|
||||
any: new Command({
|
||||
async run($) {
|
||||
const commands = await loadableCommands;
|
||||
let header = $.args.shift() as string;
|
||||
let command = commands.get(header);
|
||||
|
||||
if (!command || header === "test") {
|
||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.originalCommandName) header = command.originalCommandName;
|
||||
else console.warn(`originalCommandName isn't defined for ${header}?!`);
|
||||
|
||||
let permLevel = command.permission ?? 0;
|
||||
let usage = command.usage;
|
||||
let invalid = false;
|
||||
|
||||
let selectedCategory = "Unknown";
|
||||
|
||||
for (const [category, headers] of categories) {
|
||||
if (headers.includes(header)) {
|
||||
if (selectedCategory !== "Unknown")
|
||||
console.warn(
|
||||
`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`
|
||||
);
|
||||
else selectedCategory = toTitleCase(category);
|
||||
}
|
||||
}
|
||||
|
||||
for (const param of $.args) {
|
||||
const type = command.resolve(param);
|
||||
command = command.get(param);
|
||||
permLevel = command.permission ?? permLevel;
|
||||
|
||||
if (permLevel === -1) permLevel = command.permission;
|
||||
|
||||
switch (type) {
|
||||
case Command.TYPES.SUBCOMMAND:
|
||||
header += ` ${command.originalCommandName}`;
|
||||
break;
|
||||
case Command.TYPES.USER:
|
||||
header += " <user>";
|
||||
break;
|
||||
case Command.TYPES.NUMBER:
|
||||
header += " <number>";
|
||||
break;
|
||||
case Command.TYPES.ANY:
|
||||
header += " <any>";
|
||||
break;
|
||||
default:
|
||||
header += ` ${param}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (type === Command.TYPES.NONE) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
||||
return;
|
||||
}
|
||||
// [category, commandName, command, subcommandInfo] = resolveCommandInfo();
|
||||
|
||||
let append = "";
|
||||
|
||||
|
@ -123,18 +61,10 @@ export default new Command({
|
|||
append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None.");
|
||||
} else append = `Usage: \`${header} ${usage}\``;
|
||||
|
||||
let aliases = "None";
|
||||
|
||||
if (command.aliases.length > 0) {
|
||||
aliases = "";
|
||||
|
||||
for (let i = 0; i < command.aliases.length; i++) {
|
||||
const alias = command.aliases[i];
|
||||
aliases += `\`${alias}\``;
|
||||
|
||||
if (i !== command.aliases.length - 1) aliases += ", ";
|
||||
}
|
||||
}
|
||||
const formattedAliases: string[] = [];
|
||||
for (const alias of command.aliases) formattedAliases.push(`\`${alias}\``);
|
||||
// Short circuit an empty string, in this case, if there are no aliases.
|
||||
const aliases = formattedAliases.join(", ") || "None";
|
||||
|
||||
$.channel.send(
|
||||
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${getPermissionName(
|
||||
|
|
|
@ -2,7 +2,8 @@ import {parseVars} from "./lib";
|
|||
import {Collection} from "discord.js";
|
||||
import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js";
|
||||
import {getPrefix} from "../core/structures";
|
||||
import glob from "glob";
|
||||
import {SingleMessageOptions} from "./libd";
|
||||
import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions";
|
||||
|
||||
interface CommandMenu {
|
||||
args: any[];
|
||||
|
@ -27,7 +28,7 @@ interface CommandOptions {
|
|||
any?: Command;
|
||||
}
|
||||
|
||||
export enum TYPES {
|
||||
enum TYPES {
|
||||
SUBCOMMAND,
|
||||
USER,
|
||||
NUMBER,
|
||||
|
@ -47,7 +48,6 @@ export default class Command {
|
|||
public user: Command | null;
|
||||
public number: Command | null;
|
||||
public any: Command | null;
|
||||
public static readonly TYPES = TYPES;
|
||||
|
||||
constructor(options?: CommandOptions) {
|
||||
this.description = options?.description || "No description.";
|
||||
|
@ -120,6 +120,67 @@ export default class Command {
|
|||
} else this.run($).catch(handler.bind($));
|
||||
}
|
||||
|
||||
// Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is).
|
||||
public async actualExecute(args: string[], tmp: any): Promise<SingleMessageOptions | null> {
|
||||
// Subcommand Recursion //
|
||||
let command = commands.get(header)!;
|
||||
//resolveSubcommand()
|
||||
const params: any[] = [];
|
||||
let isEndpoint = false;
|
||||
let permLevel = command.permission ?? 0;
|
||||
|
||||
for (const param of args) {
|
||||
if (command.endpoint) {
|
||||
if (command.subcommands.size > 0 || command.user || command.number || command.any)
|
||||
console.warn("An endpoint cannot have subcommands!");
|
||||
isEndpoint = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const type = command.resolve(param);
|
||||
command = command.get(param);
|
||||
permLevel = command.permission ?? permLevel;
|
||||
|
||||
if (type === TYPES.USER) {
|
||||
const id = param.match(/\d+/g)![0];
|
||||
try {
|
||||
params.push(await message.client.users.fetch(id));
|
||||
} catch (error) {
|
||||
return message.channel.send(`No user found by the ID \`${id}\`!`);
|
||||
}
|
||||
} else if (type === TYPES.NUMBER) params.push(Number(param));
|
||||
else if (type !== TYPES.SUBCOMMAND) params.push(param);
|
||||
}
|
||||
|
||||
if (!message.member)
|
||||
return console.warn("This command was likely called from a DM channel meaning the member object is null.");
|
||||
|
||||
if (!hasPermission(message.member, permLevel)) {
|
||||
const userPermLevel = getPermissionLevel(message.member);
|
||||
return message.channel.send(
|
||||
`You don't have access to this command! Your permission level is \`${getPermissionName(
|
||||
userPermLevel
|
||||
)}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName(
|
||||
permLevel
|
||||
)}\` (${permLevel}).`
|
||||
);
|
||||
}
|
||||
|
||||
if (isEndpoint) return message.channel.send("Too many arguments!");
|
||||
|
||||
command.execute({
|
||||
args: params,
|
||||
author: message.author,
|
||||
channel: message.channel,
|
||||
client: message.client,
|
||||
guild: message.guild,
|
||||
member: message.member,
|
||||
message: message
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public resolve(param: string): TYPES {
|
||||
if (this.subcommands.has(param)) return TYPES.SUBCOMMAND;
|
||||
// Any Discord ID format will automatically format to a user ID.
|
||||
|
@ -154,84 +215,73 @@ export default class Command {
|
|||
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command.
|
||||
export const categories = new Collection<string, string[]>();
|
||||
// Returns: [category, command name, command, available subcommands: [type, subcommand]]
|
||||
public resolveCommandInfo(args: string[]): [string, string, Command, Collection<string, Command>] {
|
||||
const commands = await loadableCommands;
|
||||
let header = args.shift();
|
||||
let command = commands.get(header);
|
||||
|
||||
/** Returns the cache of the commands if it exists and searches the directory if not. */
|
||||
export const loadableCommands = (async () => {
|
||||
const commands = new Collection<string, Command>();
|
||||
// Include all .ts files recursively in "src/commands/".
|
||||
const files = await globP("src/commands/**/*.ts");
|
||||
// Extract the usable parts from "src/commands/" if:
|
||||
// - The path is 1 to 2 subdirectories (a or a/b, not a/b/c)
|
||||
// - Any leading directory isn't "modules"
|
||||
// - The filename doesn't end in .test.ts (for jest testing)
|
||||
// - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates
|
||||
const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/;
|
||||
const lists: {[category: string]: string[]} = {};
|
||||
if (!command || header === "test") {
|
||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const path of files) {
|
||||
const match = pattern.exec(path);
|
||||
if (command.originalCommandName) header = command.originalCommandName;
|
||||
else console.warn(`originalCommandName isn't defined for ${header}?!`);
|
||||
|
||||
if (match) {
|
||||
const commandID = match[1]; // e.g. "utilities/info"
|
||||
const slashIndex = commandID.indexOf("/");
|
||||
const isMiscCommand = slashIndex !== -1;
|
||||
const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous";
|
||||
const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info"
|
||||
// If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance.
|
||||
const command = (await import(`../commands/${commandID}`)).default as unknown;
|
||||
let permLevel = command.permission ?? 0;
|
||||
let usage = command.usage;
|
||||
let invalid = false;
|
||||
|
||||
if (command instanceof Command) {
|
||||
command.originalCommandName = commandName;
|
||||
let selectedCategory = "Unknown";
|
||||
|
||||
if (commands.has(commandName)) {
|
||||
for (const [category, headers] of categories) {
|
||||
if (headers.includes(header)) {
|
||||
if (selectedCategory !== "Unknown")
|
||||
console.warn(
|
||||
`Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!`
|
||||
`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`
|
||||
);
|
||||
} else {
|
||||
commands.set(commandName, command);
|
||||
}
|
||||
|
||||
for (const alias of command.aliases) {
|
||||
if (commands.has(alias)) {
|
||||
console.warn(
|
||||
`Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!`
|
||||
);
|
||||
} else {
|
||||
commands.set(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(category in lists)) lists[category] = [];
|
||||
lists[category].push(commandName);
|
||||
|
||||
console.log(`Loading Command: ${commandID}`);
|
||||
} else {
|
||||
console.warn(`Command "${commandID}" has no default export which is a Command instance!`);
|
||||
else selectedCategory = toTitleCase(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const category in lists) {
|
||||
categories.set(category, lists[category]);
|
||||
}
|
||||
for (const param of args) {
|
||||
const type = command.resolve(param);
|
||||
command = command.get(param);
|
||||
permLevel = command.permission ?? permLevel;
|
||||
|
||||
return commands;
|
||||
})();
|
||||
if (permLevel === -1) permLevel = command.permission;
|
||||
|
||||
function globP(path: string) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
glob(path, (error, files) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(files);
|
||||
switch (type) {
|
||||
case TYPES.SUBCOMMAND:
|
||||
header += ` ${command.originalCommandName}`;
|
||||
break;
|
||||
case TYPES.USER:
|
||||
header += " <user>";
|
||||
break;
|
||||
case TYPES.NUMBER:
|
||||
header += " <number>";
|
||||
break;
|
||||
case TYPES.ANY:
|
||||
header += " <any>";
|
||||
break;
|
||||
default:
|
||||
header += ` ${param}`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (type === TYPES.NONE) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If you use promises, use this function to display the error in chat.
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
import {client} from "../index";
|
||||
import Command, {loadableCommands} from "./command";
|
||||
import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions";
|
||||
import {loadableCommands} from "./loader";
|
||||
import {Permissions, Message} from "discord.js";
|
||||
import {getPrefix} from "./structures";
|
||||
import {Config} from "./structures";
|
||||
|
||||
///////////
|
||||
// Steps //
|
||||
///////////
|
||||
// 1. Someone sends a message in chat.
|
||||
// 2. Check if bot, then load commands.
|
||||
// 3. Check if "<prefix>...". If not, check if "@<bot>...". Resolve prefix and cropped message (if possible).
|
||||
// 4. Test if bot has permission to send messages.
|
||||
// 5. Once confirmed as a command, resolve the subcommand.
|
||||
// 6. Check permission level and whether or not it's an endpoint.
|
||||
// 7. Execute command if all successful.
|
||||
|
||||
// For custom message events that want to cancel this one on certain conditions.
|
||||
// For custom message events that want to cancel the command handler on certain conditions.
|
||||
const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot];
|
||||
|
||||
export function addInterceptRule(handler: (message: Message) => boolean) {
|
||||
interceptRules.push(handler);
|
||||
}
|
||||
|
||||
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
|
||||
client.on("message", async (message) => {
|
||||
for (const shouldIntercept of interceptRules) {
|
||||
if (shouldIntercept(message)) {
|
||||
|
@ -30,139 +19,57 @@ client.on("message", async (message) => {
|
|||
}
|
||||
}
|
||||
|
||||
const commands = await loadableCommands;
|
||||
|
||||
let prefix = getPrefix(message.guild);
|
||||
const originalPrefix = prefix;
|
||||
let exitEarly = !message.content.startsWith(prefix);
|
||||
const clientUser = message.client.user;
|
||||
let usesBotSpecificPrefix = false;
|
||||
|
||||
// If the client user exists, check if it starts with the bot-specific prefix.
|
||||
if (clientUser) {
|
||||
// If the prefix starts with the bot-specific prefix, go off that instead (these two options must mutually exclude each other).
|
||||
// The pattern here has an optional space at the end to capture that and make it not mess with the header and args.
|
||||
const matches = message.content.match(new RegExp(`^<@!?${clientUser.id}> ?`));
|
||||
|
||||
if (matches) {
|
||||
prefix = matches[0];
|
||||
exitEarly = false;
|
||||
usesBotSpecificPrefix = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If it doesn't start with the current normal prefix or the bot-specific unique prefix, exit the thread of execution early.
|
||||
// Inline replies should still be captured here because if it doesn't exit early, two characters for a two-length prefix would still trigger commands.
|
||||
if (exitEarly) return;
|
||||
|
||||
const [header, ...args] = message.content.substring(prefix.length).split(/ +/);
|
||||
|
||||
// If the message is just the prefix itself, move onto this block.
|
||||
if (header === "" && args.length === 0) {
|
||||
// I moved the bot-specific prefix to a separate conditional block to separate the logic.
|
||||
// And because it listens for the mention as a prefix instead of a free-form mention, inline replies (probably) shouldn't ever trigger this unintentionally.
|
||||
if (usesBotSpecificPrefix) {
|
||||
message.channel.send(`${message.author.toString()}, my prefix on this guild is \`${originalPrefix}\`.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!commands.has(header)) return;
|
||||
|
||||
// Continue if the bot has permission to send messages in this channel.
|
||||
if (
|
||||
message.channel.type === "text" &&
|
||||
!message.channel.permissionsFor(message.client.user || "")?.has(Permissions.FLAGS.SEND_MESSAGES)
|
||||
message.channel.type === "dm" ||
|
||||
message.channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)
|
||||
) {
|
||||
let status;
|
||||
const text = message.content;
|
||||
const prefix = getPrefix(message.guild);
|
||||
|
||||
if (message.member?.hasPermission(Permissions.FLAGS.ADMINISTRATOR))
|
||||
status =
|
||||
"Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended.";
|
||||
else
|
||||
status =
|
||||
"Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong.";
|
||||
|
||||
return message.author.send(
|
||||
`I don't have permission to send messages in ${message.channel.toString()}. ${status}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`
|
||||
);
|
||||
|
||||
// Subcommand Recursion //
|
||||
let command = commands.get(header)!;
|
||||
//resolveSubcommand()
|
||||
|
||||
if (!message.member)
|
||||
return console.warn("This command was likely called from a DM channel meaning the member object is null.");
|
||||
|
||||
if (!hasPermission(message.member, permLevel)) {
|
||||
const userPermLevel = getPermissionLevel(message.member);
|
||||
return message.channel.send(
|
||||
`You don't have access to this command! Your permission level is \`${getPermissionName(
|
||||
userPermLevel
|
||||
)}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName(
|
||||
permLevel
|
||||
)}\` (${permLevel}).`
|
||||
);
|
||||
}
|
||||
|
||||
if (isEndpoint) return message.channel.send("Too many arguments!");
|
||||
|
||||
// Execute with dynamic library attached. //
|
||||
// The purpose of using $.bind($) is to clone the function so as to not modify the original $.
|
||||
// The cloned function doesn't copy the properties, so Object.assign() is used.
|
||||
// Object.assign() modifies the first element and returns that, the second element applies its properties and the third element applies its own overriding the second one.
|
||||
command.execute({
|
||||
args: params,
|
||||
author: message.author,
|
||||
channel: message.channel,
|
||||
client: message.client,
|
||||
guild: message.guild,
|
||||
member: message.member,
|
||||
message: message
|
||||
});
|
||||
});
|
||||
|
||||
// Takes a base command and a list of string parameters and returns:
|
||||
// - The resolved subcommand
|
||||
// - The resolved parameters
|
||||
// - Whether or not an endpoint has been broken
|
||||
// - The permission level required
|
||||
async function resolveSubcommand(command: Command, args: string[]): [Command, any[], boolean, number] {
|
||||
const params: any[] = [];
|
||||
let isEndpoint = false;
|
||||
let permLevel = command.permission ?? 0;
|
||||
|
||||
for (const param of args) {
|
||||
if (command.endpoint) {
|
||||
if (command.subcommands.size > 0 || command.user || command.number || command.any)
|
||||
console.warn("An endpoint cannot have subcommands!");
|
||||
isEndpoint = true;
|
||||
break;
|
||||
// First, test if the message is just a ping to the bot.
|
||||
if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) {
|
||||
message.channel.send(`${message.author}, my prefix on this guild is \`${prefix}\`.`);
|
||||
}
|
||||
// Then check if it's a normal command.
|
||||
else if (text.startsWith(prefix)) {
|
||||
const [header, ...args] = text.substring(prefix.length).split(/ +/);
|
||||
const commands = await loadableCommands;
|
||||
|
||||
const type = command.resolve(param);
|
||||
command = command.get(param);
|
||||
permLevel = command.permission ?? permLevel;
|
||||
if (commands.has(header)) {
|
||||
const command = commands.get(header)!;
|
||||
|
||||
if (type === Command.TYPES.USER) {
|
||||
const id = param.match(/\d+/g)![0];
|
||||
try {
|
||||
params.push(await message.client.users.fetch(id));
|
||||
} catch (error) {
|
||||
return message.channel.send(`No user found by the ID \`${id}\`!`);
|
||||
// Send the arguments to the command to resolve and execute.
|
||||
// TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED]
|
||||
const result = await command.actualExecute(args, {
|
||||
author: message.author,
|
||||
channel: message.channel,
|
||||
client: message.client,
|
||||
guild: message.guild,
|
||||
member: message.member,
|
||||
message: message
|
||||
});
|
||||
|
||||
// If something went wrong, let the user know (like if they don't have permission to use a command).
|
||||
if (result) {
|
||||
message.channel.send(result);
|
||||
}
|
||||
}
|
||||
} else if (type === Command.TYPES.NUMBER) params.push(Number(param));
|
||||
else if (type !== Command.TYPES.SUBCOMMAND) params.push(param);
|
||||
}
|
||||
} else {
|
||||
message.author.send(
|
||||
`I don't have permission to send messages in ${message.channel}. ${
|
||||
message.member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR)
|
||||
? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended."
|
||||
: "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong."
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.once("ready", () => {
|
||||
if (client.user) {
|
||||
console.ready(`Logged in as ${client.user.username}#${client.user.discriminator}.`);
|
||||
console.ready(`Logged in as ${client.user.tag}.`);
|
||||
client.user.setActivity({
|
||||
type: "LISTENING",
|
||||
name: `${Config.prefix}help`
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import {Collection} from "discord.js";
|
||||
import glob from "glob";
|
||||
import Command from "./command";
|
||||
|
||||
// Internally, it'll keep its original capitalization. It's up to you to convert it to title case when you make a help command.
|
||||
export const categories = new Collection<string, string[]>();
|
||||
|
||||
/** Returns the cache of the commands if it exists and searches the directory if not. */
|
||||
export const loadableCommands = (async () => {
|
||||
const commands = new Collection<string, Command>();
|
||||
// Include all .ts files recursively in "src/commands/".
|
||||
const files = await globP("src/commands/**/*.ts");
|
||||
// Extract the usable parts from "src/commands/" if:
|
||||
// - The path is 1 to 2 subdirectories (a or a/b, not a/b/c)
|
||||
// - Any leading directory isn't "modules"
|
||||
// - The filename doesn't end in .test.ts (for jest testing)
|
||||
// - The filename cannot be the hardcoded top-level "template.ts", reserved for generating templates
|
||||
const pattern = /src\/commands\/(?!template\.ts)(?!modules\/)(\w+(?:\/\w+)?)(?:test\.)?\.ts/;
|
||||
const lists: {[category: string]: string[]} = {};
|
||||
|
||||
for (const path of files) {
|
||||
const match = pattern.exec(path);
|
||||
|
||||
if (match) {
|
||||
const commandID = match[1]; // e.g. "utilities/info"
|
||||
const slashIndex = commandID.indexOf("/");
|
||||
const isMiscCommand = slashIndex !== -1;
|
||||
const category = isMiscCommand ? commandID.substring(0, slashIndex) : "miscellaneous";
|
||||
const commandName = isMiscCommand ? commandID.substring(slashIndex + 1) : commandID; // e.g. "info"
|
||||
// If the dynamic import works, it must be an object at the very least. Then, just test to see if it's a proper instance.
|
||||
const command = (await import(`../commands/${commandID}`)).default as unknown;
|
||||
|
||||
if (command instanceof Command) {
|
||||
command.originalCommandName = commandName;
|
||||
|
||||
if (commands.has(commandName)) {
|
||||
console.warn(
|
||||
`Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!`
|
||||
);
|
||||
} else {
|
||||
commands.set(commandName, command);
|
||||
}
|
||||
|
||||
for (const alias of command.aliases) {
|
||||
if (commands.has(alias)) {
|
||||
console.warn(
|
||||
`Top-level alias "${alias}" from command "${commandID}" already exists either as a command or alias!`
|
||||
);
|
||||
} else {
|
||||
commands.set(alias, command);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(category in lists)) lists[category] = [];
|
||||
lists[category].push(commandName);
|
||||
|
||||
console.log(`Loading Command: ${commandID}`);
|
||||
} else {
|
||||
console.warn(`Command "${commandID}" has no default export which is a Command instance!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const category in lists) {
|
||||
categories.set(category, lists[category]);
|
||||
}
|
||||
|
||||
return commands;
|
||||
})();
|
||||
|
||||
function globP(path: string) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
glob(path, (error, files) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(files);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Gathers a list of categories and top-level commands.
|
||||
// Returns: new Collection<string category, Command[] commandList>()
|
||||
/*export async function getCommandList(): Promise<Collection<string, Command[]>> {
|
||||
const categorizedCommands = new Collection<string, Command[]>();
|
||||
const commands = await loadableCommands;
|
||||
|
||||
for (const [category, headers] of categories) {
|
||||
const commandList: Command[] = [];
|
||||
|
||||
for (const header of headers) {
|
||||
if (header !== "test") {
|
||||
// If this is somehow undefined, it'll show up as an error when implementing a help command.
|
||||
commandList.push(commands.get(header)!);
|
||||
}
|
||||
}
|
||||
|
||||
categorizedCommands.set(toTitleCase(category), commandList);
|
||||
}
|
||||
|
||||
return categorizedCommands;
|
||||
}*/
|
Loading…
Reference in New Issue