blob: 0ab5c10ff8905f3e3c8a5721b4bf62fc160890ab [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
TheCodedProf21c08592022-09-13 14:14:43 -04002import Discord, { CommandInteraction, GuildMember, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
PineaFan100df682023-01-02 13:26:08 +00003import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan4edb7762022-06-26 19:21:04 +01004import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanad54d752022-04-18 19:01:43 +01005import getEmojiByName from "../../utils/getEmojiByName.js";
pineafane625d782022-05-09 18:04:32 +01006import addPlural from "../../utils/plurals.js";
pineafan6702cef2022-06-13 17:52:37 +01007import client from "../../utils/client.js";
pineafanad54d752022-04-18 19:01:43 +01008
9const command = (builder: SlashCommandSubcommandBuilder) =>
10 builder
pineafan63fc5e22022-08-04 22:04:10 +010011 .setName("track")
12 .setDescription("Moves a user along a role track")
Skyler Grey11236ba2022-08-08 21:13:33 +010013 .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
pineafanad54d752022-04-18 19:01:43 +010014
Skyler Grey11236ba2022-08-08 21:13:33 +010015const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
pineafan63fc5e22022-08-04 22:04:10 +010016 active = active ? "ACTIVE" : "INACTIVE";
17 disabled = disabled ? "GREY." : "";
Skyler Grey11236ba2022-08-08 21:13:33 +010018 if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
19 if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
pineafan63fc5e22022-08-04 22:04:10 +010020 if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
21 return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
22};
pineafanad54d752022-04-18 19:01:43 +010023
Skyler Greyc634e2b2022-08-06 17:50:48 +010024const callback = async (interaction: CommandInteraction): Promise<unknown> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010025 const { renderUser } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010026 const member = interaction.options.getMember("user") as GuildMember;
27 const guild = interaction.guild;
pineafan63fc5e22022-08-04 22:04:10 +010028 if (!guild) return;
29 const config = await client.database.guilds.read(guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010030 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
pineafan63fc5e22022-08-04 22:04:10 +010031 let track = 0;
pineafanad54d752022-04-18 19:01:43 +010032 let generated;
pineafan63fc5e22022-08-04 22:04:10 +010033 const roles = await guild.roles.fetch();
Skyler Greyc634e2b2022-08-06 17:50:48 +010034 const memberRoles = member.roles;
pineafan63fc5e22022-08-04 22:04:10 +010035 let managed: boolean;
Skyler Greyad002172022-08-16 18:48:26 +010036 let timedOut = false;
37 while (!timedOut) {
pineafan63fc5e22022-08-04 22:04:10 +010038 const data = config.tracks[track];
Skyler Grey75ea9172022-08-06 10:22:23 +010039 if (data.manageableBy !== undefined)
40 managed = data.manageableBy.some((element: string) => {
41 return memberRoles.cache.has(element);
pineafan63fc5e22022-08-04 22:04:10 +010042 });
Skyler Grey75ea9172022-08-06 10:22:23 +010043 else managed = false;
TheCodedProf21c08592022-09-13 14:14:43 -040044 const dropdown = new Discord.SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +010045 .addOptions(
46 config.tracks.map((option, index) => {
Skyler Grey11236ba2022-08-08 21:13:33 +010047 const hasRoleInTrack = option.track.some((element: string) => {
48 return memberRoles.cache.has(element);
49 });
Skyler Grey75ea9172022-08-06 10:22:23 +010050 return new SelectMenuOption({
51 default: index === track,
52 label: option.name,
53 value: index.toString(),
Skyler Grey11236ba2022-08-08 21:13:33 +010054 description: option.track.length === 0 ? "No" : addPlural(option.track.length, "role"),
Skyler Grey75ea9172022-08-06 10:22:23 +010055 emoji: client.emojis.resolve(
Skyler Grey11236ba2022-08-08 21:13:33 +010056 getEmojiByName("TRACKS.SINGLE." + (hasRoleInTrack ? "ACTIVE" : "INACTIVE"), "id")
Skyler Grey75ea9172022-08-06 10:22:23 +010057 )
58 });
59 })
60 )
61 .setCustomId("select")
62 .setMaxValues(1);
pineafan63fc5e22022-08-04 22:04:10 +010063 const allowed = [];
Skyler Grey11236ba2022-08-08 21:13:33 +010064 generated = "**Track:** " + data.name + "\n" + "**Member:** " + renderUser(member.user) + "\n";
Skyler Grey75ea9172022-08-06 10:22:23 +010065 generated +=
Skyler Grey11236ba2022-08-08 21:13:33 +010066 (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 +010067 generated +=
68 (data.retainPrevious
69 ? "When promoted, the user keeps previous roles"
70 : "Members will lose their current role when promoted") + "\n";
71 generated +=
72 "\n" +
73 data.track
74 .map((role, index) => {
75 const allow =
Skyler Grey11236ba2022-08-08 21:13:33 +010076 roles.get(role).position >= (interaction.member as GuildMember).roles.highest.position &&
77 !managed;
Skyler Grey75ea9172022-08-06 10:22:23 +010078 allowed.push(!allow);
79 return (
80 getEmojiByName(
Skyler Grey11236ba2022-08-08 21:13:33 +010081 generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow)
Skyler Grey75ea9172022-08-06 10:22:23 +010082 ) +
83 " " +
84 roles.get(role).name +
85 " [<@&" +
86 roles.get(role).id +
87 ">]"
88 );
89 })
90 .join("\n");
pineafan63fc5e22022-08-04 22:04:10 +010091 const selected = [];
Skyler Greyc634e2b2022-08-06 17:50:48 +010092 for (const position of data.track) {
93 if (memberRoles.cache.has(position)) selected.push(position);
pineafanad54d752022-04-18 19:01:43 +010094 }
pineafan63fc5e22022-08-04 22:04:10 +010095 const conflict = data.retainPrevious ? false : selected.length > 1;
96 let conflictDropdown;
97 let currentRoleIndex;
pineafanad54d752022-04-18 19:01:43 +010098 if (conflict) {
Skyler Grey11236ba2022-08-08 21:13:33 +010099 generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
100 selected.length
101 } roles from this track. `;
pineafan63fc5e22022-08-04 22:04:10 +0100102 conflictDropdown = [];
Skyler Grey11236ba2022-08-08 21:13:33 +0100103 if (roles.get(selected[0]).position < memberRoles.highest.position || managed) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100104 generated +=
105 "In order to promote or demote this user, you must select which role the member should keep.";
106 selected.forEach((role) => {
107 conflictDropdown.push(
108 new SelectMenuOption({
109 label: roles.get(role).name,
110 value: roles.get(role).id
111 })
112 );
pineafan63fc5e22022-08-04 22:04:10 +0100113 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100114 conflictDropdown = [
TheCodedProf21c08592022-09-13 14:14:43 -0400115 new Discord.SelectMenuBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100116 .addOptions(conflictDropdown)
117 .setCustomId("conflict")
118 .setMaxValues(1)
119 .setPlaceholder("Select a role to keep")
120 ];
pineafanad54d752022-04-18 19:01:43 +0100121 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 generated +=
123 "You don't have permission to manage one or more of the users roles, and therefore can't select one to keep.";
pineafanad54d752022-04-18 19:01:43 +0100124 }
125 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100126 currentRoleIndex = selected.length === 0 ? -1 : data.track.indexOf(selected[0].toString());
pineafanad54d752022-04-18 19:01:43 +0100127 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100128 const m = (await interaction.editReply({
129 embeds: [
130 new EmojiEmbed()
131 .setEmoji("TRACKS.ICON")
132 .setTitle("Tracks")
133 .setDescription(`${generated}`)
134 .setStatus("Success")
135 ],
TheCodedProf21c08592022-09-13 14:14:43 -0400136 components: [new ActionRowBuilder().addComponents(dropdown)]
Skyler Grey75ea9172022-08-06 10:22:23 +0100137 .concat(
TheCodedProf21c08592022-09-13 14:14:43 -0400138 conflict && conflictDropdown.length ? [new ActionRowBuilder().addComponents(conflictDropdown)] : []
Skyler Grey75ea9172022-08-06 10:22:23 +0100139 )
140 .concat([
TheCodedProf21c08592022-09-13 14:14:43 -0400141 new ActionRowBuilder().addComponents([
142 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100143 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
144 .setLabel("Move up")
145 .setCustomId("promote")
TheCodedProf21c08592022-09-13 14:14:43 -0400146 .setStyle(ButtonStyle.Success)
Skyler Grey75ea9172022-08-06 10:22:23 +0100147 .setDisabled(
148 conflict ||
149 currentRoleIndex === 0 ||
Skyler Grey11236ba2022-08-08 21:13:33 +0100150 (currentRoleIndex === -1 ? false : !allowed[currentRoleIndex - 1])
Skyler Grey75ea9172022-08-06 10:22:23 +0100151 ),
TheCodedProf21c08592022-09-13 14:14:43 -0400152 new ButtonBuilder()
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
154 .setLabel("Move down")
155 .setCustomId("demote")
TheCodedProf21c08592022-09-13 14:14:43 -0400156 .setStyle(ButtonStyle.Danger)
Skyler Grey75ea9172022-08-06 10:22:23 +0100157 .setDisabled(
158 conflict ||
159 (data.nullable
160 ? currentRoleIndex <= -1
Skyler Grey11236ba2022-08-08 21:13:33 +0100161 : currentRoleIndex === data.track.length - 1 || currentRoleIndex <= -1) ||
Skyler Grey75ea9172022-08-06 10:22:23 +0100162 !allowed[currentRoleIndex]
163 )
164 ])
pineafan63fc5e22022-08-04 22:04:10 +0100165 ])
Skyler Grey75ea9172022-08-06 10:22:23 +0100166 })) as Message;
pineafanad54d752022-04-18 19:01:43 +0100167 let component;
168 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000169 component = await m.awaitMessageComponent({
170 time: 300000,
171 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
172 });
pineafanad54d752022-04-18 19:01:43 +0100173 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100174 timedOut = true;
175 continue;
pineafanad54d752022-04-18 19:01:43 +0100176 }
pineafan63fc5e22022-08-04 22:04:10 +0100177 component.deferUpdate();
pineafane23c4ec2022-07-27 21:56:27 +0100178 if (component.customId === "conflict") {
Skyler Grey11236ba2022-08-08 21:13:33 +0100179 const rolesToRemove = selected.filter((role) => role !== component.values[0]);
pineafan63fc5e22022-08-04 22:04:10 +0100180 await member.roles.remove(rolesToRemove);
pineafane23c4ec2022-07-27 21:56:27 +0100181 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100182 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100183 currentRoleIndex === -1
184 ? allowed[data.track.length - 1]
185 : allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
pineafanad54d752022-04-18 19:01:43 +0100186 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100187 if (currentRoleIndex === -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100188 await member.roles.add(data.track[data.track.length - 1]);
pineafanad54d752022-04-18 19:01:43 +0100189 } else if (currentRoleIndex < data.track.length) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100190 if (!data.retainPrevious) await member.roles.remove(data.track[currentRoleIndex]);
pineafan63fc5e22022-08-04 22:04:10 +0100191 await member.roles.add(data.track[currentRoleIndex - 1]);
pineafanad54d752022-04-18 19:01:43 +0100192 }
193 }
pineafane23c4ec2022-07-27 21:56:27 +0100194 } else if (component.customId === "demote") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100195 if (allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100196 if (currentRoleIndex === data.track.length - 1) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100197 if (data.nullable) await member.roles.remove(data.track[currentRoleIndex]);
pineafanad54d752022-04-18 19:01:43 +0100198 } else if (currentRoleIndex > -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100199 await member.roles.remove(data.track[currentRoleIndex]);
200 await member.roles.add(data.track[currentRoleIndex + 1]);
pineafanad54d752022-04-18 19:01:43 +0100201 }
202 }
pineafane23c4ec2022-07-27 21:56:27 +0100203 } else if (component.customId === "select") {
pineafan63fc5e22022-08-04 22:04:10 +0100204 track = component.values[0];
pineafanad54d752022-04-18 19:01:43 +0100205 }
206 }
pineafan63fc5e22022-08-04 22:04:10 +0100207};
pineafanad54d752022-04-18 19:01:43 +0100208
PineaFan64486c42022-12-28 09:21:04 +0000209const check = async (interaction: CommandInteraction) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100210 const tracks = (await client.database.guilds.read(interaction.guild.id)).tracks;
211 if (tracks.length === 0) throw new Error("This server does not have any tracks");
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 const member = interaction.member as GuildMember;
pineafanad54d752022-04-18 19:01:43 +0100213 // Allow the owner to promote anyone
pineafan63fc5e22022-08-04 22:04:10 +0100214 if (member.id === interaction.guild.ownerId) return true;
pineafan6702cef2022-06-13 17:52:37 +0100215 // Check if the user can manage any of the tracks
pineafan63fc5e22022-08-04 22:04:10 +0100216 let managed = false;
pineafanc1c18792022-08-03 21:41:36 +0100217 for (const element of tracks) {
pineafan63fc5e22022-08-04 22:04:10 +0100218 if (!element.track.manageableBy) continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100219 if (!element.track.manageableBy.some((role) => member.roles.cache.has(role))) continue;
pineafanc1c18792022-08-03 21:41:36 +0100220 managed = true;
221 break;
pineafan63fc5e22022-08-04 22:04:10 +0100222 }
pineafanad54d752022-04-18 19:01:43 +0100223 // Check if the user has manage_roles permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100224 if (!managed && !member.permissions.has("MANAGE_ROLES"))
Skyler Greyc634e2b2022-08-06 17:50:48 +0100225 throw new Error("You do not have the *Manage Roles* permission");
pineafanad54d752022-04-18 19:01:43 +0100226 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100227 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100228};
pineafanad54d752022-04-18 19:01:43 +0100229
230export { command };
231export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100232export { check };