diff --git a/src/commands/mod/about.ts b/src/commands/mod/about.ts index ab3ca49..0a9d962 100644 --- a/src/commands/mod/about.ts +++ b/src/commands/mod/about.ts @@ -3,7 +3,6 @@ import type { HistorySchema } from "../../utils/database.js"; import Discord, { CommandInteraction, GuildMember, - Interaction, Message, ActionRowBuilder, ButtonBuilder, @@ -401,12 +400,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "modify" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { timedOut = true; continue; diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index c3cac04..f10cf67 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -8,11 +8,8 @@ import { ActionRowBuilder, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, CommandInteraction, - Interaction, Message, - MessageComponentInteraction, ModalBuilder, - ModalSubmitInteraction, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, @@ -317,12 +314,7 @@ const wordMenu = async (interaction: StringSelectMenuInteraction, m: Message, cu await i.showModal(modal); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { break; } @@ -612,12 +604,7 @@ const mentionMenu = async (interaction: StringSelectMenuInteraction, m: Message, await i.showModal(modal); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === interaction.channelId, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { break; } @@ -912,7 +899,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } } - } while(!closed) + } while(!closed); + await interaction.deleteReply() }; diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index 6f825bc..545e3ff 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -1,195 +1,105 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; +import { getCommandMentionByName } from "../../../utils/getCommandDataByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("attachments") .setDescription("Where attachments should be logged to (Premium only)") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to log attachments in") - .addChannelTypes(ChannelType.GuildText) - .setRequired(false) - ); const callback = async (interaction: CommandInteraction): Promise => { - const m = (await interaction.reply({ + await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Attachment Log Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild!.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") - .setTitle("Attachment Log Channel") + }) + + if(!client.database.premium.hasPremium(interaction.guild!.id)) return interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Premium Required") + .setDescription(`This feature is exclusive to ${getCommandMentionByName("nucleus/premium")} servers.`) + .setStatus("Danger") + .setEmoji("NUCLEUS.PREMIUM") + ] + }); + + let data = await client.database.guilds.read(interaction.guild!.id); + let channel = data.logging.staff.channel; + + let closed = false; + do { + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + ); + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("clear") + .setLabel("Clear") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(!channel), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(channel === data.logging.staff.channel) + ); + + const embed = new EmojiEmbed() + .setTitle("Attachments") .setDescription( - "This will be the channel all attachments will be sent to.\n\n" + - `Are you sure you want to set the attachment log channel to <#${channel.id}>?` + `The channel to send all attachments from the server, allowing you to check them if they are deleted` + + `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n` ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild!.id, { - "logging.attachments.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "attachmentChannelUpdate", - displayName: "Attachment Log Channel Updated", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: interaction.guild!.id - } - }; - log(data); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("Something went wrong and the attachment log channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild!.id); - let channel = data.logging.staff.channel; + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") - let timedOut = false; - while (!timedOut) { await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription( - channel - ? `Your attachment log channel is currently set to <#${channel}>` - : "This server does not have an attachment log channel" + - (await client.database.premium.hasPremium(interaction.guild!.id) - ? "" - : "\n\nThis server does not have premium, so this feature is disabled") - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] + embeds: [embed], + components: [channelMenu, buttons] }); - let i; + + let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; 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 } - }); + i = (await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } await i.deferUpdate(); - if ((i.component as unknown as ButtonInteraction).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild!.id, null, ["logging.announcements.channel"]); - channel = null; + if(i.isButton()) { + switch (i.customId) { + case "clear": { + channel = null; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, { + "logging.attachments.channel": channel + }); + data = await client.database.guilds.read(interaction.guild!.id); + break; + } } + } else { + channel = i.values[0]!; } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Attachment Log Channel") - .setDescription( - channel - ? `Your attachment log channel is currently set to <#${channel}>` - : "This server does not have an attachment log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index f2ec13a..a1f24fa 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -1,9 +1,11 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, EmbedBuilder } from "discord.js"; -import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; -import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ChannelSelectMenuBuilder, ChannelType, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ButtonInteraction, StringSelectMenuInteraction, ChannelSelectMenuInteraction, APIMessageComponentEmoji } from "discord.js"; +import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; +import compare from "lodash"; import { toHexArray, toHexInteger } from "../../../utils/calculate.js"; +import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; +import getEmojiByName from "../../../utils/getEmojiByName.js"; const logs: Record = { channelUpdate: "Channels created, deleted or modified", @@ -24,85 +26,135 @@ const logs: Record = { webhookUpdate: "Webhooks created or deleted", guildMemberVerify: "Member runs verify", autoModeratorDeleted: "Messages auto deleted by Nucleus", - nucleusSettingsUpdated: "Nucleus' settings updated by a moderator", - ticketUpdate: "Tickets created or deleted" + ticketUpdate: "Tickets created or deleted", + //nucleusSettingsUpdated: "Nucleus' settings updated by a moderator" // TODO }; const command = (builder: SlashCommandSubcommandBuilder) => - builder.setName("events").setDescription("Sets what events should be logged"); + builder + .setName("events") + .setDescription("The general log channel for the server, and setting what events to show") const callback = async (interaction: CommandInteraction): Promise => { - await interaction.reply({ + const m = (await interaction.reply({ embeds: LoadingEmbed, - fetchReply: true, - ephemeral: true - }); - let m: Message; - let timedOut = false; + ephemeral: true, + fetchReply: true + })) as Discord.Message; + + let config = await client.database.guilds.read(interaction.guild!.id); + let data = Object.assign({}, config.logging.logs); + let closed = false; + let show = false; do { - const config = await client.database.guilds.read(interaction.guild!.id); - const converted = toHexArray(config.logging.logs.toLog); - const selectPane = new StringSelectMenuBuilder() - .setPlaceholder("Set events to log") - .setMaxValues(Object.keys(logs).length) - .setCustomId("logs") - .setMinValues(0) - Object.keys(logs).map((e, i) => { - selectPane.addOptions(new StringSelectMenuOptionBuilder() - .setLabel(logs[e]!) - .setValue(i.toString()) - .setDefault(converted.includes(e)) + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + .setChannelTypes(ChannelType.GuildText) + ) + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Enabled" : "Disabled") + .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName((data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS"), "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("remove") + .setLabel("Remove") + .setStyle(ButtonStyle.Danger) + .setDisabled(!data.channel), + new ButtonBuilder() + .setCustomId("show") + .setLabel("Manage Events") + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setDisabled(compare.isEqual(data, config.logging.logs)) + ) + + const converted = toHexArray(data.toLog); + const toLogMenu = new ActionRowBuilder() + .addComponents( + new StringSelectMenuBuilder() + .setPlaceholder("Set events to log") + .setMaxValues(Object.keys(logs).length) + .setCustomId("logs") + .setMinValues(0) + ) + Object.keys(logs).map((e) => { + toLogMenu.components[0]!.addOptions( + new StringSelectMenuOptionBuilder() + .setLabel(logs[e]!) + .setValue(e) + .setDefault(converted.includes(e)) ) }); - m = (await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Logging Events") - .setDescription( - "Below are the events being logged in the server. You can toggle them on and off in the dropdown" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents(selectPane), - new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("Select all").setStyle(ButtonStyle.Primary).setCustomId("all"), - new ButtonBuilder().setLabel("Select none").setStyle(ButtonStyle.Danger).setCustomId("none") - ]) - ] - })) as Message; - let i; + + const embed = new EmojiEmbed() + .setTitle("General Log Channel") + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") + .setDescription( + `This is the channel that all events you set to be logged will be stored\n` + + `**Channel:** ${data.channel ? `<#${data.channel}>` : "None"}\n` + ) + + let components: ActionRowBuilder[] = [channelMenu, buttons]; + if(show) components.push(toLogMenu); + + await interaction.editReply({ + embeds: [embed], + components: components + }); + + let i: ButtonInteraction | StringSelectMenuInteraction | 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 } - }); + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + }) as ButtonInteraction | StringSelectMenuInteraction | ChannelSelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } + await i.deferUpdate(); - 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 - }); - } else if (i.customId === "all") { - const newLogs = toHexInteger(Object.keys(logs).map((e) => e)); - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.toLog": newLogs - }); - } else if (i.customId === "none") { - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.toLog": 0 - }); + + if(i.isButton()) { + switch(i.customId) { + case "show": { + show = !show; + break; + } + case "switch": { + data.enabled = !data.enabled; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, {"logging.logs": data}); + config = await client.database.guilds.read(interaction.guild!.id); + data = Object.assign({}, config.logging.logs); + break; + } + case "remove": { + data.channel = null; + break; + } + } + } else if(i.isStringSelectMenu()) { + let hex = toHexInteger(i.values); + data.toLog = hex; + } else if(i.isChannelSelectMenu()) { + data.channel = i.values[0]!; } - } while (!timedOut); - await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })] }); - return; + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/logs/general.ts b/src/commands/settings/logs/general.ts deleted file mode 100644 index 9861d26..0000000 --- a/src/commands/settings/logs/general.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction, ButtonComponent } from "discord.js"; -import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; -import getEmojiByName from "../../../utils/getEmojiByName.js"; -import type { SlashCommandSubcommandBuilder } from "discord.js"; -import client from "../../../utils/client.js"; - -const command = (builder: SlashCommandSubcommandBuilder) => - builder - .setName("general") - .setDescription("Sets or shows the log channel") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to set the log channel to") - .addChannelTypes(ChannelType.GuildText) - ); - -const callback = async (interaction: CommandInteraction): Promise => { - const m = (await interaction.reply({ - embeds: LoadingEmbed, - ephemeral: true, - fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Log Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild!.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") - .setTitle("Log Channel") - .setDescription(`Are you sure you want to set the log channel to <#${channel.id}>?`) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild!.id, { - "logging.logs.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "logChannelUpdate", - displayName: "Log Channel Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: channel.guild.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("Something went wrong and the log channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild!.id); - let channel = data.logging.logs.channel; - let timedOut = false; - while (!timedOut) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log channel") - .setDescription( - channel - ? `Your log channel is currently set to <#${channel}>` - : "This server does not have a log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] - }); - let i: ButtonInteraction; - 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; - } catch (e) { - timedOut = true; - } - i = i! - await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild!.id, null, ["logging.logs.channel"]); - channel = null; - } - } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Log channel") - .setDescription( - channel - ? `Your log channel is currently set to <#${channel}>` - : "This server does not have a log channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); -}; - -const check = (interaction: CommandInteraction, _partial: boolean = false) => { - const member = interaction.member as Discord.GuildMember; - if (!member.permissions.has("ManageGuild")) - return "You must have the *Manage Server* permission to use this command"; - return true; -}; - -export { command }; -export { callback }; -export { check }; diff --git a/src/commands/settings/logs/warnings.ts b/src/commands/settings/logs/warnings.ts index 3855d34..6e5482c 100644 --- a/src/commands/settings/logs/warnings.ts +++ b/src/commands/settings/logs/warnings.ts @@ -1,8 +1,6 @@ import { LoadingEmbed } from "../../../utils/defaults.js"; -import { ChannelType } from "discord-api-types/v9"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../../utils/confirmationMessage.js"; import getEmojiByName from "../../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../../utils/client.js"; @@ -11,182 +9,86 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("warnings") .setDescription("Settings for the staff notifications channel") - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel to set the staff notifications channel to") - .addChannelTypes(ChannelType.GuildText) - .setRequired(false) - ); const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; - const m = (await interaction.reply({ + await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true - })) as Discord.Message; - if (interaction.options.get("channel")?.channel) { - let channel; - try { - channel = interaction.options.get("channel")?.channel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Staff Notifications Channel") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.TextChannel; - if (channel.guild.id !== interaction.guild.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("You must choose a channel in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("CHANNEL.TEXT.EDIT") + }) + + let data = await client.database.guilds.read(interaction.guild.id); + let channel = data.logging.staff.channel; + let closed = false; + do { + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel") + ); + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("clear") + .setLabel("Clear") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(!channel), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as Discord.APIMessageComponentEmoji) + .setDisabled(channel === data.logging.staff.channel) + ); + + const embed = new EmojiEmbed() .setTitle("Staff Notifications Channel") + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE") .setDescription( - "This will be the channel all notifications, updates, user reports etc. will be sent to.\n\n" + - `Are you sure you want to set the staff notifications channel to <#${channel.id}>?` + `Logs which require an action from a moderator or administrator will be sent to this channel.\n` + + `**Channel:** ${channel ? `<#${channel}>` : "*None*"}\n` ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "CHANNEL.TEXT.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild.id, { - "logging.staff.channel": channel.id - }); - const { log, NucleusColors, entry, renderUser, renderChannel } = client.logger; - const data = { - meta: { - type: "staffChannelUpdate", - displayName: "Staff Notifications Channel Updated", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.yellow, - emoji: "CHANNEL.TEXT.EDIT", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - channel: entry(channel.id, renderChannel(channel)) - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("Something went wrong and the staff notifications channel could not be set") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications Channel") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild.id); - let channel = data.logging.staff.channel; - let timedOut = false; - while (!timedOut) { + await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications channel") - .setDescription( - channel - ? `Your staff notifications channel is currently set to <#${channel}>` - : "This server does not have a staff notifications channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset channel") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!channel) - ]) - ] + embeds: [embed], + components: [channelMenu, buttons] }); - let i; + + let i: Discord.ButtonInteraction | Discord.SelectMenuInteraction; 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 } - }); + i = (await interaction.channel!.awaitMessageComponent({ + filter: (i) => i.user.id === interaction.user.id, + time: 300000 + })) as Discord.ButtonInteraction | Discord.SelectMenuInteraction; } catch (e) { - timedOut = true; - continue; + closed = true; + break; } await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild.id, null, ["logging.staff.channel"]); - channel = null; + if(i.isButton()) { + switch (i.customId) { + case "clear": { + channel = null; + break; + } + case "save": { + await client.database.guilds.write(interaction.guild!.id, { + "logging.warnings.channel": channel + }); + data = await client.database.guilds.read(interaction.guild!.id); + break; + } } + } else { + channel = i.values[0]!; } - } - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Staff Notifications channel") - .setDescription( - channel - ? `Your staff notifications channel is currently set to <#${channel}>` - : "This server does not have a staff notifications channel" - ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - .setFooter({ text: "Message closed" }) - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel("Clear") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Secondary) - .setDisabled(true) - ]) - ] - }); + } while (!closed); + + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/moderation.ts b/src/commands/settings/moderation.ts index ffd3063..c7f0dd0 100644 --- a/src/commands/settings/moderation.ts +++ b/src/commands/settings/moderation.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, Message, RoleSelectMenuBuilder } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, RoleSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; @@ -12,17 +12,16 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Links and text shown to a user after a moderator action is performed") const callback = async (interaction: CommandInteraction): Promise => { - await interaction.reply({ + const m = await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); - let m: Message; let timedOut = false; while (!timedOut) { const config = await client.database.guilds.read(interaction.guild!.id); const moderation = config.moderation; - m = await interaction.editReply({ + await interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Moderation Commands") @@ -152,11 +151,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { continue; } @@ -175,6 +170,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 02752c0..cccb6f6 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -144,11 +144,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { console.error(e); out = null; @@ -472,7 +468,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } } - } while (!closed) + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f8a57b7..d46b57e 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -74,7 +74,6 @@ const showModal = async (interaction: MessageComponentInteraction, current: { en type ObjectSchema = Record - const addStatsChannel = async (interaction: CommandInteraction, m: Message, currentObject: ObjectSchema): Promise => { let closed = false; let cancelled = false; @@ -172,11 +171,7 @@ const addStatsChannel = async (interaction: CommandInteraction, m: Message, curr }); showModal(i, {name: newChannelName, enabled: newChannelEnabled}) - const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id && m.user!.id === interaction.user!.id, - (i) => i.channel!.id === interaction.channel!.id && i.user!.id === interaction.user!.id && i.message!.id === m.id - ); + const out: Discord.ModalSubmitInteraction | ButtonInteraction| null = await modalInteractionCollector(m, interaction.user); if (!out) continue; if (out.isButton()) continue; newChannelName = out.fields.getTextInputValue("text"); @@ -340,11 +335,7 @@ const callback = async (interaction: CommandInteraction) => { }); let out: Discord.ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as Discord.ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { continue; } @@ -396,6 +387,7 @@ const callback = async (interaction: CommandInteraction) => { } } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 3d718dc..af74475 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -181,11 +181,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }); let out; try { - out = await modalInteractionCollector( - m, - (m) => m.user.id === interaction.user.id, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { continue; } @@ -207,6 +203,7 @@ const callback = async (interaction: CommandInteraction): Promise => { } } } + await interaction.deleteReply() }; @@ -384,11 +381,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t }); let out; try { - out = await modalInteractionCollector( - m, - (m) => m.user.id === interaction.user.id, - (m) => m.customId === "back" - ); + out = await modalInteractionCollector(m, interaction.user); } catch (e) { continue; } diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index a353324..612d069 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -63,11 +63,7 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter let out: ModalSubmitInteraction | null; try { - out = await modalInteractionCollector( - m, - (m) => m.channel!.id === interaction.channel!.id, - (_) => true - ) as ModalSubmitInteraction | null; + out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null; } catch (e) { console.error(e); out = null; @@ -440,7 +436,8 @@ const callback = async (interaction: CommandInteraction) => { } } - } while (!closed) + } while (!closed); + await interaction.deleteReply() } const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 1d695f8..920beb4 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -1,33 +1,25 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { CommandInteraction, - Interaction, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, - ModalSubmitInteraction, - Role, ButtonStyle, - StringSelectMenuBuilder, - TextInputBuilder, - EmbedBuilder, - ButtonComponent + RoleSelectMenuBuilder, + APIMessageComponentEmoji } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; -import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import { getCommandMentionByName } from "../../utils/getCommandDataByName.js"; + const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("verify") - .setDescription("Manage the role given after typing /verify") - .addRoleOption((option) => - option.setName("role").setDescription("The role to give after verifying").setRequired(false) - ); + .setDescription("Manage the role given after a user runs /verify") + const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; @@ -36,153 +28,79 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true, fetchReply: true })) as Message; - if (interaction.options.get("role")?.role) { - let role: Role; - try { - role = interaction.options.get("role")?.role as Role; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Verify Role") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - role = role as Discord.Role; - if (role.guild.id !== interaction.guild.id) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("You must choose a role in this server") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ] - }); - } - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") + + let closed = false; + let config = await client.database.guilds.read(interaction.guild.id); + let data = Object.assign({}, config.verify); + do { + console.log(config.verify, data) + const selectMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("role") + .setPlaceholder("Select a role") + ); + + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Disabled" : "Enabled") + .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success) + .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setDisabled(data.role === config.verify.role && data.enabled === config.verify.enabled) + ); + + const embed = new EmojiEmbed() .setTitle("Verify Role") - .setDescription(`Are you sure you want to set the verify role to <@&${role.id}>?`) - .setColor("Warning") - .setFailedMessage("No changes were made", "Warning", "GUILD.ROLES.DELETE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - await client.database.guilds.write(interaction.guild.id, { - "verify.role": role.id, - "verify.enabled": true - }); - const { log, NucleusColors, entry, renderUser, renderRole } = client.logger; - const data = { - meta: { - type: "verifyRoleChanged", - displayName: "Verify Role Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.green, - emoji: "CONTROL.BLOCKTICK", - timestamp: new Date().getTime() - }, - list: { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)), - role: entry(role.id, renderRole(role)) - }, - hidden: { - guild: interaction.guild.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("Something went wrong while setting the verify role") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [] - }); - } - } - let clicks = 0; - const data = await client.database.guilds.read(interaction.guild.id); - let role = data.verify.role; + .setDescription( + `Select a role to be given to users after they run ${getCommandMentionByName("verify")}` + + `\n\nCurrent role: ${config.verify.role ? `<@&${config.verify.role}>` : "None"}` + ) + .setStatus("Success") + .setEmoji("CHANNEL.TEXT.CREATE"); - let timedOut = false; - while (!timedOut) { await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Role") - .setDescription( - role ? `Your verify role is currently set to <@&${role}>` : "You have not set a verify role" - ) - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("clear") - .setLabel(clicks ? "Click again to confirm" : "Reset role") - .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setDisabled(!role), - new ButtonBuilder() - .setCustomId("send") - .setLabel("Add verify button") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] + embeds: [embed], + components: [selectMenu, buttons] }); - let i: MessageComponentInteraction; + + let i; 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 } + filter: (i) => { return i.user.id === interaction.user.id } }); } catch (e) { - timedOut = true; + closed = true; continue; } + await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clear") { - clicks ++; - if (clicks === 2) { - clicks = 0; - await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]); - role = null; + + if(i.isButton()) { + switch (i.customId) { + case "save": { + client.database.guilds.write(interaction.guild.id, {"verify": data} ) + config = await client.database.guilds.read(interaction.guild.id); + break + } + case "switch": { + data.enabled = !data.enabled; + break + } } } else { - await i.deferUpdate(); - break; + data.role = i.values[0]!; } - } // TODO: On save, clear SEN - await interaction.editReply({ - embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })], - components: [] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts index b9c1b9f..ae55fc0 100644 --- a/src/commands/settings/welcome.ts +++ b/src/commands/settings/welcome.ts @@ -1,304 +1,260 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import Discord, { - Channel, CommandInteraction, - Message, + AutocompleteInteraction, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, - Role, ButtonStyle, - AutocompleteInteraction, - GuildChannel, - EmbedBuilder + APIMessageComponentEmoji, + ChannelSelectMenuBuilder, + RoleSelectMenuBuilder, + RoleSelectMenuInteraction, + ChannelSelectMenuInteraction, + ButtonInteraction, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + ModalSubmitInteraction, } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; -import generateKeyValueList from "../../utils/generateKeyValueList.js"; -import { ChannelType } from "discord-api-types/v9"; import getEmojiByName from "../../utils/getEmojiByName.js"; +import convertCurlyBracketString from "../../utils/convertCurlyBracketString.js"; +import { modalInteractionCollector } from "../../utils/dualCollector.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("welcome") .setDescription("Messages and roles sent or given when someone joins the server") - .addStringOption((option) => - option - .setName("message") - .setDescription("The message to send when someone joins the server") - .setAutocomplete(true) - ) - .addRoleOption((option) => - option.setName("role").setDescription("The role given when someone joins the server") - ) - .addRoleOption((option) => - option.setName("ping").setDescription("The role pinged when someone joins the server") - ) - .addChannelOption((option) => - option - .setName("channel") - .setDescription("The channel the welcome message should be sent to") - .addChannelTypes(ChannelType.GuildText) - ); -const callback = async (interaction: CommandInteraction): Promise => { - const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger; - await interaction.reply({ +const callback = async (interaction: CommandInteraction): Promise => { + const { renderChannel } = client.logger; + const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); - let m: Message; - if ( - interaction.options.get("role")?.role || - interaction.options.get("channel")?.channel || - interaction.options.get("message")?.value as string - ) { - let role: Role | null; - let ping: Role | null; - let channel: Channel | null; - const message: string | null = interaction.options.get("message")?.value as string | null; - try { - role = interaction.options.get("role")?.role as Role | null; - ping = interaction.options.get("ping")?.role as Role | null; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Welcome Events") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - try { - channel = interaction.options.get("channel")?.channel as Channel | null; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Welcome Events") - .setDescription("The channel you provided is not a valid channel") - .setStatus("Danger") - ] - }); - } - const options: { - role?: string; - ping?: string; - channel?: string; - message?: string; - } = {}; - - if (role) options.role = renderRole(role); - if (ping) options.ping = renderRole(ping); - if (channel) options.channel = renderChannel(channel as GuildChannel); - if (message) options.message = "\n> " + message; - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.EDIT") - .setTitle("Welcome Events") - .setDescription(generateKeyValueList(options)) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "GUILD.ROLES.CREATE") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - try { - const toChange: { - "welcome.role"?: string; - "welcome.ping"?: string; - "welcome.channel"?: string; - "welcome.message"?: string; - } = {}; - if (role) toChange["welcome.role"] = role.id; - if (ping) toChange["welcome.ping"] = ping.id; - if (channel) toChange["welcome.channel"] = channel.id; - if (message) toChange["welcome.message"] = message; - await client.database.guilds.write(interaction.guild!.id, toChange); - const list: { - memberId: ReturnType; - changedBy: ReturnType; - role?: ReturnType; - ping?: ReturnType; - channel?: ReturnType; - message?: ReturnType; - } = { - memberId: entry(interaction.user.id, `\`${interaction.user.id}\``), - changedBy: entry(interaction.user.id, renderUser(interaction.user)) - }; - if (role) list.role = entry(role.id, renderRole(role)); - if (ping) list.ping = entry(ping.id, renderRole(ping)); - if (channel) list.channel = entry(channel.id, renderChannel(channel as GuildChannel)); - if (message) list.message = entry(message, `\`${message}\``); - const data = { - meta: { - type: "welcomeSettingsUpdated", - displayName: "Welcome Settings Changed", - calculateType: "nucleusSettingsUpdated", - color: NucleusColors.green, - emoji: "CONTROL.BLOCKTICK", - timestamp: new Date().getTime() - }, - list: list, - hidden: { - guild: interaction.guild!.id - } - }; - log(data); - } catch (e) { - console.log(e); - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription("Something went wrong while updating welcome settings") - .setStatus("Danger") - .setEmoji("GUILD.ROLES.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [] - }); - } - } - let lastClicked = null; - let timedOut = false; + let closed = false; + let config = await client.database.guilds.read(interaction.guild!.id); + let data = Object.assign({}, config.welcome); do { - const config = await client.database.guilds.read(interaction.guild!.id); - m = (await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Welcome Events") - .setDescription( - `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` + - `**Role:** ${ - config.welcome.role - ? renderRole((await interaction.guild!.roles.fetch(config.welcome.role))!) - : "*None set*" - }\n` + - `**Ping:** ${ - config.welcome.ping - ? renderRole((await interaction.guild!.roles.fetch(config.welcome.ping))!) - : "*None set*" - }\n` + - `**Channel:** ${ - config.welcome.channel - ? config.welcome.channel === "dm" - ? "DM" - : renderChannel((await interaction.guild!.channels.fetch(config.welcome.channel))!) - : "*None set*" - }` + const buttons = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("switch") + .setLabel(data.enabled ? "Enabled" : "Disabled") + .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setEmoji(getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("message") + .setLabel((data.message ? "Change" : "Set") + "Message") + .setStyle(ButtonStyle.Primary) + .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("channelDM") + .setLabel("Send in DMs") + .setStyle(ButtonStyle.Primary) + .setDisabled(data.channel === "dm"), + new ButtonBuilder() + .setCustomId("role") + .setLabel("Clear Role") + .setStyle(ButtonStyle.Danger) + .setEmoji(getEmojiByName("CONTROL.CROSS", "id") as APIMessageComponentEmoji), + new ButtonBuilder() + .setCustomId("save") + .setLabel("Save") + .setStyle(ButtonStyle.Success) + .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji) + .setDisabled( + data.enabled === config.welcome.enabled && + data.message === config.welcome.message && + data.role === config.welcome.role && + data.ping === config.welcome.ping && + data.channel === config.welcome.channel ) - .setStatus("Success") - .setEmoji("CHANNEL.TEXT.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel(lastClicked === "clear-message" ? "Click again to confirm" : "Clear Message") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-message") - .setDisabled(!config.welcome.message) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-role" ? "Click again to confirm" : "Clear Role") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-role") - .setDisabled(!config.welcome.role) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-ping" ? "Click again to confirm" : "Clear Ping") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-ping") - .setDisabled(!config.welcome.ping) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel(lastClicked === "clear-channel" ? "Click again to confirm" : "Clear Channel") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clear-channel") - .setDisabled(!config.welcome.channel) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder() - .setLabel("Set Channel to DM") - .setCustomId("set-channel-dm") - .setDisabled(config.welcome.channel === "dm") - .setStyle(ButtonStyle.Secondary) - ]) - ] - })) as Message; - let i: MessageComponentInteraction; + ); + + const channelMenu = new ActionRowBuilder() + .addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("channel") + .setPlaceholder("Select a channel to send welcome messages to") + ); + const roleMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("roleToGive") + .setPlaceholder("Select a role to give to the member when they join the server") + ); + const pingMenu = new ActionRowBuilder() + .addComponents( + new RoleSelectMenuBuilder() + .setCustomId("roleToPing") + .setPlaceholder("Select a role to ping when a member joins the server") + ); + + const embed = new EmojiEmbed() + .setTitle("Welcome Settings") + .setStatus("Success") + .setDescription( + `${getEmojiByName(data.enabled ? "CONTROL.TICK" : "CONTROL.CROSS")} Welcome messages and roles are ${data.enabled ? "enabled" : "disabled"}\n` + + `**Welcome message:** ${data.message ? + `\n> ` + + await convertCurlyBracketString( + data.message, + interaction.user.id, + interaction.user.username, + interaction.guild!.name, + interaction.guild!.members + ) + : "*None*"}\n` + + `**Send message in:** ` + (data.channel ? (data.channel == "dm" ? "DMs" : renderChannel(data.channel)) : `*None set*`) + `\n` + + `**Role to ping:** ` + (data.ping ? `<@&${data.ping}>` : `*None set*`) + `\n` + + `**Role given on join:** ` + (data.role ? `<@&${data.role}>` : `*None set*`) + ) + + await interaction.editReply({ + embeds: [embed], + components: [buttons, channelMenu, roleMenu, pingMenu] + }); + + let i: RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; 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 } - }); + filter: (interaction) => interaction.user.id === interaction.user.id, + time: 300000 + }) as RoleSelectMenuInteraction | ChannelSelectMenuInteraction | ButtonInteraction; } catch (e) { - timedOut = true; + closed = true; continue; } - await i.deferUpdate(); - if (i.customId === "clear-message") { - if (lastClicked === "clear-message") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.message": null - }); - lastClicked = null; - } else { - lastClicked = "clear-message"; - } - } else if (i.customId === "clear-role") { - if (lastClicked === "clear-role") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.role": null - }); - lastClicked = null; - } else { - lastClicked = "clear-role"; - } - } else if (i.customId === "clear-ping") { - if (lastClicked === "clear-ping") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.ping": null - }); - lastClicked = null; - } else { - lastClicked = "clear-ping"; + + if(i.isButton()) { + switch(i.customId) { + case "switch": { + await i.deferUpdate(); + data.enabled = !data.enabled; + break; + } + case "message": { + const modal = new ModalBuilder() + .setCustomId("modal") + .setTitle("Welcome Message") + .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(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(TextInputStyle.Paragraph) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ex3") + .setLabel("Member who joined (3/3) - {member:...}") + .setPlaceholder( + `{:name} - The members name\n` + ) + .setMaxLength(1) + .setRequired(false) + .setStyle(TextInputStyle.Paragraph) + ), + new ActionRowBuilder() + .addComponents( + new TextInputBuilder() + .setCustomId("message") + .setPlaceholder("Enter a message to send when someone joins the server") + .setValue(data.message ?? "") + .setLabel("Message") + .setStyle(TextInputStyle.Paragraph) + ) + ) + const button = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("back") + .setLabel("Back") + .setStyle(ButtonStyle.Secondary) + .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) + ) + await i.showModal(modal) + await i.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Welcome Settings") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + ], + components: [button] + }); + + let out: ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector(m, interaction.user) as ModalSubmitInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) break; + data.message = out.fields.getTextInputValue("message") ?? null; + break; + } + case "save": { + await i.deferUpdate(); + await client.database.guilds.write(interaction.guild!.id, {"welcome": data}); + config = await client.database.guilds.read(interaction.guild!.id); + data = Object.assign({}, config.welcome); + break; + } + case "channelDM": { + await i.deferUpdate(); + data.channel = "dm"; + break; + } + case "role": { + await i.deferUpdate(); + data.role = null; + break; + } } - } else if (i.customId === "clear-channel") { - if (lastClicked === "clear-channel") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.channel": null - }); - lastClicked = null; - } else { - lastClicked = "clear-channel"; + } else if (i.isRoleSelectMenu()) { + await i.deferUpdate(); + switch(i.customId) { + case "roleToGive": { + data.role = i.values[0]!; + break + } + case "roleToPing": { + data.ping = i.values[0]!; + break + } } - } else if (i.customId === "set-channel-dm") { - await client.database.guilds.write(interaction.guild!.id, { - "welcome.channel": "dm" - }); - lastClicked = null; + } else { + await i.deferUpdate(); + data.channel = i.values[0]!; } - } while (!timedOut); - await interaction.editReply({ - embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message timed out" })], - components: [] - }); + + } while (!closed); + await interaction.deleteReply() }; const check = (interaction: CommandInteraction, _partial: boolean = false) => { @@ -341,4 +297,4 @@ const autocomplete = async (interaction: AutocompleteInteraction): Promise` : "") + `<@${member.id}>` + content: (config.welcome.ping ? `<@&${config.welcome.ping}>` : "") + `<@${member.id}>` }); } catch (err) { singleNotify( diff --git a/src/utils/calculate.ts b/src/utils/calculate.ts index 0bd5a9f..fde1340 100644 --- a/src/utils/calculate.ts +++ b/src/utils/calculate.ts @@ -17,16 +17,14 @@ const logs = [ "webhookUpdate", "guildMemberVerify", "autoModeratorDeleted", - "nucleusSettingsUpdated", - "ticketUpdate" + "ticketUpdate", + // "nucleusSettingsUpdated" ]; const tickets = ["support", "report", "question", "issue", "suggestion", "other"]; const toHexInteger = (permissions: string[], array?: string[]): string => { - if (!array) { - array = logs; - } + if (!array) { array = logs; } let int = 0n; for (const perm of permissions) { diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 50c8e78..6805019 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -56,7 +56,7 @@ async function registerCommands() { } } - console.log(`${colours.green}Processed ${processed.length} commands`) + console.log(`${colours.green}Processed ${processed.length} commands${colours.none}`) return processed; }; diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 43a0c8f..f7cccaf 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -1,11 +1,9 @@ import { TextInputBuilder } from "discord.js"; import Discord, { CommandInteraction, - Interaction, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, ModalSubmitInteraction, ButtonStyle, TextInputStyle @@ -246,12 +244,7 @@ class confirmationMessage { }); let out; try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === this.interaction.channelId, - (m) => m.customId === "reason" - ); + out = await modalInteractionCollector(m, this.interaction.user) as Discord.ModalSubmitInteraction | null; } catch (e) { cancelled = true; continue; diff --git a/src/utils/dualCollector.ts b/src/utils/dualCollector.ts index f55716e..072f73f 100644 --- a/src/utils/dualCollector.ts +++ b/src/utils/dualCollector.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, Client, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; +import { ButtonInteraction, Client, User, Interaction, InteractionCollector, Message, MessageComponentInteraction, ModalSubmitInteraction } from "discord.js"; import client from "./client.js"; export default async function ( @@ -45,17 +45,25 @@ export default async function ( return out; } +function defaultInteractionFilter(i: MessageComponentInteraction, user: User, m: Message) { + return i.channel!.id === m.channel!.id && i.user.id === user.id +} +function defaultModalFilter(i: ModalSubmitInteraction, user: User, m: Message) { + return i.channel!.id === m.channel!.id && i.user.id === user.id +} + + export async function modalInteractionCollector( - m: Message, - modalFilter: (i: Interaction) => boolean | Promise, - interactionFilter: (i: MessageComponentInteraction) => boolean | Promise + m: Message, user: User, + modalFilter?: (i: Interaction) => boolean | Promise, + interactionFilter?: (i: MessageComponentInteraction) => boolean | Promise ): Promise { let out: ButtonInteraction | ModalSubmitInteraction; try { out = await new Promise((resolve, _reject) => { const int = m .createMessageComponentCollector({ - filter: (i: MessageComponentInteraction) => interactionFilter(i), + filter: (i: MessageComponentInteraction) => (interactionFilter ? interactionFilter(i) : true) && defaultInteractionFilter(i, user, m), time: 300000 }) .on("collect", async (i: ButtonInteraction) => { @@ -65,7 +73,7 @@ export async function modalInteractionCollector( resolve(i); }); const mod = new InteractionCollector(client as Client, { - filter: (i: Interaction) => modalFilter(i) && i.isModalSubmit(), + filter: (i: Interaction) => (modalFilter ? modalFilter(i) : true) && i.isModalSubmit() && defaultModalFilter(i, user, m), time: 300000 }).on("collect", async (i: ModalSubmitInteraction) => { int.stop(); @@ -75,6 +83,7 @@ export async function modalInteractionCollector( }); }); } catch (e) { + console.log(e); return null; } return out;