blob: f07f3ce1f05f395c8e2eed7310ef16339ca32615 [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";
TheCodedProf764e6c22023-03-11 16:07:09 -050023import _ from "lodash";
TheCodedProf46518a42023-02-18 17:08:23 -050024
25export const command = new SlashCommandSubcommandBuilder()
26 .setName("buttons")
27 .setDescription("Create clickable buttons for verifying, role menus etc.");
28
29interface Data {
Skyler Greyda16adf2023-03-05 10:22:12 +000030 buttons: string[];
31 title: string | null;
32 description: string | null;
33 color: number;
34 channel: string | null;
TheCodedProf46518a42023-02-18 17:08:23 -050035}
36
Skyler Greyda16adf2023-03-05 10:22:12 +000037const colors: Record<string, number> = {
38 RED: 0xf27878,
39 ORANGE: 0xe5ab71,
40 YELLOW: 0xf2d478,
41 GREEN: 0x65cc76,
42 BLUE: 0x72aef5,
43 PURPLE: 0xa358b2,
44 PINK: 0xd46899,
45 GRAY: 0x999999
46};
TheCodedProf46518a42023-02-18 17:08:23 -050047
48const buttonNames: Record<string, string> = {
49 verifybutton: "Verify",
50 rolemenu: "Role Menu",
TheCodedProf9c51a7e2023-02-27 17:11:13 -050051 createticket: "Create Ticket"
Skyler Greyda16adf2023-03-05 10:22:12 +000052};
TheCodedProf46518a42023-02-18 17:08:23 -050053
TheCodedProf764e6c22023-03-11 16:07:09 -050054const presetButtons = [
55 {
56 title: "Verify",
57 description: "Click the button below to get verified in the server.",
58 buttons: ["verifybutton"],
59 color: "GREEN"
60 },
61 {
62 title: "Get Roles",
63 description: "Click the button to choose which roles you would like in the server",
64 buttons: ["rolemenu"],
65 color: "BLUE"
66 },
67 {
68 title: "Create Ticket",
69 description: "Click the button below to create a ticket",
70 buttons: ["createticket"],
71 color: "RED"
72 }
TheCodedProf1cfa1ae2023-03-11 16:07:37 -050073];
TheCodedProf764e6c22023-03-11 16:07:09 -050074
TheCodedProf46518a42023-02-18 17:08:23 -050075export const callback = async (interaction: CommandInteraction): Promise<void> => {
TheCodedProf46518a42023-02-18 17:08:23 -050076 const m = await interaction.reply({
77 embeds: LoadingEmbed,
78 fetchReply: true,
79 ephemeral: true
80 });
81
82 let closed = false;
TheCodedProf764e6c22023-03-11 16:07:09 -050083 let data: Data = {
TheCodedProf46518a42023-02-18 17:08:23 -050084 buttons: [],
85 title: null,
86 description: null,
87 color: colors["RED"]!,
88 channel: interaction.channelId
Skyler Greyda16adf2023-03-05 10:22:12 +000089 };
TheCodedProf46518a42023-02-18 17:08:23 -050090 do {
Skyler Greyda16adf2023-03-05 10:22:12 +000091 const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
TheCodedProf80ad8542023-03-10 12:52:33 -050092 new ButtonBuilder()
93 .setCustomId("edit")
94 .setLabel("Edit Embed")
95 .setStyle(ButtonStyle.Secondary)
TheCodedProf764e6c22023-03-11 16:07:09 -050096 .setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
Skyler Greyda16adf2023-03-05 10:22:12 +000097 new ButtonBuilder()
98 .setCustomId("send")
99 .setLabel("Send")
100 .setStyle(ButtonStyle.Primary)
101 .setDisabled(!data.channel)
102 );
TheCodedProf46518a42023-02-18 17:08:23 -0500103
Skyler Greyda16adf2023-03-05 10:22:12 +0000104 const colorSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
105 new StringSelectMenuBuilder()
106 .setCustomId("color")
107 .setPlaceholder("Select a color")
108 .setMinValues(1)
109 .addOptions(
110 Object.keys(colors).map((color: string) => {
111 return new StringSelectMenuOptionBuilder()
112 .setLabel(lodash.capitalize(color))
113 .setValue(color)
114 .setEmoji(getEmojiByName("COLORS." + color, "id") as APIMessageComponentEmoji)
115 .setDefault(data.color === colors[color]);
116 })
117 )
118 );
TheCodedProf46518a42023-02-18 17:08:23 -0500119
TheCodedProf764e6c22023-03-11 16:07:09 -0500120 const presetSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
121 new StringSelectMenuBuilder()
122 .setCustomId("preset")
123 .setPlaceholder("Select a preset")
124 .setMaxValues(1)
125 .addOptions(
126 presetButtons.map((preset, i) => {
127 return new StringSelectMenuOptionBuilder()
128 .setLabel(preset.title)
129 .setValue(i.toString())
130 .setDescription(preset.description)
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500131 .setEmoji(getEmojiByName("COLORS." + preset.color, "id") as APIMessageComponentEmoji);
TheCodedProf764e6c22023-03-11 16:07:09 -0500132 })
133 )
TheCodedProf1cfa1ae2023-03-11 16:07:37 -0500134 );
TheCodedProf764e6c22023-03-11 16:07:09 -0500135
Skyler Greyda16adf2023-03-05 10:22:12 +0000136 const buttonSelect = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
137 new StringSelectMenuBuilder()
138 .setCustomId("button")
139 .setPlaceholder("Select buttons to add")
140 .setMinValues(1)
141 .setMaxValues(3)
142 .addOptions(
143 new StringSelectMenuOptionBuilder()
144 .setLabel("Verify")
145 .setValue("verifybutton")
146 .setDescription("Click to get verified in the server")
147 .setDefault(data.buttons.includes("verifybutton")),
148 new StringSelectMenuOptionBuilder()
149 .setLabel("Role Menu")
150 .setValue("rolemenu")
151 .setDescription("Click to customize your roles")
152 .setDefault(data.buttons.includes("rolemenu")),
153 new StringSelectMenuOptionBuilder()
154 .setLabel("Ticket")
155 .setValue("createticket")
156 .setDescription("Click to create a support ticket")
157 .setDefault(data.buttons.includes("createticket"))
158 )
159 );
TheCodedProf46518a42023-02-18 17:08:23 -0500160
Skyler Greyda16adf2023-03-05 10:22:12 +0000161 const channelMenu = new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
162 new ChannelSelectMenuBuilder()
163 .setCustomId("channel")
164 .setPlaceholder("Select a channel")
165 .setChannelTypes(
166 ChannelType.GuildText,
167 ChannelType.GuildAnnouncement,
168 ChannelType.PublicThread,
169 ChannelType.AnnouncementThread
170 )
171 );
TheCodedProf46518a42023-02-18 17:08:23 -0500172 let channelName = interaction.guild!.channels.cache.get(data.channel!)?.name;
173 if (data.channel === interaction.channelId) channelName = "this channel";
174 const embed = new EmojiEmbed()
175 .setTitle(data.title ?? "No title set")
176 .setDescription(data.description ?? "*No description set*")
177 .setColor(data.color)
Skyler Greyda16adf2023-03-05 10:22:12 +0000178 .setFooter({ text: `Click the button below to edit the embed | The embed will be sent in ${channelName}` });
TheCodedProf46518a42023-02-18 17:08:23 -0500179
180 await interaction.editReply({
181 embeds: [embed],
TheCodedProf764e6c22023-03-11 16:07:09 -0500182 components: [presetSelect, colorSelect, buttonSelect, channelMenu, buttons]
TheCodedProf46518a42023-02-18 17:08:23 -0500183 });
184
185 let i: Discord.ButtonInteraction | Discord.ChannelSelectMenuInteraction | Discord.StringSelectMenuInteraction;
186 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000187 i = (await interaction.channel!.awaitMessageComponent({
Skyler Grey21f52292023-03-10 17:58:30 +0000188 filter: (i: Discord.Interaction) =>
189 i.user.id === interaction.user.id && i.isMessageComponent() && i.message.id === m.id,
TheCodedProf46518a42023-02-18 17:08:23 -0500190 time: 300000
Skyler Greyda16adf2023-03-05 10:22:12 +0000191 })) as
192 | Discord.ButtonInteraction
193 | Discord.ChannelSelectMenuInteraction
194 | Discord.StringSelectMenuInteraction;
TheCodedProf46518a42023-02-18 17:08:23 -0500195 } catch (e) {
196 closed = true;
197 break;
198 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000199 if (i.isButton()) {
200 switch (i.customId) {
TheCodedProf46518a42023-02-18 17:08:23 -0500201 case "edit": {
202 await i.showModal(
203 new ModalBuilder()
204 .setCustomId("modal")
205 .setTitle(`Options for ${i.customId}`)
206 .addComponents(
207 new ActionRowBuilder<TextInputBuilder>().addComponents(
208 new TextInputBuilder()
209 .setCustomId("title")
210 .setLabel("Title")
211 .setMaxLength(256)
212 .setRequired(false)
213 .setStyle(TextInputStyle.Short)
214 .setValue(data.title ?? "")
215 ),
216 new ActionRowBuilder<TextInputBuilder>().addComponents(
217 new TextInputBuilder()
218 .setCustomId("description")
219 .setLabel("The text to display below the title")
220 .setMaxLength(4000)
221 .setRequired(false)
222 .setStyle(TextInputStyle.Paragraph)
223 .setValue(data.description ?? "")
224 )
225 )
226 );
227 await interaction.editReply({
228 embeds: [
229 new EmojiEmbed()
230 .setTitle("Button Editor")
231 .setDescription("Modal opened. If you can't see it, click back and try again.")
232 .setStatus("Success")
233 .setEmoji("GUILD.TICKET.OPEN")
234 ],
235 components: [
236 new ActionRowBuilder<ButtonBuilder>().addComponents([
237 new ButtonBuilder()
238 .setLabel("Back")
239 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
240 .setStyle(ButtonStyle.Primary)
241 .setCustomId("back")
242 ])
243 ]
244 });
245 let out: Discord.ModalSubmitInteraction | null;
246 try {
Skyler Greyda16adf2023-03-05 10:22:12 +0000247 out = (await modalInteractionCollector(
248 m,
249 interaction.user
250 )) as Discord.ModalSubmitInteraction | null;
TheCodedProf46518a42023-02-18 17:08:23 -0500251 } catch (e) {
252 closed = true;
253 continue;
254 }
Skyler Greyda16adf2023-03-05 10:22:12 +0000255 if (!out || out.isButton()) continue;
256 data.title =
257 out.fields.getTextInputValue("title").length === 0
258 ? null
259 : out.fields.getTextInputValue("title");
260 data.description =
261 out.fields.getTextInputValue("description").length === 0
262 ? null
263 : out.fields.getTextInputValue("description");
TheCodedProf46518a42023-02-18 17:08:23 -0500264 break;
265 }
266 case "send": {
267 await i.deferUpdate();
TheCodedProf1807fb32023-02-20 14:33:48 -0500268 const channel = interaction.guild!.channels.cache.get(data.channel!) as Discord.TextChannel;
Skyler Grey21f52292023-03-10 17:58:30 +0000269 const messageData: MessageCreateOptions = {};
Skyler Greyda16adf2023-03-05 10:22:12 +0000270 for (const button of data.buttons) {
Skyler Grey21f52292023-03-10 17:58:30 +0000271 messageData.components = [
272 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 }
TheCodedProf80ad8542023-03-10 12:52:33 -0500280 if (data.title || data.description || data.color) {
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);
TheCodedProf46518a42023-02-18 17:08:23 -0500285 messageData.embeds = [e];
286 }
287 await channel.send(messageData);
288 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};