blob: cdd218b288546adfdd22a4b5724d8d90b9dab1b2 [file] [log] [blame]
PineaFan0d06edc2023-01-17 22:10:31 +00001import { LoadingEmbed } from "../../utils/defaults.js";
TheCodedProfafca98b2023-01-17 22:25:43 -05002import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, AutocompleteInteraction } from "discord.js";
pineafan0bc04162022-07-25 17:22:26 +01003import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
4import confirmationMessage from "../../utils/confirmationMessage.js";
TheCodedProfafca98b2023-01-17 22:25:43 -05005import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan0bc04162022-07-25 17:22:26 +01006import client from "../../utils/client.js";
pineafan63fc5e22022-08-04 22:04:10 +01007import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01008import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js";
pineafan63fc5e22022-08-04 22:04:10 +01009import singleNotify from "../../utils/singleNotify.js";
pineafan708692b2022-07-24 22:16:22 +010010
11const command = (builder: SlashCommandSubcommandBuilder) =>
12 builder
pineafan63fc5e22022-08-04 22:04:10 +010013 .setName("stats")
Skyler Grey11236ba2022-08-08 21:13:33 +010014 .setDescription("Controls channels which update when someone joins or leaves the server")
15 .addChannelOption((option) => option.setName("channel").setDescription("The channel to modify"))
Skyler Grey75ea9172022-08-06 10:22:23 +010016 .addStringOption((option) =>
17 option
18 .setName("name")
Skyler Grey11236ba2022-08-08 21:13:33 +010019 .setDescription("The new channel name | Enter any text or use the extra variables like {memberCount}")
Skyler Grey75ea9172022-08-06 10:22:23 +010020 .setAutocomplete(true)
21 );
pineafan708692b2022-07-24 22:16:22 +010022
PineaFan9b2ac4d2023-01-18 14:41:07 +000023const callback = async (interaction: CommandInteraction): Promise<unknown> => { // TODO: This command feels unintuitive. Clicking a channel in the select menu deletes it
24 // instead, it should give a submenu to edit the channel, enable/disable or delete it
TheCodedProfafca98b2023-01-17 22:25:43 -050025 singleNotify("statsChannelDeleted", interaction.guild!.id, true);
Skyler Grey75ea9172022-08-06 10:22:23 +010026 const m = (await interaction.reply({
27 embeds: LoadingEmbed,
28 ephemeral: true,
29 fetchReply: true
30 })) as Message;
TheCodedProfafca98b2023-01-17 22:25:43 -050031 let config = await client.database.guilds.read(interaction.guild!.id);
32 if (interaction.options.get("name")?.value as string) {
pineafane23c4ec2022-07-27 21:56:27 +010033 let channel;
PineaFan9b2ac4d2023-01-18 14:41:07 +000034 if (Object.keys(config.stats).length >= 25) {
Skyler Grey75ea9172022-08-06 10:22:23 +010035 return await interaction.editReply({
36 embeds: [
37 new EmojiEmbed()
38 .setEmoji("CHANNEL.TEXT.DELETE")
39 .setTitle("Stats Channel")
Skyler Grey11236ba2022-08-08 21:13:33 +010040 .setDescription("You can only have 25 stats channels in a server")
Skyler Grey75ea9172022-08-06 10:22:23 +010041 .setStatus("Danger")
42 ]
43 });
pineafane23c4ec2022-07-27 21:56:27 +010044 }
pineafan708692b2022-07-24 22:16:22 +010045 try {
TheCodedProfafca98b2023-01-17 22:25:43 -050046 channel = interaction.options.get("channel")?.channel as Discord.Channel;
pineafan708692b2022-07-24 22:16:22 +010047 } catch {
Skyler Grey75ea9172022-08-06 10:22:23 +010048 return await interaction.editReply({
49 embeds: [
50 new EmojiEmbed()
51 .setEmoji("CHANNEL.TEXT.DELETE")
52 .setTitle("Stats Channel")
Skyler Grey11236ba2022-08-08 21:13:33 +010053 .setDescription("The channel you provided is not a valid channel")
Skyler Grey75ea9172022-08-06 10:22:23 +010054 .setStatus("Danger")
55 ]
56 });
pineafan708692b2022-07-24 22:16:22 +010057 }
pineafan63fc5e22022-08-04 22:04:10 +010058 channel = channel as Discord.TextChannel;
TheCodedProfafca98b2023-01-17 22:25:43 -050059 if (channel.guild.id !== interaction.guild!.id) {
Skyler Grey75ea9172022-08-06 10:22:23 +010060 return interaction.editReply({
61 embeds: [
62 new EmojiEmbed()
63 .setTitle("Stats Channel")
Skyler Grey11236ba2022-08-08 21:13:33 +010064 .setDescription("You must choose a channel in this server")
Skyler Grey75ea9172022-08-06 10:22:23 +010065 .setStatus("Danger")
66 .setEmoji("CHANNEL.TEXT.DELETE")
67 ]
68 });
pineafan708692b2022-07-24 22:16:22 +010069 }
Skyler Grey75ea9172022-08-06 10:22:23 +010070 let newName = await convertCurlyBracketString(
TheCodedProfafca98b2023-01-17 22:25:43 -050071 interaction.options.get("name")?.value as string,
72 "",
73 "",
74 interaction.guild!.name,
75 interaction.guild!.members
Skyler Grey75ea9172022-08-06 10:22:23 +010076 );
TheCodedProfafca98b2023-01-17 22:25:43 -050077 if (interaction.options.get("channel")?.channel!.type === Discord.ChannelType.GuildText) {
pineafan63fc5e22022-08-04 22:04:10 +010078 newName = newName.toLowerCase().replace(/[\s]/g, "-");
pineafan708692b2022-07-24 22:16:22 +010079 }
pineafan63fc5e22022-08-04 22:04:10 +010080 const confirmation = await new confirmationMessage(interaction)
TheCodedProfafca98b2023-01-17 22:25:43 -050081 .setEmoji("CHANNEL.TEXT.EDIT")
pineafan708692b2022-07-24 22:16:22 +010082 .setTitle("Stats Channel")
Skyler Grey75ea9172022-08-06 10:22:23 +010083 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +010084 `Are you sure you want to set <#${channel.id}> to a stats channel?\n\n*Preview: ${newName.replace(
Skyler Grey75ea9172022-08-06 10:22:23 +010085 /^ +| $/g,
86 ""
87 )}*`
88 )
pineafan708692b2022-07-24 22:16:22 +010089 .setColor("Warning")
90 .setInverted(true)
TheCodedProfafca98b2023-01-17 22:25:43 -050091 .setFailedMessage(`Could not convert <#${channel.id}> to a stats chanel.`, "Danger", "CHANNEL.TEXT.DELETE")
pineafan63fc5e22022-08-04 22:04:10 +010092 .send(true);
93 if (confirmation.cancelled) return;
pineafan708692b2022-07-24 22:16:22 +010094 if (confirmation.success) {
95 try {
TheCodedProfafca98b2023-01-17 22:25:43 -050096 const name = interaction.options.get("name")?.value as string;
97 const channel = interaction.options.get("channel")?.channel as Discord.TextChannel;
98 await client.database.guilds.write(interaction.guild!.id, {
Skyler Grey75ea9172022-08-06 10:22:23 +010099 [`stats.${channel.id}`]: { name: name, enabled: true }
100 });
Skyler Grey11236ba2022-08-08 21:13:33 +0100101 const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger;
pineafan63fc5e22022-08-04 22:04:10 +0100102 const data = {
Skyler Grey75ea9172022-08-06 10:22:23 +0100103 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100104 type: "statsChannelUpdate",
105 displayName: "Stats Channel Updated",
106 calculateType: "nucleusSettingsUpdated",
107 color: NucleusColors.yellow,
108 emoji: "CHANNEL.TEXT.EDIT",
109 timestamp: new Date().getTime()
110 },
111 list: {
Skyler Grey11236ba2022-08-08 21:13:33 +0100112 memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
113 changedBy: entry(interaction.user.id, renderUser(interaction.user)),
pineafan63fc5e22022-08-04 22:04:10 +0100114 channel: entry(channel.id, renderChannel(channel)),
Skyler Grey75ea9172022-08-06 10:22:23 +0100115 name: entry(
TheCodedProfafca98b2023-01-17 22:25:43 -0500116 interaction.options.get("name")?.value as string,
117 `\`${interaction.options.get("name")?.value as string}\``
Skyler Grey75ea9172022-08-06 10:22:23 +0100118 )
pineafan63fc5e22022-08-04 22:04:10 +0100119 },
120 hidden: {
TheCodedProfafca98b2023-01-17 22:25:43 -0500121 guild: interaction.guild!.id
pineafan708692b2022-07-24 22:16:22 +0100122 }
pineafan63fc5e22022-08-04 22:04:10 +0100123 };
124 log(data);
pineafan708692b2022-07-24 22:16:22 +0100125 } catch (e) {
pineafan63fc5e22022-08-04 22:04:10 +0100126 console.log(e);
Skyler Grey75ea9172022-08-06 10:22:23 +0100127 return interaction.editReply({
128 embeds: [
129 new EmojiEmbed()
130 .setTitle("Stats Channel")
Skyler Grey11236ba2022-08-08 21:13:33 +0100131 .setDescription("Something went wrong and the stats channel could not be set")
Skyler Grey75ea9172022-08-06 10:22:23 +0100132 .setStatus("Danger")
133 .setEmoji("CHANNEL.TEXT.DELETE")
134 ],
135 components: []
136 });
pineafan708692b2022-07-24 22:16:22 +0100137 }
138 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100139 return interaction.editReply({
140 embeds: [
141 new EmojiEmbed()
142 .setTitle("Stats Channel")
143 .setDescription("No changes were made")
144 .setStatus("Success")
145 .setEmoji("CHANNEL.TEXT.CREATE")
146 ],
147 components: []
148 });
pineafan708692b2022-07-24 22:16:22 +0100149 }
TheCodedProfafca98b2023-01-17 22:25:43 -0500150 await statsChannelAddCallback(client, interaction.member as GuildMember);
pineafan0bc04162022-07-25 17:22:26 +0100151 }
Skyler Greyad002172022-08-16 18:48:26 +0100152 let timedOut = false;
153 while (!timedOut) {
TheCodedProfafca98b2023-01-17 22:25:43 -0500154 config = await client.database.guilds.read(interaction.guild!.id);
PineaFan9b2ac4d2023-01-18 14:41:07 +0000155 const stats = config.stats;
TheCodedProfafca98b2023-01-17 22:25:43 -0500156 const selectMenu = new StringSelectMenuBuilder()
pineafan0bc04162022-07-25 17:22:26 +0100157 .setCustomId("remove")
158 .setMinValues(1)
pineafan63fc5e22022-08-04 22:04:10 +0100159 .setMaxValues(Math.max(1, Object.keys(stats).length));
Skyler Grey75ea9172022-08-06 10:22:23 +0100160 await interaction.editReply({
161 embeds: [
162 new EmojiEmbed()
163 .setTitle("Stats Channel")
164 .setDescription(
165 "The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list."
166 )
167 .setStatus("Success")
168 .setEmoji("CHANNEL.TEXT.CREATE")
169 ],
170 components: [
TheCodedProfafca98b2023-01-17 22:25:43 -0500171 new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
Skyler Grey75ea9172022-08-06 10:22:23 +0100172 Object.keys(stats).length
173 ? [
174 selectMenu
Skyler Grey11236ba2022-08-08 21:13:33 +0100175 .setPlaceholder("Select a stats channel to remove, stopping it updating")
Skyler Grey75ea9172022-08-06 10:22:23 +0100176 .addOptions(
177 Object.keys(stats).map((key) => ({
TheCodedProfafca98b2023-01-17 22:25:43 -0500178 label: interaction.guild!.channels.cache.get(key)!.name,
Skyler Grey75ea9172022-08-06 10:22:23 +0100179 value: key,
TheCodedProfafca98b2023-01-17 22:25:43 -0500180 description: `${stats[key]!.name}`
Skyler Grey75ea9172022-08-06 10:22:23 +0100181 }))
182 )
183 ]
184 : [
185 selectMenu
Skyler Grey11236ba2022-08-08 21:13:33 +0100186 .setPlaceholder("The server has no stats channels")
Skyler Grey75ea9172022-08-06 10:22:23 +0100187 .setDisabled(true)
188 .setOptions([
189 {
190 label: "*Placeholder*",
191 value: "placeholder",
192 description: "No stats channels"
193 }
194 ])
195 ]
196 )
197 ]
198 });
TheCodedProfafca98b2023-01-17 22:25:43 -0500199 let i: StringSelectMenuInteraction;
pineafan0bc04162022-07-25 17:22:26 +0100200 try {
PineaFan0d06edc2023-01-17 22:10:31 +0000201 i = await m.awaitMessageComponent({
202 time: 300000,
203 filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id }
TheCodedProfafca98b2023-01-17 22:25:43 -0500204 }) as StringSelectMenuInteraction;
Skyler Grey75ea9172022-08-06 10:22:23 +0100205 } catch (e) {
Skyler Greyad002172022-08-16 18:48:26 +0100206 timedOut = true;
207 continue;
Skyler Grey75ea9172022-08-06 10:22:23 +0100208 }
pineafan63fc5e22022-08-04 22:04:10 +0100209 i.deferUpdate();
pineafan0bc04162022-07-25 17:22:26 +0100210 if (i.customId === "remove") {
pineafan63fc5e22022-08-04 22:04:10 +0100211 const toRemove = i.values;
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 await client.database.guilds.write(
TheCodedProfafca98b2023-01-17 22:25:43 -0500213 interaction.guild!.id,
Skyler Grey75ea9172022-08-06 10:22:23 +0100214 null,
215 toRemove.map((k) => `stats.${k}`)
216 );
pineafan0bc04162022-07-25 17:22:26 +0100217 }
pineafan708692b2022-07-24 22:16:22 +0100218 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100219 await interaction.editReply({
TheCodedProfafca98b2023-01-17 22:25:43 -0500220 embeds: [new Discord.EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })],
Skyler Grey75ea9172022-08-06 10:22:23 +0100221 components: []
222 });
pineafan63fc5e22022-08-04 22:04:10 +0100223};
pineafan708692b2022-07-24 22:16:22 +0100224
PineaFan64486c42022-12-28 09:21:04 +0000225const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100226 const member = interaction.member as Discord.GuildMember;
TheCodedProfafca98b2023-01-17 22:25:43 -0500227 if (!member.permissions.has("ManageChannels"))
PineaFan0d06edc2023-01-17 22:10:31 +0000228 return "You must have the *Manage Channels* permission to use this command";
pineafan708692b2022-07-24 22:16:22 +0100229 return true;
pineafan63fc5e22022-08-04 22:04:10 +0100230};
pineafan708692b2022-07-24 22:16:22 +0100231
TheCodedProf7a9ad4b2023-01-17 22:27:36 -0500232const generateStatsChannelAutocomplete = (prompt: string): string[] => {
233 return [prompt];
234};
235
PineaFan538d3752023-01-12 21:48:23 +0000236const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
237 if (!interaction.guild) return [];
238 const prompt = interaction.options.getString("tag");
239 // generateStatsChannelAutocomplete(int.options.getString("name") ?? "")
240 const results = generateStatsChannelAutocomplete(prompt ?? "");
241 return results;
242};
243
244
245
pineafan708692b2022-07-24 22:16:22 +0100246export { command };
247export { callback };
Skyler Grey75ea9172022-08-06 10:22:23 +0100248export { check };
PineaFan0d06edc2023-01-17 22:10:31 +0000249export { autocomplete };