blob: e0e41e07aadd7a2d6eb6e02756d93e99e4dd3c77 [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";
pineafan63fc5e22022-08-04 22:04:10 +010013// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Skyler Grey75ea9172022-08-06 10:22:23 +010014// @ts-expect-error
pineafanad54d752022-04-18 19:01:43 +010015import { WrappedCheck } from "jshaiku";
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";
pineafanad54d752022-04-18 19:01:43 +010020
21const command = (builder: SlashCommandSubcommandBuilder) =>
22 builder
pineafan63fc5e22022-08-04 22:04:10 +010023 .setName("track")
24 .setDescription("Moves a user along a role track")
Skyler Grey75ea9172022-08-06 10:22:23 +010025 .addUserOption((option) =>
26 option
27 .setName("user")
28 .setDescription("The user to manage")
29 .setRequired(true)
30 );
pineafanad54d752022-04-18 19:01:43 +010031
Skyler Grey75ea9172022-08-06 10:22:23 +010032const generateFromTrack = (
33 position: number,
34 active: string | boolean,
35 size: number,
36 disabled: string | boolean
37) => {
pineafan63fc5e22022-08-04 22:04:10 +010038 active = active ? "ACTIVE" : "INACTIVE";
39 disabled = disabled ? "GREY." : "";
Skyler Grey75ea9172022-08-06 10:22:23 +010040 if (position === 0 && size === 1)
41 return "TRACKS.SINGLE." + disabled + active;
42 if (position === size - 1)
43 return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
pineafan63fc5e22022-08-04 22:04:10 +010044 if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;
45 return "TRACKS.VERTICAL.MIDDLE." + disabled + active;
46};
pineafanad54d752022-04-18 19:01:43 +010047
Skyler Grey75ea9172022-08-06 10:22:23 +010048const callback = async (
49 interaction: CommandInteraction
50): Promise<void | unknown> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010051 const { renderUser } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010052 const member = interaction.options.getMember("user") as GuildMember;
53 const guild = interaction.guild;
pineafan63fc5e22022-08-04 22:04:10 +010054 if (!guild) return;
55 const config = await client.database.guilds.read(guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010056 await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
pineafan63fc5e22022-08-04 22:04:10 +010057 let track = 0;
pineafanad54d752022-04-18 19:01:43 +010058 let generated;
pineafan63fc5e22022-08-04 22:04:10 +010059 const roles = await guild.roles.fetch();
60 const memberRoles = await member.roles;
61 let managed: boolean;
pineafanad54d752022-04-18 19:01:43 +010062 while (true) {
pineafan63fc5e22022-08-04 22:04:10 +010063 const data = config.tracks[track];
Skyler Grey75ea9172022-08-06 10:22:23 +010064 if (data.manageableBy !== undefined)
65 managed = data.manageableBy.some((element: string) => {
66 return memberRoles.cache.has(element);
pineafan63fc5e22022-08-04 22:04:10 +010067 });
Skyler Grey75ea9172022-08-06 10:22:23 +010068 else managed = false;
69 const dropdown = new Discord.MessageSelectMenu()
70 .addOptions(
71 config.tracks.map((option, index) => {
72 const hasRoleInTrack = option.track.some(
73 (element: string) => {
74 return memberRoles.cache.has(element);
75 }
76 );
77 return new SelectMenuOption({
78 default: index === track,
79 label: option.name,
80 value: index.toString(),
81 description:
82 option.track.length === 0
83 ? "No"
84 : addPlural(option.track.length, "role"),
85 emoji: client.emojis.resolve(
86 getEmojiByName(
87 "TRACKS.SINGLE." +
88 (hasRoleInTrack ? "ACTIVE" : "INACTIVE"),
89 "id"
90 )
91 )
92 });
93 })
94 )
95 .setCustomId("select")
96 .setMaxValues(1);
pineafan63fc5e22022-08-04 22:04:10 +010097 const allowed = [];
Skyler Grey75ea9172022-08-06 10:22:23 +010098 generated =
99 "**Track:** " +
100 data.name +
101 "\n" +
102 "**Member:** " +
103 renderUser(member.user) +
104 "\n";
105 generated +=
106 (data.nullable
107 ? "Members do not need a role in this track"
108 : "A role in this track is required") + "\n";
109 generated +=
110 (data.retainPrevious
111 ? "When promoted, the user keeps previous roles"
112 : "Members will lose their current role when promoted") + "\n";
113 generated +=
114 "\n" +
115 data.track
116 .map((role, index) => {
117 const allow =
118 roles.get(role).position >=
119 (interaction.member as GuildMember).roles.highest
120 .position && !managed;
121 allowed.push(!allow);
122 return (
123 getEmojiByName(
124 generateFromTrack(
125 index,
126 memberRoles.cache.has(role),
127 data.track.length,
128 allow
129 )
130 ) +
131 " " +
132 roles.get(role).name +
133 " [<@&" +
134 roles.get(role).id +
135 ">]"
136 );
137 })
138 .join("\n");
pineafan63fc5e22022-08-04 22:04:10 +0100139 const selected = [];
pineafanad54d752022-04-18 19:01:43 +0100140 for (let i = 0; i < data.track.length; i++) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100141 if (memberRoles.cache.has(data.track[i]))
142 selected.push(data.track[i]);
pineafanad54d752022-04-18 19:01:43 +0100143 }
pineafan63fc5e22022-08-04 22:04:10 +0100144 const conflict = data.retainPrevious ? false : selected.length > 1;
145 let conflictDropdown;
146 let currentRoleIndex;
pineafanad54d752022-04-18 19:01:43 +0100147 if (conflict) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100148 generated += `\n\n${getEmojiByName(
149 `PUNISH.WARN.${managed ? "YELLOW" : "RED"}`
150 )} This user has ${selected.length} roles from this track. `;
pineafan63fc5e22022-08-04 22:04:10 +0100151 conflictDropdown = [];
pineafanad54d752022-04-18 19:01:43 +0100152 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100153 roles.get(selected[0]).position <
154 memberRoles.highest.position ||
155 managed
pineafanad54d752022-04-18 19:01:43 +0100156 ) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100157 generated +=
158 "In order to promote or demote this user, you must select which role the member should keep.";
159 selected.forEach((role) => {
160 conflictDropdown.push(
161 new SelectMenuOption({
162 label: roles.get(role).name,
163 value: roles.get(role).id
164 })
165 );
pineafan63fc5e22022-08-04 22:04:10 +0100166 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100167 conflictDropdown = [
168 new Discord.MessageSelectMenu()
169 .addOptions(conflictDropdown)
170 .setCustomId("conflict")
171 .setMaxValues(1)
172 .setPlaceholder("Select a role to keep")
173 ];
pineafanad54d752022-04-18 19:01:43 +0100174 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100175 generated +=
176 "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 +0100177 }
178 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100179 currentRoleIndex =
180 selected.length === 0
181 ? -1
182 : data.track.indexOf(selected[0].toString());
pineafanad54d752022-04-18 19:01:43 +0100183 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100184 const m = (await interaction.editReply({
185 embeds: [
186 new EmojiEmbed()
187 .setEmoji("TRACKS.ICON")
188 .setTitle("Tracks")
189 .setDescription(`${generated}`)
190 .setStatus("Success")
191 ],
192 components: [new MessageActionRow().addComponents(dropdown)]
193 .concat(
194 conflict && conflictDropdown.length
195 ? [
196 new MessageActionRow().addComponents(
197 conflictDropdown
198 )
199 ]
200 : []
201 )
202 .concat([
203 new MessageActionRow().addComponents([
204 new MessageButton()
205 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
206 .setLabel("Move up")
207 .setCustomId("promote")
208 .setStyle("SUCCESS")
209 .setDisabled(
210 conflict ||
211 currentRoleIndex === 0 ||
212 (currentRoleIndex === -1
213 ? false
214 : !allowed[currentRoleIndex - 1])
215 ),
216 new MessageButton()
217 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
218 .setLabel("Move down")
219 .setCustomId("demote")
220 .setStyle("DANGER")
221 .setDisabled(
222 conflict ||
223 (data.nullable
224 ? currentRoleIndex <= -1
225 : currentRoleIndex ===
226 data.track.length - 1 ||
227 currentRoleIndex <= -1) ||
228 !allowed[currentRoleIndex]
229 )
230 ])
pineafan63fc5e22022-08-04 22:04:10 +0100231 ])
Skyler Grey75ea9172022-08-06 10:22:23 +0100232 })) as Message;
pineafanad54d752022-04-18 19:01:43 +0100233 let component;
234 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 component = await m.awaitMessageComponent({ time: 300000 });
pineafanad54d752022-04-18 19:01:43 +0100236 } catch (e) {
pineafan63fc5e22022-08-04 22:04:10 +0100237 return;
pineafanad54d752022-04-18 19:01:43 +0100238 }
pineafan63fc5e22022-08-04 22:04:10 +0100239 component.deferUpdate();
pineafane23c4ec2022-07-27 21:56:27 +0100240 if (component.customId === "conflict") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100241 const rolesToRemove = selected.filter(
242 (role) => role !== component.values[0]
243 );
pineafan63fc5e22022-08-04 22:04:10 +0100244 await member.roles.remove(rolesToRemove);
pineafane23c4ec2022-07-27 21:56:27 +0100245 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100246 if (
Skyler Grey75ea9172022-08-06 10:22:23 +0100247 currentRoleIndex === -1
248 ? allowed[data.track.length - 1]
249 : allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
pineafanad54d752022-04-18 19:01:43 +0100250 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100251 if (currentRoleIndex === -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100252 await member.roles.add(data.track[data.track.length - 1]);
pineafanad54d752022-04-18 19:01:43 +0100253 } else if (currentRoleIndex < data.track.length) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100254 if (!data.retainPrevious)
255 await member.roles.remove(data.track[currentRoleIndex]);
pineafan63fc5e22022-08-04 22:04:10 +0100256 await member.roles.add(data.track[currentRoleIndex - 1]);
pineafanad54d752022-04-18 19:01:43 +0100257 }
258 }
pineafane23c4ec2022-07-27 21:56:27 +0100259 } else if (component.customId === "demote") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 if (allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100261 if (currentRoleIndex === data.track.length - 1) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100262 if (data.nullable)
263 await member.roles.remove(data.track[currentRoleIndex]);
pineafanad54d752022-04-18 19:01:43 +0100264 } else if (currentRoleIndex > -1) {
pineafan63fc5e22022-08-04 22:04:10 +0100265 await member.roles.remove(data.track[currentRoleIndex]);
266 await member.roles.add(data.track[currentRoleIndex + 1]);
pineafanad54d752022-04-18 19:01:43 +0100267 }
268 }
pineafane23c4ec2022-07-27 21:56:27 +0100269 } else if (component.customId === "select") {
pineafan63fc5e22022-08-04 22:04:10 +0100270 track = component.values[0];
pineafanad54d752022-04-18 19:01:43 +0100271 }
272 }
pineafan63fc5e22022-08-04 22:04:10 +0100273};
pineafanad54d752022-04-18 19:01:43 +0100274
Skyler Grey75ea9172022-08-06 10:22:23 +0100275const check = async (
276 interaction: CommandInteraction,
277 _defaultCheck: WrappedCheck
278) => {
279 const tracks = (await client.database.guilds.read(interaction.guild.id))
280 .tracks;
pineafan63fc5e22022-08-04 22:04:10 +0100281 if (tracks.length === 0) throw "This server does not have any tracks";
Skyler Grey75ea9172022-08-06 10:22:23 +0100282 const member = interaction.member as GuildMember;
pineafanad54d752022-04-18 19:01:43 +0100283 // Allow the owner to promote anyone
pineafan63fc5e22022-08-04 22:04:10 +0100284 if (member.id === interaction.guild.ownerId) return true;
pineafan6702cef2022-06-13 17:52:37 +0100285 // Check if the user can manage any of the tracks
pineafan63fc5e22022-08-04 22:04:10 +0100286 let managed = false;
pineafanc1c18792022-08-03 21:41:36 +0100287 for (const element of tracks) {
pineafan63fc5e22022-08-04 22:04:10 +0100288 if (!element.track.manageableBy) continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 if (
290 !element.track.manageableBy.some((role) =>
291 member.roles.cache.has(role)
292 )
293 )
294 continue;
pineafanc1c18792022-08-03 21:41:36 +0100295 managed = true;
296 break;
pineafan63fc5e22022-08-04 22:04:10 +0100297 }
pineafanad54d752022-04-18 19:01:43 +0100298 // Check if the user has manage_roles permission
Skyler Grey75ea9172022-08-06 10:22:23 +0100299 if (!managed && !member.permissions.has("MANAGE_ROLES"))
300 throw "You do not have the *Manage Roles* permission";
pineafanad54d752022-04-18 19:01:43 +0100301 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100302 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100303};
pineafanad54d752022-04-18 19:01:43 +0100304
305export { command };
306export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100307export { check };