blob: bd63cdcf19c95093e083e6117b4211ce16d61379 [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
TheCodedProf4a6d5712023-01-19 15:54:40 -05002import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } 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";
11import type { GuildConfig } from "../../utils/database.js";
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 -050018type ChangesType = Record<string, { name?: string; enabled?: boolean; }>
19
20const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => {
21 for (const [id, { name, enabled }] of Object.entries(changes)) {
22 if (!baseObject[id]) baseObject[id] = { name: "", enabled: false};
23 if (name) baseObject[id]!.name = name;
24 if (enabled) baseObject[id]!.enabled = enabled;
25 }
26 return baseObject;
27}
28
29
PineaFan5d98a4b2023-01-19 16:15:47 +000030const callback = async (interaction: CommandInteraction) => {
TheCodedProf4a6d5712023-01-19 15:54:40 -050031 try{
PineaFan5d98a4b2023-01-19 16:15:47 +000032 if (!interaction.guild) return;
TheCodedProf4a6d5712023-01-19 15:54:40 -050033 const { renderChannel } = client.logger;
PineaFan5d98a4b2023-01-19 16:15:47 +000034 let closed = false;
35 let page = 0;
TheCodedProf4a6d5712023-01-19 15:54:40 -050036 const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
37 let changes: ChangesType = {};
PineaFan5d98a4b2023-01-19 16:15:47 +000038 do {
39 const config = await client.database.guilds.read(interaction.guild.id);
TheCodedProf4a6d5712023-01-19 15:54:40 -050040 const stats = config.stats;
41 let currentID = "";
42 let current: {
43 name: string;
44 enabled: boolean;
45 } = {
46 name: "",
47 enabled: false
48 };
49 let description = "";
PineaFan5d98a4b2023-01-19 16:15:47 +000050 let pageSelect = new StringSelectMenuBuilder()
51 .setCustomId("page")
52 .setPlaceholder("Select a stats channel to manage")
TheCodedProf4a6d5712023-01-19 15:54:40 -050053 .setDisabled(Object.keys(stats).length === 0)
pineafan0bc04162022-07-25 17:22:26 +010054 .setMinValues(1)
PineaFan5d98a4b2023-01-19 16:15:47 +000055 .setMaxValues(1);
TheCodedProf4a6d5712023-01-19 15:54:40 -050056 let actionSelect = new StringSelectMenuBuilder()
57 .setCustomId("action")
58 .setPlaceholder("Perform an action")
59 .setMinValues(1)
60 .setMaxValues(1)
61 .setDisabled(Object.keys(stats).length === 0)
62 .addOptions(
63 new StringSelectMenuOptionBuilder()
64 .setLabel("Edit")
65 .setValue("edit")
66 .setDescription("Edit the name of this stats channel")
67 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
68 new StringSelectMenuOptionBuilder()
69 .setLabel("Delete")
70 .setValue("delete")
71 .setDescription("Delete this stats channel")
72 .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
73 );
74 if (Object.keys(stats).length === 0) {
75 description = "You do not have any stats channels set up yet"
76 pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none"))
77 } else {
78 currentID = Object.keys(stats)[page]!
79 current = stats[currentID]!;
80 current = applyChanges({ [currentID]: current }, changes)[currentID]!;
81 // Propogate pageSelect with list of stats channels
82 for (const [id, { name, enabled }] of Object.entries(stats)) {
83 pageSelect.addOptions(
84 new StringSelectMenuOptionBuilder()
85 .setLabel(name)
86 .setValue(id)
87 .setDescription(`Enabled: ${enabled}`)
88 );
89 }
90 actionSelect.addOptions(new StringSelectMenuOptionBuilder()
91 .setLabel(current.enabled ? "Disable" : "Enable")
92 .setValue("toggleEnabled")
93 .setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`)
94 .setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
95 );
96 description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` +
97 `${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` +
98 `**Name:** \`${current.name}\`\n` +
99 `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}`
Skyler Grey75ea9172022-08-06 10:22:23 +0100100 }
TheCodedProf4a6d5712023-01-19 15:54:40 -0500101 const row = new ActionRowBuilder<ButtonBuilder>()
102 .addComponents(
103 new ButtonBuilder()
104 .setCustomId("back")
105 .setStyle(ButtonStyle.Primary)
106 .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
107 .setDisabled(page === 0),
108 new ButtonBuilder()
109 .setCustomId("next")
110 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
111 .setStyle(ButtonStyle.Primary)
112 .setDisabled(page === Object.keys(stats).length - 1),
113 new ButtonBuilder()
114 .setCustomId("add")
115 .setLabel("Create new")
116 .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
117 .setStyle(ButtonStyle.Secondary)
118 .setDisabled(Object.keys(stats).length >= 24),
119 new ButtonBuilder()
120 .setCustomId("save")
121 .setLabel("Save")
122 .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
123 .setStyle(ButtonStyle.Success)
124 .setDisabled(Object.keys(changes).length === 0),
125 );
126
127 let embed = new EmojiEmbed()
128 .setTitle("Stats Channels")
129 .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page))
130 .setEmoji("SETTINGS.STATS.GREEN")
131 .setStatus("Success")
132
133 interaction.editReply({
134 embeds: [embed],
135 components: [
136 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
137 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
138 row
139 ]
140 });
141 let i: MessageComponentInteraction;
142 try {
143 i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 });
144 } catch (e) {
145 closed = true;
146 continue;
147 }
148 if (i.isStringSelectMenu()) {
149 switch(i.customId) {
150 case "page":
151 page = Object.keys(stats).indexOf(i.values[0]!);
152 i.deferUpdate();
153 break;
154 case "action":
155 if(!changes[currentID]) changes[currentID] = {};
156 switch(i.values[0]!) {
157 case "edit":
158 await i.showModal(
159 new Discord.ModalBuilder()
160 .setCustomId("modal")
161 .setTitle(`Stats channel name`)
162 .addComponents(
163 new ActionRowBuilder<TextInputBuilder>().addComponents(
164 new TextInputBuilder()
165 .setCustomId("ex1")
166 .setLabel("Server Info (1/3)")
167 .setPlaceholder(
168 `{serverName} - This server's name\n\n` +
169 `These placeholders will be replaced with the server's name, etc..`
170 )
171 .setMaxLength(1)
172 .setRequired(false)
173 .setStyle(Discord.TextInputStyle.Paragraph)
174 ),
175 new ActionRowBuilder<TextInputBuilder>().addComponents(
176 new TextInputBuilder()
177 .setCustomId("ex2")
178 .setLabel("Member Counts (2/3) - {MemberCount:...}")
179 .setPlaceholder(
180 `{:all} - Total member count\n` +
181 `{:humans} - Total non-bot users\n` +
182 `{:bots} - Number of bots\n`
183 )
184 .setMaxLength(1)
185 .setRequired(false)
186 .setStyle(Discord.TextInputStyle.Paragraph)
187 ),
188 new ActionRowBuilder<TextInputBuilder>().addComponents(
189 new TextInputBuilder()
190 .setCustomId("ex3")
191 .setLabel("Latest Member (3/3) - {member:...}")
192 .setPlaceholder(
193 `{:name} - The members name\n`
194 )
195 .setMaxLength(1)
196 .setRequired(false)
197 .setStyle(Discord.TextInputStyle.Paragraph)
198 ),
199 new ActionRowBuilder<TextInputBuilder>().addComponents(
200 new TextInputBuilder()
201 .setCustomId("text")
202 .setLabel("Channel name input")
203 .setMaxLength(1000)
204 .setRequired(true)
205 .setStyle(Discord.TextInputStyle.Short)
206 .setValue(current.name)
207 )
208 )
209 );
210 await interaction.editReply({
211 embeds: [
212 new EmojiEmbed()
213 .setTitle("Stats Channel")
214 .setDescription("Modal opened. If you can't see it, click back and try again.")
215 .setStatus("Success")
216 .setEmoji("SETTINGS.STATS.GREEN")
217 ],
218 components: [
219 new ActionRowBuilder<ButtonBuilder>().addComponents(
220 new ButtonBuilder()
221 .setLabel("Back")
222 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
223 .setStyle(ButtonStyle.Primary)
224 .setCustomId("back")
225 )
226 ]
227 });
228 let out: Discord.ModalSubmitInteraction | null;
229 try {
230 out = await modalInteractionCollector(
231 m,
232 (m) => m.channel!.id === interaction.channel!.id,
233 (_) => true
234 ) as Discord.ModalSubmitInteraction | null;
235 } catch (e) {
236 continue;
237 }
238 if (!out) continue
239 if (!out.fields) continue
240 if (out.isButton()) continue;
241 const newString = out.fields.getTextInputValue("text");
242 if (!newString) continue;
243 changes[currentID]!.name = newString;
244 break;
245 case "delete":
246 changes[currentID] = {};
247 i.deferUpdate();
248 break;
249 case "toggleEnabled":
250 changes[currentID]!.enabled = !stats[currentID]!.enabled;
251 i.deferUpdate();
252 break;
253 }
254 break;
255 }
256 } else if (i.isButton()) {
257 i.deferUpdate();
258 switch(i.customId) {
259 case "back":
260 page--;
261 break;
262 case "next":
263 page++;
264 break;
265 case "add":
266 break;
267 case "save":
268 let changed = applyChanges(config.stats, changes);
269 singleNotify("statsChannelDeleted", interaction.guild.id, true)
270 config.stats = changed;
271 changes = {}
272 await client.database.guilds.write(interaction.guildId!, config);
273 }
274 }
275 console.log(changes, config.stats);
PineaFan5d98a4b2023-01-19 16:15:47 +0000276 } while (!closed);
TheCodedProf4a6d5712023-01-19 15:54:40 -0500277 } catch(e) {
278 console.log(e)
279 }
pineafan63fc5e22022-08-04 22:04:10 +0100280};
pineafan708692b2022-07-24 22:16:22 +0100281
PineaFan64486c42022-12-28 09:21:04 +0000282const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100283 const member = interaction.member as Discord.GuildMember;
TheCodedProfafca98b2023-01-17 22:25:43 -0500284 if (!member.permissions.has("ManageChannels"))
PineaFan0d06edc2023-01-17 22:10:31 +0000285 return "You must have the *Manage Channels* permission to use this command";
pineafan708692b2022-07-24 22:16:22 +0100286 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100287};
pineafan708692b2022-07-24 22:16:22 +0100288
PineaFan538d3752023-01-12 21:48:23 +0000289
pineafan708692b2022-07-24 22:16:22 +0100290export { command };
291export { callback };
PineaFan5d98a4b2023-01-19 16:15:47 +0000292export { check };