blob: c200e1868aaf74bc80d0a5f404b9c6a5996308e7 [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"},
42 "verify": {emoji: "CONTROL.BLOCKTICK"}
43}
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;
TheCodedProff86ba092023-01-27 17:10:07 -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` +
87 `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` : ``}` // FIXME
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050088 )
89 } else {
TheCodedProff86ba092023-01-27 17:10:07 -050090 let currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/'));
91 let current = commands.find((command) => command.name === currentPath[0])!;
92
93 let optionString = ``
94 let options: (ApplicationCommandOption & {
95 nameLocalized?: string;
96 descriptionLocalized?: string;
97 })[] = [];
98 //options
TheCodedProfcd187ee2023-01-27 20:46:12 -050099 if(subcommandRow.components[0]!.options.find(option => option.data.default)) {
100 let Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup
101 let Op2 = Op.options!.find(option => option.name === currentPath[2])!
102 options = Op2.options || []
103 } else if(subcommandGroupRow.components[0]!.options.find(option => option.data.default)) {
104 let Op = current.options.find(option => option.name === currentPath[1])!
105 if(Op.type === ApplicationCommandOptionType.SubcommandGroup) {
106 options = []
107 } else {
108 Op = Op as ApplicationCommandSubCommand
109 options = Op.options || []
110 }
111 } else {
112 options = current.options.filter(option => option.type !== ApplicationCommandOptionType.SubcommandGroup && option.type !== ApplicationCommandOptionType.Subcommand) || [];
113 }
TheCodedProff86ba092023-01-27 17:10:07 -0500114 for(const option of options) {
115 optionString += `> ${option.name} (${option.type})- ${option.description}\n`
116 }
117 const APICommand = client.commands["commands/" + currentPath.filter(value => value !== "" && value !== "none").join("/")]![0]
118 let allowedToRun = true;
119 if(APICommand?.check) {
120 APICommand?.check(interaction as Interaction, true)
121 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500122 embed.setDescription(
TheCodedProff86ba092023-01-27 17:10:07 -0500123 `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${currentData.mention}\n\n` +
124 `> ${currentData.description}\n\n` +
125 (APICommand ? (`${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${allowedToRun ? "" : "don't "}` +
126 `have permission to use this command\n\n`) : "") +
127 ((optionString.length > 0) ? "**Options:**\n" + optionString : "")
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500128 )
TheCodedProf92a003c2023-01-25 18:01:21 -0500129 const subcommands = current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand);
130 const subcommandGroups = current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup);
TheCodedProff86ba092023-01-27 17:10:07 -0500131
TheCodedProf92a003c2023-01-25 18:01:21 -0500132 if(subcommandGroups.length > 0) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500133 subcommandGroupRow.components[0]!
134 .addOptions(
TheCodedProff86ba092023-01-27 17:10:07 -0500135 new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[1] === "none"),
136 ...subcommandGroups.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500137 )
TheCodedProffe0a7862023-01-27 20:36:35 -0500138 if(subcommandGroupRow.components[0]!.options.find((option) => option.data.default && option.data.value !== "none")) {
TheCodedProff86ba092023-01-27 17:10:07 -0500139 let subsubcommands = (subcommandGroups.find((option) => option.name === currentPath[1])! as ApplicationCommandSubGroup).options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand) || [];
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500140 subcommandRow.components[0]!
141 .addOptions(
TheCodedProff86ba092023-01-27 17:10:07 -0500142 new StringSelectMenuOptionBuilder().setLabel("Select a subcommand").setValue("none").setDefault(currentPath[2] === "none"),
143 ...subsubcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[2] === option.name))
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500144 )
145 }
146 }
TheCodedProff86ba092023-01-27 17:10:07 -0500147 if(subcommands.length > 0) {
148 subcommandGroupRow.components[0]!
149 .addOptions(
150 ...subcommands.map((option) => new StringSelectMenuOptionBuilder().setLabel(capitalize(option.name)).setValue(option.name).setDefault(currentPath[1] === option.name))
151 )
152 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500153 }
TheCodedProff86ba092023-01-27 17:10:07 -0500154
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500155 let cmps = [commandRow];
156 if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow);
157 if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow);
158
159 await interaction.editReply({ embeds: [embed], components: cmps });
160
TheCodedProff86ba092023-01-27 17:10:07 -0500161 let i: StringSelectMenuInteraction;
162 try {
163 i = await m.awaitMessageComponent<ComponentType.StringSelect>({filter: (newInteraction) => interaction.user.id === newInteraction.user.id,time: 300000})
164 } catch (e) {
165 closed = true;
166 break;
167 }
168 await i.deferUpdate();
169 let value = i.values[0]!;
170 switch(i.customId) {
171 case "commandRow":
172 currentPath = [value, "", ""];
173 break;
174 case "subcommandGroupRow":
175 currentPath = [currentPath[0], value , ""];
176 break;
177 case "subcommandRow":
178 currentPath[2] = value;
179 break;
180 }
TheCodedProff86ba092023-01-27 17:10:07 -0500181
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500182 } while (!closed);
pineafan63fc5e22022-08-04 22:04:10 +0100183};
pineafan4f164f32022-02-26 22:07:12 +0000184
pineafan4f164f32022-02-26 22:07:12 +0000185
TheCodedProff86ba092023-01-27 17:10:07 -0500186export { command as command };
pineafan4f164f32022-02-26 22:07:12 +0000187export { callback };