You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Nucleus/src/commands/settings/stats.ts

403 lines
19 KiB

import { LoadingEmbed } from "../../utils/defaults.js";
import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, ModalBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import client from "../../utils/client.js";
import convertCurlyBracketString from "../../utils/convertCurlyBracketString.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";
3 years ago
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("stats")
.setDescription("Controls channels which update when someone joins or leaves the server")
3 years ago
const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => {
3 years ago
await interaction.showModal(
new ModalBuilder()
3 years ago
.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)
)
)
);
}
3 years ago
type ObjectSchema = Record<string, {name: string, enabled: boolean}>
3 years ago
const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise<ObjectSchema> => {
let closed = false;
let cancelled = false;
const originalObject = Object.fromEntries(Object.entries(currentObject).map(([k, v]) => [k, {...v}]));
let newChannel: string | undefined;
let newChannelName: string = "{memberCount:all}-members";
let newChannelEnabled: boolean = true;
do {
m = await interaction.editReply({
3 years ago
embeds: [new EmojiEmbed()
.setTitle("Stats Channel")
.setDescription(
`New stats channel` + (newChannel ? ` in <#${newChannel}>` : "") + "\n\n" +
`**Name:** \`${newChannelName}\`\n` +
`**Preview:** ${await convertCurlyBracketString(newChannelName, interaction.user!.id, interaction.user.username, interaction.guild!.name, interaction.guild!.members)}\n` +
`**Enabled:** ${newChannelEnabled ? "Yes" : "No"}\n\n`
)
.setEmoji("SETTINGS.STATS.GREEN")
.setStatus("Success")
], components: [
new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
new ChannelSelectMenuBuilder()
.setCustomId("channel")
.setPlaceholder("Select a channel to use")
3 years ago
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel("Cancel")
.setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
.setStyle(ButtonStyle.Danger)
.setCustomId("back"),
new ButtonBuilder()
.setLabel("Save")
.setEmoji(getEmojiByName("ICONS.SAVE", "id"))
.setStyle(ButtonStyle.Success)
.setCustomId("save"),
new ButtonBuilder()
.setLabel("Edit name")
.setEmoji(getEmojiByName("ICONS.EDIT", "id"))
.setStyle(ButtonStyle.Primary)
.setCustomId("editName"),
new ButtonBuilder()
.setLabel(newChannelEnabled ? "Enabled" : "Disabled")
.setEmoji(getEmojiByName(newChannelEnabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id"))
.setStyle(ButtonStyle.Secondary)
.setCustomId("toggleEnabled")
)
]
});
let i: ButtonInteraction | ChannelSelectMenuInteraction;
try {
i = await m.awaitMessageComponent({ time: 300000, filter: (i) => {
return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id;
}}) as ButtonInteraction | ChannelSelectMenuInteraction;
3 years ago
} catch (e) {
closed = true;
cancelled = true;
break;
}
if (i.isButton()) {
switch (i.customId) {
case "back": {
await i.deferUpdate();
3 years ago
closed = true;
break;
}
case "save": {
await i.deferUpdate();
3 years ago
if (newChannel) {
currentObject[newChannel] = {
name: newChannelName,
enabled: newChannelEnabled
}
}
closed = true;
break;
}
case "editName": {
3 years ago
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")
)
]
});
showModal(i, {name: newChannelName, enabled: newChannelEnabled})
const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector(m, interaction.user);
3 years ago
if (!out) continue;
if (out.isButton()) continue;
newChannelName = out.fields.getTextInputValue("text");
break;
}
case "toggleEnabled": {
await i.deferUpdate();
3 years ago
newChannelEnabled = !newChannelEnabled;
break;
}
3 years ago
}
} else {
await i.deferUpdate();
3 years ago
if (i.customId === "channel") {
newChannel = i.values[0];
}
}
} while (!closed)
if (cancelled) return originalObject;
if (!(newChannel && newChannelName && newChannelEnabled)) return originalObject;
return currentObject;
3 years ago
}
const callback = async (interaction: CommandInteraction) => {
if (!interaction.guild) return;
const { renderChannel } = client.logger;
const m: Message = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true });
3 years ago
let page = 0;
let closed = false;
const config = await client.database.guilds.read(interaction.guild.id);
3 years ago
let currentObject: ObjectSchema = config.stats;
3 years ago
let modified = false;
do {
const embed = new EmojiEmbed()
3 years ago
.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")
3 years ago
.setPlaceholder("Select a stats channel to manage");
const actionSelect = new StringSelectMenuBuilder()
.setCustomId("action")
.setPlaceholder("Perform an action")
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Edit")
3 years ago
.setDescription("Edit the stats channel")
.setValue("edit")
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new StringSelectMenuOptionBuilder()
.setLabel("Delete")
3 years ago
.setDescription("Delete the stats channel")
.setValue("delete")
.setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
3 years ago
);
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),
3 years ago
);
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 {
3 years ago
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)
);
3 years ago
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` +
3 years ago
`**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)
);
3 years ago
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)
);
}
}
3 years ago
interaction.editReply({embeds: [embed], components: [
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect),
new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect),
buttonRow
]});
3 years ago
let i: StringSelectMenuInteraction | ButtonInteraction;
try {
3 years ago
i = await m.awaitMessageComponent({ filter: (interaction) => interaction.user.id === interaction.user.id, time: 60000 }) as StringSelectMenuInteraction | ButtonInteraction;
} catch (e) {
closed = true;
continue;
}
3 years ago
if(i.isStringSelectMenu()) {
switch(i.customId) {
case "page": {
await i.deferUpdate();
3 years ago
page = Object.keys(currentObject).indexOf(i.values[0]!);
break;
}
case "action": {
3 years ago
modified = true;
switch(i.values[0]!) {
3 years ago
case "edit": {
showModal(i, current!)
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")
)
]
});
3 years ago
let out: Discord.ModalSubmitInteraction | null;
try {
out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
3 years ago
} catch (e) {
continue;
}
if (!out) continue
if (out.isButton()) continue;
currentObject[Object.keys(currentObject)[page]!]!.name = out.fields.getTextInputValue("text");
break;
3 years ago
}
case "toggleEnabled": {
await i.deferUpdate();
3 years ago
currentObject[Object.keys(currentObject)[page]!]!.enabled = !currentObject[Object.keys(currentObject)[page]!]!.enabled;
modified = true;
break;
3 years ago
}
case "delete": {
await i.deferUpdate();
currentObject = Object.fromEntries(Object.entries(currentObject).filter(([k]) => k !== Object.keys(currentObject)[page]!));
page = Math.min(page, Object.keys(currentObject).length - 1);
3 years ago
modified = true;
break;
3 years ago
}
}
break;
}
}
3 years ago
} else {
await i.deferUpdate();
switch(i.customId) {
case "back": {
page--;
break;
}
case "next": {
page++;
break;
}
case "add": {
3 years ago
currentObject = await addStatsChannel(interaction, m, currentObject);
page = Object.keys(currentObject).length - 1;
break;
}
case "save": {
3 years ago
client.database.guilds.write(interaction.guild.id, {stats: currentObject});
singleNotify("statsChannelDeleted", interaction.guild.id, true);
modified = false;
break;
}
}
}
3 years ago
} while (!closed);
await interaction.deleteReply()
};
const check = (interaction: CommandInteraction, _partial: boolean = false) => {
const member = interaction.member as Discord.GuildMember;
if (!member.permissions.has("ManageChannels"))
return "You must have the *Manage Channels* permission to use this command";
return true;
};
export { command };
export { callback };
export { check };