blob: 5eb1d7ece6166dda5a41f1c10178ff63988e0699 [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);
Skyler Greyda16adf2023-03-05 10:22:12 +0000188 for (let i = 0; i < maxPage; i++) {
189 selectedRoles.push([]);
190 }
PineaFan0d06edc2023-01-17 22:10:31 +0000191
192 let page = 0;
193 let complete = completedPages.every((page) => page);
194 let done = false;
195
196 while (!(complete && done)) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000197 const currentPageData = options[page]!;
TheCodedProf4a088b12023-06-06 15:29:59 -0400198 currentPageData.max = currentPageData.max === 0 ? currentPageData.options.length : currentPageData.max;
PineaFan0d06edc2023-01-17 22:10:31 +0000199 const embed = new EmojiEmbed()
200 .setTitle("Roles")
201 .setDescription(
202 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000203 `> ${currentPageData.description}\n\n` +
204 (currentPageData.min === currentPageData.max
205 ? `Select ${addPlural(currentPageData.min, "role")}`
206 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
207 "\n\n" +
208 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000209 )
210 .setStatus("Success")
211 .setEmoji("GUILD.GREEN");
212 const components = [
213 new ActionRowBuilder<ButtonBuilder>().addComponents(
214 new ButtonBuilder()
215 .setStyle(ButtonStyle.Secondary)
216 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
217 .setCustomId("back")
218 .setDisabled(page === 0),
219 new ButtonBuilder()
220 .setStyle(ButtonStyle.Secondary)
221 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
222 .setCustomId("next")
223 .setDisabled(page === maxPage - 1),
224 new ButtonBuilder()
225 .setStyle(ButtonStyle.Success)
226 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
227 .setCustomId("done")
228 .setDisabled(!complete)
229 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500230 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000231 ];
232 await interaction.editReply({
233 embeds: [embed],
234 components: components
pineafan813bdf42022-07-24 10:39:10 +0100235 });
236 let component;
237 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000238 component = await m.awaitMessageComponent({
239 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000240 filter: (i) => {
241 return (
242 i.user.id === interaction.user.id &&
243 i.channel!.id === interaction.channel!.id &&
244 i.message.id === m.id
245 );
246 }
PineaFan0d06edc2023-01-17 22:10:31 +0000247 });
pineafan813bdf42022-07-24 10:39:10 +0100248 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500249 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100250 return;
pineafan813bdf42022-07-24 10:39:10 +0100251 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000252 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000253 if (component.customId === "back") {
254 page = Math.max(0, page - 1);
255 } else if (component.customId === "next") {
256 page = Math.min(maxPage - 1, page + 1);
257 } else if (component.customId === "done") {
258 done = true;
259 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
260 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000261 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000262 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100263 }
PineaFan0d06edc2023-01-17 22:10:31 +0000264 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100265 }
PineaFan0d06edc2023-01-17 22:10:31 +0000266
267 const fullRoleList: string[] = selectedRoles.flat();
268
Skyler Greyda16adf2023-03-05 10:22:12 +0000269 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
270 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
271 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
272 let roleObjectsToAdd = fullRoleList
273 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000274 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000275 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100276 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000277 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000278 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
279 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100280 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100281 return await interaction.reply({
282 embeds: [
283 new EmojiEmbed()
284 .setTitle("Roles")
285 .setDescription(
286 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
287 )
288 .setStatus("Danger")
289 .setEmoji("GUILD.RED")
290 ],
291 components: []
292 });
pineafan813bdf42022-07-24 10:39:10 +0100293 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100294 await interaction.editReply({
295 embeds: [
296 new EmojiEmbed()
297 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000298 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100299 .setStatus("Success")
300 .setEmoji("GUILD.GREEN")
301 ],
302 components: []
303 });
pineafan63fc5e22022-08-04 22:04:10 +0100304 return;
pineafan813bdf42022-07-24 10:39:10 +0100305}