blob: 3445ea28026cda508cb5c0f2334520e9ddd4a7a2 [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import { unknownServerIcon } from "./../utils/defaults.js";
PineaFan0d06edc2023-01-17 22:10:31 +00002import {
PineaFan0d06edc2023-01-17 22:10:31 +00003 ButtonBuilder,
4 CommandInteraction,
5 ButtonStyle,
6 ButtonInteraction,
7 StringSelectMenuOptionBuilder,
8 StringSelectMenuBuilder,
9 GuildMemberRoleManager,
PineaFan638eb132023-01-19 10:41:22 +000010 Role,
11 ContextMenuCommandInteraction
PineaFan0d06edc2023-01-17 22:10:31 +000012} from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +010013import EmojiEmbed from "../utils/generateEmojiEmbed.js";
PineaFan0d06edc2023-01-17 22:10:31 +000014import { ActionRowBuilder } from "discord.js";
pineafan813bdf42022-07-24 10:39:10 +010015import getEmojiByName from "../utils/getEmojiByName.js";
16import client from "../utils/client.js";
PineaFan0d06edc2023-01-17 22:10:31 +000017import { LoadingEmbed } from "../utils/defaults.js";
Skyler Grey11236ba2022-08-08 21:13:33 +010018import type { GuildConfig } from "../utils/database.js";
Skyler Greyda16adf2023-03-05 10:22:12 +000019import { roleException } from "../utils/createTemporaryStorage.js";
20import addPlural from "../utils/plurals.js";
21import createPageIndicator from "../utils/createPageIndicator.js";
pineafan813bdf42022-07-24 10:39:10 +010022
Skyler Grey11236ba2022-08-08 21:13:33 +010023export interface RoleMenuSchema {
24 guild: string;
25 guildName: string;
26 guildIcon: string;
27 user: string;
28 username: string;
29 data: GuildConfig["roleMenu"]["options"];
PineaFan638eb132023-01-19 10:41:22 +000030 interaction: CommandInteraction | ButtonInteraction | ContextMenuCommandInteraction;
Skyler Grey11236ba2022-08-08 21:13:33 +010031}
32
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050033interface ObjectSchema {
34 name: string;
TheCodedProf4a088b12023-06-06 15:29:59 -040035 description?: string;
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050036 min: number;
37 max: number;
38 options: {
39 name: string;
40 description: string | null;
41 role: string;
42 }[];
43}
44
Skyler Greyda16adf2023-03-05 10:22:12 +000045export const configToDropdown = (
46 placeholder: string,
47 currentPageData: ObjectSchema,
pineafan72659cc2023-05-28 13:36:44 +010048 selectedRoles?: string[],
TheCodedProf4a088b12023-06-06 15:29:59 -040049 disabled: boolean = false
Skyler Greyda16adf2023-03-05 10:22:12 +000050): ActionRowBuilder<StringSelectMenuBuilder> => {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050051 return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
52 new StringSelectMenuBuilder()
53 .setCustomId("roles")
54 .setPlaceholder(placeholder)
55 .setMinValues(currentPageData.min)
56 .setMaxValues(currentPageData.max)
pineafan72659cc2023-05-28 13:36:44 +010057 .setDisabled(disabled)
Skyler Greyda16adf2023-03-05 10:22:12 +000058 .addOptions(
59 currentPageData.options.map((option: { name: string; description: string | null; role: string }) => {
60 const builder = new StringSelectMenuOptionBuilder()
61 .setLabel(option.name)
62 .setValue(option.role)
63 .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false);
64 if (option.description) builder.setDescription(option.description);
65 return builder;
66 })
67 )
68 );
69};
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050070
PineaFan538d3752023-01-12 21:48:23 +000071export async function callback(interaction: CommandInteraction | ButtonInteraction) {
PineaFan0d06edc2023-01-17 22:10:31 +000072 if (!interaction.member) return;
73 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010074 const config = await client.database.guilds.read(interaction.guild.id);
TheCodedProf4a088b12023-06-06 15:29:59 -040075 const options = config.roleMenu.options.filter((option) => option.options.length > 0 && option.enabled);
TheCodedProfe92b9b52023-03-06 17:07:34 -050076 if (!config.roleMenu.enabled) {
Skyler Grey75ea9172022-08-06 10:22:23 +010077 return await interaction.reply({
78 embeds: [
79 new EmojiEmbed()
80 .setTitle("Roles")
Skyler Greyda16adf2023-03-05 10:22:12 +000081 .setDescription(
82 "Self roles are currently disabled. Please contact a staff member or try again later."
83 )
Skyler Grey75ea9172022-08-06 10:22:23 +010084 .setStatus("Danger")
TheCodedProf48865eb2023-03-05 15:25:25 -050085 .setEmoji("GUILD.RED")
Skyler Grey75ea9172022-08-06 10:22:23 +010086 ],
87 ephemeral: true
88 });
TheCodedProfe92b9b52023-03-06 17:07:34 -050089 }
TheCodedProf920d7292023-06-05 11:02:32 -040090 if (options.length === 0) {
Skyler Grey75ea9172022-08-06 10:22:23 +010091 return await interaction.reply({
92 embeds: [
93 new EmojiEmbed()
94 .setTitle("Roles")
Skyler Greyda16adf2023-03-05 10:22:12 +000095 .setDescription(
96 "There are no roles available. Please contact a staff member if you believe this is a mistake."
97 )
Skyler Grey75ea9172022-08-06 10:22:23 +010098 .setStatus("Danger")
TheCodedProf48865eb2023-03-05 15:25:25 -050099 .setEmoji("GUILD.RED")
Skyler Grey75ea9172022-08-06 10:22:23 +0100100 ],
101 ephemeral: true
102 });
TheCodedProf920d7292023-06-05 11:02:32 -0400103 }
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500104 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
Skyler Greyda16adf2023-03-05 10:22:12 +0000105 if (config.roleMenu.allowWebUI) {
106 // TODO: Make rolemenu web ui
107 const loginMethods: { webUI: boolean } = {
PineaFan0d06edc2023-01-17 22:10:31 +0000108 webUI: false
Skyler Greyda16adf2023-03-05 10:22:12 +0000109 };
PineaFan0d06edc2023-01-17 22:10:31 +0000110 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100111 const status = await fetch(client.config.baseUrl).then((res) => res.status);
PineaFan0d06edc2023-01-17 22:10:31 +0000112 if (status !== 200) loginMethods.webUI = false;
Skyler Greyda16adf2023-03-05 10:22:12 +0000113 } catch (e) {
PineaFan0d06edc2023-01-17 22:10:31 +0000114 loginMethods.webUI = false;
115 }
116 if (Object.values(loginMethods).some((i) => i)) {
117 let code = "";
118 if (loginMethods.webUI) {
119 let length = 5;
120 let itt = 0;
121 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
122 let valid = false;
123 while (!valid) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000124 itt++;
PineaFan0d06edc2023-01-17 22:10:31 +0000125 code = "";
126 for (let i = 0; i < length; i++) {
127 code += chars.charAt(Math.floor(Math.random() * chars.length));
128 }
129 if (code in client.roleMenu) continue;
130 if (itt > 1000) {
131 itt = 0;
Skyler Greyda16adf2023-03-05 10:22:12 +0000132 length++;
PineaFan0d06edc2023-01-17 22:10:31 +0000133 continue;
134 }
135 valid = true;
136 }
137 client.roleMenu[code] = {
138 guild: interaction.guild.id,
139 guildName: interaction.guild.name,
140 guildIcon: interaction.guild.iconURL({ extension: "png" }) ?? unknownServerIcon,
141 user: interaction.member!.user.id,
142 username: interaction.member!.user.username,
143 data: config.roleMenu.options,
PineaFan638eb132023-01-19 10:41:22 +0000144 interaction: interaction as CommandInteraction | ButtonInteraction
PineaFan0d06edc2023-01-17 22:10:31 +0000145 };
146 }
147 await interaction.editReply({
148 embeds: [
149 new EmojiEmbed()
150 .setTitle("Roles")
151 .setDescription("Select how to choose your roles")
152 .setStatus("Success")
153 .setEmoji("GUILD.GREEN")
154 ],
155 components: [
156 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400157 new ButtonBuilder()
PineaFan0d06edc2023-01-17 22:10:31 +0000158 .setLabel("Online")
159 .setStyle(ButtonStyle.Link)
160 .setDisabled(!loginMethods.webUI)
161 .setURL(`${client.config.baseUrl}nucleus/rolemenu?code=${code}`),
Skyler Greyda16adf2023-03-05 10:22:12 +0000162 new ButtonBuilder().setLabel("In Discord").setStyle(ButtonStyle.Primary).setCustomId("discord")
PineaFan0d06edc2023-01-17 22:10:31 +0000163 ])
164 ]
165 });
166 let component;
167 try {
168 component = await m.awaitMessageComponent({
169 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000170 filter: (i) => {
171 return (
172 i.user.id === interaction.user.id &&
173 i.channelId === interaction.channelId &&
174 i.message.id === m.id
175 );
176 }
PineaFan0d06edc2023-01-17 22:10:31 +0000177 });
178 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500179 console.log(e);
PineaFan0d06edc2023-01-17 22:10:31 +0000180 return;
181 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000182 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000183 }
184 }
PineaFan0d06edc2023-01-17 22:10:31 +0000185 const selectedRoles: string[][] = [];
186 const maxPage = options.length;
187 const completedPages: boolean[] = options.map((option) => option.min === 0);
TheCodedProf2fee0822023-06-06 15:46:23 -0400188 let memberRoleIDs = interaction.member.roles;
189 if(memberRoleIDs instanceof GuildMemberRoleManager) {
190 memberRoleIDs = memberRoleIDs.cache.map((r) => r.id);
191 }
192 for (const page of options) {
193 selectedRoles.push(page.options.filter((option) => (memberRoleIDs as string[]).includes(option.role)).map((option) => option.role))
Skyler Greyda16adf2023-03-05 10:22:12 +0000194 }
PineaFan0d06edc2023-01-17 22:10:31 +0000195
196 let page = 0;
197 let complete = completedPages.every((page) => page);
198 let done = false;
199
200 while (!(complete && done)) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000201 const currentPageData = options[page]!;
TheCodedProf4a088b12023-06-06 15:29:59 -0400202 currentPageData.max = currentPageData.max === 0 ? currentPageData.options.length : currentPageData.max;
PineaFan0d06edc2023-01-17 22:10:31 +0000203 const embed = new EmojiEmbed()
204 .setTitle("Roles")
205 .setDescription(
206 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000207 `> ${currentPageData.description}\n\n` +
208 (currentPageData.min === currentPageData.max
209 ? `Select ${addPlural(currentPageData.min, "role")}`
210 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
211 "\n\n" +
212 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000213 )
214 .setStatus("Success")
215 .setEmoji("GUILD.GREEN");
216 const components = [
217 new ActionRowBuilder<ButtonBuilder>().addComponents(
218 new ButtonBuilder()
219 .setStyle(ButtonStyle.Secondary)
220 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
221 .setCustomId("back")
222 .setDisabled(page === 0),
223 new ButtonBuilder()
224 .setStyle(ButtonStyle.Secondary)
225 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
226 .setCustomId("next")
227 .setDisabled(page === maxPage - 1),
228 new ButtonBuilder()
229 .setStyle(ButtonStyle.Success)
230 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
231 .setCustomId("done")
232 .setDisabled(!complete)
233 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500234 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000235 ];
236 await interaction.editReply({
237 embeds: [embed],
238 components: components
pineafan813bdf42022-07-24 10:39:10 +0100239 });
240 let component;
241 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000242 component = await m.awaitMessageComponent({
243 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000244 filter: (i) => {
245 return (
246 i.user.id === interaction.user.id &&
247 i.channel!.id === interaction.channel!.id &&
248 i.message.id === m.id
249 );
250 }
PineaFan0d06edc2023-01-17 22:10:31 +0000251 });
pineafan813bdf42022-07-24 10:39:10 +0100252 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500253 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100254 return;
pineafan813bdf42022-07-24 10:39:10 +0100255 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000256 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000257 if (component.customId === "back") {
258 page = Math.max(0, page - 1);
259 } else if (component.customId === "next") {
260 page = Math.min(maxPage - 1, page + 1);
261 } else if (component.customId === "done") {
262 done = true;
263 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
264 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000265 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000266 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100267 }
PineaFan0d06edc2023-01-17 22:10:31 +0000268 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100269 }
PineaFan0d06edc2023-01-17 22:10:31 +0000270
271 const fullRoleList: string[] = selectedRoles.flat();
272
Skyler Greyda16adf2023-03-05 10:22:12 +0000273 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
274 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
275 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
276 let roleObjectsToAdd = fullRoleList
277 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000278 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000279 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100280 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000281 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000282 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
283 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100284 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100285 return await interaction.reply({
286 embeds: [
287 new EmojiEmbed()
288 .setTitle("Roles")
289 .setDescription(
290 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
291 )
292 .setStatus("Danger")
293 .setEmoji("GUILD.RED")
294 ],
295 components: []
296 });
pineafan813bdf42022-07-24 10:39:10 +0100297 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100298 await interaction.editReply({
299 embeds: [
300 new EmojiEmbed()
301 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000302 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100303 .setStatus("Success")
304 .setEmoji("GUILD.GREEN")
305 ],
306 components: []
307 });
pineafan63fc5e22022-08-04 22:04:10 +0100308 return;
pineafan813bdf42022-07-24 10:39:10 +0100309}