blob: 27b5f076d59f9f058078430c204d5b2ef8852ebb [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);
Skyler Grey75ea9172022-08-06 10:22:23 +010073 if (!config.roleMenu.enabled)
74 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 });
86 if (config.roleMenu.options.length === 0)
87 return await interaction.reply({
88 embeds: [
89 new EmojiEmbed()
90 .setTitle("Roles")
Skyler Greyda16adf2023-03-05 10:22:12 +000091 .setDescription(
92 "There are no roles available. Please contact a staff member if you believe this is a mistake."
93 )
Skyler Grey75ea9172022-08-06 10:22:23 +010094 .setStatus("Danger")
TheCodedProf48865eb2023-03-05 15:25:25 -050095 .setEmoji("GUILD.RED")
Skyler Grey75ea9172022-08-06 10:22:23 +010096 ],
97 ephemeral: true
98 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050099 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
Skyler Greyda16adf2023-03-05 10:22:12 +0000100 if (config.roleMenu.allowWebUI) {
101 // TODO: Make rolemenu web ui
102 const loginMethods: { webUI: boolean } = {
PineaFan0d06edc2023-01-17 22:10:31 +0000103 webUI: false
Skyler Greyda16adf2023-03-05 10:22:12 +0000104 };
PineaFan0d06edc2023-01-17 22:10:31 +0000105 try {
Skyler Grey11236ba2022-08-08 21:13:33 +0100106 const status = await fetch(client.config.baseUrl).then((res) => res.status);
PineaFan0d06edc2023-01-17 22:10:31 +0000107 if (status !== 200) loginMethods.webUI = false;
Skyler Greyda16adf2023-03-05 10:22:12 +0000108 } catch (e) {
PineaFan0d06edc2023-01-17 22:10:31 +0000109 loginMethods.webUI = false;
110 }
111 if (Object.values(loginMethods).some((i) => i)) {
112 let code = "";
113 if (loginMethods.webUI) {
114 let length = 5;
115 let itt = 0;
116 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
117 let valid = false;
118 while (!valid) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000119 itt++;
PineaFan0d06edc2023-01-17 22:10:31 +0000120 code = "";
121 for (let i = 0; i < length; i++) {
122 code += chars.charAt(Math.floor(Math.random() * chars.length));
123 }
124 if (code in client.roleMenu) continue;
125 if (itt > 1000) {
126 itt = 0;
Skyler Greyda16adf2023-03-05 10:22:12 +0000127 length++;
PineaFan0d06edc2023-01-17 22:10:31 +0000128 continue;
129 }
130 valid = true;
131 }
132 client.roleMenu[code] = {
133 guild: interaction.guild.id,
134 guildName: interaction.guild.name,
135 guildIcon: interaction.guild.iconURL({ extension: "png" }) ?? unknownServerIcon,
136 user: interaction.member!.user.id,
137 username: interaction.member!.user.username,
138 data: config.roleMenu.options,
PineaFan638eb132023-01-19 10:41:22 +0000139 interaction: interaction as CommandInteraction | ButtonInteraction
PineaFan0d06edc2023-01-17 22:10:31 +0000140 };
141 }
142 await interaction.editReply({
143 embeds: [
144 new EmojiEmbed()
145 .setTitle("Roles")
146 .setDescription("Select how to choose your roles")
147 .setStatus("Success")
148 .setEmoji("GUILD.GREEN")
149 ],
150 components: [
151 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400152 new ButtonBuilder()
PineaFan0d06edc2023-01-17 22:10:31 +0000153 .setLabel("Online")
154 .setStyle(ButtonStyle.Link)
155 .setDisabled(!loginMethods.webUI)
156 .setURL(`${client.config.baseUrl}nucleus/rolemenu?code=${code}`),
Skyler Greyda16adf2023-03-05 10:22:12 +0000157 new ButtonBuilder().setLabel("In Discord").setStyle(ButtonStyle.Primary).setCustomId("discord")
PineaFan0d06edc2023-01-17 22:10:31 +0000158 ])
159 ]
160 });
161 let component;
162 try {
163 component = await m.awaitMessageComponent({
164 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000165 filter: (i) => {
166 return (
167 i.user.id === interaction.user.id &&
168 i.channelId === interaction.channelId &&
169 i.message.id === m.id
170 );
171 }
PineaFan0d06edc2023-01-17 22:10:31 +0000172 });
173 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500174 console.log(e);
PineaFan0d06edc2023-01-17 22:10:31 +0000175 return;
176 }
177 component.deferUpdate();
178 }
179 }
180
181 const options = config.roleMenu.options;
182 const selectedRoles: string[][] = [];
183 const maxPage = options.length;
184 const completedPages: boolean[] = options.map((option) => option.min === 0);
Skyler Greyda16adf2023-03-05 10:22:12 +0000185 for (let i = 0; i < maxPage; i++) {
186 selectedRoles.push([]);
187 }
PineaFan0d06edc2023-01-17 22:10:31 +0000188
189 let page = 0;
190 let complete = completedPages.every((page) => page);
191 let done = false;
192
193 while (!(complete && done)) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000194 const currentPageData = options[page]!;
PineaFan0d06edc2023-01-17 22:10:31 +0000195 const embed = new EmojiEmbed()
196 .setTitle("Roles")
197 .setDescription(
198 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000199 `> ${currentPageData.description}\n\n` +
200 (currentPageData.min === currentPageData.max
201 ? `Select ${addPlural(currentPageData.min, "role")}`
202 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
203 "\n\n" +
204 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000205 )
206 .setStatus("Success")
207 .setEmoji("GUILD.GREEN");
208 const components = [
209 new ActionRowBuilder<ButtonBuilder>().addComponents(
210 new ButtonBuilder()
211 .setStyle(ButtonStyle.Secondary)
212 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
213 .setCustomId("back")
214 .setDisabled(page === 0),
215 new ButtonBuilder()
216 .setStyle(ButtonStyle.Secondary)
217 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
218 .setCustomId("next")
219 .setDisabled(page === maxPage - 1),
220 new ButtonBuilder()
221 .setStyle(ButtonStyle.Success)
222 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
223 .setCustomId("done")
224 .setDisabled(!complete)
225 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500226 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000227 ];
228 await interaction.editReply({
229 embeds: [embed],
230 components: components
pineafan813bdf42022-07-24 10:39:10 +0100231 });
232 let component;
233 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000234 component = await m.awaitMessageComponent({
235 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000236 filter: (i) => {
237 return (
238 i.user.id === interaction.user.id &&
239 i.channel!.id === interaction.channel!.id &&
240 i.message.id === m.id
241 );
242 }
PineaFan0d06edc2023-01-17 22:10:31 +0000243 });
pineafan813bdf42022-07-24 10:39:10 +0100244 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500245 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100246 return;
pineafan813bdf42022-07-24 10:39:10 +0100247 }
pineafan63fc5e22022-08-04 22:04:10 +0100248 component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000249 if (component.customId === "back") {
250 page = Math.max(0, page - 1);
251 } else if (component.customId === "next") {
252 page = Math.min(maxPage - 1, page + 1);
253 } else if (component.customId === "done") {
254 done = true;
255 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
256 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000257 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000258 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100259 }
PineaFan0d06edc2023-01-17 22:10:31 +0000260 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100261 }
PineaFan0d06edc2023-01-17 22:10:31 +0000262
263 const fullRoleList: string[] = selectedRoles.flat();
264
Skyler Greyda16adf2023-03-05 10:22:12 +0000265 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
266 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
267 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
268 let roleObjectsToAdd = fullRoleList
269 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000270 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000271 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100272 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000273 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000274 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
275 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100276 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100277 return await interaction.reply({
278 embeds: [
279 new EmojiEmbed()
280 .setTitle("Roles")
281 .setDescription(
282 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
283 )
284 .setStatus("Danger")
285 .setEmoji("GUILD.RED")
286 ],
287 components: []
288 });
pineafan813bdf42022-07-24 10:39:10 +0100289 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100290 await interaction.editReply({
291 embeds: [
292 new EmojiEmbed()
293 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000294 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100295 .setStatus("Success")
296 .setEmoji("GUILD.GREEN")
297 ],
298 components: []
299 });
pineafan63fc5e22022-08-04 22:04:10 +0100300 return;
pineafan813bdf42022-07-24 10:39:10 +0100301}