blob: 1a4c308da14626d8b28cdd75e726e4f3f8692daf [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import { LoadingEmbed } from "./../../utils/defaultEmbeds.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
3 CommandInteraction,
4 GuildMember,
5 Message,
6 MessageActionRow,
7 MessageButton
8} from "discord.js";
9import {
10 SelectMenuOption,
11 SlashCommandSubcommandBuilder
12} from "@discordjs/builders";
Skyler Grey75ea9172022-08-06 10:22:23 +010013// @ts-expect-error
pineafanad54d752022-04-18 19:01:43 +010014import { WrappedCheck } from "jshaiku";
pineafan4edb7762022-06-26 19:21:04 +010015import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanad54d752022-04-18 19:01:43 +010016import getEmojiByName from "../../utils/getEmojiByName.js";
pineafane625d782022-05-09 18:04:32 +010017import addPlural from "../../utils/plurals.js";
pineafan6702cef2022-06-13 17:52:37 +010018import client from "../../utils/client.js";
pineafanad54d752022-04-18 19:01:43 +010019
20const command = (builder: SlashCommandSubcommandBuilder) =>
21 builder
pineafan63fc5e22022-08-04 22:04:10 +010022 .setName("track")
23 .setDescription("Moves a user along a role track")
Skyler Grey75ea9172022-08-06 10:22:23 +010024 .addUserOption((option) =>
25 option
26 .setName("user")
27 .setDescription("The user to manage")
28 .setRequired(true)
29 );
pineafanad54d752022-04-18 19:01:43 +010030
Skyler Grey75ea9172022-08-06 10:22:23 +010031const generateFromTrack = (
32 position: number,
33 active: string | boolean,
34 size: number,
35 disabled: string | boolean
36) => {
pineafan63fc5e22022-08-04 22:04:10 +010037 active = active ? "ACTIVE" : "INACTIVE";
38 disabled = disabled ? "GREY." : "";
Skyler Grey75ea9172022-08-06 10:22:23 +010039 if (position === 0 && size === 1)
40 return "TRACKS.SINGLE." + disabled + active;
41 if (position === size - 1)
42 return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
pineafan63fc5e22022-08-04 22:04:10 +010043 if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
44 return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
45};
pineafanad54d752022-04-18 19:01:43 +010046
Skyler Greyc634e2b2022-08-06 17:50:48 +010047const callback = async (interaction: CommandInteraction): Promise<unknown> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010048 const { renderUser } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010049 const member = interaction.options.getMember("user") as GuildMember;
50 const guild = interaction.guild;
pineafan63fc5e22022-08-04 22:04:10 +010051 if (!guild) return;
52 const config = await client.database.guilds.read(guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010053 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
pineafan63fc5e22022-08-04 22:04:10 +010054 let track = 0;
pineafanad54d752022-04-18 19:01:43 +010055 let generated;
pineafan63fc5e22022-08-04 22:04:10 +010056 const roles = await guild.roles.fetch();
Skyler Greyc634e2b2022-08-06 17:50:48 +010057 const memberRoles = member.roles;
pineafan63fc5e22022-08-04 22:04:10 +010058 let managed: boolean;
pineafanad54d752022-04-18 19:01:43 +010059 while (true) {
pineafan63fc5e22022-08-04 22:04:10 +010060 const data = config.tracks[track];
Skyler Grey75ea9172022-08-06 10:22:23 +010061 if (data.manageableBy !== undefined)
62 managed = data.manageableBy.some((element: string) => {
63 return memberRoles.cache.has(element);
pineafan63fc5e22022-08-04 22:04:10 +010064 });
Skyler Grey75ea9172022-08-06 10:22:23 +010065 else managed = false;
66 const dropdown = new Discord.MessageSelectMenu()
67 .addOptions(
68 config.tracks.map((option, index) => {
69 const hasRoleInTrack = option.track.some(
70 (element: string) => {
71 return memberRoles.cache.has(element);
72 }
73 );
74 return new SelectMenuOption({
75 default: index === track,
76 label: option.name,
77 value: index.toString(),
78 description:
79 option.track.length === 0
80 ? "No"
81 : addPlural(option.track.length, "role"),
82 emoji: client.emojis.resolve(
83 getEmojiByName(
84 "TRACKS.SINGLE." +
85 (hasRoleInTrack ? "ACTIVE" : "INACTIVE"),
86 "id"
87 )
88 )
89 });
90 })
91 )
92 .setCustomId("select")
93 .setMaxValues(1);
pineafan63fc5e22022-08-04 22:04:10 +010094 const allowed = [];
Skyler Grey75ea9172022-08-06 10:22:23 +010095 generated =
96 "**Track:** " +
97 data.name +
98 "\n" +
99 "**Member:** " +
100 renderUser(member.user) +
101 "\n";
102 generated +=
103 (data.nullable
104 ? "Members do not need a role in this track"
105 : "A role in this track is required") + "\n";
106 generated +=
107 (data.retainPrevious
108 ? "When promoted, the user keeps previous roles"
109 : "Members will lose their current role when promoted") + "\n";
110 generated +=
111 "\n" +
112 data.track
113 .map((role, index) => {
114 const allow =
115 roles.get(role).position >=
116 (interaction.member as GuildMember).roles.highest
117 .position && !managed;
118 allowed.push(!allow);
119 return (
120 getEmojiByName(
121 generateFromTrack(
122 index,
123 memberRoles.cache.has(role),
124 data.track.length,
125 allow
126 )
127 ) +
128 " " +
129 roles.get(role).name +
130 " [<@&" +
131 roles.get(role).id +
132 ">]"
133 );
134 })
135 .join("\n");
pineafan63fc5e22022-08-04 22:04:10 +0100136 const selected = [];
Skyler Greyc634e2b2022-08-06 17:50:48 +0100137 for (const position of data.track) {
138 if (memberRoles.cache.has(position)) selected.push(position);
pineafanad54d752022-04-18 19:01:43 +0100139 }
pineafan63fc5e22022-08-04 22:04:10 +0100140 const conflict = data.retainPrevious ? false : selected.length > 1;
141 let conflictDropdown;
142 let currentRoleIndex;
pineafanad54d752022-04-18 19:01:43 +0100143 if (conflict) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100144 generated += `\n\n${getEmojiByName(
145 `PUNISH.WARN.${managed ? "YELLOW" : "RED"}`
146 )} This user has ${selected.length} roles from this track. `;
pineafan63fc5e22022-08-04 22:04:10 +0100147 conflictDropdown = [];
pineafanad54d752022-04-18 19:01:43 +0100148 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100149 roles.get(selected[0]).position <
150 memberRoles.highest.position ||
151 managed
pineafanad54d752022-04-18 19:01:43 +0100152 ) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 generated +=
154 "In order to promote or demote this user, you must select which role the member should keep.";
155 selected.forEach((role) => {
156 conflictDropdown.push(
157 new SelectMenuOption({
158 label: roles.get(role).name,
159 value: roles.get(role).id
160 })
161 );
pineafan63fc5e22022-08-04 22:04:10 +0100162 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100163 conflictDropdown = [
164 new Discord.MessageSelectMenu()
165 .addOptions(conflictDropdown)
166 .setCustomId("conflict")
167 .setMaxValues(1)
168 .setPlaceholder("Select a role to keep")
169 ];
pineafanad54d752022-04-18 19:01:43 +0100170 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100171 generated +=
172 "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 +0100173 }
174 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100175 currentRoleIndex =
176 selected.length === 0
177 ? -1
178 : data.track.indexOf(selected[0].toString());
pineafanad54d752022-04-18 19:01:43 +0100179 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100180 const m = (await interaction.editReply({
181 embeds: [
182 new EmojiEmbed()
183 .setEmoji("TRACKS.ICON")
184 .setTitle("Tracks")
185 .setDescription(`${generated}`)
186 .setStatus("Success")
187 ],
188 components: [new MessageActionRow().addComponents(dropdown)]
189 .concat(
190 conflict && conflictDropdown.length
191 ? [
192 new MessageActionRow().addComponents(
193 conflictDropdown
194 )
195 ]
196 : []
197 )
198 .concat([
199 new MessageActionRow().addComponents([
200 new MessageButton()
201 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
202 .setLabel("Move up")
203 .setCustomId("promote")
204 .setStyle("SUCCESS")
205 .setDisabled(
206 conflict ||
207 currentRoleIndex === 0 ||
208 (currentRoleIndex === -1
209 ? false
210 : !allowed[currentRoleIndex - 1])
211 ),
212 new MessageButton()
213 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
214 .setLabel("Move down")
215 .setCustomId("demote")
216 .setStyle("DANGER")
217 .setDisabled(
218 conflict ||
219 (data.nullable
220 ? currentRoleIndex <= -1
221 : currentRoleIndex ===
222 data.track.length - 1 ||
223 currentRoleIndex <= -1) ||
224 !allowed[currentRoleIndex]
225 )
226 ])
pineafan63fc5e22022-08-04 22:04:10 +0100227 ])
Skyler Grey75ea9172022-08-06 10:22:23 +0100228 })) as Message;
pineafanad54d752022-04-18 19:01:43 +0100229 let component;
230 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100231 component = await m.awaitMessageComponent({ time: 300000 });
pineafanad54d752022-04-18 19:01:43 +0100232 } catch (e) {
pineafan63fc5e22022-08-04 22:04:10 +0100233 return;
pineafanad54d752022-04-18 19:01:43 +0100234 }
pineafan63fc5e22022-08-04 22:04:10 +0100235 component.deferUpdate();
pineafane23c4ec2022-07-27 21:56:27 +0100236 if (component.customId === "conflict") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100237 const rolesToRemove = selected.filter(
238 (role) => role !== component.values[0]
239 );
pineafan63fc5e22022-08-04 22:04:10 +0100240 await member.roles.remove(rolesToRemove);
pineafane23c4ec2022-07-27 21:56:27 +0100241 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100242 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100243 currentRoleIndex === -1
244 ? allowed[data.track.length - 1]
245 : allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
pineafanad54d752022-04-18 19:01:43 +0100246 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100247 if (currentRoleIndex === -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100248 await member.roles.add(data.track[data.track.length - 1]);
pineafanad54d752022-04-18 19:01:43 +0100249 } else if (currentRoleIndex < data.track.length) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100250 if (!data.retainPrevious)
251 await member.roles.remove(data.track[currentRoleIndex]);
pineafan63fc5e22022-08-04 22:04:10 +0100252 await member.roles.add(data.track[currentRoleIndex - 1]);
pineafanad54d752022-04-18 19:01:43 +0100253 }
254 }
pineafane23c4ec2022-07-27 21:56:27 +0100255 } else if (component.customId === "demote") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100256 if (allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100257 if (currentRoleIndex === data.track.length - 1) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100258 if (data.nullable)
259 await member.roles.remove(data.track[currentRoleIndex]);
pineafanad54d752022-04-18 19:01:43 +0100260 } else if (currentRoleIndex > -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100261 await member.roles.remove(data.track[currentRoleIndex]);
262 await member.roles.add(data.track[currentRoleIndex + 1]);
pineafanad54d752022-04-18 19:01:43 +0100263 }
264 }
pineafane23c4ec2022-07-27 21:56:27 +0100265 } else if (component.customId === "select") {
pineafan63fc5e22022-08-04 22:04:10 +0100266 track = component.values[0];
pineafanad54d752022-04-18 19:01:43 +0100267 }
268 }
pineafan63fc5e22022-08-04 22:04:10 +0100269};
pineafanad54d752022-04-18 19:01:43 +0100270
Skyler Grey75ea9172022-08-06 10:22:23 +0100271const check = async (
272 interaction: CommandInteraction,
273 _defaultCheck: WrappedCheck
274) => {
275 const tracks = (await client.database.guilds.read(interaction.guild.id))
276 .tracks;
Skyler Greyc634e2b2022-08-06 17:50:48 +0100277 if (tracks.length === 0)
278 throw new Error("This server does not have any tracks");
Skyler Grey75ea9172022-08-06 10:22:23 +0100279 const member = interaction.member as GuildMember;
pineafanad54d752022-04-18 19:01:43 +0100280 // Allow the owner to promote anyone
pineafan63fc5e22022-08-04 22:04:10 +0100281 if (member.id === interaction.guild.ownerId) return true;
pineafan6702cef2022-06-13 17:52:37 +0100282 // Check if the user can manage any of the tracks
pineafan63fc5e22022-08-04 22:04:10 +0100283 let managed = false;
pineafanc1c18792022-08-03 21:41:36 +0100284 for (const element of tracks) {
pineafan63fc5e22022-08-04 22:04:10 +0100285 if (!element.track.manageableBy) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100286 if (
287 !element.track.manageableBy.some((role) =>
288 member.roles.cache.has(role)
289 )
290 )
291 continue;
pineafanc1c18792022-08-03 21:41:36 +0100292 managed = true;
293 break;
pineafan63fc5e22022-08-04 22:04:10 +0100294 }
pineafanad54d752022-04-18 19:01:43 +0100295 // Check if the user has manage_roles permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100296 if (!managed && !member.permissions.has("MANAGE_ROLES"))
Skyler Greyc634e2b2022-08-06 17:50:48 +0100297 throw new Error("You do not have the *Manage Roles* permission");
pineafanad54d752022-04-18 19:01:43 +0100298 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100299 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100300};
pineafanad54d752022-04-18 19:01:43 +0100301
302export { command };
303export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100304export { check };