blob: 16689b71656c48e210fdcc944173adbda9cffbe4 [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
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
45export const configToDropdown = (placeholder: string, currentPageData: ObjectSchema, selectedRoles?: string[]): ActionRowBuilder<StringSelectMenuBuilder> => {
46 return new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
47 new StringSelectMenuBuilder()
48 .setCustomId("roles")
49 .setPlaceholder(placeholder)
50 .setMinValues(currentPageData.min)
51 .setMaxValues(currentPageData.max)
52 .addOptions(currentPageData.options.map((option: {name: string; description: string | null; role: string;}) => {
53 const builder = new StringSelectMenuOptionBuilder()
54 .setLabel(option.name)
55 .setValue(option.role)
56 .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false);
57 if (option.description) builder.setDescription(option.description);
58 return builder;
59 }))
60 )
61}
62
PineaFan538d3752023-01-12 21:48:23 +000063export async function callback(interaction: CommandInteraction | ButtonInteraction) {
PineaFan0d06edc2023-01-17 22:10:31 +000064 if (!interaction.member) return;
65 if (!interaction.guild) return;
pineafan63fc5e22022-08-04 22:04:10 +010066 const config = await client.database.guilds.read(interaction.guild.id);
Skyler Grey75ea9172022-08-06 10:22:23 +010067 if (!config.roleMenu.enabled)
68 return await interaction.reply({
69 embeds: [
70 new EmojiEmbed()
71 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +000072 .setDescription("Self roles are currently disabled. Please contact a staff member or try again later.")
Skyler Grey75ea9172022-08-06 10:22:23 +010073 .setStatus("Danger")
PineaFan0d06edc2023-01-17 22:10:31 +000074 .setEmoji("GUILD.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +010075 ],
76 ephemeral: true
77 });
78 if (config.roleMenu.options.length === 0)
79 return await interaction.reply({
80 embeds: [
81 new EmojiEmbed()
82 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +000083 .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 +010084 .setStatus("Danger")
PineaFan0d06edc2023-01-17 22:10:31 +000085 .setEmoji("GUILD.GREEN")
Skyler Grey75ea9172022-08-06 10:22:23 +010086 ],
87 ephemeral: true
88 });
TheCodedProf1c3ad3c2023-01-25 17:58:36 -050089 const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
PineaFan0d06edc2023-01-17 22:10:31 +000090 if (config.roleMenu.allowWebUI) { // TODO: Make rolemenu web ui
91 const loginMethods: {webUI: boolean} = {
92 webUI: false
pineafan813bdf42022-07-24 10:39:10 +010093 }
PineaFan0d06edc2023-01-17 22:10:31 +000094 try {
Skyler Grey11236ba2022-08-08 21:13:33 +010095 const status = await fetch(client.config.baseUrl).then((res) => res.status);
PineaFan0d06edc2023-01-17 22:10:31 +000096 if (status !== 200) loginMethods.webUI = false;
97 } catch(e) {
98 loginMethods.webUI = false;
99 }
100 if (Object.values(loginMethods).some((i) => i)) {
101 let code = "";
102 if (loginMethods.webUI) {
103 let length = 5;
104 let itt = 0;
105 const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
106 let valid = false;
107 while (!valid) {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500108 itt ++;
PineaFan0d06edc2023-01-17 22:10:31 +0000109 code = "";
110 for (let i = 0; i < length; i++) {
111 code += chars.charAt(Math.floor(Math.random() * chars.length));
112 }
113 if (code in client.roleMenu) continue;
114 if (itt > 1000) {
115 itt = 0;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500116 length ++;
PineaFan0d06edc2023-01-17 22:10:31 +0000117 continue;
118 }
119 valid = true;
120 }
121 client.roleMenu[code] = {
122 guild: interaction.guild.id,
123 guildName: interaction.guild.name,
124 guildIcon: interaction.guild.iconURL({ extension: "png" }) ?? unknownServerIcon,
125 user: interaction.member!.user.id,
126 username: interaction.member!.user.username,
127 data: config.roleMenu.options,
PineaFan638eb132023-01-19 10:41:22 +0000128 interaction: interaction as CommandInteraction | ButtonInteraction
PineaFan0d06edc2023-01-17 22:10:31 +0000129 };
130 }
131 await interaction.editReply({
132 embeds: [
133 new EmojiEmbed()
134 .setTitle("Roles")
135 .setDescription("Select how to choose your roles")
136 .setStatus("Success")
137 .setEmoji("GUILD.GREEN")
138 ],
139 components: [
140 new ActionRowBuilder<ButtonBuilder>().addComponents([
TheCodedProf21c08592022-09-13 14:14:43 -0400141 new ButtonBuilder()
PineaFan0d06edc2023-01-17 22:10:31 +0000142 .setLabel("Online")
143 .setStyle(ButtonStyle.Link)
144 .setDisabled(!loginMethods.webUI)
145 .setURL(`${client.config.baseUrl}nucleus/rolemenu?code=${code}`),
146 new ButtonBuilder()
147 .setLabel("In Discord")
148 .setStyle(ButtonStyle.Primary)
149 .setCustomId("discord")
150 ])
151 ]
152 });
153 let component;
154 try {
155 component = await m.awaitMessageComponent({
156 time: 300000,
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500157 filter: (i) => { return i.user.id === interaction.user.id && i.channelId === interaction.channelId && i.message.id === m.id}
PineaFan0d06edc2023-01-17 22:10:31 +0000158 });
159 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500160 console.log(e);
PineaFan0d06edc2023-01-17 22:10:31 +0000161 return;
162 }
163 component.deferUpdate();
164 }
165 }
166
167 const options = config.roleMenu.options;
168 const selectedRoles: string[][] = [];
169 const maxPage = options.length;
170 const completedPages: boolean[] = options.map((option) => option.min === 0);
171 for (let i = 0; i < maxPage; i++) { selectedRoles.push([]); }
172
173 let page = 0;
174 let complete = completedPages.every((page) => page);
175 let done = false;
176
177 while (!(complete && done)) {
178 const currentPageData = options[page]!
179 const embed = new EmojiEmbed()
180 .setTitle("Roles")
181 .setDescription(
182 `**${currentPageData.name}**\n` +
183 `> ${currentPageData.description}\n\n` +
184 (currentPageData.min === currentPageData.max ? `Select ${addPlural(currentPageData.min, "role")}` :
185 `Select between ${currentPageData.min} and ${currentPageData.max} roles` + (
186 currentPageData.min === 0 ? ` or press next` : "")) + "\n\n" +
187 createPageIndicator(maxPage, page)
188 )
189 .setStatus("Success")
190 .setEmoji("GUILD.GREEN");
191 const components = [
192 new ActionRowBuilder<ButtonBuilder>().addComponents(
193 new ButtonBuilder()
194 .setStyle(ButtonStyle.Secondary)
195 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
196 .setCustomId("back")
197 .setDisabled(page === 0),
198 new ButtonBuilder()
199 .setStyle(ButtonStyle.Secondary)
200 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
201 .setCustomId("next")
202 .setDisabled(page === maxPage - 1),
203 new ButtonBuilder()
204 .setStyle(ButtonStyle.Success)
205 .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
206 .setCustomId("done")
207 .setDisabled(!complete)
208 ),
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500209 configToDropdown("Select...", currentPageData, selectedRoles[page])
PineaFan0d06edc2023-01-17 22:10:31 +0000210 ];
211 await interaction.editReply({
212 embeds: [embed],
213 components: components
pineafan813bdf42022-07-24 10:39:10 +0100214 });
215 let component;
216 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000217 component = await m.awaitMessageComponent({
218 time: 300000,
TheCodedProf267563a2023-01-21 17:00:57 -0500219 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 +0000220 });
pineafan813bdf42022-07-24 10:39:10 +0100221 } catch (e) {
TheCodedProf1c3ad3c2023-01-25 17:58:36 -0500222 console.log(e);
pineafan63fc5e22022-08-04 22:04:10 +0100223 return;
pineafan813bdf42022-07-24 10:39:10 +0100224 }
pineafan63fc5e22022-08-04 22:04:10 +0100225 component.deferUpdate();
PineaFan0d06edc2023-01-17 22:10:31 +0000226 if (component.customId === "back") {
227 page = Math.max(0, page - 1);
228 } else if (component.customId === "next") {
229 page = Math.min(maxPage - 1, page + 1);
230 } else if (component.customId === "done") {
231 done = true;
232 } else if (component.customId === "roles" && component.isStringSelectMenu()) {
233 selectedRoles[page] = component.values;
234 completedPages[page] = true
235 page = Math.min(maxPage - 1, page + 1);
pineafan813bdf42022-07-24 10:39:10 +0100236 }
PineaFan0d06edc2023-01-17 22:10:31 +0000237 complete = completedPages.every((page) => page);
pineafan813bdf42022-07-24 10:39:10 +0100238 }
PineaFan0d06edc2023-01-17 22:10:31 +0000239
240 const fullRoleList: string[] = selectedRoles.flat();
241
242 const memberRoles = (interaction.member.roles as GuildMemberRoleManager).cache.map((r) => r.id); // IDs
243 let rolesToRemove = config.roleMenu.options.map((o) => o.options.map((o) => o.role)).flat(); // IDs
244 rolesToRemove = rolesToRemove.filter((r) => memberRoles.includes(r)).filter((r) => !fullRoleList.includes(r)) // IDs
245 let roleObjectsToAdd = fullRoleList.map((r) => interaction.guild!.roles.cache.get(r)) // Role objects
246 .filter((r) => r !== undefined) as Role[];
247 roleObjectsToAdd = roleObjectsToAdd.filter((r) => !memberRoles.includes(r.id)); // Role objects
pineafan813bdf42022-07-24 10:39:10 +0100248 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000249 roleException(interaction.guild.id, interaction.user.id)
250 await (interaction.member.roles as GuildMemberRoleManager).remove(rolesToRemove);
251 await (interaction.member.roles as GuildMemberRoleManager).add(roleObjectsToAdd);
pineafan813bdf42022-07-24 10:39:10 +0100252 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100253 return await interaction.reply({
254 embeds: [
255 new EmojiEmbed()
256 .setTitle("Roles")
257 .setDescription(
258 "Something went wrong and your roles were not added. Please contact a staff member or try again later."
259 )
260 .setStatus("Danger")
261 .setEmoji("GUILD.RED")
262 ],
263 components: []
264 });
pineafan813bdf42022-07-24 10:39:10 +0100265 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100266 await interaction.editReply({
267 embeds: [
268 new EmojiEmbed()
269 .setTitle("Roles")
PineaFan0d06edc2023-01-17 22:10:31 +0000270 .setDescription("Roles have been updated")
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 .setStatus("Success")
272 .setEmoji("GUILD.GREEN")
273 ],
274 components: []
275 });
pineafan63fc5e22022-08-04 22:04:10 +0100276 return;
pineafan813bdf42022-07-24 10:39:10 +0100277}