blob: 90ef13328048a89e408a9df56a0f72bc292009bb [file] [log] [blame]
TheCodedProff86ba092023-01-27 17:10:07 -05001import {
2 ActionRowBuilder,
3 CommandInteraction,
4 StringSelectMenuBuilder,
5 ApplicationCommandOptionType,
6 ApplicationCommandType,
7 StringSelectMenuOptionBuilder,
8 SlashCommandBuilder,
9 StringSelectMenuInteraction,
10 ComponentType,
11 APIMessageComponentEmoji,
12 ApplicationCommandSubGroup,
13 PermissionsBitField,
14 Interaction,
15 ApplicationCommandOption,
16 ApplicationCommandSubCommand
17} from "discord.js";
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050018import client from "../utils/client.js";
19import EmojiEmbed from "../utils/generateEmojiEmbed.js";
20import { LoadingEmbed } from "../utils/defaults.js";
TheCodedProff86ba092023-01-27 17:10:07 -050021import { capitalize } from "../utils/generateKeyValueList.js";
22import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js";
23import getEmojiByName from "../utils/getEmojiByName.js";
pineafan4f164f32022-02-26 22:07:12 +000024
PineaFan752af462022-12-31 21:59:38 +000025const command = new SlashCommandBuilder()
26 .setName("help")
27 .setDescription("Shows help for commands");
pineafan4f164f32022-02-26 22:07:12 +000028
TheCodedProff86ba092023-01-27 17:10:07 -050029const styles: Record<string, {emoji: string}> = {
30 "help": {emoji: "NUCLEUS.LOGO"},
31 "mod": {emoji: "PUNISH.BAN.RED"},
32 "nucleus": {emoji: "NUCLEUS.LOGO"},
33 "privacy": {emoji: "NUCLEUS.LOGO"},
34 "role": {emoji: "GUILD.ROLES.DELETE"},
35 "rolemenu": {emoji: "GUILD.ROLES.DELETE"},
36 "server": {emoji: "GUILD.RED"},
37 "settings": {emoji: "GUILD.SETTINGS.RED"},
38 "tag": {emoji: "PUNISH.NICKNAME.RED"},
39 "tags": {emoji: "PUNISH.NICKNAME.RED"},
40 "ticket": {emoji: "GUILD.TICKET.CLOSE"},
41 "user": {emoji: "MEMBER.LEAVE"},
TheCodedProfa112f612023-01-28 18:06:45 -050042 "verify": {emoji: "CONTROL.REDTICK"}
TheCodedProff86ba092023-01-27 17:10:07 -050043}
44
pineafan63fc5e22022-08-04 22:04:10 +010045const callback = async (interaction: CommandInteraction): Promise<void> => {
TheCodedProff86ba092023-01-27 17:10:07 -050046 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050047 const commands = client.fetchedCommands;
48
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050049 let closed = false;
TheCodedProfa112f612023-01-28 18:06:45 -050050 let currentPath: [string, string, string] = ["", "", ""]
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050051 do {
TheCodedProff86ba092023-01-27 17:10:07 -050052 const commandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
53 .addComponents(
54 new StringSelectMenuBuilder()
55 .setCustomId("commandRow")
56 .setPlaceholder("Select a command")
57 .addOptions(
58 ...commands.filter(command => command.type === ApplicationCommandType.ChatInput).map((command) => {
59 const builder = new StringSelectMenuOptionBuilder()
60 .setLabel(capitalize(command.name))
61 .setValue(command.name)
62 .setDescription(command.description)
63 .setDefault(currentPath[0] === command.name)
64 if (styles[command.name]) builder.setEmoji(getEmojiByName(styles[command.name]!.emoji, "id") as APIMessageComponentEmoji)
65 return builder
66 })
67 )
68 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050069 const subcommandGroupRow = new ActionRowBuilder<StringSelectMenuBuilder>()
70 .addComponents(
71 new StringSelectMenuBuilder()
72 .setCustomId("subcommandGroupRow")
73 );
74 const subcommandRow = new ActionRowBuilder<StringSelectMenuBuilder>()
75 .addComponents(
76 new StringSelectMenuBuilder()
77 .setCustomId("subcommandRow")
78 );
79 const embed = new EmojiEmbed()
80 .setTitle("Help")
TheCodedProff86ba092023-01-27 17:10:07 -050081 .setStatus("Danger")
82 .setEmoji("NUCLEUS.LOGO")
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050083
TheCodedProff86ba092023-01-27 17:10:07 -050084 if(currentPath[0] === "" || currentPath[0] === "help") {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050085 embed.setDescription(
TheCodedProff86ba092023-01-27 17:10:07 -050086 `Welcome to Nucleus\n\n` +
TheCodedProf1f675042023-02-16 17:01:29 -050087 `Select a command to get started${
88 (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ?
89 `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``
90 }\n\n\n` +
91 `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` +
92 `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}`
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050093 )
94 } else {
PineaFanb0d0c242023-02-05 10:59:45 +000095 const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/'));
96 const current = commands.find((command) => command.name === currentPath[0])!;
TheCodedProff86ba092023-01-27 17:10:07 -050097
98 let optionString = ``
99 let options: (ApplicationCommandOption & {
100 nameLocalized?: string;
101 descriptionLocalized?: string;
102 })[] = [];
TheCodedProf1807fb32023-02-20 14:33:48 -0500103 //options
TheCodedProf8c295232023-01-27 20:52:24 -0500104 if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") {
PineaFanb0d0c242023-02-05 10:59:45 +0000105 const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup
106 const Op2 = Op.options!.find(option => option.name === currentPath[2])!
107 options = Op2.options ?? []
TheCodedProf8c295232023-01-27 20:52:24 -0500108 } else if(currentPath[1] !== "" && currentPath[1] !== "none") {
TheCodedProfcd187ee2023-01-27 20:46:12 -0500109 let Op = current.options.find(option => option.name === currentPath[1])!
110 if(Op.type === ApplicationCommandOptionType.SubcommandGroup) {
111 options = []
112 } else {
113 Op = Op as ApplicationCommandSubCommand
PineaFanb0d0c242023-02-05 10:59:45 +0000114 options = Op.options ?? []
TheCodedProfcd187ee2023-01-27 20:46:12 -0500115 }
116 } else {
TheCodedProf1807fb32023-02-20 14:33:48 -0500117 options = current.options.filter(option => (option.type !== ApplicationCommandOptionType.SubcommandGroup) && (option.type !== ApplicationCommandOptionType.Subcommand));
TheCodedProfcd187ee2023-01-27 20:46:12 -0500118 }
TheCodedProff86ba092023-01-27 17:10:07 -0500119 for(const option of options) {
TheCodedProf8c295232023-01-27 20:52:24 -0500120 optionString += `> ${option.name} (${ApplicationCommandOptionType[option.type]})- ${option.description}\n`
TheCodedProff86ba092023-01-27 17:10:07 -0500121 }
122 const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0]
123 let allowedToRun = true;
124 if(APICommand?.check) {
PineaFanb0d0c242023-02-05 10:59:45 +0000125 allowedToRun = await APICommand.check(interaction as Interaction, true)
TheCodedProff86ba092023-01-27 17:10:07 -0500126 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500127 embed.setDescription(
TheCodedProff86ba092023-01-27 17:10:07 -0500128 `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` +
129 `> ${currentData.description}\n\n` +
130 (APICommand ? (`${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${allowedToRun ? "" : "don't "}` +
131 `have permission to use this command\n\n`) : "") +
132 ((optionString.length > 0) ? "**Options:**\n" + optionString : "")
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500133 )
TheCodedProf92a003c2023-01-25 18:01:21 -0500134 const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand);
135 const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup);
TheCodedProff86ba092023-01-27 17:10:07 -0500136
TheCodedProf92a003c2023-01-25 18:01:21 -0500137 if(subcommandGroups.length > 0) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500138 subcommandGroupRow.components[0]!
139 .addOptions(
TheCodedProff86ba092023-01-27 17:10:07 -0500140 new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"),
141 ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500142 )
TheCodedProffe0a7862023-01-27 20:36:35 -0500143 if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500144 const subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options ?? [];
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500145 subcommandRow.components[0]!
146 .addOptions(
TheCodedProff86ba092023-01-27 17:10:07 -0500147 new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"),
148 ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[2] === option.name))
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500149 )
150 }
151 }
TheCodedProff86ba092023-01-27 17:10:07 -0500152 if(subcommands.length > 0) {
153 subcommandGroupRow.components[0]!
154 .addOptions(
155 ...subcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
156 )
157 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500158 }
TheCodedProff86ba092023-01-27 17:10:07 -0500159
PineaFanb0d0c242023-02-05 10:59:45 +0000160 const cmps = [commandRow];
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500161 if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow);
162 if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow);
163
164 await interaction.editReply({ embeds: [embed], components: cmps });
165
TheCodedProff86ba092023-01-27 17:10:07 -0500166 let i: StringSelectMenuInteraction;
167 try {
168 i = await m.awaitMessageComponent<ComponentType.StringSelect>({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000})
169 } catch (e) {
170 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000171 continue;
TheCodedProff86ba092023-01-27 17:10:07 -0500172 }
173 await i.deferUpdate();
PineaFanb0d0c242023-02-05 10:59:45 +0000174 const value = i.values[0]!;
TheCodedProff86ba092023-01-27 17:10:07 -0500175 switch(i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000176 case "commandRow": {
TheCodedProff86ba092023-01-27 17:10:07 -0500177 currentPath = [value, "", ""];
178 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000179 }
180 case "subcommandGroupRow": {
TheCodedProff86ba092023-01-27 17:10:07 -0500181 currentPath = [currentPath[0], value , ""];
182 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000183 }
184 case "subcommandRow": {
TheCodedProff86ba092023-01-27 17:10:07 -0500185 currentPath[2] = value;
186 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000187 }
TheCodedProff86ba092023-01-27 17:10:07 -0500188 }
TheCodedProff86ba092023-01-27 17:10:07 -0500189
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500190 } while (!closed);
pineafan63fc5e22022-08-04 22:04:10 +0100191};
pineafan4f164f32022-02-26 22:07:12 +0000192
pineafan4f164f32022-02-26 22:07:12 +0000193
TheCodedProff86ba092023-01-27 17:10:07 -0500194export { command as command };
pineafan4f164f32022-02-26 22:07:12 +0000195export { callback };