blob: 732c94d4ae58db8fa19364b7361b1ab7efece887 [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { unknownServerIcon } from './../utils/defaults.js';
2import {
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";
PineaFan0d06edc2023-01-17 22:10:31 +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
PineaFan538d3752023-01-12 21:48:23 +000033export async function callback(interaction: CommandInteraction | ButtonInteraction) {
PineaFan0d06edc2023-01-17 22:10:31 +000034 if (!interaction.member) return;
35 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010036 const config = await client.database.guilds.read(interaction.guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010037 if (!config.roleMenu.enabled)
38 return await interaction.reply({
39 embeds: [
40 new EmojiEmbed()
41 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +000042 .setDescription("Self roles are currently disabled. Please contact a staff member or try again later.")
Skyler Grey75ea9172022-08-06 10:22:23 +010043 .setStatus("Danger")
PineaFan0d06edc2023-01-17 22:10:31 +000044 .setEmoji("GUILD.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +010045 ],
46 ephemeral: true
47 });
48 if (config.roleMenu.options.length === 0)
49 return await interaction.reply({
50 embeds: [
51 new EmojiEmbed()
52 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +000053 .setDescription("There are no roles available. Please contact a staff member if you believe this is a mistake.")
Skyler Grey75ea9172022-08-06 10:22:23 +010054 .setStatus("Danger")
PineaFan0d06edc2023-01-17 22:10:31 +000055 .setEmoji("GUILD.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +010056 ],
57 ephemeral: true
58 });
PineaFan0d06edc2023-01-17 22:10:31 +000059 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
60 if (config.roleMenu.allowWebUI) { // TODO: Make rolemenu web ui
61 const loginMethods: {webUI: boolean} = {
62 webUI: false
pineafan813bdf42022-07-24 10:39:10 +010063 }
PineaFan0d06edc2023-01-17 22:10:31 +000064 try {
Skyler Grey11236ba2022-08-08 21:13:33 +010065 const status = await fetch(client.config.baseUrl).then((res) => res.status);
PineaFan0d06edc2023-01-17 22:10:31 +000066 if (status !== 200) loginMethods.webUI = false;
67 } catch(e) {
68 loginMethods.webUI = false;
69 }
70 if (Object.values(loginMethods).some((i) => i)) {
71 let code = "";
72 if (loginMethods.webUI) {
73 let length = 5;
74 let itt = 0;
75 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
76 let valid = false;
77 while (!valid) {
TheCodedProf4a6d5712023-01-19 15:54:40 -050078 itt ++;
PineaFan0d06edc2023-01-17 22:10:31 +000079 code = "";
80 for (let i = 0; i < length; i++) {
81 code += chars.charAt(Math.floor(Math.random() * chars.length));
82 }
83 if (code in client.roleMenu) continue;
84 if (itt > 1000) {
85 itt = 0;
TheCodedProf4a6d5712023-01-19 15:54:40 -050086 length ++;
PineaFan0d06edc2023-01-17 22:10:31 +000087 continue;
88 }
89 valid = true;
90 }
91 client.roleMenu[code] = {
92 guild: interaction.guild.id,
93 guildName: interaction.guild.name,
94 guildIcon: interaction.guild.iconURL({ extension: "png" }) ?? unknownServerIcon,
95 user: interaction.member!.user.id,
96 username: interaction.member!.user.username,
97 data: config.roleMenu.options,
PineaFan638eb132023-01-19 10:41:22 +000098 interaction: interaction as CommandInteraction | ButtonInteraction
PineaFan0d06edc2023-01-17 22:10:31 +000099 };
100 }
101 await interaction.editReply({
102 embeds: [
103 new EmojiEmbed()
104 .setTitle("Roles")
105 .setDescription("Select how to choose your roles")
106 .setStatus("Success")
107 .setEmoji("GUILD.GREEN")
108 ],
109 components: [
110 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400111 new ButtonBuilder()
PineaFan0d06edc2023-01-17 22:10:31 +0000112 .setLabel("Online")
113 .setStyle(ButtonStyle.Link)
114 .setDisabled(!loginMethods.webUI)
115 .setURL(`${client.config.baseUrl}nucleus/rolemenu?code=${code}`),
116 new ButtonBuilder()
117 .setLabel("In Discord")
118 .setStyle(ButtonStyle.Primary)
119 .setCustomId("discord")
120 ])
121 ]
122 });
123 let component;
124 try {
125 component = await m.awaitMessageComponent({
126 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500127 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id}
PineaFan0d06edc2023-01-17 22:10:31 +0000128 });
129 } catch (e) {
130 return;
131 }
132 component.deferUpdate();
133 }
134 }
135
136 const options = config.roleMenu.options;
137 const selectedRoles: string[][] = [];
138 const maxPage = options.length;
139 const completedPages: boolean[] = options.map((option) => option.min === 0);
140 for (let i = 0; i < maxPage; i++) { selectedRoles.push([]); }
141
142 let page = 0;
143 let complete = completedPages.every((page) => page);
144 let done = false;
145
146 while (!(complete && done)) {
147 const currentPageData = options[page]!
148 const embed = new EmojiEmbed()
149 .setTitle("Roles")
150 .setDescription(
151 `**${currentPageData.name}**\n` +
152 `> ${currentPageData.description}\n\n` +
153 (currentPageData.min === currentPageData.max ? `Select ${addPlural(currentPageData.min, "role")}` :
154 `Select between ${currentPageData.min} and ${currentPageData.max} roles` + (
155 currentPageData.min === 0 ? ` or press next` : "")) + "\n\n" +
156 createPageIndicator(maxPage, page)
157 )
158 .setStatus("Success")
159 .setEmoji("GUILD.GREEN");
160 const components = [
161 new ActionRowBuilder<ButtonBuilder>().addComponents(
162 new ButtonBuilder()
163 .setStyle(ButtonStyle.Secondary)
164 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
165 .setCustomId("back")
166 .setDisabled(page === 0),
167 new ButtonBuilder()
168 .setStyle(ButtonStyle.Secondary)
169 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
170 .setCustomId("next")
171 .setDisabled(page === maxPage - 1),
172 new ButtonBuilder()
173 .setStyle(ButtonStyle.Success)
174 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
175 .setCustomId("done")
176 .setDisabled(!complete)
177 ),
178 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
179 new StringSelectMenuBuilder()
180 .setCustomId("roles")
181 .setPlaceholder("Select...")
182 .setMinValues(currentPageData.min)
183 .setMaxValues(currentPageData.max)
184 .addOptions(currentPageData.options.map((option) => {
185 const builder = new StringSelectMenuOptionBuilder()
186 .setLabel(option.name)
187 .setValue(option.role)
188 .setDefault(selectedRoles[page]!.includes(option.role));
189 if (option.description) builder.setDescription(option.description);
190 return builder;
191 }))
192 )
193 ];
194 await interaction.editReply({
195 embeds: [embed],
196 components: components
pineafan813bdf42022-07-24 10:39:10 +0100197 });
198 let component;
199 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000200 component = await m.awaitMessageComponent({
201 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500202 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id}
PineaFan0d06edc2023-01-17 22:10:31 +0000203 });
pineafan813bdf42022-07-24 10:39:10 +0100204 } catch (e) {
pineafan63fc5e22022-08-04 22:04:10 +0100205 return;
pineafan813bdf42022-07-24 10:39:10 +0100206 }
pineafan63fc5e22022-08-04 22:04:10 +0100207 component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000208 if (component.customId === "back") {
209 page = Math.max(0, page - 1);
210 } else if (component.customId === "next") {
211 page = Math.min(maxPage - 1, page + 1);
212 } else if (component.customId === "done") {
213 done = true;
214 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
215 selectedRoles[page] = component.values;
216 completedPages[page] = true
217 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100218 }
PineaFan0d06edc2023-01-17 22:10:31 +0000219 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100220 }
PineaFan0d06edc2023-01-17 22:10:31 +0000221
222 const fullRoleList: string[] = selectedRoles.flat();
223
224 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
225 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
226 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)) // IDs
227 let roleObjectsToAdd = fullRoleList.map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
228 .filter((r) => r !== undefined) as Role[];
229 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100230 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000231 roleException(interaction.guild.id, interaction.user.id)
232 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
233 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100234 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 return await interaction.reply({
236 embeds: [
237 new EmojiEmbed()
238 .setTitle("Roles")
239 .setDescription(
240 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
241 )
242 .setStatus("Danger")
243 .setEmoji("GUILD.RED")
244 ],
245 components: []
246 });
pineafan813bdf42022-07-24 10:39:10 +0100247 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 await interaction.editReply({
249 embeds: [
250 new EmojiEmbed()
251 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000252 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 .setStatus("Success")
254 .setEmoji("GUILD.GREEN")
255 ],
256 components: []
257 });
pineafan63fc5e22022-08-04 22:04:10 +0100258 return;
pineafan813bdf42022-07-24 10:39:10 +0100259}