blob: 3914a6c2cb6709e92f6f133a1f7edcb5830d492f [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ActionRowBuilder,
3 APIMessageComponentEmoji,
4 ButtonBuilder,
5 ButtonInteraction,
6 ButtonStyle,
7 Collection,
8 CommandInteraction,
9 GuildMember,
10 Message,
11 ModalBuilder,
12 ModalSubmitInteraction,
13 PermissionsBitField,
14 Role,
15 RoleSelectMenuBuilder,
16 RoleSelectMenuInteraction,
17 SlashCommandSubcommandBuilder,
18 StringSelectMenuBuilder,
19 StringSelectMenuInteraction,
20 StringSelectMenuOptionBuilder,
21 TextInputBuilder,
22 TextInputStyle
23} from "discord.js";
TheCodedProfa112f612023-01-28 18:06:45 -050024import client from "../../utils/client.js";
TheCodedProfb5e9d552023-01-29 15:43:26 -050025import createPageIndicator, { createVerticalTrack } from "../../utils/createPageIndicator.js";
26import { LoadingEmbed } from "../../utils/defaults.js";
27import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
28import getEmojiByName from "../../utils/getEmojiByName.js";
29import ellipsis from "../../utils/ellipsis.js";
30import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProfa112f612023-01-28 18:06:45 -050031
Skyler Greyda16adf2023-03-05 10:22:12 +000032const { renderRole } = client.logger;
TheCodedProf4f79da12023-01-31 16:50:37 -050033
TheCodedProfa112f612023-01-28 18:06:45 -050034const command = (builder: SlashCommandSubcommandBuilder) =>
Skyler Greyda16adf2023-03-05 10:22:12 +000035 builder.setName("tracks").setDescription("Manage the tracks for the server");
TheCodedProfa112f612023-01-28 18:06:45 -050036
TheCodedProfb5e9d552023-01-29 15:43:26 -050037interface ObjectSchema {
38 name: string;
39 retainPrevious: boolean;
40 nullable: boolean;
41 track: string[];
42 manageableBy: string[];
43}
44
Skyler Greyda16adf2023-03-05 10:22:12 +000045const editName = async (
46 i: ButtonInteraction,
47 interaction: StringSelectMenuInteraction | ButtonInteraction,
48 m: Message,
49 current?: string
50) => {
TheCodedProfb5e9d552023-01-29 15:43:26 -050051 let name = current ?? "";
52 const modal = new ModalBuilder()
53 .setTitle("Edit Name and Description")
54 .setCustomId("editNameDescription")
55 .addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +000056 new ActionRowBuilder<TextInputBuilder>().addComponents(
57 new TextInputBuilder()
58 .setLabel("Name")
59 .setCustomId("name")
60 .setPlaceholder("The name of the track (e.g. Moderators)")
61 .setStyle(TextInputStyle.Short)
62 .setValue(name)
63 .setRequired(true)
64 )
65 );
66 const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
67 new ButtonBuilder()
68 .setCustomId("back")
69 .setLabel("Back")
70 .setStyle(ButtonStyle.Secondary)
71 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
72 );
TheCodedProfb5e9d552023-01-29 15:43:26 -050073
Skyler Greyda16adf2023-03-05 10:22:12 +000074 await i.showModal(modal);
TheCodedProfb5e9d552023-01-29 15:43:26 -050075 await interaction.editReply({
76 embeds: [
77 new EmojiEmbed()
78 .setTitle("Tracks")
79 .setDescription("Modal opened. If you can't see it, click back and try again.")
80 .setStatus("Success")
81 ],
82 components: [button]
83 });
84
85 let out: ModalSubmitInteraction | null;
86 try {
Skyler Greyda16adf2023-03-05 10:22:12 +000087 out = (await modalInteractionCollector(m, interaction.user)) as ModalSubmitInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -050088 } catch (e) {
89 console.error(e);
90 out = null;
91 }
Skyler Greyda16adf2023-03-05 10:22:12 +000092 if (!out) return name;
TheCodedProfb5e9d552023-01-29 15:43:26 -050093 if (out.isButton()) return name;
TheCodedProfb5e9d552023-01-29 15:43:26 -050094 name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
Skyler Greyda16adf2023-03-05 10:22:12 +000095 return name;
96};
TheCodedProfb5e9d552023-01-29 15:43:26 -050097
Skyler Greyda16adf2023-03-05 10:22:12 +000098const reorderTracks = async (
99 interaction: ButtonInteraction,
100 m: Message,
101 roles: Collection<string, Role>,
102 currentObj: string[]
103) => {
104 const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
105 new StringSelectMenuBuilder()
106 .setCustomId("reorder")
107 .setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).")
108 .setMinValues(currentObj.length)
109 .setMaxValues(currentObj.length)
110 .addOptions(
111 currentObj.map((o, i) =>
112 new StringSelectMenuOptionBuilder().setLabel(roles.get(o)!.name).setValue(i.toString())
TheCodedProfb5e9d552023-01-29 15:43:26 -0500113 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000114 )
115 );
116 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
117 new ButtonBuilder()
118 .setCustomId("back")
119 .setLabel("Back")
120 .setStyle(ButtonStyle.Secondary)
121 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
122 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500123 await interaction.editReply({
124 embeds: [
125 new EmojiEmbed()
126 .setTitle("Tracks")
127 .setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).")
128 .setStatus("Success")
129 ],
130 components: [reorderRow, buttonRow]
131 });
132 let out: StringSelectMenuInteraction | ButtonInteraction | null;
133 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000134 out = (await m.awaitMessageComponent({
TheCodedProfb5e9d552023-01-29 15:43:26 -0500135 filter: (i) => i.channel!.id === interaction.channel!.id,
136 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000137 })) as StringSelectMenuInteraction | ButtonInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500138 } catch (e) {
139 console.error(e);
140 out = null;
141 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000142 if (!out) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500143 out.deferUpdate();
144 if (out.isButton()) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500145 const values = out.values;
146
147 const newOrder: string[] = currentObj.map((_, i) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000148 const index = values.findIndex((v) => v === i.toString());
TheCodedProfb5e9d552023-01-29 15:43:26 -0500149 return currentObj[index];
150 }) as string[];
151
152 return newOrder;
Skyler Greyda16adf2023-03-05 10:22:12 +0000153};
TheCodedProfb5e9d552023-01-29 15:43:26 -0500154
Skyler Greyda16adf2023-03-05 10:22:12 +0000155const editTrack = async (
156 interaction: ButtonInteraction | StringSelectMenuInteraction,
157 message: Message,
158 roles: Collection<string, Role>,
159 current?: ObjectSchema
160) => {
TheCodedProf4f79da12023-01-31 16:50:37 -0500161 const isAdmin = (interaction.member!.permissions as PermissionsBitField).has("Administrator");
Skyler Greyda16adf2023-03-05 10:22:12 +0000162 if (!current) {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500163 current = {
164 name: "",
165 retainPrevious: false,
TheCodedProf48865eb2023-03-05 15:25:25 -0500166 nullable: true,
TheCodedProfb5e9d552023-01-29 15:43:26 -0500167 track: [],
168 manageableBy: []
Skyler Greyda16adf2023-03-05 10:22:12 +0000169 };
TheCodedProfb5e9d552023-01-29 15:43:26 -0500170 }
TheCodedProf4f79da12023-01-31 16:50:37 -0500171
Skyler Greyda16adf2023-03-05 10:22:12 +0000172 const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
173 new RoleSelectMenuBuilder().setCustomId("addRole").setPlaceholder("Select a role to add").setDisabled(!isAdmin)
174 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500175 let closed = false;
TheCodedProf48865eb2023-03-05 15:25:25 -0500176 let previousMessage = "";
TheCodedProfb5e9d552023-01-29 15:43:26 -0500177 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000178 const editableRoles: string[] = current.track
179 .map((r) => {
180 if (
181 !(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) ||
182 interaction.user.id === interaction.guild?.ownerId
183 )
184 return roles.get(r)!.name;
185 })
186 .filter((v) => v !== undefined) as string[];
187 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
188 new StringSelectMenuBuilder()
189 .setCustomId("removeRole")
190 .setPlaceholder("Select a role to remove")
191 .setDisabled(!isAdmin)
192 .addOptions(
193 editableRoles.map((r, i) => {
194 return new StringSelectMenuOptionBuilder().setLabel(r).setValue(i.toString());
195 })
196 )
197 );
198 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
199 new ButtonBuilder()
200 .setCustomId("back")
201 .setLabel("Back")
202 .setStyle(ButtonStyle.Secondary)
203 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
204 new ButtonBuilder()
205 .setCustomId("edit")
206 .setLabel("Edit Name")
207 .setStyle(ButtonStyle.Primary)
208 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
209 new ButtonBuilder()
210 .setCustomId("reorder")
211 .setLabel("Reorder")
212 .setDisabled(!isAdmin)
213 .setStyle(ButtonStyle.Primary)
214 .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
215 new ButtonBuilder()
216 .setCustomId("retainPrevious")
217 .setLabel("Retain Previous")
218 .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger)
219 .setEmoji(
220 getEmojiByName(
221 "CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"),
222 "id"
223 ) as APIMessageComponentEmoji
224 ),
225 new ButtonBuilder()
226 .setCustomId("nullable")
227 .setLabel(`Role ${current.nullable ? "Not " : ""}Required`)
TheCodedProf48865eb2023-03-05 15:25:25 -0500228 .setStyle(current.nullable ? ButtonStyle.Danger : ButtonStyle.Success)
Skyler Greyda16adf2023-03-05 10:22:12 +0000229 .setEmoji(
TheCodedProf48865eb2023-03-05 15:25:25 -0500230 getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"), "id") as APIMessageComponentEmoji
Skyler Greyda16adf2023-03-05 10:22:12 +0000231 )
TheCodedProf4f79da12023-01-31 16:50:37 -0500232 );
233
PineaFanb0d0c242023-02-05 10:59:45 +0000234 const allowed: boolean[] = [];
TheCodedProfb5e9d552023-01-29 15:43:26 -0500235 for (const role of current.track) {
236 const disabled: boolean =
237 roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
Skyler Greyda16adf2023-03-05 10:22:12 +0000238 allowed.push(disabled);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500239 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000240 const mapped = current.track.map((role) => roles.find((aRole) => aRole.id === role)!);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500241
242 const embed = new EmojiEmbed()
243 .setTitle("Tracks")
244 .setDescription(
245 `**Currently Editing:** ${current.name}\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000246 `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${
247 current.nullable ? "don't " : ""
248 }need a role in this track\n` +
249 `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${
250 current.retainPrevious ? "" : "don't "
Skyler Grey67691762023-03-06 09:58:19 +0000251 }keep all roles below their current highest\n\n` +
252 (previousMessage ? previousMessage + "\n\n" : "") +
Skyler Greyda16adf2023-03-05 10:22:12 +0000253 createVerticalTrack(
254 mapped.map((role) => renderRole(role)),
255 new Array(current.track.length).fill(false),
256 allowed
257 )
TheCodedProfb5e9d552023-01-29 15:43:26 -0500258 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000259 .setStatus("Success");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500260
Skyler Greyda16adf2023-03-05 10:22:12 +0000261 const comps: ActionRowBuilder<RoleSelectMenuBuilder | ButtonBuilder | StringSelectMenuBuilder>[] = [
262 roleSelect,
263 buttons
264 ];
265 if (current.track.length >= 1) comps.splice(1, 0, selectMenu);
TheCodedProfd0a166d2023-02-19 00:04:53 -0500266
Skyler Greyda16adf2023-03-05 10:22:12 +0000267 interaction.editReply({ embeds: [embed], components: comps });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500268
269 let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
270
271 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000272 out = (await message.awaitMessageComponent({
TheCodedProfb5e9d552023-01-29 15:43:26 -0500273 filter: (i) => i.channel!.id === interaction.channel!.id,
274 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000275 })) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500276 } catch (e) {
277 console.error(e);
278 out = null;
279 }
280
Skyler Greyda16adf2023-03-05 10:22:12 +0000281 if (!out) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500282 if (out.isButton()) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000283 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000284 case "back": {
TheCodedProfd0a166d2023-02-19 00:04:53 -0500285 out.deferUpdate();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500286 closed = true;
287 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000288 }
289 case "edit": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500290 current.name = (await editName(out, interaction, message, current.name))!;
291 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000292 }
293 case "reorder": {
TheCodedProfd0a166d2023-02-19 00:04:53 -0500294 out.deferUpdate();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500295 current.track = (await reorderTracks(out, message, roles, current.track))!;
TheCodedProf4f79da12023-01-31 16:50:37 -0500296 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000297 }
298 case "retainPrevious": {
TheCodedProfd0a166d2023-02-19 00:04:53 -0500299 out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500300 current.retainPrevious = !current.retainPrevious;
301 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000302 }
303 case "nullable": {
TheCodedProfd0a166d2023-02-19 00:04:53 -0500304 out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500305 current.nullable = !current.nullable;
306 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000307 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500308 }
309 } else if (out.isStringSelectMenu()) {
310 out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000311 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000312 case "removeRole": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000313 const index = current.track.findIndex(
314 (v) => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]
315 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500316 current.track.splice(index, 1);
317 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000318 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500319 }
320 } else {
TheCodedProf48865eb2023-03-05 15:25:25 -0500321 out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000322 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000323 case "addRole": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500324 const role = out.values![0]!;
TheCodedProf48865eb2023-03-05 15:25:25 -0500325 const roleObj = roles.get(role)!;
Skyler Grey67691762023-03-06 09:58:19 +0000326 if (roleObj.position >= (interaction.member as GuildMember).roles.highest.position) {
TheCodedProf48865eb2023-03-05 15:25:25 -0500327 previousMessage = "You can't add a role that is higher than your highest role.";
TheCodedProfd0a166d2023-02-19 00:04:53 -0500328 } else {
TheCodedProf48865eb2023-03-05 15:25:25 -0500329 if (!current.track.includes(role)) {
330 current.track.push(role);
TheCodedProf48865eb2023-03-05 15:25:25 -0500331 } else {
332 previousMessage = "That role is already on this track";
333 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500334 }
TheCodedProfb7a7b992023-03-05 16:11:59 -0500335 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500336 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000337 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500338 }
339 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000340 } while (!closed);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500341 return current;
Skyler Greyda16adf2023-03-05 10:22:12 +0000342};
TheCodedProfa112f612023-01-28 18:06:45 -0500343
344const callback = async (interaction: CommandInteraction) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000345 const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500346 const config = await client.database.guilds.read(interaction.guild!.id);
347 const tracks: ObjectSchema[] = config.tracks;
348 const roles = await interaction.guild!.roles.fetch();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500349
350 let page = 0;
351 let closed = false;
352 let modified = false;
353
354 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000355 const embed = new EmojiEmbed().setTitle("Track Settings").setEmoji("TRACKS.ICON").setStatus("Success");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500356 const noTracks = config.tracks.length === 0;
357 let current: ObjectSchema;
358
Skyler Greyda16adf2023-03-05 10:22:12 +0000359 const pageSelect = new StringSelectMenuBuilder().setCustomId("page").setPlaceholder("Select a track to manage");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500360 const actionSelect = new StringSelectMenuBuilder()
361 .setCustomId("action")
362 .setPlaceholder("Perform an action")
363 .addOptions(
364 new StringSelectMenuOptionBuilder()
365 .setLabel("Edit")
366 .setDescription("Edit this track")
367 .setValue("edit")
368 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
369 new StringSelectMenuOptionBuilder()
370 .setLabel("Delete")
371 .setDescription("Delete this track")
372 .setValue("delete")
373 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
TheCodedProfb5e9d552023-01-29 15:43:26 -0500374 );
Skyler Greyda16adf2023-03-05 10:22:12 +0000375 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
376 new ButtonBuilder()
377 .setCustomId("back")
378 .setStyle(ButtonStyle.Primary)
379 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
380 .setDisabled(page === 0),
381 new ButtonBuilder()
382 .setCustomId("next")
383 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
384 .setStyle(ButtonStyle.Primary)
TheCodedProfe92b9b52023-03-06 17:07:34 -0500385 .setDisabled(page === tracks.length - 1 || noTracks),
Skyler Greyda16adf2023-03-05 10:22:12 +0000386 new ButtonBuilder()
387 .setCustomId("add")
388 .setLabel("New Track")
389 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
390 .setStyle(ButtonStyle.Secondary)
391 .setDisabled(Object.keys(tracks).length >= 24),
392 new ButtonBuilder()
393 .setCustomId("save")
394 .setLabel("Save")
395 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
396 .setStyle(ButtonStyle.Success)
397 .setDisabled(!modified)
398 );
399 if (noTracks) {
400 embed.setDescription(
401 "No tracks have been set up yet. Use the button below to add one.\n\n" +
402 createPageIndicator(1, 1, undefined, true)
TheCodedProfb5e9d552023-01-29 15:43:26 -0500403 );
404 pageSelect.setDisabled(true);
405 actionSelect.setDisabled(true);
Skyler Greyda16adf2023-03-05 10:22:12 +0000406 pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No tracks").setValue("none"));
TheCodedProfb5e9d552023-01-29 15:43:26 -0500407 } else {
408 page = Math.min(page, Object.keys(tracks).length - 1);
409 current = tracks[page]!;
Skyler Greyda16adf2023-03-05 10:22:12 +0000410 const mapped = current.track.map((role) => roles.find((aRole) => aRole.id === role)!);
411 embed.setDescription(
412 `**Currently Editing:** ${current.name}\n\n` +
413 `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${
414 current.nullable ? "don't " : ""
415 }need a role in this track\n` +
416 `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${
417 current.retainPrevious ? "" : "don't "
418 }keep all roles below their current highest\n\n` +
419 createVerticalTrack(
420 mapped.map((role) => renderRole(role)),
421 new Array(current.track.length).fill(false)
422 ) +
423 `\n${createPageIndicator(config.tracks.length, page)}`
TheCodedProfb5e9d552023-01-29 15:43:26 -0500424 );
425
426 pageSelect.addOptions(
427 tracks.map((key: ObjectSchema, index) => {
428 return new StringSelectMenuOptionBuilder()
429 .setLabel(ellipsis(key.name, 50))
430 .setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50))
431 .setValue(index.toString());
432 })
433 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500434 }
435
Skyler Greyda16adf2023-03-05 10:22:12 +0000436 await interaction.editReply({
437 embeds: [embed],
438 components: [
439 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
440 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
441 buttonRow
442 ]
443 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500444 let i: StringSelectMenuInteraction | ButtonInteraction;
445 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000446 i = (await m.awaitMessageComponent({
447 time: 300000,
448 filter: (i) =>
449 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
450 })) as ButtonInteraction | StringSelectMenuInteraction;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500451 } catch (e) {
452 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000453 continue;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500454 }
455
456 await i.deferUpdate();
457 if (i.isButton()) {
458 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000459 case "back": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500460 page--;
461 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000462 }
463 case "next": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500464 page++;
465 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000466 }
467 case "add": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000468 const newPage = await editTrack(i, m, roles);
469 if (!newPage) break;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500470 tracks.push();
471 page = tracks.length - 1;
472 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000473 }
474 case "save": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000475 client.database.guilds.write(interaction.guild!.id, { tracks: tracks });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500476 modified = false;
Skyler Grey16ecb172023-03-05 07:30:32 +0000477 await client.memory.forceUpdate(interaction.guild!.id);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500478 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000479 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500480 }
481 } else if (i.isStringSelectMenu()) {
482 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000483 case "action": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000484 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000485 case "edit": {
486 const edited = await editTrack(i, m, roles, current!);
Skyler Greyda16adf2023-03-05 10:22:12 +0000487 if (!edited) break;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500488 tracks[page] = edited;
489 modified = true;
490 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000491 }
492 case "delete": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000493 if (page === 0 && tracks.keys.length - 1 > 0) page++;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500494 else page--;
495 tracks.splice(page, 1);
496 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000497 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500498 }
499 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000500 }
501 case "page": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500502 page = parseInt(i.values[0]!);
503 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000504 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500505 }
506 }
TheCodedProf01cba762023-02-18 15:55:05 -0500507 } while (!closed);
Skyler Greyda16adf2023-03-05 10:22:12 +0000508 await interaction.deleteReply();
509};
TheCodedProfa112f612023-01-28 18:06:45 -0500510
511const check = (interaction: CommandInteraction, _partial: boolean = false) => {
512 const member = interaction.member as GuildMember;
513 if (!member.permissions.has("ManageRoles"))
514 return "You must have the *Manage Server* permission to use this command";
515 return true;
516};
517
518export { command };
519export { callback };
520export { check };