From 1c3ad3ce4be53a12ee5df734575a613180ba90d7 Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Wed, 25 Jan 2023 17:58:36 -0500 Subject: [PATCH] worked on settings/rolemenu and help --- TODO | 14 +- TODO.json | 19 +- src/actions/roleMenu.ts | 52 ++-- src/commands/help.ts | 86 +++++- src/commands/nucleus/premium.ts | 2 +- src/commands/settings/oldStats.ts | 291 ------------------ src/commands/settings/rolemenu.ts | 344 +++++++++++++++++++++- src/commands/settings/stats.ts | 47 ++- src/config/emojis.json | 3 + src/index.ts | 8 +- src/reflex/guide.ts | 2 +- src/utils/client.ts | 4 +- src/utils/commandRegistration/register.ts | 1 + src/utils/confirmationMessage.ts | 7 +- 14 files changed, 508 insertions(+), 372 deletions(-) delete mode 100644 src/commands/settings/oldStats.ts diff --git a/TODO b/TODO index d2cd1a2..2ab95dc 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,4 @@ -Role all +? Role all Server rules verificationRequired on welcome // TODO !IMPORTANT! URL + image hash + file hash database - -ROLE MENU SETTINGS - -**Title** -> Description -name: role -name: role -name: role - -[ Select an option to remove ] -[ Reorder roles ] -< Previous page | Add role | Delete page | Add page > diff --git a/TODO.json b/TODO.json index 6f0b94e..19d077c 100644 --- a/TODO.json +++ b/TODO.json @@ -22,6 +22,21 @@ "roles": true } }, - "roleMenu": [], - "tracks": [] + "tracks": [], + "rolemenu": { + "enabled": false, + "allowWebUI": false, + "options": [ + { + "name": false, + "description": false, + "options": [ + { + "name": false, + "description": false + } + ] + } + ] + } } diff --git a/src/actions/roleMenu.ts b/src/actions/roleMenu.ts index 732c94d..16689b7 100644 --- a/src/actions/roleMenu.ts +++ b/src/actions/roleMenu.ts @@ -30,6 +30,36 @@ export interface RoleMenuSchema { interaction: CommandInteraction | ButtonInteraction | ContextMenuCommandInteraction; } +interface ObjectSchema { + name: string; + description: string; + min: number; + max: number; + options: { + name: string; + description: string | null; + role: string; + }[]; +} + +export const configToDropdown = (placeholder: string, currentPageData: ObjectSchema, selectedRoles?: string[]): ActionRowBuilder => { + return new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("roles") + .setPlaceholder(placeholder) + .setMinValues(currentPageData.min) + .setMaxValues(currentPageData.max) + .addOptions(currentPageData.options.map((option: {name: string; description: string | null; role: string;}) => { + const builder = new StringSelectMenuOptionBuilder() + .setLabel(option.name) + .setValue(option.role) + .setDefault(selectedRoles ? selectedRoles.includes(option.role) : false); + if (option.description) builder.setDescription(option.description); + return builder; + })) + ) +} + export async function callback(interaction: CommandInteraction | ButtonInteraction) { if (!interaction.member) return; if (!interaction.guild) return; @@ -56,7 +86,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti ], ephemeral: true }); - const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true }); + const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); if (config.roleMenu.allowWebUI) { // TODO: Make rolemenu web ui const loginMethods: {webUI: boolean} = { webUI: false @@ -124,9 +154,10 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti try { component = 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} + filter: (i) => { return i.user.id === interaction.user.id && i.channelId === interaction.channelId && i.message.id === m.id} }); } catch (e) { + console.log(e); return; } component.deferUpdate(); @@ -175,21 +206,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti .setCustomId("done") .setDisabled(!complete) ), - new ActionRowBuilder().addComponents( - new StringSelectMenuBuilder() - .setCustomId("roles") - .setPlaceholder("Select...") - .setMinValues(currentPageData.min) - .setMaxValues(currentPageData.max) - .addOptions(currentPageData.options.map((option) => { - const builder = new StringSelectMenuOptionBuilder() - .setLabel(option.name) - .setValue(option.role) - .setDefault(selectedRoles[page]!.includes(option.role)); - if (option.description) builder.setDescription(option.description); - return builder; - })) - ) + configToDropdown("Select...", currentPageData, selectedRoles[page]) ]; await interaction.editReply({ embeds: [embed], @@ -202,6 +219,7 @@ export async function callback(interaction: CommandInteraction | ButtonInteracti filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id} }); } catch (e) { + console.log(e); return; } component.deferUpdate(); diff --git a/src/commands/help.ts b/src/commands/help.ts index 767ca46..1a0ce16 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,17 +1,89 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction } from "discord.js"; +import { ActionRowBuilder, CommandInteraction, StringSelectMenuBuilder, ApplicationCommand, ApplicationCommandOptionType } from "discord.js"; import { SlashCommandBuilder } from "@discordjs/builders"; +import client from "../utils/client.js"; +import EmojiEmbed from "../utils/generateEmojiEmbed.js"; +import { LoadingEmbed } from "../utils/defaults.js"; const command = new SlashCommandBuilder() .setName("help") .setDescription("Shows help for commands"); const callback = async (interaction: CommandInteraction): Promise => { - interaction.reply({components: [new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel("Create ticket") - .setStyle(ButtonStyle.Primary) - .setCustomId("createticket") - )]}); // TODO: FINISH THIS FOR RELEASE + const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true }); + const commands = client.fetchedCommands; + + const commandRow = new ActionRowBuilder() + .addComponents( + commands.map((command) => { + return new StringSelectMenuBuilder() + .setCustomId(command.name) + .setPlaceholder("Select a command") + .addOptions({ + label: command.name, + value: command.name + }) + }) + ); + + let closed = false; + do { + let current: ApplicationCommand | undefined; + const subcommandGroupRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("subcommandGroupRow") + ); + const subcommandRow = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setCustomId("subcommandRow") + ); + const embed = new EmojiEmbed() + .setTitle("Help") + .setStatus("Success") + .setEmoji("📖") + + if(!current) { + embed.setDescription( + `**${"Help Menu Home"}**\n\n` + + `${"Select a command to get started"}` + ) + } else { + embed.setDescription( + `**${current.name}**\n\n` + + `${current.description}` + ) + if(current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).length > 0) { + subcommandGroupRow.components[0]! + .addOptions( + current.options.filter((option) => option.type === ApplicationCommandOptionType.SubcommandGroup).map((option) => { + return { + label: option.name, + value: option.name + } + }) + ) + } else { + if(current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).length > 0) { + subcommandRow.components[0]! + .addOptions( + current.options.filter((option) => option.type === ApplicationCommandOptionType.Subcommand).map((option) => { + return { + label: option.name, + value: option.name + } + }) + ) + } + } + } + let cmps = [commandRow]; + if(subcommandGroupRow.components[0]!.options.length > 0) cmps.push(subcommandGroupRow); + if(subcommandRow.components[0]!.options.length > 0) cmps.push(subcommandRow); + + await interaction.editReply({ embeds: [embed], components: cmps }); + + } while (!closed); }; const check = () => { diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index 8b47ab7..e74e23c 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -91,7 +91,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( `You have already activated premium on the maximum amount of servers!` + firstDescription ) - .setEmoji("NUCLEUS.LOGO") + .setEmoji("NUCLEUS.PREMIUMACTIVATE") .setStatus("Danger") ], components: [] diff --git a/src/commands/settings/oldStats.ts b/src/commands/settings/oldStats.ts deleted file mode 100644 index 8f13109..0000000 --- a/src/commands/settings/oldStats.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, MessageComponentInteraction, TextInputBuilder } from "discord.js"; -import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; -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"; -import type { GuildConfig } from "../../utils/database.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder - .setName("oldstats") - .setDescription("Controls channels which update when someone joins or leaves the server") - -type ChangesType = Record - -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; - let currentID = ""; - let current: { - name: string; - enabled: boolean; - } = { - name: "", - enabled: false - }; - let description = ""; - const pageSelect = new StringSelectMenuBuilder() - .setCustomId("page") - .setPlaceholder("Select a stats channel to manage") - .setDisabled(Object.keys(stats).length === 0) - .setMinValues(1) - .setMaxValues(1); - 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") - .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]!; - 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)}` - } - const row = new ActionRowBuilder() - .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), - ); - - const 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().addComponents(pageSelect), - new ActionRowBuilder().addComponents(actionSelect), - row - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id, time: 30000 }); - } catch (e) { - closed = true; - continue; - } - if (i.isStringSelectMenu()) { - switch(i.customId) { - case "page": - page = Object.keys(stats).indexOf(i.values[0]!); - await 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().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().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().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().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().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] = {}; - await i.deferUpdate(); - break; - case "toggleEnabled": - changes[currentID]!.enabled = !stats[currentID]!.enabled; - await i.deferUpdate(); - break; - } - break; - } - } else if (i.isButton()) { - await i.deferUpdate(); - switch(i.customId) { - case "back": - page--; - break; - case "next": - page++; - break; - case "add": - break; - case "save": - const 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); - } catch(e) { - console.log(e) - } -}; - -const check = (interaction: CommandInteraction) => { - 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 }; \ No newline at end of file diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index b62d962..f8fb6f4 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -1,16 +1,350 @@ import type Discord from "discord.js"; -import type { CommandInteraction } from "discord.js"; +import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import { LoadingEmbed } from "../../utils/defaults.js"; +import client from "../../utils/client.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import createPageIndicator from "../../utils/createPageIndicator.js"; +import { configToDropdown } from "../../actions/roleMenu.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("rolemenu") - .setDescription("rolemenu") // TODO - .addRoleOption((option) => option.setName("role").setDescription("The role to give after verifying")); // FIXME FOR FUCK SAKE + .setDescription("rolemenu") + +interface ObjectSchema { + name: string; + description: string; + min: number; + max: number; + options: { + name: string; + description: string | null; + role: string; + }[]; +} + +const editNameDescription = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { + + let {name, description} = data; + const modal = new ModalBuilder() + .setTitle("Edit Name and Description") + .setCustomId("editNameDescription") + .addComponents( + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("name") + .setPlaceholder(name ?? "") + .setStyle(TextInputStyle.Short) + .setRequired(true), + new TextInputBuilder() + .setCustomId("description") + .setPlaceholder(description ?? "") + .setStyle(TextInputStyle.Short) + ) + ) + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + + return [name, description] + +} + +const ellipsis = (str: string, max: number): string => { + if (str.length <= max) return str; + return str.slice(0, max - 3) + "..."; +} + +const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema) => { + if (!data) data = { + name: "Role Menu Page", + description: "A new role menu page", + min: 0, + max: 0, + options: [] + }; + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("addRole") + .setLabel("Add Role") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + ); + + let back = false + do { + const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options}); + const embed = new EmojiEmbed() + .setTitle(`${data.name}`) + .setStatus("Success") + .setDescription( + `**Description:**\n> ${data.description}\n\n` + + `**Min:** ${data.min}` + (data.min === 0 ? " (Members will be given a skip button)" : "") + "\n" + + `**Max:** ${data.max}\n` + ) + + interaction.editReply({embeds: [embed], components: [previewSelect, buttons]}); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + back = true; + break; + } + + if (i.isStringSelectMenu()) { + if(i.customId === "roles") { + await i.deferUpdate(); + await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])); + } + } else if (i.isButton()) { + await i.deferUpdate(); + switch (i.customId) { + case "back": + back = true; + break; + case "edit": + let [name, description] = await editNameDescription(interaction, m, data); + data.name = name ? name : data.name; + data.description = description ? description : data.description; + break; + case "addRole": + data.options.push(await createRoleMenuOptionPage(interaction, m)); + break; + } + } + + } while (!back); + return data; +} + +const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => { + const { renderRole} = client.logger; + if (!data) data = { + name: "Role Menu Option", + description: null, + role: "No role set" + }; + let back = false; + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("edit") + .setLabel("Edit Details") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji) + ); + do { + const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role"); + const embed = new EmojiEmbed() + .setTitle(`${data.name ?? "New Role Menu Option"}`) + .setStatus("Success") + .setDescription( + `**Description:**\n> ${data.description ?? "No description set"}\n\n` + + `**Role:** ${renderRole((await interaction.guild!.roles.fetch(data.role))!) ?? "No role set"}\n` + ) + + interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(roleSelect), buttons]}); + + let i: RoleSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | RoleSelectMenuInteraction; + } catch (e) { + back = true; + break; + } + + if (i.isRoleSelectMenu()) { + if(i.customId === "role") { + await i.deferUpdate(); + data.role = (i as RoleSelectMenuInteraction).values[0]!; + } + } else if (i.isButton()) { + await i.deferUpdate(); + switch (i.customId) { + case "back": + back = true; + break; + case "edit": + await i.deferUpdate(); + let [name, description] = await editNameDescription(interaction, m, data as {name: string; description: string}); + data.name = name ? name : data.name; + data.description = description ? description : data.description; + break; + } + } + } while (!back); + return data; +} const callback = async (interaction: CommandInteraction): Promise => { - console.log("we changed the charger again because fuck you"); - await interaction.reply("You're mum"); + if (!interaction.guild) return; + const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true}); + + let page = 0; + let closed = false; + const config = await client.database.guilds.read(interaction.guild.id); + let currentObject: ObjectSchema[] = config.roleMenu.options; + let modified = false; + do { + const embed = new EmojiEmbed() + .setTitle("Role Menu Settings") + .setEmoji("GUILD.GREEN") + .setStatus("Success"); + const noRoleMenus = currentObject.length === 0; + let current: ObjectSchema; + + const pageSelect = new StringSelectMenuBuilder() + .setCustomId("page") + .setPlaceholder("Select a Role Menu page to manage"); + const actionSelect = new StringSelectMenuBuilder() + .setCustomId("action") + .setPlaceholder("Perform an action") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Edit") + .setDescription("Edit this page") + .setValue("edit") + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new StringSelectMenuOptionBuilder() + .setLabel("Delete") + .setDescription("Delete this page") + .setValue("delete") + .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) + ); + const buttonRow = new ActionRowBuilder() + .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("New Page") + .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length >= 24), + new ButtonBuilder() + .setCustomId("reorder") + .setLabel("Reorder Pages") + .setEmoji(getEmojiByName("ICONS.SHUFFLE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Secondary) + .setDisabled(Object.keys(currentObject).length <= 1), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setStyle(ButtonStyle.Success) + .setDisabled(!modified), + ); + if(noRoleMenus) { + embed.setDescription("No role menu page 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 role menu pages") + .setValue("none") + ); + } else { + page = Math.min(page, Object.keys(currentObject).length - 1); + current = currentObject[page]!; + embed.setDescription(`**Currently Editing:** ${current.name}\n\n` + + `**Description:** \`${current.description}\`\n` + + `\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}` + ); + + pageSelect.addOptions( + currentObject.map((key: ObjectSchema, index) => { + return new StringSelectMenuOptionBuilder() + .setLabel(ellipsis(key.name, 50)) + .setDescription(ellipsis(key.description, 50)) + .setValue(index.toString()); + }) + ); + + } + + await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder().addComponents(actionSelect), new ActionRowBuilder().addComponents(pageSelect), buttonRow]}); + let i: StringSelectMenuInteraction | ButtonInteraction; + try { + i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction; + } catch (e) { + closed = true; + break; + } + + await i.deferUpdate(); + if (i.isButton()) { + switch (i.customId) { + case "back": + page--; + break; + case "next": + page++; + break; + case "add": + currentObject.push(await createRoleMenuPage(i, m)); + page = currentObject.length - 1; + break; + case "reorder": + break; + case "save": + client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject}); + modified = false; + break; + } + } else if (i.isStringSelectMenu()) { + switch (i.customId) { + case "action": + switch(i.values[0]) { + case "edit": + currentObject[page] = await createRoleMenuPage(i, m, current!); + modified = true; + break; + case "delete": + currentObject.splice(page, 1); + break; + } + break; + case "page": + page = parseInt(i.values[0]!); + break; + } + } + + } while (!closed) }; const check = (interaction: CommandInteraction) => { diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f191a34..91da382 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -232,29 +232,29 @@ const callback = async (interaction: CommandInteraction) => { .setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji) ); const buttonRow = new ActionRowBuilder() - .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), + .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 (noStatsChannels) { embed.setDescription("No stats channels have been set up yet. Use the button below to add one.\n\n" + @@ -275,7 +275,6 @@ const callback = async (interaction: CommandInteraction) => { .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) ); - 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` + diff --git a/src/config/emojis.json b/src/config/emojis.json index 8c45353..05a5e1d 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -1,6 +1,8 @@ { "NUCLEUS": { "LOGO": "953040840945721385", + "PREMIUMACTIVATE": "a1067536222764925068", + "PREMIUM": "1067928702027042876", "LOADING": "a946346549271732234", "INFO": { "HELP": "751751467014029322", @@ -23,6 +25,7 @@ "ATTACHMENT": "997570687193587812", "LOGGING": "999613304446144562", "SAVE": "1065722246322200586", + "SHUFFLE": "1067913930304921690", "NOTIFY": { "ON": "1000726394579464232", "OFF": "1000726363495477368" diff --git a/src/index.ts b/src/index.ts index a88cc54..b67da33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,17 +4,15 @@ import config from "./config/main.json" assert { type: "json" }; import register from "./utils/commandRegistration/register.js"; import { record as recordPerformance } from "./utils/performanceTesting/record.js"; -client.on("ready", () => { +client.on("ready", async () => { console.log(`Logged in as ${client.user!.tag}!`); register(); runServer(client); + client.fetchedCommands = await client.application?.commands.fetch()!; }); process.on("unhandledRejection", (err) => { console.error(err) }); process.on("uncaughtException", (err) => { console.error(err) }); await client.login(config.enableDevelopment ? config.developmentToken : config.token) -await recordPerformance(); - -import { getCommandMentionByName} from "./utils/getCommandMentionByName.js"; -console.log(await getCommandMentionByName("nucleus/premium")) +await recordPerformance(); \ No newline at end of file diff --git a/src/reflex/guide.ts b/src/reflex/guide.ts index e78c0dc..668b56d 100644 --- a/src/reflex/guide.ts +++ b/src/reflex/guide.ts @@ -178,7 +178,7 @@ export default async (guild: Guild, interaction?: CommandInteraction) => { "**Attachment logs**\n> When a message with attachments is edited or deleted, the logs will also include the images sent.\n" + "\nPremium is not yet available. Check `/nucleus premium` for updates on features and pricing" ) - .setEmoji("NUCLEUS.COMMANDS.LOCK") + .setEmoji("NUCLEUS.PREMIUM") .setStatus("Danger") ) .setTitle("Premium") diff --git a/src/utils/client.ts b/src/utils/client.ts index 46d9f92..a199b5b 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -1,4 +1,4 @@ -import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits } from 'discord.js'; +import Discord, { Client, Interaction, AutocompleteInteraction, GatewayIntentBits, Collection } from 'discord.js'; import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; @@ -32,7 +32,7 @@ class NucleusClient extends Client { check: (interaction: Interaction) => Promise | boolean, autocomplete: (interaction: AutocompleteInteraction) => Promise }> = {}; - + fetchedCommands: Collection = new Collection(); constructor(database: typeof NucleusClient.prototype.database) { super({ intents: [ GatewayIntentBits.Guilds, diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 4a13807..abc8c39 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -213,6 +213,7 @@ export default async function register() { await client.application?.commands.set(commandList); } } + await registerCommandHandler(); await registerEvents(); console.log(`${colours.green}Registered commands, events and context menus${colours.none}`) diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 7d54674..6dc424e 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -188,8 +188,7 @@ class confirmationMessage { }); } catch (e) { success = false; - returnComponents = true; - continue; + break; } if (component.customId === "yes") { component.deferUpdate(); @@ -277,8 +276,6 @@ class confirmationMessage { } const returnValue: Awaited> = {}; - if (returnComponents || success !== undefined) returnValue.components = this.customButtons; - if (success !== undefined) returnValue.success = success; if (cancelled) { await this.timeoutError() returnValue.cancelled = true; @@ -294,6 +291,8 @@ class confirmationMessage { }); return {success: false} } + if (returnComponents || success !== undefined) returnValue.components = this.customButtons; + if (success !== undefined) returnValue.success = success; if (newReason) returnValue.newReason = newReason; const typedReturnValue = returnValue as {cancelled: true} |