blob: 60a7eaeb78e34a2c7a6462c8fd0a9df4f0338394 [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";
TheCodedProf7a83f762023-03-06 17:17:00 -050031import _ from "lodash";
TheCodedProfa112f612023-01-28 18:06:45 -050032
Skyler Greyda16adf2023-03-05 10:22:12 +000033const { renderRole } = client.logger;
TheCodedProf4f79da12023-01-31 16:50:37 -050034
TheCodedProfa112f612023-01-28 18:06:45 -050035const command = (builder: SlashCommandSubcommandBuilder) =>
Skyler Greyda16adf2023-03-05 10:22:12 +000036 builder.setName("tracks").setDescription("Manage the tracks for the server");
TheCodedProfa112f612023-01-28 18:06:45 -050037
TheCodedProfb5e9d552023-01-29 15:43:26 -050038interface ObjectSchema {
39 name: string;
40 retainPrevious: boolean;
41 nullable: boolean;
42 track: string[];
43 manageableBy: string[];
44}
45
Skyler Greyda16adf2023-03-05 10:22:12 +000046const editName = async (
47 i: ButtonInteraction,
48 interaction: StringSelectMenuInteraction | ButtonInteraction,
49 m: Message,
50 current?: string
51) => {
TheCodedProfb5e9d552023-01-29 15:43:26 -050052 let name = current ?? "";
53 const modal = new ModalBuilder()
54 .setTitle("Edit Name and Description")
55 .setCustomId("editNameDescription")
56 .addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +000057 new ActionRowBuilder<TextInputBuilder>().addComponents(
58 new TextInputBuilder()
59 .setLabel("Name")
60 .setCustomId("name")
61 .setPlaceholder("The name of the track (e.g. Moderators)")
62 .setStyle(TextInputStyle.Short)
63 .setValue(name)
64 .setRequired(true)
65 )
66 );
67 const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
68 new ButtonBuilder()
69 .setCustomId("back")
70 .setLabel("Back")
71 .setStyle(ButtonStyle.Secondary)
72 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
73 );
TheCodedProfb5e9d552023-01-29 15:43:26 -050074
Skyler Greyda16adf2023-03-05 10:22:12 +000075 await i.showModal(modal);
TheCodedProfb5e9d552023-01-29 15:43:26 -050076 await interaction.editReply({
77 embeds: [
78 new EmojiEmbed()
79 .setTitle("Tracks")
80 .setDescription("Modal opened. If you can't see it, click back and try again.")
81 .setStatus("Success")
82 ],
83 components: [button]
84 });
85
86 let out: ModalSubmitInteraction | null;
87 try {
Skyler Greyda16adf2023-03-05 10:22:12 +000088 out = (await modalInteractionCollector(m, interaction.user)) as ModalSubmitInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -050089 } catch (e) {
90 console.error(e);
91 out = null;
92 }
Skyler Greyda16adf2023-03-05 10:22:12 +000093 if (!out) return name;
TheCodedProfb5e9d552023-01-29 15:43:26 -050094 if (out.isButton()) return name;
TheCodedProfb5e9d552023-01-29 15:43:26 -050095 name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name;
Skyler Greyda16adf2023-03-05 10:22:12 +000096 return name;
97};
TheCodedProfb5e9d552023-01-29 15:43:26 -050098
Skyler Greyda16adf2023-03-05 10:22:12 +000099const reorderTracks = async (
TheCodedProf7a83f762023-03-06 17:17:00 -0500100 interaction: ButtonInteraction | StringSelectMenuInteraction,
101 buttonInteraction: ButtonInteraction,
Skyler Greyda16adf2023-03-05 10:22:12 +0000102 m: Message,
103 roles: Collection<string, Role>,
104 currentObj: string[]
105) => {
106 const reorderRow = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
107 new StringSelectMenuBuilder()
108 .setCustomId("reorder")
109 .setPlaceholder("Select all roles in the order you want users to gain them (Lowest to highest rank).")
110 .setMinValues(currentObj.length)
111 .setMaxValues(currentObj.length)
112 .addOptions(
113 currentObj.map((o, i) =>
114 new StringSelectMenuOptionBuilder().setLabel(roles.get(o)!.name).setValue(i.toString())
TheCodedProfb5e9d552023-01-29 15:43:26 -0500115 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000116 )
117 );
118 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
119 new ButtonBuilder()
120 .setCustomId("back")
121 .setLabel("Back")
122 .setStyle(ButtonStyle.Secondary)
123 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
124 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500125 await interaction.editReply({
126 embeds: [
127 new EmojiEmbed()
128 .setTitle("Tracks")
129 .setDescription("Select all roles in the order you want users to gain them (Lowest to highest rank).")
130 .setStatus("Success")
131 ],
132 components: [reorderRow, buttonRow]
133 });
134 let out: StringSelectMenuInteraction | ButtonInteraction | null;
135 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000136 out = (await m.awaitMessageComponent({
TheCodedProf7a83f762023-03-06 17:17:00 -0500137 filter: (i) => i.channel!.id === buttonInteraction.channel!.id,
TheCodedProfb5e9d552023-01-29 15:43:26 -0500138 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000139 })) as StringSelectMenuInteraction | ButtonInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500140 } catch (e) {
141 console.error(e);
142 out = null;
143 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000144 if (!out) return;
Skyler Greyf4f21c42023-03-08 14:36:29 +0000145 await out.deferUpdate();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500146 if (out.isButton()) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500147 const values = out.values;
148
149 const newOrder: string[] = currentObj.map((_, i) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000150 const index = values.findIndex((v) => v === i.toString());
TheCodedProfb5e9d552023-01-29 15:43:26 -0500151 return currentObj[index];
152 }) as string[];
153
154 return newOrder;
Skyler Greyda16adf2023-03-05 10:22:12 +0000155};
TheCodedProfb5e9d552023-01-29 15:43:26 -0500156
TheCodedProf7a83f762023-03-06 17:17:00 -0500157const defaultTrackData = {
158 name: "",
159 retainPrevious: false,
160 nullable: true,
161 track: [],
162 manageableBy: []
163};
164
Skyler Greyda16adf2023-03-05 10:22:12 +0000165const editTrack = async (
166 interaction: ButtonInteraction | StringSelectMenuInteraction,
167 message: Message,
168 roles: Collection<string, Role>,
169 current?: ObjectSchema
170) => {
TheCodedProf4f79da12023-01-31 16:50:37 -0500171 const isAdmin = (interaction.member!.permissions as PermissionsBitField).has("Administrator");
Skyler Greyda16adf2023-03-05 10:22:12 +0000172 if (!current) {
TheCodedProf7a83f762023-03-06 17:17:00 -0500173 current = _.cloneDeep(defaultTrackData);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500174 }
TheCodedProf4f79da12023-01-31 16:50:37 -0500175
Skyler Greyda16adf2023-03-05 10:22:12 +0000176 const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
177 new RoleSelectMenuBuilder().setCustomId("addRole").setPlaceholder("Select a role to add").setDisabled(!isAdmin)
178 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500179 let closed = false;
TheCodedProf48865eb2023-03-05 15:25:25 -0500180 let previousMessage = "";
TheCodedProfb5e9d552023-01-29 15:43:26 -0500181 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000182 const editableRoles: string[] = current.track
183 .map((r) => {
184 if (
185 !(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) ||
186 interaction.user.id === interaction.guild?.ownerId
187 )
188 return roles.get(r)!.name;
189 })
190 .filter((v) => v !== undefined) as string[];
191 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
192 new StringSelectMenuBuilder()
193 .setCustomId("removeRole")
194 .setPlaceholder("Select a role to remove")
195 .setDisabled(!isAdmin)
196 .addOptions(
197 editableRoles.map((r, i) => {
198 return new StringSelectMenuOptionBuilder().setLabel(r).setValue(i.toString());
199 })
200 )
201 );
202 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
203 new ButtonBuilder()
204 .setCustomId("back")
205 .setLabel("Back")
206 .setStyle(ButtonStyle.Secondary)
207 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
208 new ButtonBuilder()
209 .setCustomId("edit")
210 .setLabel("Edit Name")
211 .setStyle(ButtonStyle.Primary)
212 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
213 new ButtonBuilder()
214 .setCustomId("reorder")
215 .setLabel("Reorder")
216 .setDisabled(!isAdmin)
217 .setStyle(ButtonStyle.Primary)
218 .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
219 new ButtonBuilder()
220 .setCustomId("retainPrevious")
221 .setLabel("Retain Previous")
222 .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger)
223 .setEmoji(
224 getEmojiByName(
225 "CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"),
226 "id"
227 ) as APIMessageComponentEmoji
228 ),
229 new ButtonBuilder()
230 .setCustomId("nullable")
231 .setLabel(`Role ${current.nullable ? "Not " : ""}Required`)
TheCodedProf48865eb2023-03-05 15:25:25 -0500232 .setStyle(current.nullable ? ButtonStyle.Danger : ButtonStyle.Success)
Skyler Greyda16adf2023-03-05 10:22:12 +0000233 .setEmoji(
TheCodedProf48865eb2023-03-05 15:25:25 -0500234 getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"), "id") as APIMessageComponentEmoji
Skyler Greyda16adf2023-03-05 10:22:12 +0000235 )
TheCodedProf4f79da12023-01-31 16:50:37 -0500236 );
237
PineaFanb0d0c242023-02-05 10:59:45 +0000238 const allowed: boolean[] = [];
TheCodedProfb5e9d552023-01-29 15:43:26 -0500239 for (const role of current.track) {
240 const disabled: boolean =
241 roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
Skyler Greyda16adf2023-03-05 10:22:12 +0000242 allowed.push(disabled);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500243 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000244 const mapped = current.track.map((role) => roles.find((aRole) => aRole.id === role)!);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500245
246 const embed = new EmojiEmbed()
247 .setTitle("Tracks")
248 .setDescription(
249 `**Currently Editing:** ${current.name}\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000250 `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${
251 current.nullable ? "don't " : ""
252 }need a role in this track\n` +
253 `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${
254 current.retainPrevious ? "" : "don't "
Skyler Grey67691762023-03-06 09:58:19 +0000255 }keep all roles below their current highest\n\n` +
256 (previousMessage ? previousMessage + "\n\n" : "") +
Skyler Greyda16adf2023-03-05 10:22:12 +0000257 createVerticalTrack(
258 mapped.map((role) => renderRole(role)),
259 new Array(current.track.length).fill(false),
260 allowed
261 )
TheCodedProfb5e9d552023-01-29 15:43:26 -0500262 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000263 .setStatus("Success");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500264
Skyler Greyda16adf2023-03-05 10:22:12 +0000265 const comps: ActionRowBuilder<RoleSelectMenuBuilder | ButtonBuilder | StringSelectMenuBuilder>[] = [
266 roleSelect,
267 buttons
268 ];
269 if (current.track.length >= 1) comps.splice(1, 0, selectMenu);
TheCodedProfd0a166d2023-02-19 00:04:53 -0500270
Skyler Greyf4f21c42023-03-08 14:36:29 +0000271 await interaction.editReply({ embeds: [embed], components: comps });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500272
273 let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
274
275 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000276 out = (await message.awaitMessageComponent({
TheCodedProfb5e9d552023-01-29 15:43:26 -0500277 filter: (i) => i.channel!.id === interaction.channel!.id,
278 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000279 })) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500280 } catch (e) {
281 console.error(e);
282 out = null;
283 }
284
Skyler Greyda16adf2023-03-05 10:22:12 +0000285 if (!out) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500286 if (out.isButton()) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000287 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000288 case "back": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000289 await out.deferUpdate();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500290 closed = true;
291 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000292 }
293 case "edit": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500294 current.name = (await editName(out, interaction, message, current.name))!;
295 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000296 }
297 case "reorder": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000298 await out.deferUpdate();
TheCodedProf7a83f762023-03-06 17:17:00 -0500299 current.track = (await reorderTracks(interaction, out, message, roles, current.track))!;
TheCodedProf4f79da12023-01-31 16:50:37 -0500300 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000301 }
302 case "retainPrevious": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000303 await out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500304 current.retainPrevious = !current.retainPrevious;
305 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000306 }
307 case "nullable": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000308 await out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500309 current.nullable = !current.nullable;
310 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000311 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500312 }
313 } else if (out.isStringSelectMenu()) {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000314 await out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000315 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000316 case "removeRole": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000317 const index = current.track.findIndex(
318 (v) => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]
319 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500320 current.track.splice(index, 1);
321 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000322 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500323 }
324 } else {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000325 await out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000326 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000327 case "addRole": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500328 const role = out.values![0]!;
TheCodedProf48865eb2023-03-05 15:25:25 -0500329 const roleObj = roles.get(role)!;
Skyler Grey67691762023-03-06 09:58:19 +0000330 if (roleObj.position >= (interaction.member as GuildMember).roles.highest.position) {
TheCodedProf48865eb2023-03-05 15:25:25 -0500331 previousMessage = "You can't add a role that is higher than your highest role.";
TheCodedProfd0a166d2023-02-19 00:04:53 -0500332 } else {
TheCodedProf48865eb2023-03-05 15:25:25 -0500333 if (!current.track.includes(role)) {
334 current.track.push(role);
TheCodedProf48865eb2023-03-05 15:25:25 -0500335 } else {
336 previousMessage = "That role is already on this track";
337 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500338 }
TheCodedProfb7a7b992023-03-05 16:11:59 -0500339 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500340 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000341 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500342 }
343 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000344 } while (!closed);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500345 return current;
Skyler Greyda16adf2023-03-05 10:22:12 +0000346};
TheCodedProfa112f612023-01-28 18:06:45 -0500347
348const callback = async (interaction: CommandInteraction) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000349 const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500350 const config = await client.database.guilds.read(interaction.guild!.id);
351 const tracks: ObjectSchema[] = config.tracks;
352 const roles = await interaction.guild!.roles.fetch();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500353
354 let page = 0;
355 let closed = false;
356 let modified = false;
357
358 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000359 const embed = new EmojiEmbed().setTitle("Track Settings").setEmoji("TRACKS.ICON").setStatus("Success");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500360 const noTracks = config.tracks.length === 0;
361 let current: ObjectSchema;
362
Skyler Greyda16adf2023-03-05 10:22:12 +0000363 const pageSelect = new StringSelectMenuBuilder().setCustomId("page").setPlaceholder("Select a track to manage");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500364 const actionSelect = new StringSelectMenuBuilder()
365 .setCustomId("action")
366 .setPlaceholder("Perform an action")
367 .addOptions(
368 new StringSelectMenuOptionBuilder()
369 .setLabel("Edit")
370 .setDescription("Edit this track")
371 .setValue("edit")
372 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
373 new StringSelectMenuOptionBuilder()
374 .setLabel("Delete")
375 .setDescription("Delete this track")
376 .setValue("delete")
377 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
TheCodedProfb5e9d552023-01-29 15:43:26 -0500378 );
Skyler Greyda16adf2023-03-05 10:22:12 +0000379 const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
380 new ButtonBuilder()
381 .setCustomId("back")
382 .setStyle(ButtonStyle.Primary)
383 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
384 .setDisabled(page === 0),
385 new ButtonBuilder()
386 .setCustomId("next")
387 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
388 .setStyle(ButtonStyle.Primary)
TheCodedProfe92b9b52023-03-06 17:07:34 -0500389 .setDisabled(page === tracks.length - 1 || noTracks),
Skyler Greyda16adf2023-03-05 10:22:12 +0000390 new ButtonBuilder()
391 .setCustomId("add")
392 .setLabel("New Track")
393 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
394 .setStyle(ButtonStyle.Secondary)
395 .setDisabled(Object.keys(tracks).length >= 24),
396 new ButtonBuilder()
397 .setCustomId("save")
398 .setLabel("Save")
399 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
400 .setStyle(ButtonStyle.Success)
401 .setDisabled(!modified)
402 );
403 if (noTracks) {
404 embed.setDescription(
405 "No tracks have been set up yet. Use the button below to add one.\n\n" +
406 createPageIndicator(1, 1, undefined, true)
TheCodedProfb5e9d552023-01-29 15:43:26 -0500407 );
408 pageSelect.setDisabled(true);
409 actionSelect.setDisabled(true);
Skyler Greyda16adf2023-03-05 10:22:12 +0000410 pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No tracks").setValue("none"));
TheCodedProfb5e9d552023-01-29 15:43:26 -0500411 } else {
412 page = Math.min(page, Object.keys(tracks).length - 1);
413 current = tracks[page]!;
Skyler Greyda16adf2023-03-05 10:22:12 +0000414 const mapped = current.track.map((role) => roles.find((aRole) => aRole.id === role)!);
415 embed.setDescription(
416 `**Currently Editing:** ${current.name}\n\n` +
417 `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${
418 current.nullable ? "don't " : ""
419 }need a role in this track\n` +
420 `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${
421 current.retainPrevious ? "" : "don't "
422 }keep all roles below their current highest\n\n` +
423 createVerticalTrack(
424 mapped.map((role) => renderRole(role)),
425 new Array(current.track.length).fill(false)
426 ) +
427 `\n${createPageIndicator(config.tracks.length, page)}`
TheCodedProfb5e9d552023-01-29 15:43:26 -0500428 );
429
430 pageSelect.addOptions(
431 tracks.map((key: ObjectSchema, index) => {
432 return new StringSelectMenuOptionBuilder()
433 .setLabel(ellipsis(key.name, 50))
434 .setDescription(ellipsis(roles.get(key.track[0]!)?.name!, 50))
435 .setValue(index.toString());
436 })
437 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500438 }
439
Skyler Greyda16adf2023-03-05 10:22:12 +0000440 await interaction.editReply({
441 embeds: [embed],
442 components: [
443 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
444 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
445 buttonRow
446 ]
447 });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500448 let i: StringSelectMenuInteraction | ButtonInteraction;
449 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000450 i = (await m.awaitMessageComponent({
451 time: 300000,
452 filter: (i) =>
453 i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId
454 })) as ButtonInteraction | StringSelectMenuInteraction;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500455 } catch (e) {
456 closed = true;
PineaFanb0d0c242023-02-05 10:59:45 +0000457 continue;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500458 }
459
460 await i.deferUpdate();
461 if (i.isButton()) {
462 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000463 case "back": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500464 page--;
465 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000466 }
467 case "next": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500468 page++;
469 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000470 }
471 case "add": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000472 const newPage = await editTrack(i, m, roles);
TheCodedProf7a83f762023-03-06 17:17:00 -0500473 if (_.isEqual(newPage, defaultTrackData)) break;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500474 tracks.push();
475 page = tracks.length - 1;
476 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000477 }
478 case "save": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000479 await client.database.guilds.write(interaction.guild!.id, { tracks: tracks });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500480 modified = false;
Skyler Grey16ecb172023-03-05 07:30:32 +0000481 await client.memory.forceUpdate(interaction.guild!.id);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500482 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000483 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500484 }
485 } else if (i.isStringSelectMenu()) {
486 switch (i.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000487 case "action": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000488 switch (i.values[0]) {
PineaFanb0d0c242023-02-05 10:59:45 +0000489 case "edit": {
490 const edited = await editTrack(i, m, roles, current!);
Skyler Greyda16adf2023-03-05 10:22:12 +0000491 if (!edited) break;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500492 tracks[page] = edited;
493 modified = true;
494 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000495 }
496 case "delete": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000497 if (page === 0 && tracks.keys.length - 1 > 0) page++;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500498 else page--;
499 tracks.splice(page, 1);
500 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000501 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500502 }
503 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000504 }
505 case "page": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500506 page = parseInt(i.values[0]!);
507 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000508 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500509 }
510 }
TheCodedProf01cba762023-02-18 15:55:05 -0500511 } while (!closed);
Skyler Greyda16adf2023-03-05 10:22:12 +0000512 await interaction.deleteReply();
513};
TheCodedProfa112f612023-01-28 18:06:45 -0500514
515const check = (interaction: CommandInteraction, _partial: boolean = false) => {
516 const member = interaction.member as GuildMember;
517 if (!member.permissions.has("ManageRoles"))
518 return "You must have the *Manage Server* permission to use this command";
519 return true;
520};
521
522export { command };
523export { callback };
524export { check };