blob: 1ca10a4fd826b0a1ecaba2f49358054eeb78241e [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
TheCodedProfc62b0702023-06-06 15:31:14 -0400114): Promise<[string | undefined, string | undefined, { min: number; max: number } | undefined]> => {
TheCodedProf4a088b12023-06-06 15:29:59 -0400115 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 )
TheCodedProfc62b0702023-06-06 15:31:14 -0400137 ];
TheCodedProf4a088b12023-06-06 15:29:59 -0400138 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 )
TheCodedProfc62b0702023-06-06 15:31:14 -0400160 );
TheCodedProf4a088b12023-06-06 15:29:59 -0400161 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500162 const modal = new ModalBuilder()
163 .setTitle("Edit Name and Description")
164 .setCustomId("editNameDescription")
TheCodedProfc62b0702023-06-06 15:31:14 -0400165 .addComponents(...components);
Skyler Greyda16adf2023-03-05 10:22:12 +0000166 const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
167 new ButtonBuilder()
168 .setCustomId("back")
169 .setLabel("Back")
170 .setStyle(ButtonStyle.Secondary)
171 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
172 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500173
Skyler Greyda16adf2023-03-05 10:22:12 +0000174 await i.showModal(modal);
TheCodedProff4facde2023-01-28 13:42:48 -0500175 await interaction.editReply({
176 embeds: [
177 new EmojiEmbed()
178 .setTitle("Role Menu")
179 .setDescription("Modal opened. If you can't see it, click back and try again.")
180 .setStatus("Success")
181 ],
182 components: [button]
183 });
184
185 let out: Discord.ModalSubmitInteraction | null;
186 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000187 out = (await modalInteractionCollector(m, interaction.user)) as Discord.ModalSubmitInteraction | null;
TheCodedProff4facde2023-01-28 13:42:48 -0500188 } catch (e) {
189 console.error(e);
190 out = null;
191 }
TheCodedProf4a088b12023-06-06 15:29:59 -0400192 if (!out) return [name, description, bounds];
193 if (out.isButton()) return [name, description, bounds];
TheCodedProff4facde2023-01-28 13:42:48 -0500194 name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
TheCodedProf4a088b12023-06-06 15:29:59 -0400195 description = out.fields.fields.find((f) => f.customId === "description")?.value;
196 if (addBounds) {
197 const min = parseInt(out.fields.fields.find((f) => f.customId === "min")?.value!);
198 const max = parseInt(out.fields.fields.find((f) => f.customId === "max")?.value!);
199 bounds = { min, max };
200 }
201 return [name, description, bounds];
Skyler Greyda16adf2023-03-05 10:22:12 +0000202};
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500203
TheCodedProf7a83f762023-03-06 17:17:00 -0500204const defaultRoleMenuData = {
TheCodedProf4a088b12023-06-06 15:29:59 -0400205 enabled: true,
pineafan72659cc2023-05-28 13:36:44 +0100206 name: "New Page",
207 description: "",
TheCodedProf7a83f762023-03-06 17:17:00 -0500208 min: 0,
pineafan72659cc2023-05-28 13:36:44 +0100209 max: 1,
TheCodedProf7a83f762023-03-06 17:17:00 -0500210 options: []
211};
212
Skyler Greyda16adf2023-03-05 10:22:12 +0000213const editRoleMenuPage = async (
214 interaction: StringSelectMenuInteraction | ButtonInteraction,
215 m: Message,
216 data?: ObjectSchema
217): Promise<ObjectSchema | null> => {
pineafan1e462ab2023-03-07 21:34:06 +0000218 if (!data) data = _.cloneDeep(defaultRoleMenuData);
Skyler Greyda16adf2023-03-05 10:22:12 +0000219 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
220 new ButtonBuilder()
221 .setCustomId("back")
222 .setLabel("Back")
223 .setStyle(ButtonStyle.Secondary)
224 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
225 new ButtonBuilder()
226 .setCustomId("edit")
227 .setLabel("Edit")
228 .setStyle(ButtonStyle.Primary)
229 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
230 new ButtonBuilder()
231 .setCustomId("addRole")
232 .setLabel("Add Role")
233 .setStyle(ButtonStyle.Secondary)
234 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
235 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500236
Skyler Greyda16adf2023-03-05 10:22:12 +0000237 let back = false;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500238 do {
pineafan72659cc2023-05-28 13:36:44 +0100239 const noRoles = data.options.length === 0;
240 const previewSelect = configToDropdown(
241 "Edit Roles",
TheCodedProfc62b0702023-06-06 15:31:14 -0400242 data.description
243 ? {
244 name: data.name,
245 description: data.description,
246 min: 1,
247 max: 1,
248 options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
249 }
250 : {
251 name: data.name,
252 min: 1,
253 max: 1,
254 options: noRoles ? [{ name: "Role 1", description: null, role: "No role set" }] : data.options
255 },
pineafan72659cc2023-05-28 13:36:44 +0100256 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;
TheCodedProfc62b0702023-06-06 15:31:14 -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 ? "" : "~~";
TheCodedProfc62b0702023-06-06 15:31:14 -0400526 return `> ${crossOut}**${key.name}:** ${
527 key.description ?? "*No description set*"
528 }${crossOut}`;
TheCodedProf920d7292023-06-05 11:02:32 -0400529 })
530 .join("\n")
531 : "")
532 );
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400533 if (currentObject.options.length > 0) {
TheCodedProf920d7292023-06-05 11:02:32 -0400534 pageSelect.addOptions(
535 currentObject.options.map((key: ObjectSchema, index) => {
536 return new StringSelectMenuOptionBuilder()
537 .setLabel(ellipsis(key.name, 50))
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400538 .setDescription(
539 ellipsis(
540 key.description?.length
541 ? key.description.length > 0
542 ? key.description
543 : "No description set"
544 : "No description set",
545 50
546 )
547 )
TheCodedProf4a088b12023-06-06 15:29:59 -0400548 .setValue((index + 1).toString());
TheCodedProf920d7292023-06-05 11:02:32 -0400549 })
550 );
551 } else {
552 pageSelect.setDisabled(true);
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400553 pageSelect.addOptions(
554 new StringSelectMenuOptionBuilder().setLabel("No role menu pages").setValue("none")
555 );
TheCodedProf920d7292023-06-05 11:02:32 -0400556 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500557 } else {
TheCodedProf920d7292023-06-05 11:02:32 -0400558 page = Math.max(Math.min(page, currentObject.options.length), 0);
559 current = currentObject.options[page - 1]!;
TheCodedProf4a088b12023-06-06 15:29:59 -0400560 (actionSelect as StringSelectMenuBuilder).addOptions(
561 new StringSelectMenuOptionBuilder()
562 .setLabel(current.enabled ? "Disable" : "Enable")
563 .setDescription("Enable or disable this page")
564 .setValue("switch")
565 .setEmoji(
566 getEmojiByName(
567 current.enabled ? "CONTROL.CROSS" : "CONTROL.TICK",
568 "id"
569 ) as APIMessageComponentEmoji
570 )
TheCodedProfc62b0702023-06-06 15:31:14 -0400571 );
Skyler Greyda16adf2023-03-05 10:22:12 +0000572 embed.setDescription(
573 `**Currently Editing:** ${current.name}\n\n` +
TheCodedProf4a088b12023-06-06 15:29:59 -0400574 `**Visible:** ${current.enabled ? `${tick} Yes` : `${cross} No`}\n\n` +
TheCodedProf920d7292023-06-05 11:02:32 -0400575 `**Description:**\n> ${current.description ?? "*No description set*"}\n` +
TheCodedProfc62b0702023-06-06 15:31:14 -0400576 `\n\n${createPageIndicator(
577 Object.keys(currentObject.options).length,
578 page - 1,
579 false,
580 Object.values(currentObject.options).map((o) => !(o.enabled ?? true))
581 )}`
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500582 );
583
584 pageSelect.addOptions(
TheCodedProf920d7292023-06-05 11:02:32 -0400585 currentObject.options.map((key: ObjectSchema, index) => {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500586 return new StringSelectMenuOptionBuilder()
587 .setLabel(ellipsis(key.name, 50))
TheCodedProffc8d6ba2023-06-05 11:16:35 -0400588 .setDescription(
589 ellipsis(
590 key.description?.length
591 ? key.description.length > 0
592 ? key.description
593 : "No description set"
594 : "No description set",
595 50
596 )
597 )
TheCodedProf4a088b12023-06-06 15:29:59 -0400598 .setValue((index + 1).toString());
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500599 })
600 );
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500601 }
602
Skyler Greyda16adf2023-03-05 10:22:12 +0000603 await interaction.editReply({
604 embeds: [embed],
605 components: [
TheCodedProf920d7292023-06-05 11:02:32 -0400606 page === 0
607 ? (actionSelect as ActionRowBuilder<ButtonBuilder>)
608 : new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
609 actionSelect as StringSelectMenuBuilder
610 ),
Skyler Greyda16adf2023-03-05 10:22:12 +0000611 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
TheCodedProf920d7292023-06-05 11:02:32 -0400612 buttonRow as ActionRowBuilder<ButtonBuilder>
Skyler Greyda16adf2023-03-05 10:22:12 +0000613 ]
614 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500615 let i: StringSelectMenuInteraction | ButtonInteraction;
616 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000617 i = (await m.awaitMessageComponent({
618 time: 300000,
619 filter: (i) =>
620 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
621 })) as ButtonInteraction | StringSelectMenuInteraction;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500622 } catch (e) {
623 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000624 continue;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500625 }
626
627 await i.deferUpdate();
628 if (i.isButton()) {
629 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000630 case "back": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500631 page--;
632 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000633 }
634 case "next": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500635 page++;
636 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000637 }
638 case "add": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000639 const newPage = await editRoleMenuPage(i, m);
pineafan44713d42023-06-03 20:29:49 +0100640 if (_.isEqual(newPage, defaultRoleMenuData)) {
641 break;
642 }
pineafan6f600f02023-06-03 20:27:07 +0100643 if (newPage) {
TheCodedProf920d7292023-06-05 11:02:32 -0400644 currentObject.options.push(newPage);
645 page = currentObject.options.length;
646 modified = true;
pineafan6f600f02023-06-03 20:27:07 +0100647 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500648 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000649 }
650 case "reorder": {
TheCodedProf920d7292023-06-05 11:02:32 -0400651 const reordered = await reorderRoleMenuPages(interaction, m, currentObject.options);
Skyler Greyda16adf2023-03-05 10:22:12 +0000652 if (!reordered) break;
TheCodedProf920d7292023-06-05 11:02:32 -0400653 currentObject.options = reordered;
654 modified = true;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500655 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000656 }
657 case "save": {
TheCodedProf920d7292023-06-05 11:02:32 -0400658 await client.database.guilds.write(interaction.guild.id, {
TheCodedProfc62b0702023-06-06 15:31:14 -0400659 roleMenu: currentObject
TheCodedProf920d7292023-06-05 11:02:32 -0400660 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500661 modified = false;
Skyler Grey16ecb172023-03-05 07:30:32 +0000662 await client.memory.forceUpdate(interaction.guild.id);
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500663 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000664 }
TheCodedProf920d7292023-06-05 11:02:32 -0400665 case "switch": {
666 currentObject.enabled = !currentObject.enabled;
667 modified = true;
668 break;
669 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500670 }
671 } else if (i.isStringSelectMenu()) {
672 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000673 case "action": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000674 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000675 case "edit": {
676 const edited = await editRoleMenuPage(i, m, current!);
Skyler Greyda16adf2023-03-05 10:22:12 +0000677 if (!edited) break;
TheCodedProf4a088b12023-06-06 15:29:59 -0400678 currentObject.options[page - 1] = edited;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500679 modified = true;
680 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000681 }
682 case "delete": {
TheCodedProf920d7292023-06-05 11:02:32 -0400683 if (page === 0 && currentObject.options.keys.length - 1 > 0) page++;
TheCodedProff4facde2023-01-28 13:42:48 -0500684 else page--;
TheCodedProf920d7292023-06-05 11:02:32 -0400685 currentObject.options.splice(page, 1);
686 modified = true;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500687 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000688 }
TheCodedProf4a088b12023-06-06 15:29:59 -0400689 case "switch": {
690 currentObject.options[page - 1]!.enabled = !currentObject.options[page - 1]!.enabled;
691 modified = true;
692 break;
693 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500694 }
695 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000696 }
697 case "page": {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500698 page = parseInt(i.values[0]!);
699 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000700 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500701 }
702 }
TheCodedProf01cba762023-02-18 15:55:05 -0500703 } while (!closed);
Skyler Greyda16adf2023-03-05 10:22:12 +0000704 await interaction.deleteReply();
pineafan63fc5e22022-08-04 22:04:10 +0100705};
pineafanda6e5342022-07-03 10:03:16 +0100706
TheCodedProff86ba092023-01-27 17:10:07 -0500707const check = (interaction: CommandInteraction, _partial: boolean = false) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100708 const member = interaction.member as Discord.GuildMember;
PineaFan0d06edc2023-01-17 22:10:31 +0000709 if (!member.permissions.has("ManageRoles"))
710 return "You must have the *Manage Roles* permission to use this command";
pineafanda6e5342022-07-03 10:03:16 +0100711 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100712};
pineafanda6e5342022-07-03 10:03:16 +0100713
714export { command };
715export { callback };
Skyler Grey75ea9172022-08-06 10:22:23 +0100716export { check };