| 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"; |
| import _ from "lodash"; |
| |
| 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: "Create Ticket" |
| }; |
| |
| const presetButtons = [ |
| { |
| title: "Verify", |
| description: "Click the button below to get verified in the server.", |
| buttons: ["verifybutton"], |
| color: "GREEN" |
| }, |
| { |
| title: "Get Roles", |
| description: "Click the button to choose which roles you would like in the server", |
| buttons: ["rolemenu"], |
| color: "BLUE" |
| }, |
| { |
| title: "Create Ticket", |
| description: "Click the button below to create a ticket", |
| buttons: ["createticket"], |
| color: "RED" |
| } |
| ]; |
| |
| 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) |
| .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji), |
| 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 presetSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( |
| new StringSelectMenuBuilder() |
| .setCustomId("preset") |
| .setPlaceholder("Select a preset") |
| .setMaxValues(1) |
| .addOptions( |
| presetButtons.map((preset, i) => { |
| return new StringSelectMenuOptionBuilder() |
| .setLabel(preset.title) |
| .setValue(i.toString()) |
| .setDescription(preset.description) |
| .setEmoji(getEmojiByName("COLORS." + preset.color, "id") as APIMessageComponentEmoji); |
| }) |
| ) |
| ); |
| |
| 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: [presetSelect, colorSelect, buttonSelect, channelMenu, buttons] |
| }); |
| |
| let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction; |
| try { |
| i = (await interaction.channel!.awaitMessageComponent({ |
| filter: (i: Discord.Interaction) => |
| i.user.id === interaction.user.id && i.isMessageComponent() && i.message.id === m.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").length === 0 |
| ? null |
| : out.fields.getTextInputValue("title"); |
| data.description = |
| out.fields.getTextInputValue("description").length === 0 |
| ? null |
| : out.fields.getTextInputValue("description"); |
| break; |
| } |
| case "send": { |
| await i.deferUpdate(); |
| const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel; |
| const messageData: MessageCreateOptions = {}; |
| for (const button of data.buttons) { |
| messageData.components = [ |
| new ActionRowBuilder<ButtonBuilder>().addComponents( |
| new ButtonBuilder() |
| .setCustomId(button) |
| .setLabel(buttonNames[button]!) |
| .setStyle(ButtonStyle.Primary) |
| ) |
| ]; |
| } |
| if (data.title || data.description || data.color) { |
| const 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()) { |
| try { |
| await i.deferUpdate(); |
| } catch (err) { |
| console.log(err); |
| } |
| switch (i.customId) { |
| case "preset": { |
| const chosen = presetButtons[parseInt(i.values[0]!)]!; |
| const newColor = colors[chosen.color!]!; |
| data = _.assign(data, chosen, { color: newColor }); |
| break; |
| } |
| 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; |
| }; |