import { ActionRowBuilder, CommandInteraction, StringSelectMenuBuilder, ApplicationCommandOptionType, ApplicationCommandType, StringSelectMenuOptionBuilder, SlashCommandBuilder, StringSelectMenuInteraction, ComponentType, APIMessageComponentEmoji, ApplicationCommandSubGroup, PermissionsBitField, Interaction, ApplicationCommandOption, ApplicationCommandSubCommand } from "discord.js"; import client from "../utils/client.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import { LoadingEmbed } from "../utils/defaults.js"; import { capitalize } from "../utils/generateKeyValueList.js"; import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js"; import getEmojiByName from "../utils/getEmojiByName.js"; const command = new SlashCommandBuilder().setName("help").setDescription("Shows help for commands"); const styles: Record = { help: { emoji: "NUCLEUS.LOGO" }, mod: { emoji: "PUNISH.BAN.RED" }, nucleus: { emoji: "NUCLEUS.LOGO" }, privacy: { emoji: "NUCLEUS.LOGO" }, role: { emoji: "GUILD.ROLES.DELETE" }, rolemenu: { emoji: "GUILD.ROLES.DELETE" }, server: { emoji: "GUILD.RED" }, settings: { emoji: "GUILD.SETTINGS.RED" }, tag: { emoji: "PUNISH.NICKNAME.RED" }, tags: { emoji: "PUNISH.NICKNAME.RED" }, ticket: { emoji: "GUILD.TICKET.CLOSE" }, user: { emoji: "MEMBER.LEAVE" }, verify: { emoji: "CONTROL.REDTICK" } }; const callback = async (interaction: CommandInteraction): Promise => { const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); const commands = client.fetchedCommands; let closed = false; let currentPath: [string, string, string] = ["", "", ""]; do { const commandRow = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId("commandRow") .setPlaceholder("Select a command") .addOptions( ...commands .filter((command) => command.type === ApplicationCommandType.ChatInput) .map((command) => { const builder = new StringSelectMenuOptionBuilder() .setLabel(capitalize(command.name)) .setValue(command.name) .setDescription(command.description) .setDefault(currentPath[0] === command.name); if (styles[command.name]) builder.setEmoji( getEmojiByName(styles[command.name]!.emoji, "id") as APIMessageComponentEmoji ); return builder; }) ) ); const subcommandGroupRow = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder().setCustomId("subcommandGroupRow") ); const subcommandRow = new ActionRowBuilder().addComponents( new StringSelectMenuBuilder().setCustomId("subcommandRow") ); const embed = new EmojiEmbed().setTitle("Help").setStatus("Danger").setEmoji("NUCLEUS.LOGO"); if (currentPath[0] === "" || currentPath[0] === "help") { embed.setDescription( `Welcome to Nucleus\n\n` + `Select a command to get started${ (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : `` }\n\n\n` + `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` + `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}\n` + `Our support server can be found [here](https://discord.gg/bPaNnxe), and we can be emailed at support@clicks.codes` ); } else { const currentData = getCommandByName( currentPath.filter((value) => value !== "" && value !== "none").join("/") ); const current = commands.find((command) => command.name === currentPath[0])!; let optionString = ``; let options: (ApplicationCommandOption & { nameLocalized?: string; descriptionLocalized?: string; })[] = []; //options if ( currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none" ) { const Op = current.options.find( (option) => option.name === currentPath[1] )! as ApplicationCommandSubGroup; const Op2 = Op.options!.find((option) => option.name === currentPath[2])!; options = Op2.options ?? []; } else if (currentPath[1] !== "" && currentPath[1] !== "none") { let Op = current.options.find((option) => option.name === currentPath[1])!; if (Op.type === ApplicationCommandOptionType.SubcommandGroup) { options = []; } else { Op = Op as ApplicationCommandSubCommand; options = Op.options ?? []; } } else { options = current.options.filter( (option) => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand ); } for (const option of options) { optionString += `> \`${option.name}\` (${ApplicationCommandOptionType[option.type]}) - ${ option.description }\n`; } const APICommand = client.commands[ "commands/" + currentPath.filter((value) => value !== "" && value !== "none").join("/") ]![0]; let allowedToRun = true; if (APICommand?.check) { allowedToRun = await APICommand.check(interaction as Interaction, true); } embed.setDescription( `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${ currentData.mention }\n\n` + `> ${currentData.description}\n\n` + (APICommand ? `${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${ allowedToRun ? "" : "don't " }` + `have permission to use this command\n\n` : "") + (optionString.length > 0 ? "**Options:**\n" + optionString : "") ); const subcommands = current.options.filter( (option) => option.type === ApplicationCommandOptionType.Subcommand ); const subcommandGroups = current.options.filter( (option) => option.type === ApplicationCommandOptionType.SubcommandGroup ); if (subcommandGroups.length > 0) { subcommandGroupRow.components[0]!.addOptions( new StringSelectMenuOptionBuilder() .setLabel("Select a subcommand") .setValue("none") .setDefault(currentPath[1] === "none"), ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder() .setLabel(capitalize(option.name)) .setValue(option.name) .setDefault(currentPath[1] === option.name) ) ); if ( subcommandGroupRow.components[0]!.options.find( (option) => option.data.default && option.data.value !== "none" ) ) { const subsubcommands = ( subcommandGroups.find( (option) => option.name === currentPath[1] )! as ApplicationCommandSubGroup ).options ?? []; subcommandRow.components[0]!.addOptions( new StringSelectMenuOptionBuilder() .setLabel("Select a subcommand") .setValue("none") .setDefault(currentPath[2] === "none"), ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder() .setLabel(capitalize(option.name)) .setValue(option.name) .setDefault(currentPath[2] === option.name) ) ); } } if (subcommands.length > 0) { subcommandGroupRow.components[0]!.addOptions( ...subcommands.map((option) => new StringSelectMenuOptionBuilder() .setLabel(capitalize(option.name)) .setValue(option.name) .setDefault(currentPath[1] === option.name) ) ); } } const cmps = [commandRow]; if (subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow); if (subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow); await interaction.editReply({ embeds: [embed], components: cmps }); let i: StringSelectMenuInteraction; try { i = await m.awaitMessageComponent({ filter: (newInteraction) => interaction.user.id === newInteraction.user.id, time: 300000 }); } catch (e) { closed = true; continue; } await i.deferUpdate(); const value = i.values[0]!; switch (i.customId) { case "commandRow": { currentPath = [value, "", ""]; break; } case "subcommandGroupRow": { currentPath = [currentPath[0], value, ""]; break; } case "subcommandRow": { currentPath[2] = value; break; } } } while (!closed); }; export { command as command }; export { callback };