blob: a2ce8b24c479cbdb44e5ce89f008c01053345850 [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;
TheCodedProf9f24ffc2023-06-06 15:48:30 -0400189 if (memberRoleIDs instanceof GuildMemberRoleManager) {
TheCodedProf2fee0822023-06-06 15:46:23 -0400190 memberRoleIDs = memberRoleIDs.cache.map((r) => r.id);
191 }
192 for (const page of options) {
TheCodedProf9f24ffc2023-06-06 15:48:30 -0400193 selectedRoles.push(
194 page.options
195 .filter((option) => (memberRoleIDs as string[]).includes(option.role))
196 .map((option) => option.role)
197 );
Skyler Greyda16adf2023-03-05 10:22:12 +0000198 }
PineaFan0d06edc2023-01-17 22:10:31 +0000199
200 let page = 0;
201 let complete = completedPages.every((page) => page);
202 let done = false;
203
204 while (!(complete && done)) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000205 const currentPageData = options[page]!;
TheCodedProf4a088b12023-06-06 15:29:59 -0400206 currentPageData.max = currentPageData.max === 0 ? currentPageData.options.length : currentPageData.max;
PineaFan0d06edc2023-01-17 22:10:31 +0000207 const embed = new EmojiEmbed()
208 .setTitle("Roles")
209 .setDescription(
210 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000211 `> ${currentPageData.description}\n\n` +
212 (currentPageData.min === currentPageData.max
213 ? `Select ${addPlural(currentPageData.min, "role")}`
214 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
215 "\n\n" +
216 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000217 )
218 .setStatus("Success")
219 .setEmoji("GUILD.GREEN");
220 const components = [
221 new ActionRowBuilder<ButtonBuilder>().addComponents(
222 new ButtonBuilder()
223 .setStyle(ButtonStyle.Secondary)
224 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
225 .setCustomId("back")
226 .setDisabled(page === 0),
227 new ButtonBuilder()
228 .setStyle(ButtonStyle.Secondary)
229 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
230 .setCustomId("next")
231 .setDisabled(page === maxPage - 1),
232 new ButtonBuilder()
233 .setStyle(ButtonStyle.Success)
234 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
235 .setCustomId("done")
236 .setDisabled(!complete)
237 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500238 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000239 ];
240 await interaction.editReply({
241 embeds: [embed],
242 components: components
pineafan813bdf42022-07-24 10:39:10 +0100243 });
244 let component;
245 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000246 component = await m.awaitMessageComponent({
247 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000248 filter: (i) => {
249 return (
250 i.user.id === interaction.user.id &&
251 i.channel!.id === interaction.channel!.id &&
252 i.message.id === m.id
253 );
254 }
PineaFan0d06edc2023-01-17 22:10:31 +0000255 });
pineafan813bdf42022-07-24 10:39:10 +0100256 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500257 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100258 return;
pineafan813bdf42022-07-24 10:39:10 +0100259 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000260 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000261 if (component.customId === "back") {
262 page = Math.max(0, page - 1);
263 } else if (component.customId === "next") {
264 page = Math.min(maxPage - 1, page + 1);
265 } else if (component.customId === "done") {
266 done = true;
267 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
268 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000269 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000270 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100271 }
PineaFan0d06edc2023-01-17 22:10:31 +0000272 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100273 }
PineaFan0d06edc2023-01-17 22:10:31 +0000274
275 const fullRoleList: string[] = selectedRoles.flat();
276
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
278 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
279 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
280 let roleObjectsToAdd = fullRoleList
281 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000282 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000283 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100284 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000285 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000286 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
287 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100288 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100289 return await interaction.reply({
290 embeds: [
291 new EmojiEmbed()
292 .setTitle("Roles")
293 .setDescription(
294 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
295 )
296 .setStatus("Danger")
297 .setEmoji("GUILD.RED")
298 ],
299 components: []
300 });
pineafan813bdf42022-07-24 10:39:10 +0100301 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100302 await interaction.editReply({
303 embeds: [
304 new EmojiEmbed()
305 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000306 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100307 .setStatus("Success")
308 .setEmoji("GUILD.GREEN")
309 ],
310 components: []
311 });
pineafan63fc5e22022-08-04 22:04:10 +0100312 return;
pineafan813bdf42022-07-24 10:39:10 +0100313}