'use strict';
const { Collection } = require("discord.js");
const { SlashCommandBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder } = require("@discordjs/builders");
const { getLocalisation: getText, getCommandLocalisations, getCommandOptionLocalisations } = require("./LanguageHandler")
const { isAbsolute, normalize, resolve, extname, dirname, basename } = require("path");
const { existsSync, statSync, readdirSync } = require("fs");
const { debug, warn, error, info } = require("./LoggingHandler");
const { CommandTypes, CommandRoles, CommandOptionTypes } = require("../types");
const { v4: uuidv4 } = require("uuid");
const { arrayToConsoleList } = require("../util");
/**
* A class that handles loading, registering and running slash commands.
*/
class CommandHandler {
/**
* Collection containing all commands.
* @type {Collection<String, import("../types").Command>}
* @static
*/
static commands = new Collection();
/**
* Filepaths to folders containing commands.
* @type {String[]}
* @static
* @private
*/
static #directories = [];
/**
* The Discord client the CommandHandler works with.
* @type {import("discord.js").Client}
* @static
* @private
*/
static #client;
/**
* Attach a client to the CommandHandler.
* @param {import("discord.js").Client} client The Discord client to attach.
* @returns {CommandHandler} A reference to the CommandHandler class.
* @static
*/
static attachClient(client) {
CommandHandler.#client = client;
return CommandHandler;
}
/**
* Add a directory to scan for commands.
* @param {String} dir The absolute path of the directory to add.
* @returns {CommandHandler} A reference to the CommandHandler class.
* @static
* @example
* // Adds the directory "commands" to the command handler.
*
* const { join } = require("path");
*
* CommandHandler.addCommandDirectory(join(__dirname, "commands"));
*/
static addCommandDirectory(dir) {
// if no client attached
if (!CommandHandler.#client) throw new Error(getText(undefined, ["generic", "errors", "noClient"]));
// return if no dir to add
if (!dir) return CommandHandler;
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "addDirAttempt"], dir));
// check path is absolute
if (!isAbsolute(dir)) throw new Error(getText(CommandHandler.#client.consoleLang, ["generic", "errors", "path", "notAbsolute"], dir));
// check path exists
if (!existsSync(dir)) throw new Error(getText(CommandHandler.#client.consoleLang, ["generic", "errors", "path", "doesNotExist"], dir));
// check path is a directory
if (!statSync(dir).isDirectory()) throw new Error(getText(CommandHandler.#client.consoleLang, ["generic", "errors", "directory", "invalid"], dir));
// add path to directories
CommandHandler.#directories.push(normalize(dir));
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "addedDir"], dir));
// return reference to CommandHandler
return CommandHandler;
}
/**
* Remove a directory to scan for commands.
* @param {String} dir The absolute path of the directory to remove.
* @returns {CommandHandler} A reference to the CommandHandler class.
* @static
* @example
* // Removes the directory "commands" from the command handler.
*
* const { join } = require("path");
*
* CommandHandler.removeCommandDirectory(join(__dirname, "commands"));
*/
static removeCommandDirectory(dir) {
// if no client attached
if (!CommandHandler.#client) throw new Error(getText(undefined, ["generic", "errors", "noClient"]));
// return if no dir to remove
if (!dir) return CommandHandler;
// return if dir not contained
if (!this.#directories.includes(normalize(dir))) return CommandHandler;
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "removeDirAttempt"], dir));
// else remove the dir
CommandHandler.#directories.splice(CommandHandler.#directories.indexOf(normalize(dir)), 1);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "removedDir"], dir));
// return reference to CommandHandler
return CommandHandler;
}
/**
* Load all commands.
* @returns {CommandHandler} A reference to the CommandHandler class.
* @static
* @example
* // Load all commands in the directory "commands"
*
* const { join } = require("path");
*
* CommandHandler.addCommandDirectory(join(__dirname, "commands"));
* CommandHandler.loadCommands();
*/
static loadCommands() {
// if no client attached
if (!CommandHandler.#client) throw new Error(getText(undefined, ["generic", "errors", "noClient"]));
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadingCommands"]));
// reset commands
CommandHandler.commands = new Collection();
// test for directories that contain each other
let overlapTest = CommandHandler.#directories.map(x => normalize(x));
if (overlapTest.some((x, i, a) => a.some(y => y != x && y.includes(x)))) warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "overlappingDirectories"]));
// loop through directories
for (const directory of CommandHandler.#directories) {
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadingFromDirectory"], directory));
// get folders within the directory
let types = readdirSync(directory);
// filter the folders to find the types of command folders
types = types.filter(x => statSync(resolve(directory, x)).isDirectory()).map(x => x.toUpperCase()).filter(x => CommandTypes[x] !== undefined).map(x => CommandTypes[x]);
// skip if none found
if (!types.length) {
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "noCommandsFound"], directory));
continue;
}
// fancy message with info
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", types.includes(CommandTypes.GLOBAL) ? types.includes(CommandTypes.GUILD) ? "loadingFolderGlobalAndGuild" : "loadingFolderGlobal" : "loadingFolderGuild"]));
// RIGHT THEN IT'S GO TIMEEEEEEEEEEEEE
let toRegister = [];
if (types.includes(CommandTypes.GLOBAL)) {
// global commands dir
let toSearch = resolve(directory, "global");
// top level - solo commands and headers for subcommands
let topLevelCommands = readdirSync(toSearch).map(x => resolve(toSearch, x)).filter(x => !statSync(x).isDirectory());
// filter out non js files
topLevelCommands = topLevelCommands.filter(x => extname(x) == ".js");
// for each top level command
for (const commandPath of topLevelCommands) {
try {
// delete cached files
delete require.cache[require.resolve(commandPath)];
/** @type {import("../types").Command} */
const command = require(commandPath);
// check for a command name
if (!command.name) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noName"], commandPath));
continue;
}
// set the command's role (for subcommand structure)
if (!command.execute) command.role = CommandRoles.CONTAINER;
else command.role = CommandRoles.COMMAND;
// set description
if (!command.description) command.description = "No description set.";
// perms stuff
if (!command.botPerms) command.botPerms = [];
if (!command.userPerms) command.userPerms = [];
// check for options
if (!command.options) command.options = [];
// file path
command.filePath = commandPath;
// unique id (for structure calculation later)
command.id = uuidv4();
// add to commands to register
toRegister.push(command);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadedTopLevelCommand"], command.name));
} catch (e) {
error(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "error", "cannotLoad"], commandPath, e.stack));
}
}
}
// guild specific commands
if (types.includes(CommandTypes.GUILD)) {
// directory to search
let toSearch = resolve(directory, "guild");
// each guild id (folder name)
let guilds = readdirSync(toSearch);
// for each guild id
for (const g of guilds) {
// search for specific guild
let toSearch = resolve(directory, "guild", g);
// top level - solo commands and headers for subcommands
let topLevelCommands = readdirSync(toSearch).map(x => resolve(toSearch, x)).filter(x => !statSync(x).isDirectory());
// filter out non js files
topLevelCommands = topLevelCommands.filter(x => extname(x) == ".js");
// for each command path
for (const commandPath of topLevelCommands) {
try {
// delete cached files
delete require.cache[require.resolve(commandPath)];
/** @type {import("../types").Command} */
const command = require(commandPath);
// check for a command name
if (!command.name) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noName"], commandPath));
continue;
}
// set the command's role (for subcommand structure)
if (!command.execute) command.role = CommandRoles.CONTAINER;
else command.role = CommandRoles.COMMAND;
// set description
if (!command.description) command.description = "No description set.";
// perms stuff
if (!command.botPerms) command.botPerms = [];
if (!command.userPerms) command.userPerms = [];
// check for options
if (!command.options) command.options = [];
// guild the command belongs to
command.guild = g;
// file path
command.filePath = commandPath;
// unique id (for structure calculation later)
command.id = uuidv4();
// add to commands to register
toRegister.push(command);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadedTopLevelCommandGuild"], command.name, command.guild));
} catch (e) {
error(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "error", "cannotLoad"], commandPath, e.stack));
}
}
}
}
// subcommand finding time woo
let commandsToSearch = [...toRegister];
for (const command of commandsToSearch) {
// check for subcommand folder
let subcommandDir = resolve(dirname(command.filePath), command.name);
// if no subcommand folder
if (!existsSync(subcommandDir)) {
// but there's supposed to be one
if (command.role == CommandRoles.CONTAINER) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noExecute"], command.name));
toRegister = toRegister.filter(x => x.id != command.id);
}
continue;
}
// if the subcommand folder has an identity crisis
if (!statSync(subcommandDir).isDirectory()) {
// but it's not meant to
if (command.role == CommandRoles.CONTAINER) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "folderIdentityCrisis"], subcommandDir, command.name));
toRegister = toRegister.filter(x => x.id != command.id);
}
continue;
}
// ok so there is actually a subcommand folder, find subcommands
let subcommandFilePaths = readdirSync(subcommandDir).map(x => resolve(subcommandDir, x)).filter(x => !statSync(x).isDirectory()).filter(x => extname(x) == ".js");
if (!subcommandFilePaths.length && command.role == CommandRoles.CONTAINER) {
// why did i bother to find the folder when there weren't any subcommands anyway
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noSubcommands"], command.name));
toRegister = toRegister.filter(x => x.id != command.id);
continue;
}
// subcommand groups are a thing but no one really cares about them
if (subcommandFilePaths.some(x => /^group\./g.test(basename(x)))) {
// oh ok nvm someone cares about them
let groupDefininitionPaths = subcommandFilePaths.filter(x => /^group\./g.test(basename(x)));
// for each definition
for (const groupPath of groupDefininitionPaths) {
try {
// delete cached files
delete require.cache[require.resolve(groupPath)];
// import the group thing
/** @type {import("../types").Command} */
const groupDefinition = require(groupPath);
// ignore if no name
if (!groupDefinition.name) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noName"], groupPath));
continue;
}
// check for description
if (!groupDefinition.description) groupDefinition.description = "No description set.";
// link file path
groupDefinition.filePath = groupPath;
// assign a unique id
groupDefinition.id = uuidv4();
// set the role
groupDefinition.role = CommandRoles.SUBCOMMAND_CONTAINER;
// parent-child stuff
groupDefinition.parent = command.id;
if (!command.chilren) command.children = [];
command.children.push(groupDefinition.id);
// add to register list
toRegister.push(groupDefinition);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadedGroupDefinition"], command.name, groupDefinition.name));
} catch (e) {
error(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "error", "cannotLoad"], groupPath, e.stack));
}
}
}
// remove any subcommand group definitions from the list of subcommands
subcommandFilePaths = subcommandFilePaths.filter(x => !/^group\./g.test(basename(x)));
// test for paths with the 3 dot thing grouped subcommands need
if (subcommandFilePaths.some(x => /(?<!^group)\.[^.]+\.[^.]+/g.test(basename(x)))) {
let groupedSubcommandFilePaths = subcommandFilePaths.filter(x => /(?<!^group)\.[^.]+\.[^.]+/g.test(basename(x)));
for (const groupedSubcommandPath of groupedSubcommandFilePaths) {
// get subcommand group name
let group = basename(groupedSubcommandPath).split(".")[0];
// ensure the group exists
if (!toRegister.find(x => x.role == CommandRoles.SUBCOMMAND_CONTAINER && x.name == group)) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "cannotFindGroup"], groupedSubcommandPath, group));
continue;
}
// right then actually load the thing
try {
// delete cached files
delete require.cache[require.resolve(groupedSubcommandPath)];
// importtttttttttttt
/** @type {import("../types").Command} */
const groupedSubcommand = require(groupedSubcommandPath);
// check for name
if (!groupedSubcommand.name) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noName"], groupedSubcommandPath));
continue;
}
// set the command's role (for subcommand structure)
groupedSubcommand.role = CommandRoles.SUBCOMMAND;
// set description
if (!groupedSubcommand.description) groupedSubcommand.description = "No description set.";
// perms stuff
if (!groupedSubcommand.botPerms) groupedSubcommand.botPerms = [];
if (!groupedSubcommand.userPerms) groupedSubcommand.userPerms = [];
// check for options
if (!groupedSubcommand.options) groupedSubcommand.options = [];
// file path
groupedSubcommand.filePath = groupedSubcommandPath;
// unique id (for structure calculation later)
groupedSubcommand.id = uuidv4();
// parent-child stuff
/** @type {import("../types").Command} */
let parent = toRegister.find(x => x.role == CommandRoles.SUBCOMMAND_CONTAINER && x.name == group);
groupedSubcommand.parent = parent.id;
if (!parent.children) parent.children = [];
parent.children.push(groupedSubcommand.id);
// add to register list
toRegister.push(groupedSubcommand);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadedGroupedCommand"], command.name, parent.name, groupedSubcommand.name));
} catch (e) {
error(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "error", "cannotLoad"], groupedSubcommandPath, e.stack));
}
}
}
// remove grouped commands from the list of subcommands
subcommandFilePaths = subcommandFilePaths.filter(x => !/(?<!^group)\.[^.]+\.[^.]+/g.test(basename(x)));
// anyway load the subcommands
for (const subcommandPath of subcommandFilePaths) {
try {
// delete cached files
delete require.cache[require.resolve(subcommandPath)];
/** @type {import("../types").Command} */
const subcommand = require(subcommandPath);
// check for a command name
if (!subcommand.name) {
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "noName"], subcommandPath));
continue;
}
// set the command's role (for subcommand structure)
subcommand.role = CommandRoles.SUBCOMMAND;
// set description
if (!subcommand.description) subcommand.description = "No description set.";
// perms stuff
if (!subcommand.botPerms) subcommand.botPerms = [];
if (!subcommand.userPerms) subcommand.userPerms = [];
// check for options
if (!subcommand.options) subcommand.options = [];
// file path
subcommand.filePath = subcommandPath;
// unique id (for structure calculation later)
subcommand.id = uuidv4();
// parent-child stuff for structure
subcommand.parent = command.id;
if (!command.children) command.children = [];
command.children.push(subcommand.id);
// add to commands to register
toRegister.push(subcommand);
debug(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "debug", "loadedSubcommand"], command.name, subcommand.name));
} catch (e) {
error(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "error", "cannotLoad"], subcommandPath, e.stack));
}
}
}
// now actually register them
toRegister.forEach(x => CommandHandler.commands.set(x.id, x));
}
info(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "info", "loadedCommands"]));
return CommandHandler;
}
/**
* Get a global command from a path.
* @param {...String} path The path of the command.
* @returns {import("../types").Command} The command.
*/
static getCommand(...path) {
// return nothing if no path
if (!path.length) return;
let commandsToSearch = CommandHandler.commands.filter(x => !x.guild);
// find top level command
/** @type {import("../types").Command} */
let firstLevel = commandsToSearch.find(x => x.name == path[0] && (x.role == CommandRoles.COMMAND || x.role == CommandRoles.CONTAINER));
// return nothing if not found
if (!firstLevel) return;
// return if nothing else to search for
if (!path[1]) return firstLevel;
// else carry on searching
/** @type {import("../types").Command} */
let secondLevel = CommandHandler.commands.find(x => x.name == path[1] && x.parent == firstLevel.id);
// return nothing if not found
if (!secondLevel) return;
// return if nothing else to search for
if (!path[2]) return secondLevel;
// else carry on searching
/** @type {import("../types").Command} */
let thirdLevel = CommandHandler.commands.find(x => x.name == path[2] && x.parent == secondLevel.id);
return thirdLevel;
}
/**
* Get a guild specific command from a path.
* @param {string} guildID The guild's ID.
* @param {...any} path The path of the command.
* @returns {import("../types").Command} The command.
*/
static getGuildCommand(guildID, ...path) {
// return nothing if no path
if (!path.length) return;
let topLevelCommandsToSearch = CommandHandler.commands.filter(x => x.guild == guildID);
// find top level command
/** @type {import("../types").Command} */
let firstLevel = topLevelCommandsToSearch.find(x => x.name == path[0] && (x.role == CommandRoles.COMMAND || x.role == CommandRoles.CONTAINER));
// return nothing if not found
if (!firstLevel) return;
// return if nothing else to search for
if (!path[1]) return firstLevel;
// else carry on searching
/** @type {import("../types").Command} */
let secondLevel = CommandHandler.commands.find(x => x.name == path[1] && x.parent == firstLevel.id);
// return nothing if not found
if (!secondLevel) return;
// return if nothing else to search for
if (!path[2]) return secondLevel;
// else carry on searching
/** @type {import("../types").Command} */
let thirdLevel = CommandHandler.commands.find(x => x.name == path[2] && x.parent == secondLevel.id);
return thirdLevel;
}
/**
* Generate the command structure in order to register commands with Discord.
* @param {import("discord.js").Snowflake=} guildID The ID of the guild to generate a structure for.
* @returns {import("discord-api-types/v10").RESTPostAPIApplicationCommandsJSONBody} The structure.
* @static
*/
static generateStructure(guildID) {
// if no client attached
if (!CommandHandler.#client) throw new Error(getText(undefined, ["generic", "errors", "noClient"]));
// get commands to generate structure for
let commandsToUse = guildID ? CommandHandler.commands.filter(x => x.guild == guildID) : CommandHandler.commands.filter(x => !x.guild);
// return if no commands to generate structure for
if (!commandsToUse.size) return [];
// output array
let structure = [];
// get top level commands
let topLevel = commandsToUse.filter(x => x.role == CommandRoles.COMMAND || x.role == CommandRoles.CONTAINER);
// generate each command
topLevel.forEach(command => {
// set basic command info
let toPush = new SlashCommandBuilder()
.setName(command.name)
.setDescription(command.description)
.setNameLocalizations(getCommandLocalisations(command).name)
.setDescriptionLocalizations(getCommandLocalisations(command).description);
// set options
if (command.options?.length) {
// for each option
command.options.forEach(option => {
// determine the option type + the function to use
switch (option.type) {
case CommandOptionTypes.STRING:
case CommandOptionTypes.INTEGER:
case CommandOptionTypes.NUMBER:
toPush[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => {
x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(command)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(command)[option.name]?.description || {});
if (option.choices?.length) x.setChoices(...option.choices);
return x;
}
);
break;
default:
toPush[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(command)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(command)[option.name]?.description || {})
);
}
});
}
// deal with children
if (command.children?.length) {
for (const childID of command.children) {
// find the child object
let child = CommandHandler.commands.find(x => x.id == childID);
// ignore if there is no child
if (!child) continue;
// else determine type
switch (child.role) {
case CommandRoles.SUBCOMMAND: // (normal subcommand)
// set basic command info
let subcommand = new SlashCommandSubcommandBuilder()
.setName(child.name)
.setDescription(child.description)
.setNameLocalizations(getCommandLocalisations(child).name)
.setDescriptionLocalizations(getCommandLocalisations(child).description);
// set options
if (child.options?.length) {
// for each option
child.options.forEach(option => {
// determine the option type + the function to use
switch (option.type) {
case CommandOptionTypes.STRING:
case CommandOptionTypes.INTEGER:
case CommandOptionTypes.NUMBER:
subcommand[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => {
x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(child)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(child)[option.name]?.description || {});
if (option.choices?.length) x.setChoices(...option.choices);
return x;
}
);
break;
default:
subcommand[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(child)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(child)[option.name]?.description || {})
);
}
});
}
// add the subcommand to the command
toPush.addSubcommand(subcommand);
break;
case CommandRoles.SUBCOMMAND_CONTAINER: // (subcommand group def)
// set basic info
let subcommandGroup = new SlashCommandSubcommandGroupBuilder()
.setName(child.name)
.setDescription(child.description)
.setNameLocalizations(getCommandLocalisations(child).name)
.setDescriptionLocalizations(getCommandLocalisations(child).description);
// ignore if empty group
if (!child.children?.length) continue;
for (const childChildID of child.children) {
// get the child child
let childChild = CommandHandler.commands.find(x => x.id == childChildID);
// ignore if child child does not exist
if (!childChild) continue;
// set basic command info
let subcommand = new SlashCommandSubcommandBuilder()
.setName(childChild.name)
.setDescription(childChild.description)
.setNameLocalizations(getCommandLocalisations(childChild).name)
.setDescriptionLocalizations(getCommandLocalisations(childChild).description);
// set options
if (childChild.options?.length) {
// for each option
childChild.options.forEach(option => {
// determine the option type + the function to use
switch (option.type) {
case CommandOptionTypes.STRING:
case CommandOptionTypes.INTEGER:
case CommandOptionTypes.NUMBER:
subcommand[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => {
x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(childChild)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(childChild)[option.name]?.description || {});
if (option.choices?.length) x.setChoices(...option.choices);
return x;
}
);
break;
default:
subcommand[`add${Object.keys(CommandOptionTypes)[option.type].charAt(0) + Object.keys(CommandOptionTypes)[option.type].slice(1).toLowerCase()}Option`](
// actually add the option
x => x.setName(option.name)
.setDescription(option.description)
.setRequired(option.required || false)
.setNameLocalizations(getCommandOptionLocalisations(childChild)[option.name]?.name || {})
.setDescriptionLocalizations(getCommandOptionLocalisations(childChild)[option.name]?.description || {})
);
}
});
}
// add to subcommand group
subcommandGroup.addSubcommand(subcommand);
}
// add subcommand group to command
toPush.addSubcommandGroup(subcommandGroup);
break;
default:
warn(getText(CommandHandler.#client.consoleLang, ["handlers", "command", "warn", "invalidRole"], command.name, child.name, child.role));
continue;
}
}
}
// push command to structure
structure.push(toPush);
});
// convert structure to JSON
structure = structure.map(x => x.toJSON());
return structure;
}
}
module.exports = CommandHandler;