blob: 41820ac27ff3a08079009a0c35101790ae45b81c [file] [log] [blame]
Samuel Shuert27bf3cd2023-03-03 15:51:25 -05001import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, Role, RoleSelectMenuBuilder, RoleSelectMenuInteraction, UserSelectMenuBuilder, UserSelectMenuInteraction } from "discord.js";
2import type { SlashCommandSubcommandBuilder } from "discord.js";
3import client from "../../utils/client.js";
4import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
5import { LoadingEmbed } from "../../utils/defaults.js";
6import getEmojiByName from "../../utils/getEmojiByName.js";
7import listToAndMore from "../../utils/listToAndMore.js"
8
9const { renderUser } = client.logger;
10
11const canEdit = (role: Role, member: GuildMember, me: GuildMember): [string, boolean] => {
12 if(role.position >= me.roles.highest.position ||
13 role.position >= member.roles.highest.position
14 ) return [`~~<@&${role.id}>~~`, false];
15 return [`<@&${role.id}>`, true];
16};
17
18const command = (builder: SlashCommandSubcommandBuilder) =>
19 builder
20 .setName("role")
21 .setDescription("Gives or removes a role from someone")
22 .addUserOption((option) => option.setName("user").setDescription("The user to give or remove the role from"))
23
24const callback = async (interaction: CommandInteraction): Promise<unknown> => {
25 const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
26
27 let member = interaction.options.getMember("user") as GuildMember | null;
28
29 if(!member) {
30 const memberEmbed = new EmojiEmbed()
31 .setTitle("Role")
32 .setDescription(`Please choose a member to edit the roles of.`)
33 .setEmoji("GUILD.ROLES.CREATE")
34 .setStatus("Success");
35 const memberChooser = new ActionRowBuilder<UserSelectMenuBuilder>().addComponents(
36 new UserSelectMenuBuilder()
37 .setCustomId("memberChooser")
38 .setPlaceholder("Select a member")
39 );
40 await interaction.editReply({embeds: [memberEmbed], components: [memberChooser]});
41
42 const filter = (i: UserSelectMenuInteraction) => i.customId === "memberChooser" && i.user.id === interaction.user.id;
43
44 let i: UserSelectMenuInteraction | null;
45 try {
46 i = await m.awaitMessageComponent<5>({ filter, time: 300000});
47 } catch (e) {
48 return;
49 }
50
51 memberEmbed.setDescription(`Editing roles for ${renderUser(i.values[0]!)}`);
52 await i.deferUpdate();
53 await interaction.editReply({ embeds: LoadingEmbed, components: [] })
54 member = await interaction.guild?.members.fetch(i.values[0]!)!;
55
56 }
57
58 let closed = false;
59 let rolesToChange: string[] = [];
60 const roleAdd = new ActionRowBuilder<RoleSelectMenuBuilder>()
61 .addComponents(
62 new RoleSelectMenuBuilder()
63 .setCustomId("roleAdd")
64 .setPlaceholder("Select a role to add")
65 .setMaxValues(25)
66 );
67
68 do {
69
70 const removing = rolesToChange.filter((r) => member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
71 const adding = rolesToChange.filter((r) => !member!.roles.cache.has(r)).map((r) => canEdit(interaction.guild?.roles.cache.get(r)!, interaction.member as GuildMember, interaction.guild?.members.me!)[0])
72 const embed = new EmojiEmbed()
73 .setTitle("Role")
74 .setDescription(
75 `${getEmojiByName("ICONS.EDIT")} Editing roles for <@${member.id}>\n\n` +
76 `Adding:\n` +
77 `${listToAndMore(adding.length > 0 ? adding : ["None"], 5)}\n` +
78 `Removing:\n` +
79 `${listToAndMore(removing.length > 0 ? removing : ["None"], 5)}\n`
80 )
81 .setEmoji("GUILD.ROLES.CREATE")
82 .setStatus("Success");
83
84 const buttons = new ActionRowBuilder<ButtonBuilder>()
85 .addComponents(
86 new ButtonBuilder()
87 .setCustomId("roleSave")
88 .setLabel("Apply")
89 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
90 .setStyle(ButtonStyle.Success),
91 new ButtonBuilder()
92 .setCustomId("roleDiscard")
93 .setLabel("Reset")
94 .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji)
95 .setStyle(ButtonStyle.Danger)
96 );
97
98 await interaction.editReply({ embeds: [embed], components: [roleAdd, buttons] });
99
100 let i: RoleSelectMenuInteraction | ButtonInteraction | null;
101 try {
102 i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 300000 }) as RoleSelectMenuInteraction | ButtonInteraction;
103 } catch (e) {
104 closed = true;
105 continue;
106 }
107
108 i.deferUpdate();
109 if(i.isButton()) {
110 switch(i.customId) {
111 case "roleSave": {
112 const roles = rolesToChange.map((r) => interaction.guild?.roles.cache.get(r)!);
113 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
114 const rolesToAdd: Role[] = [];
115 const rolesToRemove: Role[] = [];
116 for(const role of roles) {
117 if(!canEdit(role, interaction.member as GuildMember, interaction.guild?.members.me!)[1]) continue;
118 if(member.roles.cache.has(role.id)) {
119 rolesToRemove.push(role);
120 } else {
121 rolesToAdd.push(role);
122 }
123 }
124 await member.roles.add(rolesToAdd);
125 await member.roles.remove(rolesToRemove);
126 rolesToChange = [];
127 break;
128 }
129 case "roleDiscard": {
130 rolesToChange = [];
131 await interaction.editReply({ embeds: LoadingEmbed, components: [] });
132 break;
133 }
134 }
135 } else {
136 rolesToChange = i.values;
137 }
138
139 } while (!closed);
140
141};
142
143const check = (interaction: CommandInteraction, partial: boolean = false) => {
144 const member = interaction.member as GuildMember;
145 // Check if the user has manage_roles permission
146 if (!member.permissions.has("ManageRoles")) return "You do not have the *Manage Roles* permission";
147 if (partial) return true;
148 if (!interaction.guild) return
149 const me = interaction.guild.members.me!;
150 // Check if Nucleus has permission to role
151 if (!me.permissions.has("ManageRoles")) return "I do not have the *Manage Roles* permission";
152 // Allow the owner to role anyone
153 if (member.id === interaction.guild.ownerId) return true;
154 // Allow role
155 return true;
156};
157
158export { command };
159export { callback };
160export { check };