intermediate update
diff --git a/src/commands/settings/commands.ts b/src/commands/settings/commands.ts
index 25034b2..5cafcc5 100644
--- a/src/commands/settings/commands.ts
+++ b/src/commands/settings/commands.ts
@@ -191,7 +191,7 @@
} catch (e) {
continue;
}
- if (!out) continue
+ if (!out || out.isButton()) continue
const buttonText = out.fields.getTextInputValue("name");
const buttonLink = out.fields.getTextInputValue("url").replace(/{id}/gi, "{id}");
const current = chosen;
diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts
index 6fb2461..e9e8dce 100644
--- a/src/commands/settings/logs/attachment.ts
+++ b/src/commands/settings/logs/attachment.ts
@@ -158,7 +158,7 @@
}
i.deferUpdate();
if ((i.component as unknown as ButtonInteraction).customId === "clear") {
- clicks += 1;
+ clicks ++;
if (clicks === 2) {
clicks = 0;
await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]);
diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/channel.ts
index 9b594e8..0852e67 100644
--- a/src/commands/settings/logs/channel.ts
+++ b/src/commands/settings/logs/channel.ts
@@ -151,7 +151,7 @@
i = i!
i.deferUpdate();
if ((i.component as ButtonComponent).customId === "clear") {
- clicks += 1;
+ clicks ++;
if (clicks === 2) {
clicks = 0;
await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]);
diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts
index fbe79fa..7332c7b 100644
--- a/src/commands/settings/logs/events.ts
+++ b/src/commands/settings/logs/events.ts
@@ -83,8 +83,8 @@
continue;
}
i.deferUpdate();
- if (i.customId === "logs") {
- const selected = (i as StringSelectMenuInteraction).values;
+ if (i.isStringSelectMenu() && i.customId === "logs") {
+ const selected = i.values;
const newLogs = toHexInteger(selected.map((e: string) => Object.keys(logs)[parseInt(e)]!));
await client.database.guilds.write(interaction.guild!.id, {
"logging.logs.toLog": newLogs
diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/staff.ts
index 3a0f395..d6379f0 100644
--- a/src/commands/settings/logs/staff.ts
+++ b/src/commands/settings/logs/staff.ts
@@ -155,7 +155,7 @@
}
i.deferUpdate();
if ((i.component as ButtonComponent).customId === "clear") {
- clicks += 1;
+ clicks ++;
if (clicks === 2) {
clicks = 0;
await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]);
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index 1093bd2..df99510 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -1,45 +1,283 @@
import { LoadingEmbed } from "../../utils/defaults.js";
-import Discord, { CommandInteraction, Message, ActionRowBuilder, GuildMember, StringSelectMenuBuilder, StringSelectMenuInteraction, SelectMenuOptionBuilder } from "discord.js";
+import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
-import confirmationMessage from "../../utils/confirmationMessage.js";
import type { SlashCommandSubcommandBuilder } from "@discordjs/builders";
import client from "../../utils/client.js";
import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js";
import { callback as statsChannelAddCallback } from "../../reflex/statsChannelUpdate.js";
import singleNotify from "../../utils/singleNotify.js";
+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 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 = {};
do {
const config = await client.database.guilds.read(interaction.guild.id);
- const stats = config.stats; // stats: Record<string, { name: string; enabled: boolean }>
- if (!stats) {
- await interaction.editReply({embeds: [new EmojiEmbed()
- .setTitle("Stats channels")
- .setDescription("You don't have ant stats channels yet")
- .setStatus("Success")
- .setEmoji("")
- ]})
- }
+ const stats = config.stats;
+ let currentID = "";
+ let current: {
+ name: string;
+ enabled: boolean;
+ } = {
+ name: "",
+ enabled: false
+ };
+ let description = "";
let pageSelect = new StringSelectMenuBuilder()
.setCustomId("page")
.setPlaceholder("Select a stats channel to manage")
+ .setDisabled(Object.keys(stats).length === 0)
.setMinValues(1)
.setMaxValues(1);
- for (const [id, { name, enabled }] of Object.entries(stats)) {
- pageSelect.addOption()
+ let actionSelect = new StringSelectMenuBuilder()
+ .setCustomId("action")
+ .setPlaceholder("Perform an action")
+ .setMinValues(1)
+ .setMaxValues(1)
+ .setDisabled(Object.keys(stats).length === 0)
+ .addOptions(
+ new StringSelectMenuOptionBuilder()
+ .setLabel("Edit")
+ .setValue("edit")
+ .setDescription("Edit the name of this stats channel")
+ .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
+ new StringSelectMenuOptionBuilder()
+ .setLabel("Delete")
+ .setValue("delete")
+ .setDescription("Delete this stats channel")
+ .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
+ );
+ 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"))
+ } 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}`)
+ );
+ }
+ 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` +
+ `${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)}`
}
- // [ Action... ] -> Edit, delete, reorder
- // [Back][Next][Add]
+ 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),
+ );
+
+ 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),
+ row
+ ]
+ });
+ let i: MessageComponentInteraction;
+ try {
+ i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id, time: 30000 });
+ } catch (e) {
+ closed = true;
+ continue;
+ }
+ if (i.isStringSelectMenu()) {
+ switch(i.customId) {
+ case "page":
+ page = Object.keys(stats).indexOf(i.values[0]!);
+ i.deferUpdate();
+ break;
+ case "action":
+ if(!changes[currentID]) changes[currentID] = {};
+ 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)
+ )
+ )
+ );
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed()
+ .setTitle("Stats Channel")
+ .setDescription("Modal opened. If you can't see it, click back and try again.")
+ .setStatus("Success")
+ .setEmoji("SETTINGS.STATS.GREEN")
+ ],
+ components: [
+ new ActionRowBuilder<ButtonBuilder>().addComponents(
+ new ButtonBuilder()
+ .setLabel("Back")
+ .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+ .setStyle(ButtonStyle.Primary)
+ .setCustomId("back")
+ )
+ ]
+ });
+ 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] = {};
+ i.deferUpdate();
+ break;
+ case "toggleEnabled":
+ changes[currentID]!.enabled = !stats[currentID]!.enabled;
+ i.deferUpdate();
+ break;
+ }
+ break;
+ }
+ } else if (i.isButton()) {
+ i.deferUpdate();
+ switch(i.customId) {
+ case "back":
+ page--;
+ break;
+ case "next":
+ page++;
+ break;
+ 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);
+ }
+ }
+ console.log(changes, config.stats);
} while (!closed);
- closed = true;
+ } catch(e) {
+ console.log(e)
+ }
};
const check = (interaction: CommandInteraction) => {
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index aa25b69..8f9f688 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -11,11 +11,9 @@
MessageComponentInteraction,
StringSelectMenuBuilder,
Role,
- StringSelectMenuInteraction,
ButtonStyle,
TextInputBuilder,
ButtonComponent,
- StringSelectMenuComponent,
ModalSubmitInteraction,
APIMessageComponentEmoji
} from "discord.js";
@@ -390,14 +388,14 @@
innerTimedOut = true;
continue;
}
- if ((i.component as StringSelectMenuComponent).customId === "template") {
+ if (i.isStringSelectMenu() && i.customId === "template") {
i.deferUpdate();
await interaction.channel!.send({
embeds: [
new EmojiEmbed()
- .setTitle(ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
+ .setTitle(ticketMessages[parseInt(i.values[0]!)]!.label)
.setDescription(
- ticketMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
+ ticketMessages[parseInt(i.values[0]!)]!.description
)
.setStatus("Success")
.setEmoji("GUILD.TICKET.OPEN")
@@ -643,16 +641,16 @@
timedOut = true;
continue;
}
- if ((i.component as StringSelectMenuComponent).customId === "types") {
+ if (i.isStringSelectMenu() && i.customId === "types") {
i.deferUpdate();
- const types = toHexInteger((i as StringSelectMenuInteraction).values, ticketTypes);
+ const types = toHexInteger(i.values, ticketTypes);
await client.database.guilds.write(interaction.guild!.id, {
"tickets.types": types
});
data.types = types;
- } else if ((i.component as StringSelectMenuComponent).customId === "removeTypes") {
+ } else if (i.isStringSelectMenu() && i.customId === "removeTypes") {
i.deferUpdate();
- const types = (i as StringSelectMenuInteraction).values;
+ const types = i.values;
let customTypes = data.customTypes;
if (customTypes) {
customTypes = customTypes.filter((t) => !types.includes(t));
@@ -708,6 +706,7 @@
} catch (e) {
continue;
}
+ if (!out || out.isButton()) continue;
out = out as ModalSubmitInteraction;
let toAdd = out.fields.getTextInputValue("type");
if (!toAdd) {
diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts
index 0f9f4a0..d3971a8 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -10,10 +10,8 @@
Role,
ButtonStyle,
StringSelectMenuBuilder,
- StringSelectMenuComponent,
TextInputBuilder,
EmbedBuilder,
- StringSelectMenuInteraction,
ButtonComponent
} from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
@@ -170,7 +168,7 @@
}
i.deferUpdate();
if ((i.component as ButtonComponent).customId === "clear") {
- clicks += 1;
+ clicks ++;
if (clicks === 2) {
clicks = 0;
await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]);
@@ -257,14 +255,14 @@
innerTimedOut = true;
continue;
}
- if ((i.component as StringSelectMenuComponent).customId === "template") {
+ if (i.isStringSelectMenu() && i.customId === "template") {
i.deferUpdate();
await interaction.channel!.send({
embeds: [
new EmojiEmbed()
- .setTitle(verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.label)
+ .setTitle(verifyMessages[parseInt(i.values[0]!)]!.label)
.setDescription(
- verifyMessages[parseInt((i as StringSelectMenuInteraction).values[0]!)]!.description
+ verifyMessages[parseInt(i.values[0]!)]!.description
)
.setStatus("Success")
.setEmoji("CONTROL.BLOCKTICK")
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
index 42376e8..abedec5 100644
--- a/src/commands/settings/welcome.ts
+++ b/src/commands/settings/welcome.ts
@@ -309,7 +309,7 @@
};
const autocomplete = async (interaction: AutocompleteInteraction): Promise<string[]> => {
- const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+ const validReplacements = ["serverName", "memberCount:all", "memberCount:bots", "memberCount:humans"]
if (!interaction.guild) return [];
const prompt = interaction.options.getString("message");
const autocompletions = [];