blob: 333dbdcdb530290b3aafcda57eb668329ad34d63 [file] [log] [blame]
TheCodedProfafca98b2023-01-17 22:25:43 -05001import type Discord from "discord.js";
Skyler Greyda16adf2023-03-05 10:22:12 +00002import {
3 ActionRowBuilder,
4 APIMessageComponentEmoji,
5 ButtonBuilder,
6 ButtonInteraction,
7 ButtonStyle,
8 CommandInteraction,
9 Message,
10 ModalBuilder,
11 RoleSelectMenuBuilder,
12 RoleSelectMenuInteraction,
13 StringSelectMenuBuilder,
14 StringSelectMenuInteraction,
15 StringSelectMenuOptionBuilder,
16 TextInputBuilder,
17 TextInputStyle
18} from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -050019import type { SlashCommandSubcommandBuilder } from "discord.js";
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050020import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
21import { LoadingEmbed } from "../../utils/defaults.js";
22import client from "../../utils/client.js";
23import getEmojiByName from "../../utils/getEmojiByName.js";
24import createPageIndicator from "../../utils/createPageIndicator.js";
25import { configToDropdown } from "../../actions/roleMenu.js";
TheCodedProff4facde2023-01-28 13:42:48 -050026import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProfb5e9d552023-01-29 15:43:26 -050027import ellipsis from "../../utils/ellipsis.js";
TheCodedProfe92b9b52023-03-06 17:07:34 -050028import _ from "lodash";
TheCodedProfa112f612023-01-28 18:06:45 -050029
TheCodedProf4a088b12023-06-06 15:29:59 -040030const cross = getEmojiByName("CONTROL.CROSS");
31const tick = getEmojiByName("CONTROL.TICK");
TheCodedProfe92b9b52023-03-06 17:07:34 -050032const isEqual = _.isEqual;
TheCodedProfa112f612023-01-28 18:06:45 -050033
TheCodedProf920d7292023-06-05 11:02:32 -040034const command = (builder: SlashCommandSubcommandBuilder) =>
35 builder.setName("rolemenu").setDescription("Allows you to change settings for the servers rolemenu");
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050036
37interface ObjectSchema {
TheCodedProf4a088b12023-06-06 15:29:59 -040038 enabled?: boolean;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050039 name: string;
TheCodedProf4a088b12023-06-06 15:29:59 -040040 description?: string;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050041 min: number;
42 max: number;
43 options: {
44 name: string;
45 description: string | null;
46 role: string;
47 }[];
48}
49
TheCodedProff4facde2023-01-28 13:42:48 -050050const defaultRolePageConfig = {
51 name: "Role Menu Page",
52 description: "A new role menu page",
53 min: 0,
54 max: 0,
Skyler Greyda16adf2023-03-05 10:22:12 +000055 options: [{ name: "Role 1", description: null, role: "No role set" }]
56};
TheCodedProff4facde2023-01-28 13:42:48 -050057
TheCodedProfa112f612023-01-28 18:06:45 -050058const reorderRoleMenuPages = async (interaction: CommandInteraction, m: Message, currentObj: ObjectSchema[]) => {
Skyler Greyda16adf2023-03-05 10:22:12 +000059 const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
60 new StringSelectMenuBuilder()
61 .setCustomId("reorder")
62 .setPlaceholder("Select all pages in the order you want them to appear.")
63 .setMinValues(currentObj.length)
64 .setMaxValues(currentObj.length)
65 .addOptions(
66 currentObj.map((o, i) => new StringSelectMenuOptionBuilder().setLabel(o.name).setValue(i.toString()))
67 )
68 );
69 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
70 new ButtonBuilder()
71 .setCustomId("back")
72 .setLabel("Back")
73 .setStyle(ButtonStyle.Secondary)
74 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
75 );
TheCodedProfa112f612023-01-28 18:06:45 -050076 await interaction.editReply({
77 embeds: [
78 new EmojiEmbed()
79 .setTitle("Role Menu")
80 .setDescription("Select pages in the order you want them to appear.")
81 .setStatus("Success")
82 ],
83 components: [reorderRow, buttonRow]
84 });
85 let out: StringSelectMenuInteraction | ButtonInteraction | null;
86 try {
Skyler Greyda16adf2023-03-05 10:22:12 +000087 out = (await m.awaitMessageComponent({
TheCodedProfa112f612023-01-28 18:06:45 -050088 filter: (i) => i.channel!.id === interaction.channel!.id,
89 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +000090 })) as StringSelectMenuInteraction | ButtonInteraction | null;
TheCodedProfa112f612023-01-28 18:06:45 -050091 } catch (e) {
92 console.error(e);
93 out = null;
94 }
Skyler Greyda16adf2023-03-05 10:22:12 +000095 if (!out) return;
Skyler Greyf4f21c42023-03-08 14:36:29 +000096 await out.deferUpdate();
TheCodedProfa112f612023-01-28 18:06:45 -050097 if (out.isButton()) return;
TheCodedProfa112f612023-01-28 18:06:45 -050098 const values = out.values;
99
100 const newOrder: ObjectSchema[] = currentObj.map((_, i) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000101 const index = values.findIndex((v) => v === i.toString());
TheCodedProfa112f612023-01-28 18:06:45 -0500102 return currentObj[index];
103 }) as ObjectSchema[];
104
105 return newOrder;
Skyler Greyda16adf2023-03-05 10:22:12 +0000106};
TheCodedProfa112f612023-01-28 18:06:45 -0500107
Skyler Greyda16adf2023-03-05 10:22:12 +0000108const editNameDescription = async (
109 i: ButtonInteraction,
110 interaction: StringSelectMenuInteraction | ButtonInteraction,
111 m: Message,
TheCodedProf4a088b12023-06-06 15:29:59 -0400112 data: { name?: string; description?: string; bounds?: { min: number; max: number } },
113 addBounds: boolean = false
114): Promise<[string | undefined, string | undefined, { min: number, max: number } | undefined]> => {
115 let { name, description, bounds } = data;
116 const components = [
117 new ActionRowBuilder<TextInputBuilder>().addComponents(
118 new TextInputBuilder()
119 .setLabel("Name")
120 .setCustomId("name")
121 .setPlaceholder("The name of the role (e.g. Programmer)")
122 .setStyle(TextInputStyle.Short)
123 .setValue(name ?? "")
124 .setRequired(true)
125 .setMaxLength(100)
126 ),
127 new ActionRowBuilder<TextInputBuilder>().addComponents(
128 new TextInputBuilder()
129 .setLabel("Description")
130 .setCustomId("description")
131 .setPlaceholder("A short description of the role (e.g. A role for people who code)")
132 .setStyle(TextInputStyle.Short)
133 .setValue(description ?? "")
134 .setRequired(false)
135 .setMaxLength(100)
136 )
137 ]
138 if (addBounds && bounds) {
139 components.push(
140 new ActionRowBuilder<TextInputBuilder>().addComponents(
141 new TextInputBuilder()
142 .setLabel("Minimum")
143 .setCustomId("min")
144 .setPlaceholder("The minimum number of roles a user can select")
145 .setStyle(TextInputStyle.Short)
146 .setValue(bounds.min.toString())
147 .setRequired(true)
148 .setMaxLength(2)
149 ),
150 new ActionRowBuilder<TextInputBuilder>().addComponents(
151 new TextInputBuilder()
152 .setLabel("Maximum")
153 .setCustomId("max")
154 .setPlaceholder("The maximum number of roles a user can select (0 for no limit)")
155 .setStyle(TextInputStyle.Short)
156 .setValue(bounds.max.toString())
157 .setRequired(true)
158 .setMaxLength(2)
159 )
160 )
161 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500162 const modal = new ModalBuilder()
163 .setTitle("Edit Name and Description")
164 .setCustomId("editNameDescription")
165 .addComponents(
TheCodedProf4a088b12023-06-06 15:29:59 -0400166 ...components
Skyler Greyda16adf2023-03-05 10:22:12 +0000167 );
168 const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
169 new ButtonBuilder()
170 .setCustomId("back")
171 .setLabel("Back")
172 .setStyle(ButtonStyle.Secondary)
173 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
174 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500175
Skyler Greyda16adf2023-03-05 10:22:12 +0000176 await i.showModal(modal);
TheCodedProff4facde2023-01-28 13:42:48 -0500177 await interaction.editReply({
178 embeds: [
179 new EmojiEmbed()
180 .setTitle("Role Menu")
181 .setDescription("Modal opened. If you can't see it, click back and try again.")
182 .setStatus("Success")
183 ],
184 components: [button]
185 });
186
187 let out: Discord.ModalSubmitInteraction | null;
188 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000189 out = (await modalInteractionCollector(m, interaction.user)) as Discord.ModalSubmitInteraction | null;
TheCodedProff4facde2023-01-28 13:42:48 -0500190 } catch (e) {
191 console.error(e);
192 out = null;
193 }
TheCodedProf4a088b12023-06-06 15:29:59 -0400194 if (!out) return [name, description, bounds];
195 if (out.isButton()) return [name, description, bounds];
TheCodedProff4facde2023-01-28 13:42:48 -0500196 name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
TheCodedProf4a088b12023-06-06 15:29:59 -0400197 description = out.fields.fields.find((f) => f.customId === "description")?.value;
198 if (addBounds) {
199 const min = parseInt(out.fields.fields.find((f) => f.customId === "min")?.value!);
200 const max = parseInt(out.fields.fields.find((f) => f.customId === "max")?.value!);
201 bounds = { min, max };
202 }
203 return [name, description, bounds];
Skyler Greyda16adf2023-03-05 10:22:12 +0000204};
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500205
TheCodedProf7a83f762023-03-06 17:17:00 -0500206const defaultRoleMenuData = {
TheCodedProf4a088b12023-06-06 15:29:59 -0400207 enabled: true,
pineafan72659cc2023-05-28 13:36:44 +0100208 name: "New Page",
209 description: "",
TheCodedProf7a83f762023-03-06 17:17:00 -0500210 min: 0,
pineafan72659cc2023-05-28 13:36:44 +0100211 max: 1,
TheCodedProf7a83f762023-03-06 17:17:00 -0500212 options: []
213};
214
Skyler Greyda16adf2023-03-05 10:22:12 +0000215const editRoleMenuPage = async (
216 interaction: StringSelectMenuInteraction | ButtonInteraction,
217 m: Message,
218 data?: ObjectSchema
219): Promise<ObjectSchema | null> => {
pineafan1e462ab2023-03-07 21:34:06 +0000220 if (!data) data = _.cloneDeep(defaultRoleMenuData);
Skyler Greyda16adf2023-03-05 10:22:12 +0000221 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
222 new ButtonBuilder()
223 .setCustomId("back")
224 .setLabel("Back")
225 .setStyle(ButtonStyle.Secondary)
226 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
227 new ButtonBuilder()
228 .setCustomId("edit")
229 .setLabel("Edit")
230 .setStyle(ButtonStyle.Primary)
231 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
232 new ButtonBuilder()
233 .setCustomId("addRole")
234 .setLabel("Add Role")
235 .setStyle(ButtonStyle.Secondary)
236 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
237 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500238
Skyler Greyda16adf2023-03-05 10:22:12 +0000239 let back = false;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500240 do {
pineafan72659cc2023-05-28 13:36:44 +0100241 const noRoles = data.options.length === 0;
242 const previewSelect = configToDropdown(
243 "Edit Roles",
TheCodedProf4a088b12023-06-06 15:29:59 -0400244 data.description ? {
pineafan72659cc2023-05-28 13:36:44 +0100245 name: data.name,
TheCodedProf4a088b12023-06-06 15:29:59 -0400246 description: data.description,
247 min: 1,
248 max: 1,
249 options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
250 } : {
251 name: data.name,
pineafan72659cc2023-05-28 13:36:44 +0100252 min: 1,
253 max: 1,
254 options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
255 },
256 undefined,
257 noRoles
258 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500259 const embed = new EmojiEmbed()
260 .setTitle(`${data.name}`)
261 .setStatus("Success")
262 .setDescription(
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400263 `**Description:**\n> ${data.description ?? "*No description set*"}\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000264 `**Min:** ${data.min}` +
265 (data.min === 0 ? " (Members will be given a skip button)" : "") +
266 "\n" +
pineafan72659cc2023-05-28 13:36:44 +0100267 `**Max:** ${data.max}\n` +
268 `\n**Roles:** ${data.options.length === 0 ? "*No roles set*" : data.options.length}`
Skyler Greyda16adf2023-03-05 10:22:12 +0000269 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500270
Skyler Greyf4f21c42023-03-08 14:36:29 +0000271 await interaction.editReply({ embeds: [embed], components: [previewSelect, buttons] });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500272 let i: StringSelectMenuInteraction | ButtonInteraction;
273 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000274 i = (await m.awaitMessageComponent({
275 time: 300000,
276 filter: (i) =>
277 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
278 })) as ButtonInteraction | StringSelectMenuInteraction;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500279 } catch (e) {
280 back = true;
281 break;
282 }
283
284 if (i.isStringSelectMenu()) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000285 if (i.customId === "roles") {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500286 await i.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000287 await createRoleMenuOptionPage(
288 interaction,
289 m,
pineafan72659cc2023-05-28 13:36:44 +0100290 data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]),
291 false
Skyler Greyda16adf2023-03-05 10:22:12 +0000292 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500293 }
294 } else if (i.isButton()) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500295 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000296 case "back": {
TheCodedProff4facde2023-01-28 13:42:48 -0500297 await i.deferUpdate();
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500298 back = true;
299 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000300 }
301 case "edit": {
TheCodedProf4a088b12023-06-06 15:29:59 -0400302 const [name, description, _bounds] = await editNameDescription(i, interaction, m, data);
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500303 data.name = name ? name : data.name;
TheCodedProf4a088b12023-06-06 15:29:59 -0400304 description ? data.description = description : null;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500305 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000306 }
307 case "addRole": {
TheCodedProff4facde2023-01-28 13:42:48 -0500308 await i.deferUpdate();
pineafan72659cc2023-05-28 13:36:44 +0100309 const out = await createRoleMenuOptionPage(interaction, m, undefined, true);
310 if (out) data.options.push(out);
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500311 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000312 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500313 }
314 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500315 } while (!back);
Skyler Greyda16adf2023-03-05 10:22:12 +0000316 if (isEqual(data, defaultRolePageConfig)) return null;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500317 return data;
Skyler Greyda16adf2023-03-05 10:22:12 +0000318};
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500319
Skyler Greyda16adf2023-03-05 10:22:12 +0000320const createRoleMenuOptionPage = async (
321 interaction: StringSelectMenuInteraction | ButtonInteraction,
322 m: Message,
pineafan72659cc2023-05-28 13:36:44 +0100323 data?: { name: string; description: string | null; role: string },
324 newRole: boolean = false
Skyler Greyda16adf2023-03-05 10:22:12 +0000325) => {
pineafan72659cc2023-05-28 13:36:44 +0100326 const initialData = _.cloneDeep(data);
Skyler Greyda16adf2023-03-05 10:22:12 +0000327 const { renderRole } = client.logger;
328 if (!data)
329 data = {
330 name: "New role Menu Option",
331 description: null,
332 role: ""
333 };
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500334 let back = false;
Skyler Greyda16adf2023-03-05 10:22:12 +0000335 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
336 new ButtonBuilder()
337 .setCustomId("back")
pineafan72659cc2023-05-28 13:36:44 +0100338 .setLabel(newRole ? "Add" : "Back")
339 .setStyle(newRole ? ButtonStyle.Success : ButtonStyle.Secondary)
340 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji),
Skyler Greyda16adf2023-03-05 10:22:12 +0000341 new ButtonBuilder()
342 .setCustomId("edit")
343 .setLabel("Edit Details")
344 .setStyle(ButtonStyle.Primary)
pineafan72659cc2023-05-28 13:36:44 +0100345 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
346 new ButtonBuilder()
347 .setCustomId("delete")
348 .setLabel("Delete")
349 .setStyle(ButtonStyle.Danger)
350 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji),
351 new ButtonBuilder()
352 .setCustomId("cancel")
353 .setLabel("Cancel")
354 .setStyle(ButtonStyle.Secondary)
355 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
Skyler Greyda16adf2023-03-05 10:22:12 +0000356 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500357 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000358 const roleSelect = new RoleSelectMenuBuilder()
359 .setCustomId("role")
pineafan72659cc2023-05-28 13:36:44 +0100360 .setPlaceholder(data.role ? "Change role to" : "Select a role");
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500361 const embed = new EmojiEmbed()
PineaFanb0d0c242023-02-05 10:59:45 +0000362 .setTitle(`${data.name}`)
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500363 .setStatus("Success")
364 .setDescription(
365 `**Description:**\n> ${data.description ?? "No description set"}\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000366 `**Role:** ${
367 data.role ? renderRole((await interaction.guild!.roles.fetch(data.role))!) : "No role set"
368 }\n`
369 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500370
Skyler Greyf4f21c42023-03-08 14:36:29 +0000371 await interaction.editReply({
Skyler Greyda16adf2023-03-05 10:22:12 +0000372 embeds: [embed],
373 components: [new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(roleSelect), buttons]
374 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500375
376 let i: RoleSelectMenuInteraction | ButtonInteraction;
377 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000378 i = (await m.awaitMessageComponent({
379 time: 300000,
380 filter: (i) =>
381 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
382 })) as ButtonInteraction | RoleSelectMenuInteraction;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500383 } catch (e) {
384 back = true;
385 break;
386 }
387
388 if (i.isRoleSelectMenu()) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000389 if (i.customId === "role") {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500390 await i.deferUpdate();
391 data.role = (i as RoleSelectMenuInteraction).values[0]!;
pineafan72659cc2023-05-28 13:36:44 +0100392 await interaction.editReply({
393 embeds: [
394 new EmojiEmbed().setTitle(`Applying changes`).setStatus("Danger").setEmoji("NUCLEUS.LOADING")
395 ],
396 components: []
397 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500398 }
399 } else if (i.isButton()) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500400 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000401 case "back": {
TheCodedProff4facde2023-01-28 13:42:48 -0500402 await i.deferUpdate();
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500403 back = true;
404 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000405 }
406 case "edit": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000407 const [name, description] = await editNameDescription(
408 i,
409 interaction,
410 m,
411 data as { name: string; description: string }
412 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500413 data.name = name ? name : data.name;
414 data.description = description ? description : data.description;
415 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000416 }
pineafan72659cc2023-05-28 13:36:44 +0100417 case "delete": {
418 await i.deferUpdate();
419 return null;
420 }
421 case "cancel": {
422 await i.deferUpdate();
423 if (newRole) return null;
424 else return initialData;
425 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500426 }
427 }
428 } while (!back);
429 return data;
Skyler Greyda16adf2023-03-05 10:22:12 +0000430};
pineafanda6e5342022-07-03 10:03:16 +0100431
pineafan63fc5e22022-08-04 22:04:10 +0100432const callback = async (interaction: CommandInteraction): Promise<void> => {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500433 if (!interaction.guild) return;
Skyler Greyda16adf2023-03-05 10:22:12 +0000434 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500435
436 let page = 0;
437 let closed = false;
438 const config = await client.database.guilds.read(interaction.guild.id);
TheCodedProf920d7292023-06-05 11:02:32 -0400439 const currentObject: typeof config.roleMenu = _.cloneDeep(config.roleMenu);
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500440 let modified = false;
441 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000442 const embed = new EmojiEmbed().setTitle("Role Menu").setEmoji("GUILD.GREEN").setStatus("Success");
TheCodedProf920d7292023-06-05 11:02:32 -0400443 const noRoleMenus = currentObject.options.length === 0;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500444 let current: ObjectSchema;
445
446 const pageSelect = new StringSelectMenuBuilder()
447 .setCustomId("page")
448 .setPlaceholder("Select a Role Menu page to manage");
TheCodedProf920d7292023-06-05 11:02:32 -0400449 let actionSelect;
450 if (page === 0) {
451 actionSelect = new ActionRowBuilder<ButtonBuilder>().addComponents(
452 new ButtonBuilder()
453 .setCustomId("switch")
454 .setLabel(currentObject.enabled ? "Enabled" : "Disabled")
455 .setStyle(currentObject.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
456 .setEmoji(
457 getEmojiByName(
458 currentObject.enabled ? "CONTROL.TICK" : "CONTROL.CROSS",
459 "id"
460 ) as APIMessageComponentEmoji
461 )
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500462 );
TheCodedProf920d7292023-06-05 11:02:32 -0400463 } else {
464 actionSelect = new StringSelectMenuBuilder()
465 .setCustomId("action")
466 .setPlaceholder("Perform an action")
467 .addOptions(
468 new StringSelectMenuOptionBuilder()
469 .setLabel("Edit")
470 .setDescription("Edit this page")
471 .setValue("edit")
472 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
473 new StringSelectMenuOptionBuilder()
474 .setLabel("Delete")
475 .setDescription("Delete this page")
476 .setValue("delete")
477 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
478 );
479 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000480 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
481 new ButtonBuilder()
482 .setCustomId("back")
483 .setStyle(ButtonStyle.Primary)
484 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
485 .setDisabled(page === 0),
486 new ButtonBuilder()
487 .setCustomId("next")
488 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
489 .setStyle(ButtonStyle.Primary)
TheCodedProf920d7292023-06-05 11:02:32 -0400490 .setDisabled(page === Object.keys(currentObject.options).length || noRoleMenus),
Skyler Greyda16adf2023-03-05 10:22:12 +0000491 new ButtonBuilder()
492 .setCustomId("add")
493 .setLabel("New Page")
494 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
495 .setStyle(ButtonStyle.Secondary)
TheCodedProf920d7292023-06-05 11:02:32 -0400496 .setDisabled(Object.keys(currentObject.options).length >= 24),
Skyler Greyda16adf2023-03-05 10:22:12 +0000497 new ButtonBuilder()
498 .setCustomId("reorder")
499 .setLabel("Reorder Pages")
500 .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji)
501 .setStyle(ButtonStyle.Secondary)
TheCodedProf920d7292023-06-05 11:02:32 -0400502 .setDisabled(Object.keys(currentObject.options).length <= 1),
Skyler Greyda16adf2023-03-05 10:22:12 +0000503 new ButtonBuilder()
504 .setCustomId("save")
505 .setLabel("Save")
506 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
507 .setStyle(ButtonStyle.Success)
508 .setDisabled(!modified)
509 );
510 if (noRoleMenus) {
511 embed.setDescription(
512 "No role menu pages have been set up yet. Use the button below to add one.\n\n" +
513 createPageIndicator(1, 1, undefined, true)
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500514 );
515 pageSelect.setDisabled(true);
TheCodedProf920d7292023-06-05 11:02:32 -0400516 if (page > 0) (actionSelect as StringSelectMenuBuilder).setDisabled(true);
Skyler Greyda16adf2023-03-05 10:22:12 +0000517 pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No role menu pages").setValue("none"));
TheCodedProf920d7292023-06-05 11:02:32 -0400518 } else if (page === 0) {
TheCodedProf920d7292023-06-05 11:02:32 -0400519 embed.setDescription(
PineappleFan0c1d9bf2023-06-06 18:35:44 +0100520 `**Enabled:** ${currentObject.enabled ? `${tick} Yes` : `${cross} No`}\n\n` +
TheCodedProf920d7292023-06-05 11:02:32 -0400521 `**Pages:** ${currentObject.options.length}\n` +
522 (currentObject.options.length > 0
523 ? currentObject.options
524 .map((key: ObjectSchema) => {
TheCodedProf4a088b12023-06-06 15:29:59 -0400525 const crossOut = key.enabled ? "" : "~~";
526 return `> ${crossOut}**${key.name}:** ${key.description ?? "*No description set*"}${crossOut}`;
TheCodedProf920d7292023-06-05 11:02:32 -0400527 })
528 .join("\n")
529 : "")
530 );
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400531 if (currentObject.options.length > 0) {
TheCodedProf920d7292023-06-05 11:02:32 -0400532 pageSelect.addOptions(
533 currentObject.options.map((key: ObjectSchema, index) => {
534 return new StringSelectMenuOptionBuilder()
535 .setLabel(ellipsis(key.name, 50))
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400536 .setDescription(
537 ellipsis(
538 key.description?.length
539 ? key.description.length > 0
540 ? key.description
541 : "No description set"
542 : "No description set",
543 50
544 )
545 )
TheCodedProf4a088b12023-06-06 15:29:59 -0400546 .setValue((index + 1).toString());
TheCodedProf920d7292023-06-05 11:02:32 -0400547 })
548 );
549 } else {
550 pageSelect.setDisabled(true);
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400551 pageSelect.addOptions(
552 new StringSelectMenuOptionBuilder().setLabel("No role menu pages").setValue("none")
553 );
TheCodedProf920d7292023-06-05 11:02:32 -0400554 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500555 } else {
TheCodedProf920d7292023-06-05 11:02:32 -0400556 page = Math.max(Math.min(page, currentObject.options.length), 0);
557 current = currentObject.options[page - 1]!;
TheCodedProf4a088b12023-06-06 15:29:59 -0400558 (actionSelect as StringSelectMenuBuilder).addOptions(
559 new StringSelectMenuOptionBuilder()
560 .setLabel(current.enabled ? "Disable" : "Enable")
561 .setDescription("Enable or disable this page")
562 .setValue("switch")
563 .setEmoji(
564 getEmojiByName(
565 current.enabled ? "CONTROL.CROSS" : "CONTROL.TICK",
566 "id"
567 ) as APIMessageComponentEmoji
568 )
569 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000570 embed.setDescription(
571 `**Currently Editing:** ${current.name}\n\n` +
TheCodedProf4a088b12023-06-06 15:29:59 -0400572 `**Visible:** ${current.enabled ? `${tick} Yes` : `${cross} No`}\n\n` +
TheCodedProf920d7292023-06-05 11:02:32 -0400573 `**Description:**\n> ${current.description ?? "*No description set*"}\n` +
TheCodedProf4a088b12023-06-06 15:29:59 -0400574 `\n\n${createPageIndicator(Object.keys(currentObject.options).length, page - 1, false, Object.values(currentObject.options).map((o) => !(o.enabled ?? true)))}`
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500575 );
576
577 pageSelect.addOptions(
TheCodedProf920d7292023-06-05 11:02:32 -0400578 currentObject.options.map((key: ObjectSchema, index) => {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500579 return new StringSelectMenuOptionBuilder()
580 .setLabel(ellipsis(key.name, 50))
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400581 .setDescription(
582 ellipsis(
583 key.description?.length
584 ? key.description.length > 0
585 ? key.description
586 : "No description set"
587 : "No description set",
588 50
589 )
590 )
TheCodedProf4a088b12023-06-06 15:29:59 -0400591 .setValue((index + 1).toString());
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500592 })
593 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500594 }
595
Skyler Greyda16adf2023-03-05 10:22:12 +0000596 await interaction.editReply({
597 embeds: [embed],
598 components: [
TheCodedProf920d7292023-06-05 11:02:32 -0400599 page === 0
600 ? (actionSelect as ActionRowBuilder<ButtonBuilder>)
601 : new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
602 actionSelect as StringSelectMenuBuilder
603 ),
Skyler Greyda16adf2023-03-05 10:22:12 +0000604 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
TheCodedProf920d7292023-06-05 11:02:32 -0400605 buttonRow as ActionRowBuilder<ButtonBuilder>
Skyler Greyda16adf2023-03-05 10:22:12 +0000606 ]
607 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500608 let i: StringSelectMenuInteraction | ButtonInteraction;
609 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000610 i = (await m.awaitMessageComponent({
611 time: 300000,
612 filter: (i) =>
613 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
614 })) as ButtonInteraction | StringSelectMenuInteraction;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500615 } catch (e) {
616 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000617 continue;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500618 }
619
620 await i.deferUpdate();
621 if (i.isButton()) {
622 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000623 case "back": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500624 page--;
625 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000626 }
627 case "next": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500628 page++;
629 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000630 }
631 case "add": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000632 const newPage = await editRoleMenuPage(i, m);
pineafan44713d42023-06-03 20:29:49 +0100633 if (_.isEqual(newPage, defaultRoleMenuData)) {
634 break;
635 }
pineafan6f600f02023-06-03 20:27:07 +0100636 if (newPage) {
TheCodedProf920d7292023-06-05 11:02:32 -0400637 currentObject.options.push(newPage);
638 page = currentObject.options.length;
639 modified = true;
pineafan6f600f02023-06-03 20:27:07 +0100640 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500641 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000642 }
643 case "reorder": {
TheCodedProf920d7292023-06-05 11:02:32 -0400644 const reordered = await reorderRoleMenuPages(interaction, m, currentObject.options);
Skyler Greyda16adf2023-03-05 10:22:12 +0000645 if (!reordered) break;
TheCodedProf920d7292023-06-05 11:02:32 -0400646 currentObject.options = reordered;
647 modified = true;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500648 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000649 }
650 case "save": {
TheCodedProf920d7292023-06-05 11:02:32 -0400651 await client.database.guilds.write(interaction.guild.id, {
TheCodedProf4a088b12023-06-06 15:29:59 -0400652 "roleMenu": currentObject
TheCodedProf920d7292023-06-05 11:02:32 -0400653 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500654 modified = false;
Skyler Grey16ecb172023-03-05 07:30:32 +0000655 await client.memory.forceUpdate(interaction.guild.id);
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500656 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000657 }
TheCodedProf920d7292023-06-05 11:02:32 -0400658 case "switch": {
659 currentObject.enabled = !currentObject.enabled;
660 modified = true;
661 break;
662 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500663 }
664 } else if (i.isStringSelectMenu()) {
665 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000666 case "action": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000667 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000668 case "edit": {
669 const edited = await editRoleMenuPage(i, m, current!);
Skyler Greyda16adf2023-03-05 10:22:12 +0000670 if (!edited) break;
TheCodedProf4a088b12023-06-06 15:29:59 -0400671 currentObject.options[page - 1] = edited;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500672 modified = true;
673 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000674 }
675 case "delete": {
TheCodedProf920d7292023-06-05 11:02:32 -0400676 if (page === 0 && currentObject.options.keys.length - 1 > 0) page++;
TheCodedProff4facde2023-01-28 13:42:48 -0500677 else page--;
TheCodedProf920d7292023-06-05 11:02:32 -0400678 currentObject.options.splice(page, 1);
679 modified = true;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500680 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000681 }
TheCodedProf4a088b12023-06-06 15:29:59 -0400682 case "switch": {
683 currentObject.options[page - 1]!.enabled = !currentObject.options[page - 1]!.enabled;
684 modified = true;
685 break;
686 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500687 }
688 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000689 }
690 case "page": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500691 page = parseInt(i.values[0]!);
692 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000693 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500694 }
695 }
TheCodedProf01cba762023-02-18 15:55:05 -0500696 } while (!closed);
Skyler Greyda16adf2023-03-05 10:22:12 +0000697 await interaction.deleteReply();
pineafan63fc5e22022-08-04 22:04:10 +0100698};
pineafanda6e5342022-07-03 10:03:16 +0100699
TheCodedProff86ba092023-01-27 17:10:07 -0500700const check = (interaction: CommandInteraction, _partial: boolean = false) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100701 const member = interaction.member as Discord.GuildMember;
PineaFan0d06edc2023-01-17 22:10:31 +0000702 if (!member.permissions.has("ManageRoles"))
703 return "You must have the *Manage Roles* permission to use this command";
pineafanda6e5342022-07-03 10:03:16 +0100704 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100705};
pineafanda6e5342022-07-03 10:03:16 +0100706
707export { command };
708export { callback };
Skyler Grey75ea9172022-08-06 10:22:23 +0100709export { check };