blob: 8b623a4ca1da8bdd716fd3a668c5c0ad0e614297 [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;
TheCodedProf920d7292023-06-05 11:02:32 -040035 description: string | null;
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[],
49 disabled?: boolean
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);
TheCodedProf920d7292023-06-05 11:02:32 -040075 const options = config.roleMenu.options.filter((option) => option.options.length > 0);
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]!;
PineaFan0d06edc2023-01-17 22:10:31 +0000198 const embed = new EmojiEmbed()
199 .setTitle("Roles")
200 .setDescription(
201 `**${currentPageData.name}**\n` +
Skyler Greyda16adf2023-03-05 10:22:12 +0000202 `> ${currentPageData.description}\n\n` +
203 (currentPageData.min === currentPageData.max
204 ? `Select ${addPlural(currentPageData.min, "role")}`
205 : `Select between ${currentPageData.min} and ${currentPageData.max} roles then press next`) +
206 "\n\n" +
207 createPageIndicator(maxPage, page)
PineaFan0d06edc2023-01-17 22:10:31 +0000208 )
209 .setStatus("Success")
210 .setEmoji("GUILD.GREEN");
211 const components = [
212 new ActionRowBuilder<ButtonBuilder>().addComponents(
213 new ButtonBuilder()
214 .setStyle(ButtonStyle.Secondary)
215 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
216 .setCustomId("back")
217 .setDisabled(page === 0),
218 new ButtonBuilder()
219 .setStyle(ButtonStyle.Secondary)
220 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
221 .setCustomId("next")
222 .setDisabled(page === maxPage - 1),
223 new ButtonBuilder()
224 .setStyle(ButtonStyle.Success)
225 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
226 .setCustomId("done")
227 .setDisabled(!complete)
228 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500229 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000230 ];
231 await interaction.editReply({
232 embeds: [embed],
233 components: components
pineafan813bdf42022-07-24 10:39:10 +0100234 });
235 let component;
236 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000237 component = await m.awaitMessageComponent({
238 time: 300000,
Skyler Greyda16adf2023-03-05 10:22:12 +0000239 filter: (i) => {
240 return (
241 i.user.id === interaction.user.id &&
242 i.channel!.id === interaction.channel!.id &&
243 i.message.id === m.id
244 );
245 }
PineaFan0d06edc2023-01-17 22:10:31 +0000246 });
pineafan813bdf42022-07-24 10:39:10 +0100247 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500248 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100249 return;
pineafan813bdf42022-07-24 10:39:10 +0100250 }
Skyler Greyf4f21c42023-03-08 14:36:29 +0000251 await component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000252 if (component.customId === "back") {
253 page = Math.max(0, page - 1);
254 } else if (component.customId === "next") {
255 page = Math.min(maxPage - 1, page + 1);
256 } else if (component.customId === "done") {
257 done = true;
258 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
259 selectedRoles[page] = component.values;
Skyler Greyda16adf2023-03-05 10:22:12 +0000260 completedPages[page] = true;
PineaFan0d06edc2023-01-17 22:10:31 +0000261 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100262 }
PineaFan0d06edc2023-01-17 22:10:31 +0000263 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100264 }
PineaFan0d06edc2023-01-17 22:10:31 +0000265
266 const fullRoleList: string[] = selectedRoles.flat();
267
Skyler Greyda16adf2023-03-05 10:22:12 +0000268 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
269 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
270 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)); // IDs
271 let roleObjectsToAdd = fullRoleList
272 .map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
PineaFan0d06edc2023-01-17 22:10:31 +0000273 .filter((r) => r !== undefined) as Role[];
Skyler Greyda16adf2023-03-05 10:22:12 +0000274 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100275 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000276 roleException(interaction.guild.id, interaction.user.id);
PineaFan0d06edc2023-01-17 22:10:31 +0000277 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
278 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100279 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100280 return await interaction.reply({
281 embeds: [
282 new EmojiEmbed()
283 .setTitle("Roles")
284 .setDescription(
285 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
286 )
287 .setStatus("Danger")
288 .setEmoji("GUILD.RED")
289 ],
290 components: []
291 });
pineafan813bdf42022-07-24 10:39:10 +0100292 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100293 await interaction.editReply({
294 embeds: [
295 new EmojiEmbed()
296 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000297 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100298 .setStatus("Success")
299 .setEmoji("GUILD.GREEN")
300 ],
301 components: []
302 });
pineafan63fc5e22022-08-04 22:04:10 +0100303 return;
pineafan813bdf42022-07-24 10:39:10 +0100304}