blob: 118afc2ab776b368ff5dbad821e591475bf5eccc [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
TheCodedProfc2acbcc2023-01-20 17:23:51 -05002import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction } from "discord.js";
pineafan0bc04162022-07-25 17:22:26 +01003import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
TheCodedProfafca98b2023-01-17 22:25:43 -05004import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan0bc04162022-07-25 17:22:26 +01005import client from "../../utils/client.js";
pineafan63fc5e22022-08-04 22:04:10 +01006import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
pineafan63fc5e22022-08-04 22:04:10 +01007import singleNotify from "../../utils/singleNotify.js";
TheCodedProf4a6d5712023-01-19 15:54:40 -05008import getEmojiByName from "../../utils/getEmojiByName.js";
9import createPageIndicator from "../../utils/createPageIndicator.js";
10import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProfc2acbcc2023-01-20 17:23:51 -050011
pineafan708692b2022-07-24 22:16:22 +010012
13const command = (builder: SlashCommandSubcommandBuilder) =>
14 builder
pineafan63fc5e22022-08-04 22:04:10 +010015 .setName("stats")
Skyler Grey11236ba2022-08-08 21:13:33 +010016 .setDescription("Controls channels which update when someone joins or leaves the server")
pineafan708692b2022-07-24 22:16:22 +010017
TheCodedProf4a6d5712023-01-19 15:54:40 -050018
TheCodedProfc2acbcc2023-01-20 17:23:51 -050019const showModal = async (interaction: StringSelectMenuInteraction, current: { enabled: boolean; name: string; }) => {
20 await interaction.showModal(
21 new Discord.ModalBuilder()
22 .setCustomId("modal")
23 .setTitle(`Stats channel name`)
24 .addComponents(
25 new ActionRowBuilder<TextInputBuilder>().addComponents(
26 new TextInputBuilder()
27 .setCustomId("ex1")
28 .setLabel("Server Info (1/3)")
29 .setPlaceholder(
30 `{serverName} - This server's name\n\n` +
31 `These placeholders will be replaced with the server's name, etc..`
32 )
33 .setMaxLength(1)
34 .setRequired(false)
35 .setStyle(Discord.TextInputStyle.Paragraph)
36 ),
37 new ActionRowBuilder<TextInputBuilder>().addComponents(
38 new TextInputBuilder()
39 .setCustomId("ex2")
40 .setLabel("Member Counts (2/3) - {MemberCount:...}")
41 .setPlaceholder(
42 `{:all} - Total member count\n` +
43 `{:humans} - Total non-bot users\n` +
44 `{:bots} - Number of bots\n`
45 )
46 .setMaxLength(1)
47 .setRequired(false)
48 .setStyle(Discord.TextInputStyle.Paragraph)
49 ),
50 new ActionRowBuilder<TextInputBuilder>().addComponents(
51 new TextInputBuilder()
52 .setCustomId("ex3")
53 .setLabel("Latest Member (3/3) - {member:...}")
54 .setPlaceholder(
55 `{:name} - The members name\n`
56 )
57 .setMaxLength(1)
58 .setRequired(false)
59 .setStyle(Discord.TextInputStyle.Paragraph)
60 ),
61 new ActionRowBuilder<TextInputBuilder>().addComponents(
62 new TextInputBuilder()
63 .setCustomId("text")
64 .setLabel("Channel name input")
65 .setMaxLength(1000)
66 .setRequired(true)
67 .setStyle(Discord.TextInputStyle.Short)
68 .setValue(current.name)
69 )
70 )
71 );
TheCodedProf4a6d5712023-01-19 15:54:40 -050072}
73
PineaFan5d98a4b2023-01-19 16:15:47 +000074const callback = async (interaction: CommandInteraction) => {
75 if (!interaction.guild) return;
TheCodedProf4a6d5712023-01-19 15:54:40 -050076 const { renderChannel } = client.logger;
TheCodedProf4a6d5712023-01-19 15:54:40 -050077 const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
TheCodedProfc2acbcc2023-01-20 17:23:51 -050078 let page = 0;
79 let closed = false;
80 const config = await client.database.guilds.read(interaction.guild.id);
81 const currentObject = config.stats;
82 let modified = false;
PineaFan5d98a4b2023-01-19 16:15:47 +000083 do {
TheCodedProfc2acbcc2023-01-20 17:23:51 -050084 let embed = new EmojiEmbed()
85 .setTitle("Stats Settings")
86 .setEmoji("SETTINGS.STATS.GREEN")
87 .setStatus("Success");
88 const noStatsChannels = Object.keys(currentObject).length === 0;
89 let current: { enabled: boolean; name: string; };
90
91 const pageSelect = new StringSelectMenuBuilder()
PineaFan5d98a4b2023-01-19 16:15:47 +000092 .setCustomId("page")
TheCodedProfc2acbcc2023-01-20 17:23:51 -050093 .setPlaceholder("Select a stats channel to manage");
94 const actionSelect = new StringSelectMenuBuilder()
TheCodedProf4a6d5712023-01-19 15:54:40 -050095 .setCustomId("action")
96 .setPlaceholder("Perform an action")
TheCodedProf4a6d5712023-01-19 15:54:40 -050097 .addOptions(
98 new StringSelectMenuOptionBuilder()
99 .setLabel("Edit")
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500100 .setDescription("Edit the stats channel")
TheCodedProf4a6d5712023-01-19 15:54:40 -0500101 .setValue("edit")
TheCodedProf4a6d5712023-01-19 15:54:40 -0500102 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
103 new StringSelectMenuOptionBuilder()
104 .setLabel("Delete")
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500105 .setDescription("Delete the stats channel")
TheCodedProf4a6d5712023-01-19 15:54:40 -0500106 .setValue("delete")
TheCodedProf4a6d5712023-01-19 15:54:40 -0500107 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500108 );
109 const buttonRow = new ActionRowBuilder<ButtonBuilder>()
110 .addComponents(
111 new ButtonBuilder()
112 .setCustomId("back")
113 .setStyle(ButtonStyle.Primary)
114 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
115 .setDisabled(page === 0),
116 new ButtonBuilder()
117 .setCustomId("next")
118 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
119 .setStyle(ButtonStyle.Primary)
120 .setDisabled(page === Object.keys(currentObject).length - 1),
121 new ButtonBuilder()
122 .setCustomId("add")
123 .setLabel("Create new")
124 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
125 .setStyle(ButtonStyle.Secondary)
126 .setDisabled(Object.keys(currentObject).length >= 24),
127 new ButtonBuilder()
128 .setCustomId("save")
129 .setLabel("Save")
130 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
131 .setStyle(ButtonStyle.Success)
132 .setDisabled(modified),
TheCodedProf4a6d5712023-01-19 15:54:40 -0500133 );
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500134 if (noStatsChannels) {
135 embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" +
136 createPageIndicator(1, 1, undefined, true)
137 );
138 pageSelect.setDisabled(true);
139 actionSelect.setDisabled(true);
140 pageSelect.addOptions(new StringSelectMenuOptionBuilder()
141 .setLabel("No stats channels")
142 .setValue("none")
143 );
TheCodedProf4a6d5712023-01-19 15:54:40 -0500144 } else {
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500145 page = Math.min(page, Object.keys(currentObject).length - 1);
146 current = currentObject[Object.keys(config.stats)[page]!]!
TheCodedProf4a6d5712023-01-19 15:54:40 -0500147 actionSelect.addOptions(new StringSelectMenuOptionBuilder()
148 .setLabel(current.enabled ? "Disable" : "Enable")
149 .setValue("toggleEnabled")
150 .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`)
151 .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
152 );
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500153
154 embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` +
TheCodedProf4a6d5712023-01-19 15:54:40 -0500155 `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` +
156 `**Name:** \`${current.name}\`\n` +
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500157 `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + '\n\n' +
158 createPageIndicator(Object.keys(config.stats).length, page)
TheCodedProf4a6d5712023-01-19 15:54:40 -0500159 );
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500160 for (const [id, { name, enabled }] of Object.entries(currentObject)) {
161 pageSelect.addOptions(new StringSelectMenuOptionBuilder()
162 .setLabel(`${name} (${renderChannel(id)})`)
163 .setEmoji(getEmojiByName(enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
164 .setDescription(`${enabled ? "Enabled" : "Disabled"}`)
165 .setValue(id)
166 );
167 }
168 }
TheCodedProf4a6d5712023-01-19 15:54:40 -0500169
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500170 interaction.editReply({embeds: [embed], components: [
171 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
172 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
173 buttonRow
174 ]});
TheCodedProf4a6d5712023-01-19 15:54:40 -0500175
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500176 let i: StringSelectMenuInteraction | ButtonInteraction;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500177 try {
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500178 i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500179 } catch (e) {
180 closed = true;
181 continue;
182 }
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500183
184 if(i.isStringSelectMenu()) {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500185 switch(i.customId) {
186 case "page":
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500187 page = Object.keys(currentObject).indexOf(i.values[0]!);
TheCodedProf4a6d5712023-01-19 15:54:40 -0500188 break;
189 case "action":
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500190 modified = true;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500191 switch(i.values[0]!) {
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500192 case "edit": {
193 showModal(i, current!)
TheCodedProf4a6d5712023-01-19 15:54:40 -0500194 await interaction.editReply({
195 embeds: [
196 new EmojiEmbed()
197 .setTitle("Stats Channel")
198 .setDescription("Modal opened. If you can't see it, click back and try again.")
199 .setStatus("Success")
200 .setEmoji("SETTINGS.STATS.GREEN")
201 ],
202 components: [
203 new ActionRowBuilder<ButtonBuilder>().addComponents(
204 new ButtonBuilder()
205 .setLabel("Back")
206 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
207 .setStyle(ButtonStyle.Primary)
208 .setCustomId("back")
209 )
210 ]
211 });
TheCodedProf4a6d5712023-01-19 15:54:40 -0500212 break;
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500213 }
214 case "toggleEnabled": {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500215 i.deferUpdate();
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500216 currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled;
217 modified = true;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500218 break;
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500219 }
220 case "delete": {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500221 i.deferUpdate();
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500222 delete currentObject[Object.keys(currentObject)[page]!];
223 modified = true;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500224 break;
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500225 }
TheCodedProf4a6d5712023-01-19 15:54:40 -0500226 }
227 break;
228 }
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500229 } else {
TheCodedProf4a6d5712023-01-19 15:54:40 -0500230 i.deferUpdate();
231 switch(i.customId) {
232 case "back":
233 page--;
234 break;
235 case "next":
236 page++;
237 break;
238 case "add":
239 break;
240 case "save":
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500241 client.database.guilds.write(interaction.guild.id, {stats: currentObject});
242 singleNotify("statsChannelDeleted", interaction.guild.id, true);
243 modified = false;
244 break;
TheCodedProf4a6d5712023-01-19 15:54:40 -0500245 }
246 }
TheCodedProfc2acbcc2023-01-20 17:23:51 -0500247
PineaFan5d98a4b2023-01-19 16:15:47 +0000248 } while (!closed);
pineafan63fc5e22022-08-04 22:04:10 +0100249};
pineafan708692b2022-07-24 22:16:22 +0100250
PineaFan64486c42022-12-28 09:21:04 +0000251const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100252 const member = interaction.member as Discord.GuildMember;
TheCodedProfafca98b2023-01-17 22:25:43 -0500253 if (!member.permissions.has("ManageChannels"))
PineaFan0d06edc2023-01-17 22:10:31 +0000254 return "You must have the *Manage Channels* permission to use this command";
pineafan708692b2022-07-24 22:16:22 +0100255 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100256};
pineafan708692b2022-07-24 22:16:22 +0100257
PineaFan538d3752023-01-12 21:48:23 +0000258
pineafan708692b2022-07-24 22:16:22 +0100259export { command };
260export { callback };
PineaFan5d98a4b2023-01-19 16:15:47 +0000261export { check };