Nucleus stats
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index bd63cdc..118afc2 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -1,5 +1,5 @@
import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js";
+import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import client from "../../utils/client.js";
@@ -8,205 +8,189 @@
import getEmojiByName from "../../utils/getEmojiByName.js";
import createPageIndicator from "../../utils/createPageIndicator.js";
import { modalInteractionCollector } from "../../utils/dualCollector.js";
-import type { GuildConfig } from "../../utils/database.js";
+
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("stats")
.setDescription("Controls channels which update when someone joins or leaves the server")
-type ChangesType = Record<string, { name?: string; enabled?: boolean; }>
-const applyChanges = (baseObject: GuildConfig['stats'], changes: ChangesType): GuildConfig['stats'] => {
- for (const [id, { name, enabled }] of Object.entries(changes)) {
- if (!baseObject[id]) baseObject[id] = { name: "", enabled: false};
- if (name) baseObject[id]!.name = name;
- if (enabled) baseObject[id]!.enabled = enabled;
- }
- return baseObject;
+const showModal = async (interaction: StringSelectMenuInteraction, current: { enabled: boolean; name: string; }) => {
+ await interaction.showModal(
+ new Discord.ModalBuilder()
+ .setCustomId("modal")
+ .setTitle(`Stats channel name`)
+ .addComponents(
+ new ActionRowBuilder<TextInputBuilder>().addComponents(
+ new TextInputBuilder()
+ .setCustomId("ex1")
+ .setLabel("Server Info (1/3)")
+ .setPlaceholder(
+ `{serverName} - This server's name\n\n` +
+ `These placeholders will be replaced with the server's name, etc..`
+ )
+ .setMaxLength(1)
+ .setRequired(false)
+ .setStyle(Discord.TextInputStyle.Paragraph)
+ ),
+ new ActionRowBuilder<TextInputBuilder>().addComponents(
+ new TextInputBuilder()
+ .setCustomId("ex2")
+ .setLabel("Member Counts (2/3) - {MemberCount:...}")
+ .setPlaceholder(
+ `{:all} - Total member count\n` +
+ `{:humans} - Total non-bot users\n` +
+ `{:bots} - Number of bots\n`
+ )
+ .setMaxLength(1)
+ .setRequired(false)
+ .setStyle(Discord.TextInputStyle.Paragraph)
+ ),
+ new ActionRowBuilder<TextInputBuilder>().addComponents(
+ new TextInputBuilder()
+ .setCustomId("ex3")
+ .setLabel("Latest Member (3/3) - {member:...}")
+ .setPlaceholder(
+ `{:name} - The members name\n`
+ )
+ .setMaxLength(1)
+ .setRequired(false)
+ .setStyle(Discord.TextInputStyle.Paragraph)
+ ),
+ new ActionRowBuilder<TextInputBuilder>().addComponents(
+ new TextInputBuilder()
+ .setCustomId("text")
+ .setLabel("Channel name input")
+ .setMaxLength(1000)
+ .setRequired(true)
+ .setStyle(Discord.TextInputStyle.Short)
+ .setValue(current.name)
+ )
+ )
+ );
}
-
const callback = async (interaction: CommandInteraction) => {
- try{
if (!interaction.guild) return;
const { renderChannel } = client.logger;
- let closed = false;
- let page = 0;
const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
- let changes: ChangesType = {};
+ let page = 0;
+ let closed = false;
+ const config = await client.database.guilds.read(interaction.guild.id);
+ const currentObject = config.stats;
+ let modified = false;
do {
- const config = await client.database.guilds.read(interaction.guild.id);
- const stats = config.stats;
- let currentID = "";
- let current: {
- name: string;
- enabled: boolean;
- } = {
- name: "",
- enabled: false
- };
- let description = "";
- let pageSelect = new StringSelectMenuBuilder()
+ let embed = new EmojiEmbed()
+ .setTitle("Stats Settings")
+ .setEmoji("SETTINGS.STATS.GREEN")
+ .setStatus("Success");
+ const noStatsChannels = Object.keys(currentObject).length === 0;
+ let current: { enabled: boolean; name: string; };
+
+ const pageSelect = new StringSelectMenuBuilder()
.setCustomId("page")
- .setPlaceholder("Select a stats channel to manage")
- .setDisabled(Object.keys(stats).length === 0)
- .setMinValues(1)
- .setMaxValues(1);
- let actionSelect = new StringSelectMenuBuilder()
+ .setPlaceholder("Select a stats channel to manage");
+ const actionSelect = new StringSelectMenuBuilder()
.setCustomId("action")
.setPlaceholder("Perform an action")
- .setMinValues(1)
- .setMaxValues(1)
- .setDisabled(Object.keys(stats).length === 0)
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Edit")
+ .setDescription("Edit the stats channel")
.setValue("edit")
- .setDescription("Edit the name of this stats channel")
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new StringSelectMenuOptionBuilder()
.setLabel("Delete")
+ .setDescription("Delete the stats channel")
.setValue("delete")
- .setDescription("Delete this stats channel")
.setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+ );
+ const buttonRow = new ActionRowBuilder<ButtonBuilder>()
+ .addComponents(
+ new ButtonBuilder()
+ .setCustomId("back")
+ .setStyle(ButtonStyle.Primary)
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
+ .setDisabled(page === 0),
+ new ButtonBuilder()
+ .setCustomId("next")
+ .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
+ .setStyle(ButtonStyle.Primary)
+ .setDisabled(page === Object.keys(currentObject).length - 1),
+ new ButtonBuilder()
+ .setCustomId("add")
+ .setLabel("Create new")
+ .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
+ .setStyle(ButtonStyle.Secondary)
+ .setDisabled(Object.keys(currentObject).length >= 24),
+ new ButtonBuilder()
+ .setCustomId("save")
+ .setLabel("Save")
+ .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
+ .setStyle(ButtonStyle.Success)
+ .setDisabled(modified),
);
- if (Object.keys(stats).length === 0) {
- description = "You do not have any stats channels set up yet"
- pageSelect.addOptions(new StringSelectMenuOptionBuilder().setLabel("No stats channels").setValue("none"))
+ if (noStatsChannels) {
+ embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" +
+ createPageIndicator(1, 1, undefined, true)
+ );
+ pageSelect.setDisabled(true);
+ actionSelect.setDisabled(true);
+ pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+ .setLabel("No stats channels")
+ .setValue("none")
+ );
} else {
- currentID = Object.keys(stats)[page]!
- current = stats[currentID]!;
- current = applyChanges({ [currentID]: current }, changes)[currentID]!;
- // Propogate pageSelect with list of stats channels
- for (const [id, { name, enabled }] of Object.entries(stats)) {
- pageSelect.addOptions(
- new StringSelectMenuOptionBuilder()
- .setLabel(name)
- .setValue(id)
- .setDescription(`Enabled: ${enabled}`)
- );
- }
+ page = Math.min(page, Object.keys(currentObject).length - 1);
+ current = currentObject[Object.keys(config.stats)[page]!]!
actionSelect.addOptions(new StringSelectMenuOptionBuilder()
.setLabel(current.enabled ? "Disable" : "Enable")
.setValue("toggleEnabled")
.setDescription(`Currently ${current.enabled ? "Enabled" : "Disabled"}, click to ${current.enabled ? "disable" : "enable"} this channel`)
.setEmoji(getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
);
- description = `**Currently Editing:** ${renderChannel(currentID)}\n\n` +
+
+ embed.setDescription(`**Currently Editing:** ${renderChannel(Object.keys(currentObject)[page]!)}\n\n` +
`${getEmojiByName(current.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Currently ${current.enabled ? "Enabled" : "Disabled"}\n` +
`**Name:** \`${current.name}\`\n` +
- `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}`
- }
- const row = new ActionRowBuilder<ButtonBuilder>()
- .addComponents(
- new ButtonBuilder()
- .setCustomId("back")
- .setStyle(ButtonStyle.Primary)
- .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
- .setDisabled(page === 0),
- new ButtonBuilder()
- .setCustomId("next")
- .setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
- .setStyle(ButtonStyle.Primary)
- .setDisabled(page === Object.keys(stats).length - 1),
- new ButtonBuilder()
- .setCustomId("add")
- .setLabel("Create new")
- .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(Object.keys(stats).length >= 24),
- new ButtonBuilder()
- .setCustomId("save")
- .setLabel("Save")
- .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
- .setStyle(ButtonStyle.Success)
- .setDisabled(Object.keys(changes).length === 0),
+ `**Preview:** ${await convertCurlyBracketString(current.name, interaction.user.id, interaction.user.username, interaction.guild.name, interaction.guild.members)}` + '\n\n' +
+ createPageIndicator(Object.keys(config.stats).length, page)
);
+ for (const [id, { name, enabled }] of Object.entries(currentObject)) {
+ pageSelect.addOptions(new StringSelectMenuOptionBuilder()
+ .setLabel(`${name} (${renderChannel(id)})`)
+ .setEmoji(getEmojiByName(enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji)
+ .setDescription(`${enabled ? "Enabled" : "Disabled"}`)
+ .setValue(id)
+ );
+ }
+ }
- let embed = new EmojiEmbed()
- .setTitle("Stats Channels")
- .setDescription(description + "\n\n" + createPageIndicator(Object.keys(stats).length, page))
- .setEmoji("SETTINGS.STATS.GREEN")
- .setStatus("Success")
+ interaction.editReply({embeds: [embed], components: [
+ new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
+ new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
+ buttonRow
+ ]});
- interaction.editReply({
- embeds: [embed],
- components: [
- new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
- new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
- row
- ]
- });
- let i: MessageComponentInteraction;
+ let i: StringSelectMenuInteraction | ButtonInteraction;
try {
- i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 });
+ i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction;
} catch (e) {
closed = true;
continue;
}
- if (i.isStringSelectMenu()) {
+
+ if(i.isStringSelectMenu()) {
switch(i.customId) {
case "page":
- page = Object.keys(stats).indexOf(i.values[0]!);
- i.deferUpdate();
+ page = Object.keys(currentObject).indexOf(i.values[0]!);
break;
case "action":
- if(!changes[currentID]) changes[currentID] = {};
+ modified = true;
switch(i.values[0]!) {
- case "edit":
- await i.showModal(
- new Discord.ModalBuilder()
- .setCustomId("modal")
- .setTitle(`Stats channel name`)
- .addComponents(
- new ActionRowBuilder<TextInputBuilder>().addComponents(
- new TextInputBuilder()
- .setCustomId("ex1")
- .setLabel("Server Info (1/3)")
- .setPlaceholder(
- `{serverName} - This server's name\n\n` +
- `These placeholders will be replaced with the server's name, etc..`
- )
- .setMaxLength(1)
- .setRequired(false)
- .setStyle(Discord.TextInputStyle.Paragraph)
- ),
- new ActionRowBuilder<TextInputBuilder>().addComponents(
- new TextInputBuilder()
- .setCustomId("ex2")
- .setLabel("Member Counts (2/3) - {MemberCount:...}")
- .setPlaceholder(
- `{:all} - Total member count\n` +
- `{:humans} - Total non-bot users\n` +
- `{:bots} - Number of bots\n`
- )
- .setMaxLength(1)
- .setRequired(false)
- .setStyle(Discord.TextInputStyle.Paragraph)
- ),
- new ActionRowBuilder<TextInputBuilder>().addComponents(
- new TextInputBuilder()
- .setCustomId("ex3")
- .setLabel("Latest Member (3/3) - {member:...}")
- .setPlaceholder(
- `{:name} - The members name\n`
- )
- .setMaxLength(1)
- .setRequired(false)
- .setStyle(Discord.TextInputStyle.Paragraph)
- ),
- new ActionRowBuilder<TextInputBuilder>().addComponents(
- new TextInputBuilder()
- .setCustomId("text")
- .setLabel("Channel name input")
- .setMaxLength(1000)
- .setRequired(true)
- .setStyle(Discord.TextInputStyle.Short)
- .setValue(current.name)
- )
- )
- );
+ case "edit": {
+ showModal(i, current!)
await interaction.editReply({
embeds: [
new EmojiEmbed()
@@ -225,35 +209,24 @@
)
]
});
- let out: Discord.ModalSubmitInteraction | null;
- try {
- out = await modalInteractionCollector(
- m,
- (m) => m.channel!.id === interaction.channel!.id,
- (_) => true
- ) as Discord.ModalSubmitInteraction | null;
- } catch (e) {
- continue;
- }
- if (!out) continue
- if (!out.fields) continue
- if (out.isButton()) continue;
- const newString = out.fields.getTextInputValue("text");
- if (!newString) continue;
- changes[currentID]!.name = newString;
break;
- case "delete":
- changes[currentID] = {};
+ }
+ case "toggleEnabled": {
i.deferUpdate();
+ currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled;
+ modified = true;
break;
- case "toggleEnabled":
- changes[currentID]!.enabled = !stats[currentID]!.enabled;
+ }
+ case "delete": {
i.deferUpdate();
+ delete currentObject[Object.keys(currentObject)[page]!];
+ modified = true;
break;
+ }
}
break;
}
- } else if (i.isButton()) {
+ } else {
i.deferUpdate();
switch(i.customId) {
case "back":
@@ -265,18 +238,14 @@
case "add":
break;
case "save":
- let changed = applyChanges(config.stats, changes);
- singleNotify("statsChannelDeleted", interaction.guild.id, true)
- config.stats = changed;
- changes = {}
- await client.database.guilds.write(interaction.guildId!, config);
+ client.database.guilds.write(interaction.guild.id, {stats: currentObject});
+ singleNotify("statsChannelDeleted", interaction.guild.id, true);
+ modified = false;
+ break;
}
}
- console.log(changes, config.stats);
+
} while (!closed);
- } catch(e) {
- console.log(e)
- }
};
const check = (interaction: CommandInteraction) => {