blob: 8c991e318d11f1fdced9b136ba50f2a150f12264 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import { LoadingEmbed } from "./../../utils/defaultEmbeds.js";
Skyler Grey11236ba2022-08-08 21:13:33 +01002import Discord, { CommandInteraction, GuildMember, Message, MessageActionRow, MessageButton } from "discord.js";
3import { SelectMenuOption, SlashCommandSubcommandBuilder } from "@discordjs/builders";
Skyler Grey75ea9172022-08-06 10:22:23 +01004// @ts-expect-error
pineafanad54d752022-04-18 19:01:43 +01005import { WrappedCheck } from "jshaiku";
pineafan4edb7762022-06-26 19:21:04 +01006import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanad54d752022-04-18 19:01:43 +01007import getEmojiByName from "../../utils/getEmojiByName.js";
pineafane625d782022-05-09 18:04:32 +01008import addPlural from "../../utils/plurals.js";
pineafan6702cef2022-06-13 17:52:37 +01009import client from "../../utils/client.js";
pineafanad54d752022-04-18 19:01:43 +010010
11const command = (builder: SlashCommandSubcommandBuilder) =>
12 builder
pineafan63fc5e22022-08-04 22:04:10 +010013 .setName("track")
14 .setDescription("Moves a user along a role track")
Skyler Grey11236ba2022-08-08 21:13:33 +010015 .addUserOption((option) => option.setName("user").setDescription("The user to manage").setRequired(true));
pineafanad54d752022-04-18 19:01:43 +010016
Skyler Grey11236ba2022-08-08 21:13:33 +010017const generateFromTrack = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
pineafan63fc5e22022-08-04 22:04:10 +010018 active = active ? "ACTIVE" : "INACTIVE";
19 disabled = disabled ? "GREY." : "";
Skyler Grey11236ba2022-08-08 21:13:33 +010020 if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
21 if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
pineafan63fc5e22022-08-04 22:04:10 +010022 if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
23 return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
24};
pineafanad54d752022-04-18 19:01:43 +010025
Skyler Greyc634e2b2022-08-06 17:50:48 +010026const callback = async (interaction: CommandInteraction): Promise<unknown> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010027 const { renderUser } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010028 const member = interaction.options.getMember("user") as GuildMember;
29 const guild = interaction.guild;
pineafan63fc5e22022-08-04 22:04:10 +010030 if (!guild) return;
31 const config = await client.database.guilds.read(guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010032 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
pineafan63fc5e22022-08-04 22:04:10 +010033 let track = 0;
pineafanad54d752022-04-18 19:01:43 +010034 let generated;
pineafan63fc5e22022-08-04 22:04:10 +010035 const roles = await guild.roles.fetch();
Skyler Greyc634e2b2022-08-06 17:50:48 +010036 const memberRoles = member.roles;
pineafan63fc5e22022-08-04 22:04:10 +010037 let managed: boolean;
Skyler Greyad002172022-08-16 18:48:26 +010038 let timedOut = false;
39 while (!timedOut) {
pineafan63fc5e22022-08-04 22:04:10 +010040 const data = config.tracks[track];
Skyler Grey75ea9172022-08-06 10:22:23 +010041 if (data.manageableBy !== undefined)
42 managed = data.manageableBy.some((element: string) => {
43 return memberRoles.cache.has(element);
pineafan63fc5e22022-08-04 22:04:10 +010044 });
Skyler Grey75ea9172022-08-06 10:22:23 +010045 else managed = false;
46 const dropdown = new Discord.MessageSelectMenu()
47 .addOptions(
48 config.tracks.map((option, index) => {
Skyler Grey11236ba2022-08-08 21:13:33 +010049 const hasRoleInTrack = option.track.some((element: string) => {
50 return memberRoles.cache.has(element);
51 });
Skyler Grey75ea9172022-08-06 10:22:23 +010052 return new SelectMenuOption({
53 default: index === track,
54 label: option.name,
55 value: index.toString(),
Skyler Grey11236ba2022-08-08 21:13:33 +010056 description: option.track.length === 0 ? "No" : addPlural(option.track.length, "role"),
Skyler Grey75ea9172022-08-06 10:22:23 +010057 emoji: client.emojis.resolve(
Skyler Grey11236ba2022-08-08 21:13:33 +010058 getEmojiByName("TRACKS.SINGLE." + (hasRoleInTrack ? "ACTIVE" : "INACTIVE"), "id")
Skyler Grey75ea9172022-08-06 10:22:23 +010059 )
60 });
61 })
62 )
63 .setCustomId("select")
64 .setMaxValues(1);
pineafan63fc5e22022-08-04 22:04:10 +010065 const allowed = [];
Skyler Grey11236ba2022-08-08 21:13:33 +010066 generated = "**Track:** " + data.name + "\n" + "**Member:** " + renderUser(member.user) + "\n";
Skyler Grey75ea9172022-08-06 10:22:23 +010067 generated +=
Skyler Grey11236ba2022-08-08 21:13:33 +010068 (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 +010069 generated +=
70 (data.retainPrevious
71 ? "When promoted, the user keeps previous roles"
72 : "Members will lose their current role when promoted") + "\n";
73 generated +=
74 "\n" +
75 data.track
76 .map((role, index) => {
77 const allow =
Skyler Grey11236ba2022-08-08 21:13:33 +010078 roles.get(role).position >= (interaction.member as GuildMember).roles.highest.position &&
79 !managed;
Skyler Grey75ea9172022-08-06 10:22:23 +010080 allowed.push(!allow);
81 return (
82 getEmojiByName(
Skyler Grey11236ba2022-08-08 21:13:33 +010083 generateFromTrack(index, memberRoles.cache.has(role), data.track.length, allow)
Skyler Grey75ea9172022-08-06 10:22:23 +010084 ) +
85 " " +
86 roles.get(role).name +
87 " [<@&" +
88 roles.get(role).id +
89 ">]"
90 );
91 })
92 .join("\n");
pineafan63fc5e22022-08-04 22:04:10 +010093 const selected = [];
Skyler Greyc634e2b2022-08-06 17:50:48 +010094 for (const position of data.track) {
95 if (memberRoles.cache.has(position)) selected.push(position);
pineafanad54d752022-04-18 19:01:43 +010096 }
pineafan63fc5e22022-08-04 22:04:10 +010097 const conflict = data.retainPrevious ? false : selected.length > 1;
98 let conflictDropdown;
99 let currentRoleIndex;
pineafanad54d752022-04-18 19:01:43 +0100100 if (conflict) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100101 generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${
102 selected.length
103 } roles from this track. `;
pineafan63fc5e22022-08-04 22:04:10 +0100104 conflictDropdown = [];
Skyler Grey11236ba2022-08-08 21:13:33 +0100105 if (roles.get(selected[0]).position < memberRoles.highest.position || managed) {
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) => {
109 conflictDropdown.push(
110 new SelectMenuOption({
111 label: roles.get(role).name,
112 value: roles.get(role).id
113 })
114 );
pineafan63fc5e22022-08-04 22:04:10 +0100115 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100116 conflictDropdown = [
117 new Discord.MessageSelectMenu()
118 .addOptions(conflictDropdown)
119 .setCustomId("conflict")
120 .setMaxValues(1)
121 .setPlaceholder("Select a role to keep")
122 ];
pineafanad54d752022-04-18 19:01:43 +0100123 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100124 generated +=
125 "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 +0100126 }
127 } else {
Skyler Grey11236ba2022-08-08 21:13:33 +0100128 currentRoleIndex = selected.length === 0 ? -1 : data.track.indexOf(selected[0].toString());
pineafanad54d752022-04-18 19:01:43 +0100129 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100130 const m = (await interaction.editReply({
131 embeds: [
132 new EmojiEmbed()
133 .setEmoji("TRACKS.ICON")
134 .setTitle("Tracks")
135 .setDescription(`${generated}`)
136 .setStatus("Success")
137 ],
138 components: [new MessageActionRow().addComponents(dropdown)]
139 .concat(
Skyler Grey11236ba2022-08-08 21:13:33 +0100140 conflict && conflictDropdown.length ? [new MessageActionRow().addComponents(conflictDropdown)] : []
Skyler Grey75ea9172022-08-06 10:22:23 +0100141 )
142 .concat([
143 new MessageActionRow().addComponents([
144 new MessageButton()
145 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
146 .setLabel("Move up")
147 .setCustomId("promote")
148 .setStyle("SUCCESS")
149 .setDisabled(
150 conflict ||
151 currentRoleIndex === 0 ||
Skyler Grey11236ba2022-08-08 21:13:33 +0100152 (currentRoleIndex === -1 ? false : !allowed[currentRoleIndex - 1])
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 ),
154 new MessageButton()
155 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
156 .setLabel("Move down")
157 .setCustomId("demote")
158 .setStyle("DANGER")
159 .setDisabled(
160 conflict ||
161 (data.nullable
162 ? currentRoleIndex <= -1
Skyler Grey11236ba2022-08-08 21:13:33 +0100163 : currentRoleIndex === data.track.length - 1 || currentRoleIndex <= -1) ||
Skyler Grey75ea9172022-08-06 10:22:23 +0100164 !allowed[currentRoleIndex]
165 )
166 ])
pineafan63fc5e22022-08-04 22:04:10 +0100167 ])
Skyler Grey75ea9172022-08-06 10:22:23 +0100168 })) as Message;
pineafanad54d752022-04-18 19:01:43 +0100169 let component;
170 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 component = await m.awaitMessageComponent({ time: 300000 });
pineafanad54d752022-04-18 19:01:43 +0100172 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100173 timedOut = true;
174 continue;
pineafanad54d752022-04-18 19:01:43 +0100175 }
pineafan63fc5e22022-08-04 22:04:10 +0100176 component.deferUpdate();
pineafane23c4ec2022-07-27 21:56:27 +0100177 if (component.customId === "conflict") {
Skyler Grey11236ba2022-08-08 21:13:33 +0100178 const rolesToRemove = selected.filter((role) => role !== component.values[0]);
pineafan63fc5e22022-08-04 22:04:10 +0100179 await member.roles.remove(rolesToRemove);
pineafane23c4ec2022-07-27 21:56:27 +0100180 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100181 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100182 currentRoleIndex === -1
183 ? allowed[data.track.length - 1]
184 : allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
pineafanad54d752022-04-18 19:01:43 +0100185 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100186 if (currentRoleIndex === -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100187 await member.roles.add(data.track[data.track.length - 1]);
pineafanad54d752022-04-18 19:01:43 +0100188 } else if (currentRoleIndex < data.track.length) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100189 if (!data.retainPrevious) await member.roles.remove(data.track[currentRoleIndex]);
pineafan63fc5e22022-08-04 22:04:10 +0100190 await member.roles.add(data.track[currentRoleIndex - 1]);
pineafanad54d752022-04-18 19:01:43 +0100191 }
192 }
pineafane23c4ec2022-07-27 21:56:27 +0100193 } else if (component.customId === "demote") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100194 if (allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100195 if (currentRoleIndex === data.track.length - 1) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100196 if (data.nullable) await member.roles.remove(data.track[currentRoleIndex]);
pineafanad54d752022-04-18 19:01:43 +0100197 } else if (currentRoleIndex > -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100198 await member.roles.remove(data.track[currentRoleIndex]);
199 await member.roles.add(data.track[currentRoleIndex + 1]);
pineafanad54d752022-04-18 19:01:43 +0100200 }
201 }
pineafane23c4ec2022-07-27 21:56:27 +0100202 } else if (component.customId === "select") {
pineafan63fc5e22022-08-04 22:04:10 +0100203 track = component.values[0];
pineafanad54d752022-04-18 19:01:43 +0100204 }
205 }
pineafan63fc5e22022-08-04 22:04:10 +0100206};
pineafanad54d752022-04-18 19:01:43 +0100207
Skyler Grey11236ba2022-08-08 21:13:33 +0100208const check = async (interaction: CommandInteraction, _defaultCheck: WrappedCheck) => {
209 const tracks = (await client.database.guilds.read(interaction.guild.id)).tracks;
210 if (tracks.length === 0) throw new Error("This server does not have any tracks");
Skyler Grey75ea9172022-08-06 10:22:23 +0100211 const member = interaction.member as GuildMember;
pineafanad54d752022-04-18 19:01:43 +0100212 // Allow the owner to promote anyone
pineafan63fc5e22022-08-04 22:04:10 +0100213 if (member.id === interaction.guild.ownerId) return true;
pineafan6702cef2022-06-13 17:52:37 +0100214 // Check if the user can manage any of the tracks
pineafan63fc5e22022-08-04 22:04:10 +0100215 let managed = false;
pineafanc1c18792022-08-03 21:41:36 +0100216 for (const element of tracks) {
pineafan63fc5e22022-08-04 22:04:10 +0100217 if (!element.track.manageableBy) continue;
Skyler Grey11236ba2022-08-08 21:13:33 +0100218 if (!element.track.manageableBy.some((role) => member.roles.cache.has(role))) continue;
pineafanc1c18792022-08-03 21:41:36 +0100219 managed = true;
220 break;
pineafan63fc5e22022-08-04 22:04:10 +0100221 }
pineafanad54d752022-04-18 19:01:43 +0100222 // Check if the user has manage_roles permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100223 if (!managed && !member.permissions.has("MANAGE_ROLES"))
Skyler Greyc634e2b2022-08-06 17:50:48 +0100224 throw new Error("You do not have the *Manage Roles* permission");
pineafanad54d752022-04-18 19:01:43 +0100225 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100226 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100227};
pineafanad54d752022-04-18 19:01:43 +0100228
229export { command };
230export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100231export { check };