blob: e4bda0db5506a16779ffc4402d62ea095fc864fa [file] [log] [blame]
TheCodedProf46518a42023-02-18 17:08:23 -05001import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, MessageCreateOptions, ModalBuilder, SlashCommandSubcommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
2import type Discord from "discord.js";
3import { LoadingEmbed } from "../../utils/defaults.js";
4import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
5import lodash from "lodash";
6import getEmojiByName from "../../utils/getEmojiByName.js";
7import { modalInteractionCollector } from "../../utils/dualCollector.js";
8
9export const command = new SlashCommandSubcommandBuilder()
10 .setName("buttons")
11 .setDescription("Create clickable buttons for verifying, role menus etc.");
12
13interface Data {
14 buttons: string[],
15 title: string | null,
16 description: string | null,
17 color: number,
18 channel: string | null
19}
20
21const colors: Record<string, number> = {
22 RED: 0xF27878,
23 ORANGE: 0xE5AB71,
24 YELLOW: 0xF2D478,
25 GREEN: 0x65CC76,
26 BLUE: 0x72AEF5,
27 PURPLE: 0xA358B2,
28 PINK: 0xD46899,
29 GRAY: 0x999999,
30}
31
32const buttonNames: Record<string, string> = {
33 verifybutton: "Verify",
34 rolemenu: "Role Menu",
35 createticket: "Ticket"
36}
37
38export const callback = async (interaction: CommandInteraction): Promise<void> => {
39
40 const m = await interaction.reply({
41 embeds: LoadingEmbed,
42 fetchReply: true,
43 ephemeral: true
44 });
45
46 let closed = false;
TheCodedProf1807fb32023-02-20 14:33:48 -050047 const data: Data = {
TheCodedProf46518a42023-02-18 17:08:23 -050048 buttons: [],
49 title: null,
50 description: null,
51 color: colors["RED"]!,
52 channel: interaction.channelId
53 }
54 do {
55
56 const buttons = new ActionRowBuilder<ButtonBuilder>()
57 .addComponents(
58 new ButtonBuilder()
59 .setCustomId("edit")
60 .setLabel("Edit Embed")
61 .setStyle(ButtonStyle.Secondary),
62 new ButtonBuilder()
63 .setCustomId("send")
64 .setLabel("Send")
65 .setStyle(ButtonStyle.Primary)
66 .setDisabled(!data.channel)
67 );
68
69 const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
70 .addComponents(
71 new StringSelectMenuBuilder()
72 .setCustomId("color")
73 .setPlaceholder("Select a color")
74 .setMinValues(1)
75 .addOptions(
76 Object.keys(colors).map((color: string) => {
77 return new StringSelectMenuOptionBuilder()
78 .setLabel(lodash.capitalize(color))
79 .setValue(color)
80 .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
81 .setDefault(data.color === colors[color])
82 }
83 )
84 )
85 );
86
87 const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>()
88 .addComponents(
89 new StringSelectMenuBuilder()
90 .setCustomId("button")
91 .setPlaceholder("Select buttons to add")
92 .setMinValues(1)
93 .setMaxValues(3)
94 .addOptions(
95 new StringSelectMenuOptionBuilder()
96 .setLabel("Verify")
97 .setValue("verifybutton")
98 .setDescription("Click to get verified in the server")
99 .setDefault(data.buttons.includes("verifybutton")),
100 new StringSelectMenuOptionBuilder()
101 .setLabel("Role Menu")
102 .setValue("rolemenu")
103 .setDescription("Click to customize your roles")
104 .setDefault(data.buttons.includes("rolemenu")),
105 new StringSelectMenuOptionBuilder()
106 .setLabel("Ticket")
107 .setValue("createticket")
108 .setDescription("Click to create a support ticket")
109 .setDefault(data.buttons.includes("createticket"))
110 )
111 )
112
113 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>()
114 .addComponents(
115 new ChannelSelectMenuBuilder()
116 .setCustomId("channel")
117 .setPlaceholder("Select a channel")
118 .setChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.PublicThread, ChannelType.AnnouncementThread)
119 )
120 let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
121 if (data.channel === interaction.channelId) channelName = "this channel";
122 const embed = new EmojiEmbed()
123 .setTitle(data.title ?? "No title set")
124 .setDescription(data.description ?? "*No description set*")
125 .setColor(data.color)
126 .setFooter({text: `Click the button below to edit the embed | The embed will be sent in ${channelName}`});
127
128
129 await interaction.editReply({
130 embeds: [embed],
131 components: [colorSelect, buttonSelect, channelMenu, buttons]
132 });
133
134 let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
135 try {
136 i = await interaction.channel!.awaitMessageComponent({
pineafan96228bd2023-02-21 14:22:55 +0000137 filter: (i: Discord.Interaction) => i.user.id === interaction.user.id,
TheCodedProf46518a42023-02-18 17:08:23 -0500138 time: 300000
139 }) as Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
140 } catch (e) {
141 closed = true;
142 break;
143 }
144 if(i.isButton()) {
145 switch(i.customId) {
146 case "edit": {
147 await i.showModal(
148 new ModalBuilder()
149 .setCustomId("modal")
150 .setTitle(`Options for ${i.customId}`)
151 .addComponents(
152 new ActionRowBuilder<TextInputBuilder>().addComponents(
153 new TextInputBuilder()
154 .setCustomId("title")
155 .setLabel("Title")
156 .setMaxLength(256)
157 .setRequired(false)
158 .setStyle(TextInputStyle.Short)
159 .setValue(data.title ?? "")
160 ),
161 new ActionRowBuilder<TextInputBuilder>().addComponents(
162 new TextInputBuilder()
163 .setCustomId("description")
164 .setLabel("The text to display below the title")
165 .setMaxLength(4000)
166 .setRequired(false)
167 .setStyle(TextInputStyle.Paragraph)
168 .setValue(data.description ?? "")
169 )
170 )
171 );
172 await interaction.editReply({
173 embeds: [
174 new EmojiEmbed()
175 .setTitle("Button Editor")
176 .setDescription("Modal opened. If you can't see it, click back and try again.")
177 .setStatus("Success")
178 .setEmoji("GUILD.TICKET.OPEN")
179 ],
180 components: [
181 new ActionRowBuilder<ButtonBuilder>().addComponents([
182 new ButtonBuilder()
183 .setLabel("Back")
184 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
185 .setStyle(ButtonStyle.Primary)
186 .setCustomId("back")
187 ])
188 ]
189 });
190 let out: Discord.ModalSubmitInteraction | null;
191 try {
192 out = await modalInteractionCollector(m, interaction.user) as Discord.ModalSubmitInteraction | null;
193 } catch (e) {
194 closed = true;
195 continue;
196 }
197 if (!out || out.isButton()) continue
198 data.title = out.fields.getTextInputValue("title");
199 data.description = out.fields.getTextInputValue("description");
200 break;
201 }
202 case "send": {
203 await i.deferUpdate();
TheCodedProf1807fb32023-02-20 14:33:48 -0500204 const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
205 const components = new ActionRowBuilder<ButtonBuilder>();
206 for(const button of data.buttons) {
TheCodedProf46518a42023-02-18 17:08:23 -0500207 components.addComponents(
208 new ButtonBuilder()
209 .setCustomId(button)
210 .setLabel(buttonNames[button]!)
211 .setStyle(ButtonStyle.Primary)
212 );
213 }
TheCodedProf1807fb32023-02-20 14:33:48 -0500214 const messageData: MessageCreateOptions = {components: [components]}
TheCodedProf46518a42023-02-18 17:08:23 -0500215 if (data.title || data.description) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500216 const e = new EmojiEmbed()
TheCodedProf46518a42023-02-18 17:08:23 -0500217 if(data.title) e.setTitle(data.title);
218 if(data.description) e.setDescription(data.description);
219 if(data.color) e.setColor(data.color);
220 messageData.embeds = [e];
221 }
222 await channel.send(messageData);
223 break;
224 }
225 }
226 } else if(i.isStringSelectMenu()) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500227 try {await i.deferUpdate();} catch (err) {console.log(err)}
TheCodedProf46518a42023-02-18 17:08:23 -0500228 switch(i.customId) {
229 case "color": {
230 data.color = colors[i.values[0]!]!;
231 break;
232 }
233 case "button": {
234 data.buttons = i.values;
235 break;
236 }
237 }
238 } else {
239 await i.deferUpdate();
240 data.channel = i.values[0]!;
241 }
242
243 } while (!closed);
244 await interaction.deleteReply();
245}
246
247export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
248 const member = interaction.member as Discord.GuildMember;
249 if (!member.permissions.has("ManageMessages"))
250 return "You must have the *Manage Messages* permission to use this command";
251 return true;
252};