blob: e608f31cd1426cbff92b7a502ffacb82ac8fcd2c [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);
TheCodedProf764e6c22023-03-11 16:07:09 -0500174 current.name = "Default";
TheCodedProfb5e9d552023-01-29 15:43:26 -0500175 }
TheCodedProf4f79da12023-01-31 16:50:37 -0500176
Skyler Greyda16adf2023-03-05 10:22:12 +0000177 const roleSelect = new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
178 new RoleSelectMenuBuilder().setCustomId("addRole").setPlaceholder("Select a role to add").setDisabled(!isAdmin)
179 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500180 let closed = false;
TheCodedProf48865eb2023-03-05 15:25:25 -0500181 let previousMessage = "";
TheCodedProfb5e9d552023-01-29 15:43:26 -0500182 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000183 const editableRoles: string[] = current.track
184 .map((r) => {
185 if (
186 !(roles.get(r)!.position >= (interaction.member as GuildMember).roles.highest.position) ||
187 interaction.user.id === interaction.guild?.ownerId
188 )
189 return roles.get(r)!.name;
190 })
191 .filter((v) => v !== undefined) as string[];
192 const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
193 new StringSelectMenuBuilder()
194 .setCustomId("removeRole")
195 .setPlaceholder("Select a role to remove")
196 .setDisabled(!isAdmin)
197 .addOptions(
198 editableRoles.map((r, i) => {
199 return new StringSelectMenuOptionBuilder().setLabel(r).setValue(i.toString());
200 })
201 )
202 );
203 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
204 new ButtonBuilder()
205 .setCustomId("back")
206 .setLabel("Back")
207 .setStyle(ButtonStyle.Secondary)
208 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
209 new ButtonBuilder()
210 .setCustomId("edit")
211 .setLabel("Edit Name")
212 .setStyle(ButtonStyle.Primary)
213 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
214 new ButtonBuilder()
215 .setCustomId("reorder")
216 .setLabel("Reorder")
217 .setDisabled(!isAdmin)
218 .setStyle(ButtonStyle.Primary)
219 .setEmoji(getEmojiByName("ICONS.REORDER", "id") as APIMessageComponentEmoji),
220 new ButtonBuilder()
221 .setCustomId("retainPrevious")
222 .setLabel("Retain Previous")
223 .setStyle(current.retainPrevious ? ButtonStyle.Success : ButtonStyle.Danger)
224 .setEmoji(
225 getEmojiByName(
226 "CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"),
227 "id"
228 ) as APIMessageComponentEmoji
229 ),
230 new ButtonBuilder()
231 .setCustomId("nullable")
232 .setLabel(`Role ${current.nullable ? "Not " : ""}Required`)
TheCodedProf48865eb2023-03-05 15:25:25 -0500233 .setStyle(current.nullable ? ButtonStyle.Danger : ButtonStyle.Success)
Skyler Greyda16adf2023-03-05 10:22:12 +0000234 .setEmoji(
TheCodedProf48865eb2023-03-05 15:25:25 -0500235 getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"), "id") as APIMessageComponentEmoji
Skyler Greyda16adf2023-03-05 10:22:12 +0000236 )
TheCodedProf4f79da12023-01-31 16:50:37 -0500237 );
238
PineaFanb0d0c242023-02-05 10:59:45 +0000239 const allowed: boolean[] = [];
TheCodedProfb5e9d552023-01-29 15:43:26 -0500240 for (const role of current.track) {
241 const disabled: boolean =
242 roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position;
Skyler Greyda16adf2023-03-05 10:22:12 +0000243 allowed.push(disabled);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500244 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000245 const mapped = current.track.map((role) => roles.find((aRole) => aRole.id === role)!);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500246
247 const embed = new EmojiEmbed()
248 .setTitle("Tracks")
249 .setDescription(
250 `**Currently Editing:** ${current.name}\n\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000251 `${getEmojiByName("CONTROL." + (current.nullable ? "CROSS" : "TICK"))} Members ${
252 current.nullable ? "don't " : ""
253 }need a role in this track\n` +
254 `${getEmojiByName("CONTROL." + (current.retainPrevious ? "TICK" : "CROSS"))} Members ${
255 current.retainPrevious ? "" : "don't "
Skyler Grey67691762023-03-06 09:58:19 +0000256 }keep all roles below their current highest\n\n` +
257 (previousMessage ? previousMessage + "\n\n" : "") +
Skyler Greyda16adf2023-03-05 10:22:12 +0000258 createVerticalTrack(
259 mapped.map((role) => renderRole(role)),
260 new Array(current.track.length).fill(false),
261 allowed
262 )
TheCodedProfb5e9d552023-01-29 15:43:26 -0500263 )
Skyler Greyda16adf2023-03-05 10:22:12 +0000264 .setStatus("Success");
TheCodedProfb5e9d552023-01-29 15:43:26 -0500265
Skyler Greyda16adf2023-03-05 10:22:12 +0000266 const comps: ActionRowBuilder<RoleSelectMenuBuilder | ButtonBuilder | StringSelectMenuBuilder>[] = [
267 roleSelect,
268 buttons
269 ];
270 if (current.track.length >= 1) comps.splice(1, 0, selectMenu);
TheCodedProfd0a166d2023-02-19 00:04:53 -0500271
Skyler Greyf4f21c42023-03-08 14:36:29 +0000272 await interaction.editReply({ embeds: [embed], components: comps });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500273
274 let out: ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
275
276 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 out = (await message.awaitMessageComponent({
TheCodedProfb5e9d552023-01-29 15:43:26 -0500278 filter: (i) => i.channel!.id === interaction.channel!.id,
279 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000280 })) as ButtonInteraction | RoleSelectMenuInteraction | StringSelectMenuInteraction | null;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500281 } catch (e) {
282 console.error(e);
283 out = null;
284 }
285
Skyler Greyda16adf2023-03-05 10:22:12 +0000286 if (!out) return;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500287 if (out.isButton()) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000288 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000289 case "back": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000290 await out.deferUpdate();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500291 closed = true;
292 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000293 }
294 case "edit": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500295 current.name = (await editName(out, interaction, message, current.name))!;
296 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000297 }
298 case "reorder": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000299 await out.deferUpdate();
TheCodedProf7a83f762023-03-06 17:17:00 -0500300 current.track = (await reorderTracks(interaction, out, message, roles, current.track))!;
TheCodedProf4f79da12023-01-31 16:50:37 -0500301 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000302 }
303 case "retainPrevious": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000304 await out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500305 current.retainPrevious = !current.retainPrevious;
306 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000307 }
308 case "nullable": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000309 await out.deferUpdate();
TheCodedProf4f79da12023-01-31 16:50:37 -0500310 current.nullable = !current.nullable;
311 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000312 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500313 }
314 } else if (out.isStringSelectMenu()) {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000315 await out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000316 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000317 case "removeRole": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000318 const index = current.track.findIndex(
319 (v) => v === editableRoles[parseInt((out! as StringSelectMenuInteraction).values![0]!)]
320 );
TheCodedProfb5e9d552023-01-29 15:43:26 -0500321 current.track.splice(index, 1);
322 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000323 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500324 }
325 } else {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000326 await out.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000327 switch (out.customId) {
PineaFanb0d0c242023-02-05 10:59:45 +0000328 case "addRole": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500329 const role = out.values![0]!;
TheCodedProf48865eb2023-03-05 15:25:25 -0500330 const roleObj = roles.get(role)!;
Skyler Grey67691762023-03-06 09:58:19 +0000331 if (roleObj.position >= (interaction.member as GuildMember).roles.highest.position) {
TheCodedProf48865eb2023-03-05 15:25:25 -0500332 previousMessage = "You can't add a role that is higher than your highest role.";
TheCodedProfd0a166d2023-02-19 00:04:53 -0500333 } else {
TheCodedProf48865eb2023-03-05 15:25:25 -0500334 if (!current.track.includes(role)) {
335 current.track.push(role);
TheCodedProf48865eb2023-03-05 15:25:25 -0500336 } else {
337 previousMessage = "That role is already on this track";
338 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500339 }
TheCodedProfb7a7b992023-03-05 16:11:59 -0500340 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500341 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000342 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500343 }
344 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000345 } while (!closed);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500346 return current;
Skyler Greyda16adf2023-03-05 10:22:12 +0000347};
TheCodedProfa112f612023-01-28 18:06:45 -0500348
349const callback = async (interaction: CommandInteraction) => {
Skyler Greyda16adf2023-03-05 10:22:12 +0000350 const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
TheCodedProfb5e9d552023-01-29 15:43:26 -0500351 const config = await client.database.guilds.read(interaction.guild!.id);
TheCodedProf764e6c22023-03-11 16:07:09 -0500352 const tracks: ObjectSchema[] = _.cloneDeep(config.tracks);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500353 const roles = await interaction.guild!.roles.fetch();
TheCodedProfb5e9d552023-01-29 15:43:26 -0500354
355 let page = 0;
356 let closed = false;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500357
358 do {
Skyler Greyda16adf2023-03-05 10:22:12 +0000359 const embed = new EmojiEmbed().setTitle("Track Settings").setEmoji("TRACKS.ICON").setStatus("Success");
TheCodedProf764e6c22023-03-11 16:07:09 -0500360 const noTracks = tracks.length === 0;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500361 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)
TheCodedProf764e6c22023-03-11 16:07:09 -0500401 .setDisabled(_.isEqual(tracks, config.tracks))
Skyler Greyda16adf2023-03-05 10:22:12 +0000402 );
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;
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500474 if (!newPage) break;
TheCodedProf764e6c22023-03-11 16:07:09 -0500475 tracks.push(newPage);
TheCodedProfb5e9d552023-01-29 15:43:26 -0500476 page = tracks.length - 1;
477 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000478 }
479 case "save": {
Skyler Greyf4f21c42023-03-08 14:36:29 +0000480 await client.database.guilds.write(interaction.guild!.id, { tracks: tracks });
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;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500493 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000494 }
495 case "delete": {
Skyler Greyda16adf2023-03-05 10:22:12 +0000496 if (page === 0 && tracks.keys.length - 1 > 0) page++;
TheCodedProfb5e9d552023-01-29 15:43:26 -0500497 else page--;
498 tracks.splice(page, 1);
499 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000500 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500501 }
502 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000503 }
504 case "page": {
TheCodedProfb5e9d552023-01-29 15:43:26 -0500505 page = parseInt(i.values[0]!);
506 break;
PineaFanb0d0c242023-02-05 10:59:45 +0000507 }
TheCodedProfb5e9d552023-01-29 15:43:26 -0500508 }
509 }
TheCodedProf01cba762023-02-18 15:55:05 -0500510 } while (!closed);
Skyler Greyda16adf2023-03-05 10:22:12 +0000511 await interaction.deleteReply();
512};
TheCodedProfa112f612023-01-28 18:06:45 -0500513
514const check = (interaction: CommandInteraction, _partial: boolean = false) => {
515 const member = interaction.member as GuildMember;
516 if (!member.permissions.has("ManageRoles"))
517 return "You must have the *Manage Server* permission to use this command";
518 return true;
519};
520
521export { command };
522export { callback };
523export { check };