blob: c2e09860fee51e78de3db9e04e710b3bf9cbf8a2 [file] [log] [blame]
pineafane23c4ec2022-07-27 21:56:27 +01001import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
pineafanad54d752022-04-18 19:01:43 +01002import Discord, { CommandInteraction, GuildMember, Message, MessageActionRow, MessageButton } from "discord.js";
3import { SelectMenuOption, SlashCommandSubcommandBuilder } from "@discordjs/builders";
4import { WrappedCheck } from "jshaiku";
pineafan4edb7762022-06-26 19:21:04 +01005import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
pineafanad54d752022-04-18 19:01:43 +01006import getEmojiByName from "../../utils/getEmojiByName.js";
pineafane625d782022-05-09 18:04:32 +01007import addPlural from "../../utils/plurals.js";
pineafan6702cef2022-06-13 17:52:37 +01008import client from "../../utils/client.js";
pineafanad54d752022-04-18 19:01:43 +01009
10const command = (builder: SlashCommandSubcommandBuilder) =>
11 builder
12 .setName("track")
13 .setDescription("Moves a user along a role track")
14 .addUserOption(option => option.setName("user").setDescription("The user to manage").setRequired(true))
15
16const generateFromTrack = (position: number, active: any, size: number, disabled: any) => {
17 active = active ? "ACTIVE" : "INACTIVE"
18 disabled = disabled ? "GREY." : ""
pineafane23c4ec2022-07-27 21:56:27 +010019 if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active
20 if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active
21 if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active
pineafanad54d752022-04-18 19:01:43 +010022 return "TRACKS.VERTICAL.MIDDLE." + disabled + active
23}
24
pineafan4edb7762022-06-26 19:21:04 +010025const callback = async (interaction: CommandInteraction): Promise<any> => {
PineappleFanb3dd83c2022-06-17 10:53:48 +010026 const { renderUser } = client.logger;
pineafanad54d752022-04-18 19:01:43 +010027 const member = interaction.options.getMember("user") as GuildMember;
28 const guild = interaction.guild;
pineafan4edb7762022-06-26 19:21:04 +010029 let config = await client.database.guilds.read(guild.id);
pineafane23c4ec2022-07-27 21:56:27 +010030 await interaction.reply({embeds: LoadingEmbed, ephemeral: true})
pineafanad54d752022-04-18 19:01:43 +010031 let track = 0
32 let generated;
33 const roles = await guild.roles.fetch()
34 let memberRoles = await member.roles
pineafan4edb7762022-06-26 19:21:04 +010035 let managed
pineafanad54d752022-04-18 19:01:43 +010036 while (true) {
37 let data = config.tracks[track]
pineafan4edb7762022-06-26 19:21:04 +010038 if (data.manageableBy !== undefined) managed = data.manageableBy.some(element => {return memberRoles.cache.has(element)})
39 else managed = false
pineafanad54d752022-04-18 19:01:43 +010040 let dropdown = new Discord.MessageSelectMenu().addOptions(config.tracks.map((option, index) => {
41 let hasRoleInTrack = option.track.some(element => {return memberRoles.cache.has(element)})
42 return new SelectMenuOption({
pineafane23c4ec2022-07-27 21:56:27 +010043 default: index === track,
pineafanad54d752022-04-18 19:01:43 +010044 label: option.name,
45 value: index.toString(),
pineafane23c4ec2022-07-27 21:56:27 +010046 description: option.track.length === 0 ? "No" : addPlural(option.track.length, "role"),
PineappleFanb3dd83c2022-06-17 10:53:48 +010047 emoji: client.emojis.resolve(getEmojiByName("TRACKS.SINGLE." + (hasRoleInTrack ? "ACTIVE" : "INACTIVE"), "id"))
pineafanad54d752022-04-18 19:01:43 +010048 })
49 })).setCustomId("select").setMaxValues(1)
50 let allowed = []
51 generated = "**Track:** " + data.name + "\n" + "**Member:** " + renderUser(member.user) + "\n"
52 generated += (data.nullable ? "Members do not need a role in this track" : "A role in this track is required") + "\n"
53 generated += (data.retainPrevious ? "When promoted, the user keeps previous roles" : "Members will lose their current role when promoted") + "\n"
54 generated += "\n" + data.track.map((role, index) => {
55 let allow = (roles.get(role).position >= (interaction.member as GuildMember).roles.highest.position) && !managed
56 allowed.push(!allow)
57 return getEmojiByName(generateFromTrack(
58 index,
59 memberRoles.cache.has(role),
60 data.track.length,
61 allow
62 )) + " " +
63 roles.get(role).name + " [<@&" + roles.get(role).id + ">]"
64 }).join("\n")
65 let selected = [];
66 for (let i = 0; i < data.track.length; i++) {
67 if (memberRoles.cache.has(data.track[i])) selected.push(data.track[i])
68 }
69 let conflict = data.retainPrevious ? false : selected.length > 1;
70 let conflictDropdown
71 let currentRoleIndex
72 if (conflict) {
73 generated += `\n\n${getEmojiByName(`PUNISH.WARN.${managed ? "YELLOW" : "RED"}`)} This user has ${selected.length} roles from this track. `
74 conflictDropdown = []
75 if (
76 (roles.get(selected[0]).position < memberRoles.highest.position) || managed
77 ) {
78 generated += `In order to promote or demote this user, you must select which role the member should keep.`
79 selected.forEach(role => {
80 conflictDropdown.push(new SelectMenuOption({
81 label: roles.get(role).name,
82 value: roles.get(role).id,
83 }))
84 })
85 conflictDropdown = [new Discord.MessageSelectMenu()
86 .addOptions(conflictDropdown)
87 .setCustomId("conflict")
88 .setMaxValues(1)
89 .setPlaceholder("Select a role to keep")]
90 } else {
91 generated += "You don't have permission to manage one or more of the users roles, and therefore can't select one to keep."
92 }
93 } else {
pineafane23c4ec2022-07-27 21:56:27 +010094 currentRoleIndex = selected.length === 0 ? -1 : data.track.indexOf(selected[0].toString())
pineafanad54d752022-04-18 19:01:43 +010095 }
pineafan4edb7762022-06-26 19:21:04 +010096 let m = await interaction.editReply({embeds: [new EmojiEmbed()
pineafanad54d752022-04-18 19:01:43 +010097 .setEmoji("TRACKS.ICON")
98 .setTitle("Tracks")
99 .setDescription(`${generated}`)
100 .setStatus("Success")
101 ], components: [
102 new MessageActionRow().addComponents(dropdown)
103 ]
104 .concat(conflict && conflictDropdown.length ? [new MessageActionRow().addComponents(conflictDropdown)] : [])
105 .concat([
106 new MessageActionRow().addComponents([
107 new MessageButton()
108 .setEmoji(getEmojiByName("CONTROL.UP", "id"))
109 .setLabel("Move up")
110 .setCustomId("promote")
111 .setStyle("SUCCESS")
pineafane23c4ec2022-07-27 21:56:27 +0100112 .setDisabled(conflict || currentRoleIndex === 0 || (currentRoleIndex === -1 ? false : !allowed[currentRoleIndex - 1])),
pineafanad54d752022-04-18 19:01:43 +0100113 new MessageButton()
114 .setEmoji(getEmojiByName("CONTROL.DOWN", "id"))
115 .setLabel("Move down")
116 .setCustomId("demote")
117 .setStyle("DANGER")
118 .setDisabled(conflict || (
119 data.nullable ? currentRoleIndex <= -1 :
pineafane23c4ec2022-07-27 21:56:27 +0100120 currentRoleIndex === data.track.length - 1 || currentRoleIndex <= -1
pineafanad54d752022-04-18 19:01:43 +0100121 ) || !allowed[currentRoleIndex]),
122 ])
123 ])})
124 let component;
125 try {
pineafanc6158ab2022-06-17 16:34:07 +0100126 component = await (m as Message).awaitMessageComponent({time: 300000});
pineafanad54d752022-04-18 19:01:43 +0100127 } catch (e) {
128 return
129 }
130 component.deferUpdate()
pineafane23c4ec2022-07-27 21:56:27 +0100131 if (component.customId === "conflict") {
132 let rolesToRemove = selected.filter(role => role !== component.values[0])
pineafanad54d752022-04-18 19:01:43 +0100133 await member.roles.remove(rolesToRemove)
pineafane23c4ec2022-07-27 21:56:27 +0100134 } else if (component.customId === "promote") {
pineafanad54d752022-04-18 19:01:43 +0100135 if (
pineafane23c4ec2022-07-27 21:56:27 +0100136 currentRoleIndex === -1 ? allowed[data.track.length - 1] :
pineafanad54d752022-04-18 19:01:43 +0100137 allowed[currentRoleIndex - 1] && allowed[currentRoleIndex]
138 ) {
pineafane23c4ec2022-07-27 21:56:27 +0100139 if (currentRoleIndex === -1) {
pineafanad54d752022-04-18 19:01:43 +0100140 await member.roles.add(data.track[data.track.length - 1])
141 } else if (currentRoleIndex < data.track.length) {
142 if (!data.retainPrevious) await member.roles.remove(data.track[currentRoleIndex])
143 await member.roles.add(data.track[currentRoleIndex - 1])
144 }
145 }
pineafane23c4ec2022-07-27 21:56:27 +0100146 } else if (component.customId === "demote") {
pineafanad54d752022-04-18 19:01:43 +0100147 if(allowed[currentRoleIndex]) {
pineafane23c4ec2022-07-27 21:56:27 +0100148 if (currentRoleIndex === data.track.length - 1) {
pineafanad54d752022-04-18 19:01:43 +0100149 if (data.nullable) await member.roles.remove(data.track[currentRoleIndex])
150 } else if (currentRoleIndex > -1) {
151 await member.roles.remove(data.track[currentRoleIndex])
152 await member.roles.add(data.track[currentRoleIndex + 1])
153 }
154 }
pineafane23c4ec2022-07-27 21:56:27 +0100155 } else if (component.customId === "select") {
pineafanad54d752022-04-18 19:01:43 +0100156 track = component.values[0]
157 }
158 }
159}
160
pineafan6702cef2022-06-13 17:52:37 +0100161const check = async (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
pineafan4edb7762022-06-26 19:21:04 +0100162 let tracks = (await client.database.guilds.read(interaction.guild.id)).tracks
pineafan73a7c4a2022-07-24 10:38:04 +0100163 if (tracks.length === 0) throw "This server does not have any tracks"
PineappleFan19b002b2022-05-19 11:54:01 +0100164 let member = (interaction.member as GuildMember)
pineafanad54d752022-04-18 19:01:43 +0100165 // Allow the owner to promote anyone
pineafane23c4ec2022-07-27 21:56:27 +0100166 if (member.id === interaction.guild.ownerId) return true
pineafan6702cef2022-06-13 17:52:37 +0100167 // Check if the user can manage any of the tracks
pineafan6702cef2022-06-13 17:52:37 +0100168 let managed = false
pineafanc1c18792022-08-03 21:41:36 +0100169 for (const element of tracks) {
170 if (!element.track.manageableBy) continue
171 if (!element.track.manageableBy.some(role => member.roles.cache.has(role))) continue
172 managed = true;
173 break;
174 };
pineafanad54d752022-04-18 19:01:43 +0100175 // Check if the user has manage_roles permission
pineafane23c4ec2022-07-27 21:56:27 +0100176 if (!managed && ! member.permissions.has("MANAGE_ROLES")) throw "You do not have the *Manage Roles* permission";
pineafanad54d752022-04-18 19:01:43 +0100177 // Allow track
pineafan6702cef2022-06-13 17:52:37 +0100178 return true;
pineafanad54d752022-04-18 19:01:43 +0100179}
180
181export { command };
182export { callback };
pineafanc6158ab2022-06-17 16:34:07 +0100183export { check };
184