blob: 74c255c572ef3562acb4a0b8fb788462ac913280 [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,
9 MessageCreateOptions,
10 ModalBuilder,
11 SlashCommandSubcommandBuilder,
12 StringSelectMenuBuilder,
13 StringSelectMenuOptionBuilder,
14 TextInputBuilder,
15 TextInputStyle
16} from "discord.js";
TheCodedProf46518a42023-02-18 17:08:23 -050017import type Discord from "discord.js";
18import { LoadingEmbed } from "../../utils/defaults.js";
19import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
20import lodash from "lodash";
21import getEmojiByName from "../../utils/getEmojiByName.js";
22import { modalInteractionCollector } from "../../utils/dualCollector.js";
23
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
53export const callback = async (interaction: CommandInteraction): Promise<void> => {
TheCodedProf46518a42023-02-18 17:08:23 -050054 const m = await interaction.reply({
55 embeds: LoadingEmbed,
56 fetchReply: true,
57 ephemeral: true
58 });
59
60 let closed = false;
TheCodedProf1807fb32023-02-20 14:33:48 -050061 const data: Data = {
TheCodedProf46518a42023-02-18 17:08:23 -050062 buttons: [],
63 title: null,
64 description: null,
65 color: colors["RED"]!,
66 channel: interaction.channelId
Skyler Greyda16adf2023-03-05 10:22:12 +000067 };
TheCodedProf46518a42023-02-18 17:08:23 -050068 do {
Skyler Greyda16adf2023-03-05 10:22:12 +000069 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
TheCodedProf80ad8542023-03-10 12:52:33 -050070 new ButtonBuilder()
71 .setCustomId("edit")
72 .setLabel("Edit Embed")
73 .setStyle(ButtonStyle.Secondary)
Skyler Grey21f52292023-03-10 17:58:30 +000074 .setEmoji(getEmojiByName("ICONS.EDIT") as APIMessageComponentEmoji),
Skyler Greyda16adf2023-03-05 10:22:12 +000075 new ButtonBuilder()
76 .setCustomId("send")
77 .setLabel("Send")
78 .setStyle(ButtonStyle.Primary)
79 .setDisabled(!data.channel)
80 );
TheCodedProf46518a42023-02-18 17:08:23 -050081
Skyler Greyda16adf2023-03-05 10:22:12 +000082 const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
83 new StringSelectMenuBuilder()
84 .setCustomId("color")
85 .setPlaceholder("Select a color")
86 .setMinValues(1)
87 .addOptions(
88 Object.keys(colors).map((color: string) => {
89 return new StringSelectMenuOptionBuilder()
90 .setLabel(lodash.capitalize(color))
91 .setValue(color)
92 .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
93 .setDefault(data.color === colors[color]);
94 })
95 )
96 );
TheCodedProf46518a42023-02-18 17:08:23 -050097
Skyler Greyda16adf2023-03-05 10:22:12 +000098 const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
99 new StringSelectMenuBuilder()
100 .setCustomId("button")
101 .setPlaceholder("Select buttons to add")
102 .setMinValues(1)
103 .setMaxValues(3)
104 .addOptions(
105 new StringSelectMenuOptionBuilder()
106 .setLabel("Verify")
107 .setValue("verifybutton")
108 .setDescription("Click to get verified in the server")
109 .setDefault(data.buttons.includes("verifybutton")),
110 new StringSelectMenuOptionBuilder()
111 .setLabel("Role Menu")
112 .setValue("rolemenu")
113 .setDescription("Click to customize your roles")
114 .setDefault(data.buttons.includes("rolemenu")),
115 new StringSelectMenuOptionBuilder()
116 .setLabel("Ticket")
117 .setValue("createticket")
118 .setDescription("Click to create a support ticket")
119 .setDefault(data.buttons.includes("createticket"))
120 )
121 );
TheCodedProf46518a42023-02-18 17:08:23 -0500122
Skyler Greyda16adf2023-03-05 10:22:12 +0000123 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
124 new ChannelSelectMenuBuilder()
125 .setCustomId("channel")
126 .setPlaceholder("Select a channel")
127 .setChannelTypes(
128 ChannelType.GuildText,
129 ChannelType.GuildAnnouncement,
130 ChannelType.PublicThread,
131 ChannelType.AnnouncementThread
132 )
133 );
TheCodedProf46518a42023-02-18 17:08:23 -0500134 let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
135 if (data.channel === interaction.channelId) channelName = "this channel";
136 const embed = new EmojiEmbed()
137 .setTitle(data.title ?? "No title set")
138 .setDescription(data.description ?? "*No description set*")
139 .setColor(data.color)
Skyler Greyda16adf2023-03-05 10:22:12 +0000140 .setFooter({ text: `Click the button below to edit the embed | The embed will be sent in ${channelName}` });
TheCodedProf46518a42023-02-18 17:08:23 -0500141
142 await interaction.editReply({
143 embeds: [embed],
144 components: [colorSelect, buttonSelect, channelMenu, buttons]
145 });
146
147 let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
148 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000149 i = (await interaction.channel!.awaitMessageComponent({
Skyler Grey21f52292023-03-10 17:58:30 +0000150 filter: (i: Discord.Interaction) =>
151 i.user.id === interaction.user.id && i.isMessageComponent() && i.message.id === m.id,
TheCodedProf46518a42023-02-18 17:08:23 -0500152 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000153 })) as
154 | Discord.ButtonInteraction
155 | Discord.ChannelSelectMenuInteraction
156 | Discord.StringSelectMenuInteraction;
TheCodedProf46518a42023-02-18 17:08:23 -0500157 } catch (e) {
158 closed = true;
159 break;
160 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000161 if (i.isButton()) {
162 switch (i.customId) {
TheCodedProf46518a42023-02-18 17:08:23 -0500163 case "edit": {
164 await i.showModal(
165 new ModalBuilder()
166 .setCustomId("modal")
167 .setTitle(`Options for ${i.customId}`)
168 .addComponents(
169 new ActionRowBuilder<TextInputBuilder>().addComponents(
170 new TextInputBuilder()
171 .setCustomId("title")
172 .setLabel("Title")
173 .setMaxLength(256)
174 .setRequired(false)
175 .setStyle(TextInputStyle.Short)
176 .setValue(data.title ?? "")
177 ),
178 new ActionRowBuilder<TextInputBuilder>().addComponents(
179 new TextInputBuilder()
180 .setCustomId("description")
181 .setLabel("The text to display below the title")
182 .setMaxLength(4000)
183 .setRequired(false)
184 .setStyle(TextInputStyle.Paragraph)
185 .setValue(data.description ?? "")
186 )
187 )
188 );
189 await interaction.editReply({
190 embeds: [
191 new EmojiEmbed()
192 .setTitle("Button Editor")
193 .setDescription("Modal opened. If you can't see it, click back and try again.")
194 .setStatus("Success")
195 .setEmoji("GUILD.TICKET.OPEN")
196 ],
197 components: [
198 new ActionRowBuilder<ButtonBuilder>().addComponents([
199 new ButtonBuilder()
200 .setLabel("Back")
201 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
202 .setStyle(ButtonStyle.Primary)
203 .setCustomId("back")
204 ])
205 ]
206 });
207 let out: Discord.ModalSubmitInteraction | null;
208 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000209 out = (await modalInteractionCollector(
210 m,
211 interaction.user
212 )) as Discord.ModalSubmitInteraction | null;
TheCodedProf46518a42023-02-18 17:08:23 -0500213 } catch (e) {
214 closed = true;
215 continue;
216 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000217 if (!out || out.isButton()) continue;
218 data.title =
219 out.fields.getTextInputValue("title").length === 0
220 ? null
221 : out.fields.getTextInputValue("title");
222 data.description =
223 out.fields.getTextInputValue("description").length === 0
224 ? null
225 : out.fields.getTextInputValue("description");
TheCodedProf46518a42023-02-18 17:08:23 -0500226 break;
227 }
228 case "send": {
229 await i.deferUpdate();
TheCodedProf1807fb32023-02-20 14:33:48 -0500230 const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
Skyler Grey21f52292023-03-10 17:58:30 +0000231 const messageData: MessageCreateOptions = {};
Skyler Greyda16adf2023-03-05 10:22:12 +0000232 for (const button of data.buttons) {
Skyler Grey21f52292023-03-10 17:58:30 +0000233 messageData.components = [
234 new ActionRowBuilder<ButtonBuilder>().addComponents(
235 new ButtonBuilder()
236 .setCustomId(button)
237 .setLabel(buttonNames[button]!)
238 .setStyle(ButtonStyle.Primary)
239 )
240 ];
Skyler Greyda16adf2023-03-05 10:22:12 +0000241 }
TheCodedProf80ad8542023-03-10 12:52:33 -0500242 if (data.title || data.description || data.color) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000243 const e = new EmojiEmbed();
244 if (data.title) e.setTitle(data.title);
245 if (data.description) e.setDescription(data.description);
246 if (data.color) e.setColor(data.color);
TheCodedProf46518a42023-02-18 17:08:23 -0500247 messageData.embeds = [e];
248 }
249 await channel.send(messageData);
250 break;
251 }
252 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000253 } else if (i.isStringSelectMenu()) {
254 try {
255 await i.deferUpdate();
256 } catch (err) {
257 console.log(err);
258 }
259 switch (i.customId) {
TheCodedProf46518a42023-02-18 17:08:23 -0500260 case "color": {
261 data.color = colors[i.values[0]!]!;
262 break;
263 }
264 case "button": {
265 data.buttons = i.values;
266 break;
267 }
268 }
269 } else {
270 await i.deferUpdate();
271 data.channel = i.values[0]!;
272 }
TheCodedProf46518a42023-02-18 17:08:23 -0500273 } while (!closed);
274 await interaction.deleteReply();
Skyler Greyda16adf2023-03-05 10:22:12 +0000275};
TheCodedProf46518a42023-02-18 17:08:23 -0500276
277export const check = (interaction: CommandInteraction, _partial: boolean = false) => {
278 const member = interaction.member as Discord.GuildMember;
279 if (!member.permissions.has("ManageMessages"))
280 return "You must have the *Manage Messages* permission to use this command";
281 return true;
282};