blob: b98e8213852dc26fe987cb1456cb9b3d2a7dae6e [file] [log] [blame]
Skyler Greyda16adf2023-03-05 10:22:12 +00001import {
2 ActionRowBuilder,
3 APIMessageComponentEmoji,
4 ButtonBuilder,
5 ButtonStyle,
6 ChannelSelectMenuBuilder,
7 ChannelType,
8 CommandInteraction,
Skyler Greyda16adf2023-03-05 10:22:12 +00009 ModalBuilder,
10 SlashCommandSubcommandBuilder,
11 StringSelectMenuBuilder,
12 StringSelectMenuOptionBuilder,
13 TextInputBuilder,
14 TextInputStyle
15} from "discord.js";
TheCodedProf46518a42023-02-18 17:08:23 -050016import type Discord from "discord.js";
17import { LoadingEmbed } from "../../utils/defaults.js";
18import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
19import lodash from "lodash";
20import getEmojiByName from "../../utils/getEmojiByName.js";
21import { modalInteractionCollector } from "../../utils/dualCollector.js";
TheCodedProf764e6c22023-03-11 16:07:09 -050022import _ from "lodash";
TheCodedProf46518a42023-02-18 17:08:23 -050023
24export const command = new SlashCommandSubcommandBuilder()
25 .setName("buttons")
26 .setDescription("Create clickable buttons for verifying, role menus etc.");
27
28interface Data {
Skyler Greyda16adf2023-03-05 10:22:12 +000029 buttons: string[];
30 title: string | null;
31 description: string | null;
32 color: number;
33 channel: string | null;
TheCodedProf46518a42023-02-18 17:08:23 -050034}
35
Skyler Greyda16adf2023-03-05 10:22:12 +000036const colors: Record<string, number> = {
37 RED: 0xf27878,
38 ORANGE: 0xe5ab71,
39 YELLOW: 0xf2d478,
40 GREEN: 0x65cc76,
41 BLUE: 0x72aef5,
42 PURPLE: 0xa358b2,
43 PINK: 0xd46899,
44 GRAY: 0x999999
45};
TheCodedProf46518a42023-02-18 17:08:23 -050046
47const buttonNames: Record<string, string> = {
48 verifybutton: "Verify",
49 rolemenu: "Role Menu",
TheCodedProf9c51a7e2023-02-27 17:11:13 -050050 createticket: "Create Ticket"
Skyler Greyda16adf2023-03-05 10:22:12 +000051};
TheCodedProf46518a42023-02-18 17:08:23 -050052
TheCodedProf764e6c22023-03-11 16:07:09 -050053const presetButtons = [
54 {
55 title: "Verify",
56 description: "Click the button below to get verified in the server.",
57 buttons: ["verifybutton"],
58 color: "GREEN"
59 },
60 {
61 title: "Get Roles",
62 description: "Click the button to choose which roles you would like in the server",
63 buttons: ["rolemenu"],
64 color: "BLUE"
65 },
66 {
67 title: "Create Ticket",
68 description: "Click the button below to create a ticket",
69 buttons: ["createticket"],
70 color: "RED"
71 }
TheCodedProf1cfa1ae2023-03-11 16:07:37 -050072];
TheCodedProf764e6c22023-03-11 16:07:09 -050073
TheCodedProf46518a42023-02-18 17:08:23 -050074export const callback = async (interaction: CommandInteraction): Promise<void> => {
TheCodedProf46518a42023-02-18 17:08:23 -050075 const m = await interaction.reply({
76 embeds: LoadingEmbed,
77 fetchReply: true,
78 ephemeral: true
79 });
80
81 let closed = false;
TheCodedProf764e6c22023-03-11 16:07:09 -050082 let data: Data = {
TheCodedProf46518a42023-02-18 17:08:23 -050083 buttons: [],
84 title: null,
85 description: null,
86 color: colors["RED"]!,
87 channel: interaction.channelId
Skyler Greyda16adf2023-03-05 10:22:12 +000088 };
TheCodedProf46518a42023-02-18 17:08:23 -050089 do {
Skyler Greyda16adf2023-03-05 10:22:12 +000090 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
TheCodedProf80ad8542023-03-10 12:52:33 -050091 new ButtonBuilder()
92 .setCustomId("edit")
93 .setLabel("Edit Embed")
94 .setStyle(ButtonStyle.Secondary)
TheCodedProf764e6c22023-03-11 16:07:09 -050095 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
Skyler Greyda16adf2023-03-05 10:22:12 +000096 new ButtonBuilder()
97 .setCustomId("send")
98 .setLabel("Send")
99 .setStyle(ButtonStyle.Primary)
TheCodedProf35e18772023-04-23 16:17:24 -0400100 .setDisabled(!(data.channel && (data.title ?? data.description ?? data.buttons.length)))
Skyler Greyda16adf2023-03-05 10:22:12 +0000101 );
TheCodedProf46518a42023-02-18 17:08:23 -0500102
Skyler Greyda16adf2023-03-05 10:22:12 +0000103 const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
104 new StringSelectMenuBuilder()
105 .setCustomId("color")
106 .setPlaceholder("Select a color")
107 .setMinValues(1)
108 .addOptions(
109 Object.keys(colors).map((color: string) => {
110 return new StringSelectMenuOptionBuilder()
111 .setLabel(lodash.capitalize(color))
112 .setValue(color)
113 .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
114 .setDefault(data.color === colors[color]);
115 })
116 )
117 );
TheCodedProf46518a42023-02-18 17:08:23 -0500118
TheCodedProf764e6c22023-03-11 16:07:09 -0500119 const presetSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
120 new StringSelectMenuBuilder()
121 .setCustomId("preset")
122 .setPlaceholder("Select a preset")
123 .setMaxValues(1)
124 .addOptions(
125 presetButtons.map((preset, i) => {
126 return new StringSelectMenuOptionBuilder()
127 .setLabel(preset.title)
128 .setValue(i.toString())
129 .setDescription(preset.description)
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500130 .setEmoji(getEmojiByName("COLORS." + preset.color, "id") as APIMessageComponentEmoji);
TheCodedProf764e6c22023-03-11 16:07:09 -0500131 })
132 )
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500133 );
TheCodedProf764e6c22023-03-11 16:07:09 -0500134
Skyler Greyda16adf2023-03-05 10:22:12 +0000135 const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
136 new StringSelectMenuBuilder()
137 .setCustomId("button")
138 .setPlaceholder("Select buttons to add")
TheCodedProf35e18772023-04-23 16:17:24 -0400139 .setMinValues(0)
Skyler Greyda16adf2023-03-05 10:22:12 +0000140 .setMaxValues(3)
141 .addOptions(
142 new StringSelectMenuOptionBuilder()
143 .setLabel("Verify")
144 .setValue("verifybutton")
145 .setDescription("Click to get verified in the server")
146 .setDefault(data.buttons.includes("verifybutton")),
147 new StringSelectMenuOptionBuilder()
148 .setLabel("Role Menu")
149 .setValue("rolemenu")
150 .setDescription("Click to customize your roles")
151 .setDefault(data.buttons.includes("rolemenu")),
152 new StringSelectMenuOptionBuilder()
153 .setLabel("Ticket")
154 .setValue("createticket")
155 .setDescription("Click to create a support ticket")
156 .setDefault(data.buttons.includes("createticket"))
157 )
158 );
TheCodedProf46518a42023-02-18 17:08:23 -0500159
Skyler Greyda16adf2023-03-05 10:22:12 +0000160 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
161 new ChannelSelectMenuBuilder()
162 .setCustomId("channel")
163 .setPlaceholder("Select a channel")
164 .setChannelTypes(
165 ChannelType.GuildText,
166 ChannelType.GuildAnnouncement,
167 ChannelType.PublicThread,
168 ChannelType.AnnouncementThread
169 )
170 );
TheCodedProf46518a42023-02-18 17:08:23 -0500171 let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
172 if (data.channel === interaction.channelId) channelName = "this channel";
173 const embed = new EmojiEmbed()
174 .setTitle(data.title ?? "No title set")
175 .setDescription(data.description ?? "*No description set*")
176 .setColor(data.color)
TheCodedProf35e18772023-04-23 16:17:24 -0400177 .setFooter({ text: `The embed will be sent in ${channelName} | Click the button below to edit the embed` });
TheCodedProf46518a42023-02-18 17:08:23 -0500178
179 await interaction.editReply({
180 embeds: [embed],
TheCodedProf764e6c22023-03-11 16:07:09 -0500181 components: [presetSelect, colorSelect, buttonSelect, channelMenu, buttons]
TheCodedProf46518a42023-02-18 17:08:23 -0500182 });
183
184 let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
185 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000186 i = (await interaction.channel!.awaitMessageComponent({
Skyler Grey21f52292023-03-10 17:58:30 +0000187 filter: (i: Discord.Interaction) =>
188 i.user.id === interaction.user.id && i.isMessageComponent() && i.message.id === m.id,
TheCodedProf46518a42023-02-18 17:08:23 -0500189 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000190 })) as
191 | Discord.ButtonInteraction
192 | Discord.ChannelSelectMenuInteraction
193 | Discord.StringSelectMenuInteraction;
TheCodedProf46518a42023-02-18 17:08:23 -0500194 } catch (e) {
195 closed = true;
196 break;
197 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000198 if (i.isButton()) {
199 switch (i.customId) {
TheCodedProf46518a42023-02-18 17:08:23 -0500200 case "edit": {
201 await i.showModal(
202 new ModalBuilder()
203 .setCustomId("modal")
204 .setTitle(`Options for ${i.customId}`)
205 .addComponents(
206 new ActionRowBuilder<TextInputBuilder>().addComponents(
207 new TextInputBuilder()
208 .setCustomId("title")
209 .setLabel("Title")
210 .setMaxLength(256)
211 .setRequired(false)
212 .setStyle(TextInputStyle.Short)
213 .setValue(data.title ?? "")
214 ),
215 new ActionRowBuilder<TextInputBuilder>().addComponents(
216 new TextInputBuilder()
217 .setCustomId("description")
218 .setLabel("The text to display below the title")
219 .setMaxLength(4000)
220 .setRequired(false)
221 .setStyle(TextInputStyle.Paragraph)
222 .setValue(data.description ?? "")
223 )
224 )
225 );
226 await interaction.editReply({
227 embeds: [
228 new EmojiEmbed()
229 .setTitle("Button Editor")
230 .setDescription("Modal opened. If you can't see it, click back and try again.")
231 .setStatus("Success")
232 .setEmoji("GUILD.TICKET.OPEN")
233 ],
234 components: [
235 new ActionRowBuilder<ButtonBuilder>().addComponents([
236 new ButtonBuilder()
237 .setLabel("Back")
238 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
239 .setStyle(ButtonStyle.Primary)
240 .setCustomId("back")
241 ])
242 ]
243 });
244 let out: Discord.ModalSubmitInteraction | null;
245 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000246 out = (await modalInteractionCollector(
247 m,
248 interaction.user
249 )) as Discord.ModalSubmitInteraction | null;
TheCodedProf46518a42023-02-18 17:08:23 -0500250 } catch (e) {
251 closed = true;
252 continue;
253 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000254 if (!out || out.isButton()) continue;
255 data.title =
256 out.fields.getTextInputValue("title").length === 0
257 ? null
258 : out.fields.getTextInputValue("title");
259 data.description =
260 out.fields.getTextInputValue("description").length === 0
261 ? null
262 : out.fields.getTextInputValue("description");
TheCodedProf46518a42023-02-18 17:08:23 -0500263 break;
264 }
265 case "send": {
266 await i.deferUpdate();
TheCodedProf1807fb32023-02-20 14:33:48 -0500267 const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
Skyler Greyf25451c2023-04-23 21:26:11 +0000268 let components: ActionRowBuilder<ButtonBuilder>[] = [];
TheCodedProf35e18772023-04-23 16:17:24 -0400269 let embeds: EmojiEmbed[] = [];
Skyler Greyda16adf2023-03-05 10:22:12 +0000270 for (const button of data.buttons) {
TheCodedProf35e18772023-04-23 16:17:24 -0400271 components = [
Skyler Grey21f52292023-03-10 17:58:30 +0000272 new ActionRowBuilder<ButtonBuilder>().addComponents(
273 new ButtonBuilder()
274 .setCustomId(button)
275 .setLabel(buttonNames[button]!)
276 .setStyle(ButtonStyle.Primary)
277 )
278 ];
Skyler Greyda16adf2023-03-05 10:22:12 +0000279 }
TheCodedProf35e18772023-04-23 16:17:24 -0400280 if (data.title || data.description) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000281 const e = new EmojiEmbed();
282 if (data.title) e.setTitle(data.title);
283 if (data.description) e.setDescription(data.description);
284 if (data.color) e.setColor(data.color);
Skyler Greyf25451c2023-04-23 21:26:11 +0000285 embeds = [e];
TheCodedProf46518a42023-02-18 17:08:23 -0500286 }
Skyler Greyf25451c2023-04-23 21:26:11 +0000287 await channel.send({ embeds, components });
TheCodedProf46518a42023-02-18 17:08:23 -0500288 break;
289 }
290 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000291 } else if (i.isStringSelectMenu()) {
292 try {
293 await i.deferUpdate();
294 } catch (err) {
295 console.log(err);
296 }
297 switch (i.customId) {
TheCodedProf764e6c22023-03-11 16:07:09 -0500298 case "preset": {
299 const chosen = presetButtons[parseInt(i.values[0]!)]!;
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500300 const newColor = colors[chosen.color!]!;
TheCodedProf764e6c22023-03-11 16:07:09 -0500301 data = _.assign(data, chosen, { color: newColor });
302 break;
303 }
TheCodedProf46518a42023-02-18 17:08:23 -0500304 case "color": {
305 data.color = colors[i.values[0]!]!;
306 break;
307 }
308 case "button": {
309 data.buttons = i.values;
310 break;
311 }
312 }
313 } else {
314 await i.deferUpdate();
315 data.channel = i.values[0]!;
316 }
TheCodedProf46518a42023-02-18 17:08:23 -0500317 } while (!closed);
318 await interaction.deleteReply();
Skyler Greyda16adf2023-03-05 10:22:12 +0000319};
TheCodedProf46518a42023-02-18 17:08:23 -0500320
321export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
322 const member = interaction.member as Discord.GuildMember;
323 if (!member.permissions.has("ManageMessages"))
324 return "You must have the *Manage Messages* permission to use this command";
325 return true;
326};