| import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, MessageCreateOptions, ModalBuilder, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; |
| import type Discord from "discord.js"; |
| import { LoadingEmbed } from "../../utils/defaults.js"; |
| import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; |
| import lodash from "lodash"; |
| import getEmojiByName from "../../utils/getEmojiByName.js"; |
| import { modalInteractionCollector } from "../../utils/dualCollector.js"; |
| |
| export const command = new SlashCommandSubcommandBuilder() |
| .setName("buttons") |
| .setDescription("Create clickable buttons for verifying, role menus etc."); |
| |
| interface Data { |
| buttons: string[], |
| title: string | null, |
| description: string | null, |
| color: number, |
| channel: string | null |
| } |
| |
| const colors: Record<string, number> = { |
| RED: 0xF27878, |
| ORANGE: 0xE5AB71, |
| YELLOW: 0xF2D478, |
| GREEN: 0x65CC76, |
| BLUE: 0x72AEF5, |
| PURPLE: 0xA358B2, |
| PINK: 0xD46899, |
| GRAY: 0x999999, |
| } |
| |
| const buttonNames: Record<string, string> = { |
| verifybutton: "Verify", |
| rolemenu: "Role Menu", |
| createticket: "Ticket" |
| } |
| |
| export const callback = async (interaction: CommandInteraction): Promise<void> => { |
| |
| const m = await interaction.reply({ |
| embeds: LoadingEmbed, |
| fetchReply: true, |
| ephemeral: true |
| }); |
| |
| let closed = false; |
| let data: Data = { |
| buttons: [], |
| title: null, |
| description: null, |
| color: colors["RED"]!, |
| channel: interaction.channelId |
| } |
| do { |
| |
| const buttons = new ActionRowBuilder<ButtonBuilder>() |
| .addComponents( |
| new ButtonBuilder() |
| .setCustomId("edit") |
| .setLabel("Edit Embed") |
| .setStyle(ButtonStyle.Secondary), |
| new ButtonBuilder() |
| .setCustomId("send") |
| .setLabel("Send") |
| .setStyle(ButtonStyle.Primary) |
| .setDisabled(!data.channel) |
| ); |
| |
| const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>() |
| .addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("color") |
| .setPlaceholder("Select a color") |
| .setMinValues(1) |
| .addOptions( |
| Object.keys(colors).map((color: string) => { |
| return new StringSelectMenuOptionBuilder() |
| .setLabel(lodash.capitalize(color)) |
| .setValue(color) |
| .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji) |
| .setDefault(data.color === colors[color]) |
| } |
| ) |
| ) |
| ); |
| |
| const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>() |
| .addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("button") |
| .setPlaceholder("Select buttons to add") |
| .setMinValues(1) |
| .setMaxValues(3) |
| .addOptions( |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Verify") |
| .setValue("verifybutton") |
| .setDescription("Click to get verified in the server") |
| .setDefault(data.buttons.includes("verifybutton")), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Role Menu") |
| .setValue("rolemenu") |
| .setDescription("Click to customize your roles") |
| .setDefault(data.buttons.includes("rolemenu")), |
| new StringSelectMenuOptionBuilder() |
| .setLabel("Ticket") |
| .setValue("createticket") |
| .setDescription("Click to create a support ticket") |
| .setDefault(data.buttons.includes("createticket")) |
| ) |
| ) |
| |
| const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>() |
| .addComponents( |
| new ChannelSelectMenuBuilder() |
| .setCustomId("channel") |
| .setPlaceholder("Select a channel") |
| .setChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.PublicThread, ChannelType.AnnouncementThread) |
| ) |
| let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name; |
| if (data.channel === interaction.channelId) channelName = "this channel"; |
| const embed = new EmojiEmbed() |
| .setTitle(data.title ?? "No title set") |
| .setDescription(data.description ?? "*No description set*") |
| .setColor(data.color) |
| .setFooter({text: `Click the button below to edit the embed | The embed will be sent in ${channelName}`}); |
| |
| |
| await interaction.editReply({ |
| embeds: [embed], |
| components: [colorSelect, buttonSelect, channelMenu, buttons] |
| }); |
| |
| let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; |
| try { |
| i = await interaction.channel!.awaitMessageComponent({ |
| filter: (i) => i.user.id === interaction.user.id, |
| time: 300000 |
| }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; |
| } catch (e) { |
| closed = true; |
| break; |
| } |
| if(i.isButton()) { |
| switch(i.customId) { |
| case "edit": { |
| await i.showModal( |
| new ModalBuilder() |
| .setCustomId("modal") |
| .setTitle(`Options for ${i.customId}`) |
| .addComponents( |
| new ActionRowBuilder<TextInputBuilder>().addComponents( |
| new TextInputBuilder() |
| .setCustomId("title") |
| .setLabel("Title") |
| .setMaxLength(256) |
| .setRequired(false) |
| .setStyle(TextInputStyle.Short) |
| .setValue(data.title ?? "") |
| ), |
| new ActionRowBuilder<TextInputBuilder>().addComponents( |
| new TextInputBuilder() |
| .setCustomId("description") |
| .setLabel("The text to display below the title") |
| .setMaxLength(4000) |
| .setRequired(false) |
| .setStyle(TextInputStyle.Paragraph) |
| .setValue(data.description ?? "") |
| ) |
| ) |
| ); |
| await interaction.editReply({ |
| embeds: [ |
| new EmojiEmbed() |
| .setTitle("Button Editor") |
| .setDescription("Modal opened. If you can't see it, click back and try again.") |
| .setStatus("Success") |
| .setEmoji("GUILD.TICKET.OPEN") |
| ], |
| components: [ |
| new ActionRowBuilder<ButtonBuilder>().addComponents([ |
| new ButtonBuilder() |
| .setLabel("Back") |
| .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) |
| .setStyle(ButtonStyle.Primary) |
| .setCustomId("back") |
| ]) |
| ] |
| }); |
| let out: Discord.ModalSubmitInteraction | null; |
| try { |
| out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null; |
| } catch (e) { |
| closed = true; |
| continue; |
| } |
| if (!out || out.isButton()) continue |
| data.title = out.fields.getTextInputValue("title"); |
| data.description = out.fields.getTextInputValue("description"); |
| break; |
| } |
| case "send": { |
| await i.deferUpdate(); |
| let channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel; |
| let components = new ActionRowBuilder<ButtonBuilder>(); |
| for(let button of data.buttons) { |
| components.addComponents( |
| new ButtonBuilder() |
| .setCustomId(button) |
| .setLabel(buttonNames[button]!) |
| .setStyle(ButtonStyle.Primary) |
| ); |
| } |
| let messageData: MessageCreateOptions = {components: [components]} |
| if (data.title || data.description) { |
| let e = new EmojiEmbed() |
| if(data.title) e.setTitle(data.title); |
| if(data.description) e.setDescription(data.description); |
| if(data.color) e.setColor(data.color); |
| messageData.embeds = [e]; |
| } |
| await channel.send(messageData); |
| break; |
| } |
| } |
| } else if(i.isStringSelectMenu()) { |
| await i.deferUpdate(); |
| switch(i.customId) { |
| case "color": { |
| data.color = colors[i.values[0]!]!; |
| break; |
| } |
| case "button": { |
| data.buttons = i.values; |
| break; |
| } |
| } |
| } else { |
| await i.deferUpdate(); |
| data.channel = i.values[0]!; |
| } |
| |
| } while (!closed); |
| await interaction.deleteReply(); |
| } |
| |
| export const check = (interaction: CommandInteraction, _partial: boolean = false) => { |
| const member = interaction.member as Discord.GuildMember; |
| if (!member.permissions.has("ManageMessages")) |
| return "You must have the *Manage Messages* permission to use this command"; |
| return true; |
| }; |