mirror of
https://github.com/keanuplayz/TravBot-v3.git
synced 2024-08-15 02:33:12 +00:00
Implemented rough draft of info resolver method
This commit is contained in:
parent
2a4d08d0bc
commit
6ed4c0988f
4 changed files with 184 additions and 125 deletions
|
@ -1,9 +1,9 @@
|
||||||
import Command from "../../core/command";
|
import {Command, NamedCommand} from "../../core/command";
|
||||||
import {toTitleCase} from "../../core/lib";
|
import {toTitleCase} from "../../core/lib";
|
||||||
import {loadableCommands, categories} from "../../core/loader";
|
import {loadableCommands, categories} from "../../core/loader";
|
||||||
import {getPermissionName} from "../../core/permissions";
|
import {getPermissionName} from "../../core/permissions";
|
||||||
|
|
||||||
export default new Command({
|
export default new NamedCommand({
|
||||||
description: "Lists all commands. If a command is specified, their arguments are listed as well.",
|
description: "Lists all commands. If a command is specified, their arguments are listed as well.",
|
||||||
usage: "([command, [subcommand/type], ...])",
|
usage: "([command, [subcommand/type], ...])",
|
||||||
aliases: ["h"],
|
aliases: ["h"],
|
||||||
|
@ -16,13 +16,7 @@ export default new Command({
|
||||||
|
|
||||||
for (const header of headers) {
|
for (const header of headers) {
|
||||||
if (header !== "test") {
|
if (header !== "test") {
|
||||||
const command = commands.get(header);
|
const command = commands.get(header)!;
|
||||||
|
|
||||||
if (!command)
|
|
||||||
return console.warn(
|
|
||||||
`Command "${header}" of category "${category}" unexpectedly doesn't exist!`
|
|
||||||
);
|
|
||||||
|
|
||||||
output += `\n- \`${header}\`: ${command.description}`;
|
output += `\n- \`${header}\`: ${command.description}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,44 +26,63 @@ export default new Command({
|
||||||
},
|
},
|
||||||
any: new Command({
|
any: new Command({
|
||||||
async run($) {
|
async run($) {
|
||||||
// [category, commandName, command, subcommandInfo] = resolveCommandInfo();
|
// Setup the root command
|
||||||
|
const commands = await loadableCommands;
|
||||||
|
let header = $.args.shift() as string;
|
||||||
|
let command = commands.get(header);
|
||||||
|
if (!command || header === "test") return $.channel.send(`No command found by the name \`${header}\`.`);
|
||||||
|
if (!(command instanceof NamedCommand))
|
||||||
|
return $.channel.send(`Command is not a proper instance of NamedCommand.`);
|
||||||
|
if (command.name) header = command.name;
|
||||||
|
|
||||||
|
// Search categories
|
||||||
|
let category = "Unknown";
|
||||||
|
for (const [referenceCategory, headers] of categories) {
|
||||||
|
if (headers.includes(header)) {
|
||||||
|
category = toTitleCase(referenceCategory);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather info
|
||||||
|
const result = await command.resolveInfo($.args);
|
||||||
|
|
||||||
|
if (result.type === "error") return $.channel.send(result.message);
|
||||||
|
|
||||||
let append = "";
|
let append = "";
|
||||||
|
command = result.command;
|
||||||
|
|
||||||
if (usage === "") {
|
if (command.usage === "") {
|
||||||
const list: string[] = [];
|
const list: string[] = [];
|
||||||
|
|
||||||
command.subcommands.forEach((subcmd, subtag) => {
|
for (const [tag, subcommand] of result.keyedSubcommandInfo) {
|
||||||
// Don't capture duplicates generated from aliases.
|
const customUsage = subcommand.usage ? ` ${subcommand.usage}` : "";
|
||||||
if (subcmd.originalCommandName === subtag) {
|
list.push(`- \`${header} ${tag}${customUsage}\` - ${subcommand.description}`);
|
||||||
const customUsage = subcmd.usage ? ` ${subcmd.usage}` : "";
|
}
|
||||||
list.push(`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const addDynamicType = (cmd: Command | null, type: string) => {
|
for (const [type, subcommand] of result.subcommandInfo) {
|
||||||
if (cmd) {
|
const customUsage = subcommand.usage ? ` ${subcommand.usage}` : "";
|
||||||
const customUsage = cmd.usage ? ` ${cmd.usage}` : "";
|
list.push(`- \`${header} ${type}${customUsage}\` - ${subcommand.description}`);
|
||||||
list.push(`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`);
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
addDynamicType(command.user, "user");
|
|
||||||
addDynamicType(command.number, "number");
|
|
||||||
addDynamicType(command.any, "any");
|
|
||||||
|
|
||||||
append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None.");
|
append = "Usages:" + (list.length > 0 ? `\n${list.join("\n")}` : " None.");
|
||||||
} else append = `Usage: \`${header} ${usage}\``;
|
} else {
|
||||||
|
append = `Usage: \`${header} ${command.usage}\``;
|
||||||
|
}
|
||||||
|
|
||||||
const formattedAliases: string[] = [];
|
let aliases = "N/A";
|
||||||
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(
|
if (command instanceof NamedCommand) {
|
||||||
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${getPermissionName(
|
const formattedAliases: string[] = [];
|
||||||
permLevel
|
for (const alias of command.aliases) formattedAliases.push(`\`${alias}\``);
|
||||||
)}\` (${permLevel})\nDescription: ${command.description}\n${append}`,
|
// Short circuit an empty string, in this case, if there are no aliases.
|
||||||
|
aliases = formattedAliases.join(", ") || "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $.channel.send(
|
||||||
|
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName(
|
||||||
|
result.permission
|
||||||
|
)}\` (${result.permission})\nDescription: ${command.description}\n${append}`,
|
||||||
{split: true}
|
{split: true}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {parseVars, requireAllCasesHandledFor} from "./lib";
|
import {parseVars} from "./lib";
|
||||||
import {Collection} from "discord.js";
|
import {Collection} from "discord.js";
|
||||||
import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js";
|
import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js";
|
||||||
import {getPrefix} from "../core/structures";
|
import {getPrefix} from "../core/structures";
|
||||||
|
@ -82,6 +82,36 @@ interface ExecuteCommandMetadata {
|
||||||
channelType: CHANNEL_TYPE;
|
channelType: CHANNEL_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CommandInfo {
|
||||||
|
readonly type: "info";
|
||||||
|
readonly command: Command;
|
||||||
|
readonly subcommandInfo: Collection<string, Command>;
|
||||||
|
readonly keyedSubcommandInfo: Collection<string, NamedCommand>;
|
||||||
|
readonly permission: number;
|
||||||
|
readonly nsfw: boolean;
|
||||||
|
readonly channelType: CHANNEL_TYPE;
|
||||||
|
readonly args: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandInfoError {
|
||||||
|
readonly type: "error";
|
||||||
|
readonly message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandInfoMetadata {
|
||||||
|
permission: number;
|
||||||
|
nsfw: boolean;
|
||||||
|
channelType: CHANNEL_TYPE;
|
||||||
|
args: string[];
|
||||||
|
usage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultMetadata = {
|
||||||
|
permission: 0,
|
||||||
|
nsfw: false,
|
||||||
|
channelType: CHANNEL_TYPE.ANY
|
||||||
|
};
|
||||||
|
|
||||||
export class Command {
|
export class Command {
|
||||||
public readonly description: string;
|
public readonly description: string;
|
||||||
public readonly endpoint: boolean;
|
public readonly endpoint: boolean;
|
||||||
|
@ -90,7 +120,7 @@ export class Command {
|
||||||
public readonly nsfw: boolean | null; // null (default) indicates to inherit
|
public readonly nsfw: boolean | null; // null (default) indicates to inherit
|
||||||
public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit
|
public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit
|
||||||
protected run: (($: CommandMenu) => Promise<any>) | string;
|
protected run: (($: CommandMenu) => Promise<any>) | string;
|
||||||
protected readonly subcommands: Collection<string, Command>; // This is the final data structure you'll actually use to work with the commands the aliases point to.
|
protected readonly subcommands: Collection<string, NamedCommand>; // This is the final data structure you'll actually use to work with the commands the aliases point to.
|
||||||
protected user: Command | null;
|
protected user: Command | null;
|
||||||
protected number: Command | null;
|
protected number: Command | null;
|
||||||
protected any: Command | null;
|
protected any: Command | null;
|
||||||
|
@ -124,7 +154,7 @@ export class Command {
|
||||||
// This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
|
// This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
|
||||||
for (const name in options.subcommands) {
|
for (const name in options.subcommands) {
|
||||||
const subcmd = options.subcommands[name];
|
const subcmd = options.subcommands[name];
|
||||||
subcmd.originalCommandName = name;
|
subcmd.name = name;
|
||||||
const aliases = subcmd.aliases;
|
const aliases = subcmd.aliases;
|
||||||
|
|
||||||
for (const alias of aliases) {
|
for (const alias of aliases) {
|
||||||
|
@ -153,7 +183,7 @@ export class Command {
|
||||||
const param = args.shift();
|
const param = args.shift();
|
||||||
|
|
||||||
// If there are no arguments left, execute the current command. Otherwise, continue on.
|
// If there are no arguments left, execute the current command. Otherwise, continue on.
|
||||||
if (!param) {
|
if (param === undefined) {
|
||||||
// See if there is anything that'll prevent the user from executing the command.
|
// See if there is anything that'll prevent the user from executing the command.
|
||||||
|
|
||||||
// 1. Does this command specify a required channel type? If so, does the channel type match?
|
// 1. Does this command specify a required channel type? If so, does the channel type match?
|
||||||
|
@ -218,13 +248,9 @@ export class Command {
|
||||||
// If the current command is an endpoint but there are still some arguments left, don't continue.
|
// If the current command is an endpoint but there are still some arguments left, don't continue.
|
||||||
if (this.endpoint) return {content: "Too many arguments!"};
|
if (this.endpoint) return {content: "Too many arguments!"};
|
||||||
|
|
||||||
// If the current command's permission level isn't -1 (inherit), then set the permission metadata equal to that.
|
// Update inherited properties if the current command specifies a property.
|
||||||
if (this.permission !== -1) metadata.permission = this.permission;
|
if (this.permission !== -1) metadata.permission = this.permission;
|
||||||
|
|
||||||
// If the current command has an NSFW setting specified, set it.
|
|
||||||
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
||||||
|
|
||||||
// If the current command doesn't inherit its channel type, set it.
|
|
||||||
if (this.channelType !== null) metadata.channelType = this.channelType;
|
if (this.channelType !== null) metadata.channelType = this.channelType;
|
||||||
|
|
||||||
// Resolve the value of the current command's argument (adding it to the resolved args),
|
// Resolve the value of the current command's argument (adding it to the resolved args),
|
||||||
|
@ -257,11 +283,97 @@ export class Command {
|
||||||
// Note: Do NOT add a return statement here. In case one of the other sections is missing
|
// Note: Do NOT add a return statement here. In case one of the other sections is missing
|
||||||
// a return statement, there'll be a compile error to catch that.
|
// a return statement, there'll be a compile error to catch that.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands.
|
||||||
|
public async resolveInfo(args: string[]): Promise<CommandInfo | CommandInfoError> {
|
||||||
|
return this.resolveInfoInternal(args, {...defaultMetadata, args: [], usage: ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveInfoInternal(
|
||||||
|
args: string[],
|
||||||
|
metadata: CommandInfoMetadata
|
||||||
|
): Promise<CommandInfo | CommandInfoError> {
|
||||||
|
const param = args.shift();
|
||||||
|
|
||||||
|
// If there are no arguments left, return the data or an error message.
|
||||||
|
if (param === undefined) {
|
||||||
|
const keyedSubcommandInfo = new Collection<string, NamedCommand>();
|
||||||
|
const subcommandInfo = new Collection<string, Command>();
|
||||||
|
|
||||||
|
// Get all the subcommands of the current command but without aliases.
|
||||||
|
for (const [tag, command] of this.subcommands.entries()) {
|
||||||
|
// Don't capture duplicates generated from aliases.
|
||||||
|
if (tag === command.name) {
|
||||||
|
keyedSubcommandInfo.set(tag, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then get all the generic subcommands.
|
||||||
|
if (this.user) subcommandInfo.set("<user>", this.user);
|
||||||
|
if (this.number) subcommandInfo.set("<number>", this.number);
|
||||||
|
if (this.any) subcommandInfo.set("<any>", this.any);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "info",
|
||||||
|
command: this,
|
||||||
|
keyedSubcommandInfo,
|
||||||
|
subcommandInfo,
|
||||||
|
...metadata
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update inherited properties if the current command specifies a property.
|
||||||
|
if (this.permission !== -1) metadata.permission = this.permission;
|
||||||
|
if (this.nsfw !== null) metadata.nsfw = this.nsfw;
|
||||||
|
if (this.channelType !== null) metadata.channelType = this.channelType;
|
||||||
|
if (this.usage !== "") metadata.usage = this.usage;
|
||||||
|
|
||||||
|
// Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand.
|
||||||
|
if (param === "<user>") {
|
||||||
|
if (this.user) {
|
||||||
|
metadata.args.push("<user>");
|
||||||
|
return this.user.resolveInfoInternal(args, metadata);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\``
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (param === "<number>") {
|
||||||
|
if (this.number) {
|
||||||
|
metadata.args.push("<number>");
|
||||||
|
return this.number.resolveInfoInternal(args, metadata);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\``
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (param === "<any>") {
|
||||||
|
if (this.any) {
|
||||||
|
metadata.args.push("<any>");
|
||||||
|
return this.any.resolveInfoInternal(args, metadata);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\``
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (this.subcommands?.has(param)) {
|
||||||
|
metadata.args.push(param);
|
||||||
|
return this.subcommands.get(param)!.resolveInfoInternal(args, metadata);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: "error",
|
||||||
|
message: `No subcommand found by the argument list: \`${metadata.args.join(" ")}\``
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NamedCommand extends Command {
|
export class NamedCommand extends Command {
|
||||||
public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases.
|
public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases.
|
||||||
public originalCommandName: string | null; // If the command is an alias, what's the original name?
|
private originalCommandName: string | null; // If the command is an alias, what's the original name?
|
||||||
|
|
||||||
constructor(options?: NamedCommandOptions) {
|
constructor(options?: NamedCommandOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -269,74 +381,14 @@ export class NamedCommand extends Command {
|
||||||
this.originalCommandName = null;
|
this.originalCommandName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns: [category, command name, command, available subcommands: [type, subcommand]]
|
public get name(): string {
|
||||||
public async resolveInfo(args: string[]): [string, string, Command, Collection<string, Command>] | null {
|
if (this.originalCommandName === null) throw new Error("originalCommandName must be set before accessing it!");
|
||||||
// For debug info, use this.originalCommandName? (if it exists?)
|
else return this.originalCommandName;
|
||||||
const commands = await loadableCommands;
|
}
|
||||||
let header = args.shift();
|
|
||||||
let command = commands.get(header);
|
|
||||||
|
|
||||||
if (!command || header === "test") {
|
public set name(value: string) {
|
||||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
if (this.originalCommandName !== null)
|
||||||
return;
|
throw new Error(`originalCommandName cannot be set twice! Attempted to set the value to "${value}".`);
|
||||||
}
|
else this.originalCommandName = value;
|
||||||
|
|
||||||
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 over to doing `$help info <user>`
|
|
||||||
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;
|
|
||||||
case TYPES.NONE:
|
|
||||||
header += ` ${param}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
requireAllCasesHandledFor(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === TYPES.NONE) {
|
|
||||||
invalid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invalid) {
|
|
||||||
$.channel.send(`No command found by the name \`${header}\`!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {loadableCommands} from "./loader";
|
||||||
import {Permissions, Message} from "discord.js";
|
import {Permissions, Message} from "discord.js";
|
||||||
import {getPrefix} from "./structures";
|
import {getPrefix} from "./structures";
|
||||||
import {Config} from "./structures";
|
import {Config} from "./structures";
|
||||||
import {CHANNEL_TYPE} from "./command";
|
import {defaultMetadata} from "./command";
|
||||||
|
|
||||||
// For custom message events that want to cancel the command handler 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];
|
const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot];
|
||||||
|
@ -12,12 +12,6 @@ export function addInterceptRule(handler: (message: Message) => boolean) {
|
||||||
interceptRules.push(handler);
|
interceptRules.push(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMetadata = {
|
|
||||||
permission: 0,
|
|
||||||
nsfw: false,
|
|
||||||
channelType: CHANNEL_TYPE.ANY
|
|
||||||
};
|
|
||||||
|
|
||||||
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
|
// Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
|
||||||
// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
|
// Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
|
||||||
client.on("message", async (message) => {
|
client.on("message", async (message) => {
|
||||||
|
@ -51,7 +45,7 @@ client.on("message", async (message) => {
|
||||||
// Send the arguments to the command to resolve and execute.
|
// Send the arguments to the command to resolve and execute.
|
||||||
const result = await command.execute(args, menu, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
args,
|
args: [...args],
|
||||||
...defaultMetadata
|
...defaultMetadata
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,7 +77,7 @@ client.on("message", async (message) => {
|
||||||
// Send the arguments to the command to resolve and execute.
|
// Send the arguments to the command to resolve and execute.
|
||||||
const result = await command.execute(args, menu, {
|
const result = await command.execute(args, menu, {
|
||||||
header,
|
header,
|
||||||
args,
|
args: [...args],
|
||||||
...defaultMetadata
|
...defaultMetadata
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const loadableCommands = (async () => {
|
||||||
const command = (await import(`../commands/${commandID}`)).default as unknown;
|
const command = (await import(`../commands/${commandID}`)).default as unknown;
|
||||||
|
|
||||||
if (command instanceof NamedCommand) {
|
if (command instanceof NamedCommand) {
|
||||||
command.originalCommandName = commandName;
|
command.name = commandName;
|
||||||
|
|
||||||
if (commands.has(commandName)) {
|
if (commands.has(commandName)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|
Loading…
Reference in a new issue