blob: 74b6c4110372083d89fc84332df9fc820c622898 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ActionRowBuilder,
3 APIMessageComponentEmoji,
4 ButtonBuilder,
5 ButtonInteraction,
6 ButtonStyle,
7 CommandInteraction,
8 GuildMember,
9 Role,
10 RoleSelectMenuBuilder,
11 RoleSelectMenuInteraction,
12 UserSelectMenuBuilder,
13 UserSelectMenuInteraction
14} from "discord.js";
TheCodedProf4a958a12023-02-14 13:41:51 -050015import type { SlashCommandSubcommandBuilder } from "discord.js";
16import client from "../../utils/client.js";
17import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
18import { LoadingEmbed } from "../../utils/defaults.js";
19import getEmojiByName from "../../utils/getEmojiByName.js";
Skyler Greyda16adf2023-03-05 10:22:12 +000020import listToAndMore from "../../utils/listToAndMore.js";
TheCodedProf4a958a12023-02-14 13:41:51 -050021
22const { renderUser } = client.logger;
23
24const canEdit = (role: Role, member: GuildMember, me: GuildMember): [string, boolean] => {
Skyler Greyda16adf2023-03-05 10:22:12 +000025 if (role.position >= me.roles.highest.position || role.position >= member.roles.highest.position)
26 return [`~~<@&${role.id}>~~`, false];
TheCodedProf4a958a12023-02-14 13:41:51 -050027 return [`<@&${role.id}>`, true];
28};
29
30const command = (builder: SlashCommandSubcommandBuilder) =>
31 builder
32 .setName("role")
33 .setDescription("Gives or removes a role from someone")
Skyler Greyda16adf2023-03-05 10:22:12 +000034 .addUserOption((option) => option.setName("user").setDescription("The user to give or remove the role from"));
TheCodedProf4a958a12023-02-14 13:41:51 -050035
36const callback = async (interaction: CommandInteraction): Promise<unknown> => {
37 const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
38
39 let member = interaction.options.getMember("user") as GuildMember | null;
40
Skyler Greyda16adf2023-03-05 10:22:12 +000041 if (!member) {
TheCodedProf1807fb32023-02-20 14:33:48 -050042 const memberEmbed = new EmojiEmbed()
TheCodedProf4a958a12023-02-14 13:41:51 -050043 .setTitle("Role")
44 .setDescription(`Please choose a member to edit the roles of.`)
45 .setEmoji("GUILD.ROLES.CREATE")
46 .setStatus("Success");
TheCodedProf1807fb32023-02-20 14:33:48 -050047 const memberChooser = new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
Skyler Greyda16adf2023-03-05 10:22:12 +000048 new UserSelectMenuBuilder().setCustomId("memberChooser").setPlaceholder("Select a member")
TheCodedProf4a958a12023-02-14 13:41:51 -050049 );
Skyler Greyda16adf2023-03-05 10:22:12 +000050 await interaction.editReply({ embeds: [memberEmbed], components: [memberChooser] });
TheCodedProf4a958a12023-02-14 13:41:51 -050051
Skyler Greyda16adf2023-03-05 10:22:12 +000052 const filter = (i: UserSelectMenuInteraction) =>
53 i.customId === "memberChooser" && i.user.id === interaction.user.id;
TheCodedProf4a958a12023-02-14 13:41:51 -050054
55 let i: UserSelectMenuInteraction | null;
56 try {
Skyler Greyda16adf2023-03-05 10:22:12 +000057 i = await m.awaitMessageComponent<5>({ filter, time: 300000 });
TheCodedProf4a958a12023-02-14 13:41:51 -050058 } catch (e) {
59 return;
60 }
61
TheCodedProf4a958a12023-02-14 13:41:51 -050062 memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`);
63 await i.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +000064 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
TheCodedProf4a958a12023-02-14 13:41:51 -050065 member = await interaction.guild?.members.fetch(i.values[0]!)!;
TheCodedProf4a958a12023-02-14 13:41:51 -050066 }
67
68 let closed = false;
69 let rolesToChange: string[] = [];
Skyler Greyda16adf2023-03-05 10:22:12 +000070 const roleAdd = new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(
71 new RoleSelectMenuBuilder().setCustomId("roleAdd").setPlaceholder("Select a role to add").setMaxValues(25)
72 );
TheCodedProf4a958a12023-02-14 13:41:51 -050073
74 do {
Skyler Greyda16adf2023-03-05 10:22:12 +000075 const removing = rolesToChange
76 .filter((r) => member!.roles.cache.has(r))
77 .map(
78 (r) =>
79 canEdit(
80 interaction.guild?.roles.cache.get(r)!,
81 interaction.member as GuildMember,
82 interaction.guild?.members.me!
83 )[0]
TheCodedProf4a958a12023-02-14 13:41:51 -050084 );
Skyler Greyda16adf2023-03-05 10:22:12 +000085 const adding = rolesToChange
86 .filter((r) => !member!.roles.cache.has(r))
87 .map(
88 (r) =>
89 canEdit(
90 interaction.guild?.roles.cache.get(r)!,
91 interaction.member as GuildMember,
92 interaction.guild?.members.me!
93 )[0]
94 );
95 const embed = new EmojiEmbed()
96 .setTitle("Role")
97 .setDescription(
98 `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` +
99 `Adding:\n` +
100 `${listToAndMore(adding.length > 0 ? adding : ["None"], 5)}\n` +
101 `Removing:\n` +
102 `${listToAndMore(removing.length > 0 ? removing : ["None"], 5)}\n`
103 )
104 .setEmoji("GUILD.ROLES.CREATE")
105 .setStatus("Success");
106
107 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
108 new ButtonBuilder()
109 .setCustomId("roleSave")
110 .setLabel("Apply")
111 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
112 .setStyle(ButtonStyle.Success),
113 new ButtonBuilder()
114 .setCustomId("roleDiscard")
115 .setLabel("Reset")
116 .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji)
117 .setStyle(ButtonStyle.Danger)
118 );
TheCodedProf4a958a12023-02-14 13:41:51 -0500119
120 await interaction.editReply({ embeds: [embed], components: [roleAdd, buttons] });
121
122 let i: RoleSelectMenuInteraction | ButtonInteraction | null;
123 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000124 i = (await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 })) as
125 | RoleSelectMenuInteraction
126 | ButtonInteraction;
TheCodedProf4a958a12023-02-14 13:41:51 -0500127 } catch (e) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500128 closed = true;
129 continue;
TheCodedProf4a958a12023-02-14 13:41:51 -0500130 }
131
TheCodedProf4a958a12023-02-14 13:41:51 -0500132 i.deferUpdate();
Skyler Greyda16adf2023-03-05 10:22:12 +0000133 if (i.isButton()) {
134 switch (i.customId) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500135 case "roleSave": {
TheCodedProf4a958a12023-02-14 13:41:51 -0500136 const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!);
137 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
TheCodedProf1807fb32023-02-20 14:33:48 -0500138 const rolesToAdd: Role[] = [];
139 const rolesToRemove: Role[] = [];
Skyler Greyda16adf2023-03-05 10:22:12 +0000140 for (const role of roles) {
141 if (!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1])
142 continue;
143 if (member.roles.cache.has(role.id)) {
TheCodedProf4a958a12023-02-14 13:41:51 -0500144 rolesToRemove.push(role);
145 } else {
146 rolesToAdd.push(role);
147 }
148 }
149 await member.roles.add(rolesToAdd);
150 await member.roles.remove(rolesToRemove);
151 rolesToChange = [];
152 break;
TheCodedProf1807fb32023-02-20 14:33:48 -0500153 }
154 case "roleDiscard": {
TheCodedProf4a958a12023-02-14 13:41:51 -0500155 rolesToChange = [];
156 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
157 break;
TheCodedProf1807fb32023-02-20 14:33:48 -0500158 }
TheCodedProf4a958a12023-02-14 13:41:51 -0500159 }
160 } else {
161 rolesToChange = i.values;
162 }
TheCodedProf4a958a12023-02-14 13:41:51 -0500163 } while (!closed);
TheCodedProf4a958a12023-02-14 13:41:51 -0500164};
165
166const check = (interaction: CommandInteraction, partial: boolean = false) => {
167 const member = interaction.member as GuildMember;
168 // Check if the user has manage_roles permission
169 if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
170 if (partial) return true;
Skyler Greyda16adf2023-03-05 10:22:12 +0000171 if (!interaction.guild) return;
TheCodedProf4a958a12023-02-14 13:41:51 -0500172 const me = interaction.guild.members.me!;
173 // Check if Nucleus has permission to role
174 if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
175 // Allow the owner to role anyone
176 if (member.id === interaction.guild.ownerId) return true;
177 // Allow role
178 return true;
179};
180
181export { command };
182export { callback };
183export { check };