blob: 1ce05eeb7772461b8ceaad66dc86f05ace4aa100 [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
Skyler Greyda16adf2023-03-05 10:22:12 +00002import Discord, {
3 CommandInteraction,
4 GuildMember,
5 Message,
6 ActionRowBuilder,
7 ButtonBuilder,
8 ButtonStyle,
9 APIMessageComponentEmoji,
10 StringSelectMenuBuilder,
11 MessageComponentInteraction,
12 StringSelectMenuInteraction,
13 StringSelectMenuOptionBuilder
14} from "discord.js";
TheCodedProff86ba092023-01-27 17:10:07 -050015import type { SlashCommandSubcommandBuilder } from "discord.js";
pineafan4edb7762022-06-26 19:21:04 +010016import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanad54d752022-04-18 19:01:43 +010017import getEmojiByName from "../../utils/getEmojiByName.js";
pineafane625d782022-05-09 18:04:32 +010018import addPlural from "../../utils/plurals.js";
pineafan6702cef2022-06-13 17:52:37 +010019import client from "../../utils/client.js";
TheCodedProfb5e9d552023-01-29 15:43:26 -050020import { createVerticalTrack } from "../../utils/createPageIndicator.js";
pineafanad54d752022-04-18 19:01:43 +010021
22const command = (builder: SlashCommandSubcommandBuilder) =>
23 builder
pineafan63fc5e22022-08-04 22:04:10 +010024 .setName("track")
25 .setDescription("Moves a user along a role track")
Skyler Grey11236ba2022-08-08 21:13:33 +010026 .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
pineafanad54d752022-04-18 19:01:43 +010027
Skyler Greyc634e2b2022-08-06 17:50:48 +010028const callback = async (interaction: CommandInteraction): Promise<unknown> => {
Skyler Greyda16adf2023-03-05 10:22:12 +000029 const { renderUser, renderRole } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010030 const member = interaction.options.getMember("user") as GuildMember;
31 const guild = interaction.guild;
pineafan63fc5e22022-08-04 22:04:10 +010032 if (!guild) return;
33 const config = await client.database.guilds.read(guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010034 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
pineafan63fc5e22022-08-04 22:04:10 +010035 let track = 0;
pineafanad54d752022-04-18 19:01:43 +010036 let generated;
pineafan63fc5e22022-08-04 22:04:10 +010037 const roles = await guild.roles.fetch();
Skyler Greyc634e2b2022-08-06 17:50:48 +010038 const memberRoles = member.roles;
pineafan63fc5e22022-08-04 22:04:10 +010039 let managed: boolean;
Skyler Greyad002172022-08-16 18:48:26 +010040 let timedOut = false;
41 while (!timedOut) {
TheCodedProf2ec2ba62023-01-18 22:26:20 -050042 const data = config.tracks[track]!;
PineaFan638eb132023-01-19 10:41:22 +000043 if (data.manageableBy.length)
Skyler Grey75ea9172022-08-06 10:22:23 +010044 managed = data.manageableBy.some((element: string) => {
45 return memberRoles.cache.has(element);
pineafan63fc5e22022-08-04 22:04:10 +010046 });
Skyler Grey75ea9172022-08-06 10:22:23 +010047 else managed = false;
TheCodedProf2ec2ba62023-01-18 22:26:20 -050048 const dropdown = new Discord.StringSelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +010049 .addOptions(
50 config.tracks.map((option, index) => {
TheCodedProfb5e9d552023-01-29 15:43:26 -050051 const hasRoleInTrack: boolean = option.track.some((element: string) => {
Skyler Grey11236ba2022-08-08 21:13:33 +010052 return memberRoles.cache.has(element);
53 });
TheCodedProfb5e9d552023-01-29 15:43:26 -050054 return new StringSelectMenuOptionBuilder({
Skyler Grey75ea9172022-08-06 10:22:23 +010055 default: index === track,
56 label: option.name,
57 value: index.toString(),
Skyler Grey11236ba2022-08-08 21:13:33 +010058 description: option.track.length === 0 ? "No" : addPlural(option.track.length, "role"),
Skyler Grey75ea9172022-08-06 10:22:23 +010059 emoji: client.emojis.resolve(
Skyler Grey11236ba2022-08-08 21:13:33 +010060 getEmojiByName("TRACKS.SINGLE." + (hasRoleInTrack ? "ACTIVE" : "INACTIVE"), "id")
TheCodedProf2ec2ba62023-01-18 22:26:20 -050061 ) as APIMessageComponentEmoji
Skyler Grey75ea9172022-08-06 10:22:23 +010062 });
63 })
64 )
65 .setCustomId("select")
66 .setMaxValues(1);
TheCodedProf2ec2ba62023-01-18 22:26:20 -050067 const allowed: boolean[] = [];
Skyler Grey11236ba2022-08-08 21:13:33 +010068 generated = "**Track:** " + data.name + "\n" + "**Member:** " + renderUser(member.user) + "\n";
Skyler Grey75ea9172022-08-06 10:22:23 +010069 generated +=
Skyler Grey11236ba2022-08-08 21:13:33 +010070 (data.nullable ? "Members do not need a role in this track" : "A role in this track is required") + "\n";
Skyler Grey75ea9172022-08-06 10:22:23 +010071 generated +=
72 (data.retainPrevious
73 ? "When promoted, the user keeps previous roles"
74 : "Members will lose their current role when promoted") + "\n";
TheCodedProfb5e9d552023-01-29 15:43:26 -050075 for (const role of data.track) {
76 const disabled: boolean =
77 roles.get(role)!.position >= (interaction.member as GuildMember).roles.highest.position && !managed;
Skyler Greyda16adf2023-03-05 10:22:12 +000078 allowed.push(!disabled);
TheCodedProfb5e9d552023-01-29 15:43:26 -050079 }
Skyler Greyda16adf2023-03-05 10:22:12 +000080 generated +=
81 "\n" +
82 createVerticalTrack(
83 data.track.map((role) => renderRole(roles.get(role)!)),
84 data.track.map((role) => memberRoles.cache.has(role)),
85 allowed.map((allow) => !allow)
86 );
pineafan63fc5e22022-08-04 22:04:10 +010087 const selected = [];
Skyler Greyc634e2b2022-08-06 17:50:48 +010088 for (const position of data.track) {
89 if (memberRoles.cache.has(position)) selected.push(position);
pineafanad54d752022-04-18 19:01:43 +010090 }
pineafan63fc5e22022-08-04 22:04:10 +010091 const conflict = data.retainPrevious ? false : selected.length > 1;
TheCodedProf2ec2ba62023-01-18 22:26:20 -050092 let conflictDropdown: StringSelectMenuBuilder[] = [];
TheCodedProfb5e9d552023-01-29 15:43:26 -050093 const conflictDropdownOptions: StringSelectMenuOptionBuilder[] = [];
TheCodedProf2ec2ba62023-01-18 22:26:20 -050094 let currentRoleIndex: number = -1;
pineafanad54d752022-04-18 19:01:43 +010095 if (conflict) {
Skyler Grey11236ba2022-08-08 21:13:33 +010096 generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
97 selected.length
98 } roles from this track. `;
pineafan63fc5e22022-08-04 22:04:10 +010099 conflictDropdown = [];
TheCodedProf764e6c22023-03-11 16:07:09 -0500100 const yourRoles = guild.members.cache.get(interaction.user.id)!.roles;
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500101 if (
102 (roles.get(selected[0]!)!.position < yourRoles.highest.position &&
103 roles.get(selected[0]!)!.position < guild.members.me!.roles.highest.position!) ||
104 managed
105 ) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 generated +=
107 "In order to promote or demote this user, you must select which role the member should keep.";
108 selected.forEach((role) => {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500109 conflictDropdownOptions.push(
TheCodedProfb5e9d552023-01-29 15:43:26 -0500110 new StringSelectMenuOptionBuilder()
111 .setLabel(roles.get(role)!.name)
112 .setValue(roles.get(role)!.id)
Skyler Grey75ea9172022-08-06 10:22:23 +0100113 );
pineafan63fc5e22022-08-04 22:04:10 +0100114 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100115 conflictDropdown = [
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500116 new Discord.StringSelectMenuBuilder()
117 .addOptions(conflictDropdownOptions)
Skyler Grey75ea9172022-08-06 10:22:23 +0100118 .setCustomId("conflict")
119 .setMaxValues(1)
120 .setPlaceholder("Select a role to keep")
121 ];
pineafanad54d752022-04-18 19:01:43 +0100122 } else {
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500123 if (roles.get(selected[0]!)!.position >= yourRoles.highest.position) {
TheCodedProf764e6c22023-03-11 16:07:09 -0500124 generated +=
125 "You don't have permission to manage one or more of the user's roles, and therefore can't select one to keep.";
126 } else {
127 generated +=
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500128 "I don't have permission to manage one or more of the user's roles, and therefore can't select one to keep.";
TheCodedProf764e6c22023-03-11 16:07:09 -0500129 }
pineafanad54d752022-04-18 19:01:43 +0100130 }
131 } else {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500132 currentRoleIndex = selected.length === 0 ? -1 : data.track.indexOf(selected[0]!.toString());
pineafanad54d752022-04-18 19:01:43 +0100133 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100134 const m = (await interaction.editReply({
135 embeds: [
136 new EmojiEmbed()
137 .setEmoji("TRACKS.ICON")
138 .setTitle("Tracks")
139 .setDescription(`${generated}`)
140 .setStatus("Success")
141 ],
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500142 components: [new ActionRowBuilder<StringSelectMenuBuilder | ButtonBuilder>().addComponents(dropdown)]
Skyler Grey75ea9172022-08-06 10:22:23 +0100143 .concat(
Skyler Greyda16adf2023-03-05 10:22:12 +0000144 conflict && conflictDropdown.length
145 ? [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(conflictDropdown)]
146 : []
Skyler Grey75ea9172022-08-06 10:22:23 +0100147 )
148 .concat([
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500149 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400150 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100151 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
152 .setLabel("Move up")
153 .setCustomId("promote")
TheCodedProf21c08592022-09-13 14:14:43 -0400154 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100155 .setDisabled(
156 conflict ||
157 currentRoleIndex === 0 ||
Skyler Grey11236ba2022-08-08 21:13:33 +0100158 (currentRoleIndex === -1 ? false : !allowed[currentRoleIndex - 1])
Skyler Grey75ea9172022-08-06 10:22:23 +0100159 ),
TheCodedProf21c08592022-09-13 14:14:43 -0400160 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100161 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
162 .setLabel("Move down")
163 .setCustomId("demote")
TheCodedProf21c08592022-09-13 14:14:43 -0400164 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100165 .setDisabled(
166 conflict ||
167 (data.nullable
168 ? currentRoleIndex <= -1
Skyler Grey11236ba2022-08-08 21:13:33 +0100169 : currentRoleIndex === data.track.length - 1 || currentRoleIndex <= -1) ||
Skyler Grey75ea9172022-08-06 10:22:23 +0100170 !allowed[currentRoleIndex]
171 )
172 ])
pineafan63fc5e22022-08-04 22:04:10 +0100173 ])
Skyler Grey75ea9172022-08-06 10:22:23 +0100174 })) as Message;
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500175 let component: MessageComponentInteraction;
pineafanad54d752022-04-18 19:01:43 +0100176 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000177 component = await m.awaitMessageComponent({
178 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000179 filter: (i) => {
180 return (
181 i.user.id === interaction.user.id &&
182 i.channel!.id === interaction.channel!.id &&
183 i.message.id === m.id
184 );
185 }
PineaFan0d06edc2023-01-17 22:10:31 +0000186 });
pineafanad54d752022-04-18 19:01:43 +0100187 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100188 timedOut = true;
189 continue;
pineafanad54d752022-04-18 19:01:43 +0100190 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000191 await component.deferUpdate();
pineafane23c4ec2022-07-27 21:56:27 +0100192 if (component.customId === "conflict") {
Skyler Greyda16adf2023-03-05 10:22:12 +0000193 const rolesToRemove = selected.filter(
194 (role) => role !== (component as StringSelectMenuInteraction).values[0]
195 );
TheCodedProf764e6c22023-03-11 16:07:09 -0500196
pineafan63fc5e22022-08-04 22:04:10 +0100197 await member.roles.remove(rolesToRemove);
pineafane23c4ec2022-07-27 21:56:27 +0100198 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100199 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100200 currentRoleIndex === -1
201 ? allowed[data.track.length - 1]
202 : allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
pineafanad54d752022-04-18 19:01:43 +0100203 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100204 if (currentRoleIndex === -1) {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500205 await member.roles.add(data.track[data.track.length - 1]!);
pineafanad54d752022-04-18 19:01:43 +0100206 } else if (currentRoleIndex < data.track.length) {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500207 if (!data.retainPrevious) await member.roles.remove(data.track[currentRoleIndex]!);
208 await member.roles.add(data.track[currentRoleIndex - 1]!);
pineafanad54d752022-04-18 19:01:43 +0100209 }
210 }
pineafane23c4ec2022-07-27 21:56:27 +0100211 } else if (component.customId === "demote") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 if (allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100213 if (currentRoleIndex === data.track.length - 1) {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500214 if (data.nullable) await member.roles.remove(data.track[currentRoleIndex]!);
pineafanad54d752022-04-18 19:01:43 +0100215 } else if (currentRoleIndex > -1) {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500216 await member.roles.remove(data.track[currentRoleIndex]!);
217 await member.roles.add(data.track[currentRoleIndex + 1]!);
pineafanad54d752022-04-18 19:01:43 +0100218 }
219 }
pineafane23c4ec2022-07-27 21:56:27 +0100220 } else if (component.customId === "select") {
PineaFan638eb132023-01-19 10:41:22 +0000221 track = parseInt((component as StringSelectMenuInteraction).values[0]!);
pineafanad54d752022-04-18 19:01:43 +0100222 }
223 }
pineafan63fc5e22022-08-04 22:04:10 +0100224};
pineafanad54d752022-04-18 19:01:43 +0100225
TheCodedProff86ba092023-01-27 17:10:07 -0500226const check = async (interaction: CommandInteraction, _partial: boolean = false) => {
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500227 const tracks = (await client.database.guilds.read(interaction.guild!.id)).tracks;
PineaFan5d98a4b2023-01-19 16:15:47 +0000228 if (tracks.length === 0) return "This server does not have any tracks";
Skyler Grey75ea9172022-08-06 10:22:23 +0100229 const member = interaction.member as GuildMember;
pineafanad54d752022-04-18 19:01:43 +0100230 // Allow the owner to promote anyone
TheCodedProf2ec2ba62023-01-18 22:26:20 -0500231 if (member.id === interaction.guild!.ownerId) return true;
pineafan6702cef2022-06-13 17:52:37 +0100232 // Check if the user can manage any of the tracks
pineafan63fc5e22022-08-04 22:04:10 +0100233 let managed = false;
Skyler Greyda16adf2023-03-05 10:22:12 +0000234 const memberRoles = member.roles.cache.map((r) => r.id);
pineafanc1c18792022-08-03 21:41:36 +0100235 for (const element of tracks) {
PineaFan638eb132023-01-19 10:41:22 +0000236 if (!element.manageableBy.length) continue;
237 if (!element.manageableBy.some((role: string) => memberRoles.includes(role))) continue;
pineafanc1c18792022-08-03 21:41:36 +0100238 managed = true;
239 break;
pineafan63fc5e22022-08-04 22:04:10 +0100240 }
pineafanad54d752022-04-18 19:01:43 +0100241 // Check if the user has manage_roles permission
PineaFan5d98a4b2023-01-19 16:15:47 +0000242 if (!managed && !member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
pineafanad54d752022-04-18 19:01:43 +0100243 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100244 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100245};
pineafanad54d752022-04-18 19:01:43 +0100246
247export { command };
248export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100249export { check };