Built-in Help Command
This is the built-in help command, this will generate an embed with Buttons, and the buttons are named based of your category folder, from your command folder.
Info
Max category this can handle is 5 categories due to MessageActionRow limit, therefore if you make more than 5 categories in your command folder this command will not work!
If you want to modify this built-in file you can disable it in DiscordFeaturesHandlerOptions and create the following help command file and will need to include your own data property if you want it to be a slash command.
/**
* Display all commands based off the user's permission level defined in config.js
*/
const {
ActionRowBuilder,
ButtonBuilder,
EmbedBuilder,
ButtonStyle,
} = require("discord.js");
const customIds = [
"dfh_help_1",
"dfh_help_2",
"dfh_help_3",
"dfh_help_4",
"dfh_help_5",
];
module.exports = {
name: "help",
description: "List all of my commands or info about a specific command.",
aliases: ["commands"],
customIds,
maxArgs: 1,
usage: "<command name>",
async executePrefix(message, args, client) {
// using the built-in functions and get the permission level of user
const level = client.getPermissionsLevel({
author: message.author,
channel: message.channel,
guild: message.guild,
guildMember: message.member,
});
// filter the commands saved in new collection object
const commands = await client.commands.filter(
(cmd) => cmd.permissions <= level
);
const data = getSortedCommandArray(client, commands);
if (!args.length) {
// get embed with data and categorized all the commands displayed
const embed = getInitialEmbed(data, client);
//get rows of buttons based of cmds categories
const row = getButtonRows(data);
//send initial message and await
message.reply({
embeds: [embed],
components: [row],
});
} else {
//display the command info requested from user's call
const name = args[0].toLowerCase();
const response = await getSingleCmd(commands, name, client);
return message.reply(response).catch((error) => console.log(error));
}
},
async execute(interaction, client, level) {
await interaction.deferReply({
ephemeral: true,
});
const { options } = interaction;
const name = options.getString("cmd_name");
const commands = await client.commands.filter(
(cmd) => cmd.permissions <= level
);
const data = getSortedCommandArray(client, commands);
if (!name) {
const embed = getInitialEmbed(data, client);
const row = getButtonRows(data);
await interaction.editReply({
embeds: [embed],
components: [row],
});
} else {
//display the command info requested from user's call
const response = await getSingleCmd(commands, name, client);
return await interaction
.editReply(response)
.catch((error) => console.log(error));
}
},
async customIdInteraction(interaction, client, level) {
const commands = await client.commands.filter(
(cmd) => cmd.permissions <= level
);
const data = getSortedCommandArray(client, commands);
const customId = interaction.customId;
try {
if (customIds.includes(customId)) {
const newEmbed = getUpdateEmbed(data, customId, client);
await interaction.update({ embeds: [newEmbed] });
} else {
console.log("Invalid category: " + customId, data);
await interaction.reply({ content: "Invalid category: " + customId });
}
} catch (error) {
console.error("Error handling customIdInteraction:", error);
await interaction
.reply({
content: "An error occurred while processing your request.",
ephemeral: true,
})
.catch(() => {});
}
},
};
/**
*
* @param {Client} client discord client object
* @param {Array<string>} commands all commands based off user's permission lvl
* formatted array of all commands categorized based off sub folder names
* @returns
*/
const getSortedCommandArray = (client, commands) => {
const dataArray = [];
const prefix = Array.isArray(client.config.prefix)
? client.config.prefix[0]
: client.config.prefix;
const commandNames = commands.map((cmd) => cmd.name);
const longestName = commandNames.reduce(function (a, b) {
return a.length > b.length ? a : b;
});
const sorted = commands.sort((p, c) =>
p.category > c.category
? 1
: p.name > c.name && p.category === c.category
? 1
: -1
);
let category = "";
let index = -1;
sorted.map((command) => {
let temp = {
category: "",
customId:
dataArray.length < 5
? customIds[dataArray.length]
: `dfh_help_${dataArray.length}`,
commands: [],
};
if (!category || category != command.category) {
category = command.category;
temp = { ...temp, category };
dataArray.push(temp);
}
index = dataArray.findIndex((element) => element.category === category);
temp = dataArray[index];
temp.commands.push({
name: `${prefix}${command.name}`,
description: `${command.description}`,
});
dataArray[index] = temp;
});
return dataArray;
};
/**
*
* @param {Array<string>} data the data to display on the embed
* @param {Client} client Discord client object
* @returns EmbedBuilder to display
*/
const getInitialEmbed = (data, client) => {
const categories = data.map((cat) => `**${cat.category}**`).join(`\n`);
const defaultEmbed = new EmbedBuilder().setTitle("Help Menu").setAuthor({
name: `${client.user.username} Help Menu`,
iconURL: `${client.user.avatarURL()}`,
}).setDescription(`There are ${data.length} categories!\n${categories}
Click the respective buttons to see the commands of the category. `);
return defaultEmbed;
};
/**
*
* @param {Array<string>} data the data to display on the embed
* @param {Number} customId index of category to show
* @param {Client} client Discord client object
* @returns EmbedBuilder to display
*/
const getUpdateEmbed = (data, customId, client) => {
const index = data.findIndex((d) => d.customId === customId);
const cmds = data[index].commands
.map((cmd) => {
let cmdName = cmd.name
.replace(
Array.isArray(client.config.prefix)
? client.config.prefix[0]
: client.config.prefix,
""
)
.toProperCase();
return `**${cmdName}**\n${cmd.description}\n`;
})
.join("\n");
return new EmbedBuilder()
.setAuthor({
name: `${client.user.username} Help Menu`,
iconURL: `${client.user.avatarURL()}`,
})
.setTitle(`${data[index].category} Category`)
.setDescription(cmds)
.setFields({
name: `To see a more details about a specific command type following and replace "name" with the command name:`,
value: `/help name or ${
Array.isArray(client.config.prefix)
? client.config.prefix[0]
: client.config.prefix
}help name`,
});
};
/**
*
* @param {Array<string>} data of all commands
* @param {Boolean} disabled the button once timer expires
* @returns different colors variations for the component
*/
const getButtonRows = (data, disabled = false) => {
const colorForCategory = [
{
name: "admin",
color: ButtonStyle.Secondary,
},
{
name: "commands",
color: ButtonStyle.Primary,
},
{
name: "miscellaneous",
color: ButtonStyle.Success,
},
{
name: "system",
color: ButtonStyle.Secondary,
},
];
const defaultColor = ButtonStyle.Primary;
const btnArray = data.map((res, i) => {
const catName = res.category;
const index = colorForCategory.findIndex(
(colors) => colors.name === catName.toLowerCase()
);
const style = index !== -1 ? colorForCategory[index].color : defaultColor;
return new ButtonBuilder()
.setCustomId(res.customId)
.setLabel(catName)
.setStyle(style)
.setDisabled(disabled);
});
let row = new ActionRowBuilder();
if (btnArray.length > 0) {
btnArray.map((btn) => row.addComponents(btn));
}
return row;
};
/**
*
* @param {Array<string>} commands listed for the users to see
* @param {string} name of the command to lookup
* @param {client} client Discord client object
* @returns information about the command requested to lookup
*/
const getSingleCmd = async (commands, name, client) => {
const prefix = Array.isArray(client.config.prefix)
? client.config.prefix[0]
: client.config.prefix;
const command = await commands.find(
(cmd) =>
cmd.name === name ||
(Array.isArray(cmd.aliases) && cmd.aliases.includes(name))
);
const slashCommand = await commands.find(
(cmd) => cmd.data && cmd.data.name === name
);
if (slashCommand) {
const fieldObj = [];
fieldObj.push({
name: `Category:`,
value: `${slashCommand.category}`,
inline: true,
});
fieldObj.push({
name: `Usage:`,
value: `/${slashCommand.name}`,
inline: true,
});
if (slashCommand.executePrefix) {
fieldObj.push({
name: `Using prefix:`,
value: `${prefix}${slashCommand.name} ${slashCommand.usage}${
slashCommand.aliases
? `, ${prefix}${slashCommand.aliases.join(`, ${prefix}`)}`
: ""
}`,
});
}
try {
const embed = new EmbedBuilder()
.setAuthor({
name: `${client.user.tag}`,
iconURL: `${client.user.avatarURL()}`,
})
.setTitle(`${slashCommand.data.name.toProperCase()} Command`)
.setDescription(slashCommand.data.description)
.setTimestamp()
.setFields(fieldObj);
return { embeds: [embed] };
} catch (e) {
console.log(e);
}
} else if (command) {
const fieldObj = [];
const aliases = command.aliases ? command.aliases.join(", ") : null;
if (aliases) {
fieldObj.push({
name: `Aliases:`,
value: `${aliases}`,
inline: true,
});
}
fieldObj.push({
name: `Category:`,
value: `${command.category}`,
inline: true,
});
if (command.usage.length !== 0) {
fieldObj.push({
name: `Usage:`,
value: `${prefix}${command.name} ${command.usage}`,
});
}
fieldObj.push({
name: `Slash:`,
value: `${command.data ? `True` : `False`}`,
inline: true,
});
try {
const embed = new EmbedBuilder()
.setAuthor({
name: `${client.user.tag}`,
iconURL: `${client.user.avatarURL()}`,
})
.setTitle(`${command.name.toProperCase()} Command`)
.setDescription(command.description)
.setTimestamp()
.setFields(fieldObj);
return { embeds: [embed] };
} catch (e) {
console.log(e);
}
} else {
return {
content: `The command, **${name}**
+ does not exist!`,
};
}
};