blob: 69973931161d827357f742d15931a78d41ee8d48 [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;
35 description: string;
36 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,
48 selectedRoles?: string[]
49): ActionRowBuilder<StringSelectMenuBuilder> => {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050050 return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
51 new StringSelectMenuBuilder()
52 .setCustomId("roles")
53 .setPlaceholder(placeholder)
54 .setMinValues(currentPageData.min)
55 .setMaxValues(currentPageData.max)
Skyler Greyda16adf2023-03-05 10:22:12 +000056 .addOptions(
57 currentPageData.options.map((option: { name: string; description: string | null; role: string }) => {
58 const builder = new StringSelectMenuOptionBuilder()
59 .setLabel(option.name)
60 .setValue(option.role)
61 .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false);
62 if (option.description) builder.setDescription(option.description);
63 return builder;
64 })
65 )
66 );
67};
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050068
PineaFan538d3752023-01-12 21:48:23 +000069export async function callback(interaction: CommandInteraction | ButtonInteraction) {
PineaFan0d06edc2023-01-17 22:10:31 +000070 if (!interaction.member) return;
71 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010072 const config = await client.database.guilds.read(interaction.guild.id);
TheCodedProfe92b9b52023-03-06 17:07:34 -050073 if (!config.roleMenu.enabled) {
Skyler Grey75ea9172022-08-06 10:22:23 +010074 return await interaction.reply({
75 embeds: [
76 new EmojiEmbed()
77 .setTitle("Roles")
Skyler Greyda16adf2023-03-05 10:22:12 +000078 .setDescription(
79 "Self roles are currently disabled. Please contact a staff member or try again later."
80 )
Skyler Grey75ea9172022-08-06 10:22:23 +010081 .setStatus("Danger")
TheCodedProf48865eb2023-03-05 15:25:25 -050082 .setEmoji("GUILD.RED")
Skyler Grey75ea9172022-08-06 10:22:23 +010083 ],
84 ephemeral: true
85 });
TheCodedProfe92b9b52023-03-06 17:07:34 -050086 }
Skyler Grey75ea9172022-08-06 10:22:23 +010087 if (config.roleMenu.options.length === 0)
88 return await interaction.reply({
89 embeds: [
90 new EmojiEmbed()
91 .setTitle("Roles")
Skyler Greyda16adf2023-03-05 10:22:12 +000092 .setDescription(
93 "There are no roles available. Please contact a staff member if you believe this is a mistake."
94 )
Skyler Grey75ea9172022-08-06 10:22:23 +010095 .setStatus("Danger")
TheCodedProf48865eb2023-03-05 15:25:25 -050096 .setEmoji("GUILD.RED")
Skyler Grey75ea9172022-08-06 10:22:23 +010097 ],
98 ephemeral: true
99 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500100 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
Skyler Greyda16adf2023-03-05 10:22:12 +0000101 if (config.roleMenu.allowWebUI) {
102 // TODO: Make rolemenu web ui
103 const loginMethods: { webUI: boolean } = {
PineaFan0d06edc2023-01-17 22:10:31 +0000104 webUI: false
Skyler Greyda16adf2023-03-05 10:22:12 +0000105 };
PineaFan0d06edc2023-01-17 22:10:31 +0000106 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100107 const status = await fetch(client.config.baseUrl).then((res) => res.status);
PineaFan0d06edc2023-01-17 22:10:31 +0000108 if (status !== 200) loginMethods.webUI = false;
Skyler Greyda16adf2023-03-05 10:22:12 +0000109 } catch (e) {
PineaFan0d06edc2023-01-17 22:10:31 +0000110 loginMethods.webUI = false;
111 }
112 if (Object.values(loginMethods).some((i) => i)) {
113 let code = "";
114 if (loginMethods.webUI) {
115 let length = 5;
116 let itt = 0;
117 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
118 let valid = false;
119 while (!valid) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000120 itt++;
PineaFan0d06edc2023-01-17 22:10:31 +0000121 code = "";
122 for (let i = 0; i < length; i++) {
123 code += chars.charAt(Math.floor(Math.random() * chars.length));
124 }
125 if (code in client.roleMenu) continue;
126 if (itt > 1000) {
127 itt = 0;
Skyler Greyda16adf2023-03-05 10:22:12 +0000128 length++;
PineaFan0d06edc2023-01-17 22:10:31 +0000129 continue;
130 }
131 valid = true;
132 }
133 client.roleMenu[code] = {
134 guild: interaction.guild.id,
135 guildName: interaction.guild.name,
136 guildIcon: interaction.guild.iconURL({ extension: "png" }) ?? unknownServerIcon,
137 user: interaction.member!.user.id,
138 username: interaction.member!.user.username,
139 data: config.roleMenu.options,
PineaFan638eb132023-01-19 10:41:22 +0000140 interaction: interaction as CommandInteraction | ButtonInteraction
PineaFan0d06edc2023-01-17 22:10:31 +0000141 };
142 }
143 await interaction.editReply({
144 embeds: [
145 new EmojiEmbed()
146 .setTitle("Roles")
147 .setDescription("Select how to choose your roles")
148 .setStatus("Success")
149 .setEmoji("GUILD.GREEN")
150 ],
151 components: [
152 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400153 new ButtonBuilder()
PineaFan0d06edc2023-01-17 22:10:31 +0000154 .setLabel("Online")
155 .setStyle(ButtonStyle.Link)
156 .setDisabled(!loginMethods.webUI)
157 .setURL(`${client.config.baseUrl}nucleus/rolemenu?code=${code}`),
Skyler Greyda16adf2023-03-05 10:22:12 +0000158 new ButtonBuilder().setLabel("In Discord").setStyle(ButtonStyle.Primary).setCustomId("discord")
PineaFan0d06edc2023-01-17 22:10:31 +0000159 ])
160 ]
161 });
162 let component;
163 try {
164 component = await m.awaitMessageComponent({
165 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000166 filter: (i) => {
167 return (
168 i.user.id === interaction.user.id &&
169 i.channelId === interaction.channelId &&
170 i.message.id === m.id
171 );
172 }
PineaFan0d06edc2023-01-17 22:10:31 +0000173 });
174 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500175 console.log(e);
PineaFan0d06edc2023-01-17 22:10:31 +0000176 return;
177 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000178 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000179 }
180 }
181
182 const options = config.roleMenu.options;
183 const selectedRoles: string[][] = [];
184 const maxPage = options.length;
185 const completedPages: boolean[] = options.map((option) => option.min === 0);
Skyler Greyda16adf2023-03-05 10:22:12 +0000186 for (let i = 0; i < maxPage; i++) {
187 selectedRoles.push([]);
188 }
PineaFan0d06edc2023-01-17 22:10:31 +0000189
190 let page = 0;
191 let complete = completedPages.every((page) => page);
192 let done = false;
193
194 while (!(complete && done)) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000195 const currentPageData = options[page]!;
PineaFan0d06edc2023-01-17 22:10:31 +0000196 const embed = new EmojiEmbed()
197 .setTitle("Roles")
198 .setDescription(
199 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000200 `> ${currentPageData.description}\n\n` +
201 (currentPageData.min === currentPageData.max
202 ? `Select ${addPlural(currentPageData.min, "role")}`
203 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
204 "\n\n" +
205 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000206 )
207 .setStatus("Success")
208 .setEmoji("GUILD.GREEN");
209 const components = [
210 new ActionRowBuilder<ButtonBuilder>().addComponents(
211 new ButtonBuilder()
212 .setStyle(ButtonStyle.Secondary)
213 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
214 .setCustomId("back")
215 .setDisabled(page === 0),
216 new ButtonBuilder()
217 .setStyle(ButtonStyle.Secondary)
218 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
219 .setCustomId("next")
220 .setDisabled(page === maxPage - 1),
221 new ButtonBuilder()
222 .setStyle(ButtonStyle.Success)
223 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
224 .setCustomId("done")
225 .setDisabled(!complete)
226 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500227 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000228 ];
229 await interaction.editReply({
230 embeds: [embed],
231 components: components
pineafan813bdf42022-07-24 10:39:10 +0100232 });
233 let component;
234 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000235 component = await m.awaitMessageComponent({
236 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000237 filter: (i) => {
238 return (
239 i.user.id === interaction.user.id &&
240 i.channel!.id === interaction.channel!.id &&
241 i.message.id === m.id
242 );
243 }
PineaFan0d06edc2023-01-17 22:10:31 +0000244 });
pineafan813bdf42022-07-24 10:39:10 +0100245 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500246 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100247 return;
pineafan813bdf42022-07-24 10:39:10 +0100248 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000249 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000250 if (component.customId === "back") {
251 page = Math.max(0, page - 1);
252 } else if (component.customId === "next") {
253 page = Math.min(maxPage - 1, page + 1);
254 } else if (component.customId === "done") {
255 done = true;
256 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
257 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000258 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000259 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100260 }
PineaFan0d06edc2023-01-17 22:10:31 +0000261 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100262 }
PineaFan0d06edc2023-01-17 22:10:31 +0000263
264 const fullRoleList: string[] = selectedRoles.flat();
265
Skyler Greyda16adf2023-03-05 10:22:12 +0000266 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
267 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
268 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
269 let roleObjectsToAdd = fullRoleList
270 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000271 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000272 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100273 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000274 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000275 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
276 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100277 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100278 return await interaction.reply({
279 embeds: [
280 new EmojiEmbed()
281 .setTitle("Roles")
282 .setDescription(
283 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
284 )
285 .setStatus("Danger")
286 .setEmoji("GUILD.RED")
287 ],
288 components: []
289 });
pineafan813bdf42022-07-24 10:39:10 +0100290 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100291 await interaction.editReply({
292 embeds: [
293 new EmojiEmbed()
294 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000295 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100296 .setStatus("Success")
297 .setEmoji("GUILD.GREEN")
298 ],
299 components: []
300 });
pineafan63fc5e22022-08-04 22:04:10 +0100301 return;
pineafan813bdf42022-07-24 10:39:10 +0100302}