finished?
diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts
new file mode 100644
index 0000000..4a299b2
--- /dev/null
+++ b/src/commands/server/buttons.ts
@@ -0,0 +1,252 @@
+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;
+};