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;
+};
diff --git a/src/config/emojis.json b/src/config/emojis.json
index e4afdfb..35743e1 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -349,7 +349,7 @@
"TOP": {
"ACTIVE": "963122664648630293",
"INACTIVE": "963122659862917140",
- "GREY": {
+ "GRAY": {
"ACTIVE": "963123505052934144",
"INACTIVE": "963123495221469194"
}
@@ -357,7 +357,7 @@
"MIDDLE": {
"ACTIVE": "963122679332880384",
"INACTIVE": "963122673246937199",
- "GREY": {
+ "GRAY": {
"ACTIVE": "963123517702955018",
"INACTIVE": "963123511927390329"
}
@@ -365,7 +365,7 @@
"BOTTOM": {
"ACTIVE": "963122691752218624",
"INACTIVE": "963122685691453552",
- "GREY": {
+ "GRAY": {
"ACTIVE": "963123529988059187",
"INACTIVE": "963123523742748742"
}
@@ -374,10 +374,20 @@
"SINGLE": {
"ACTIVE": "963361162215424060",
"INACTIVE": "963361431758176316",
- "GREY": {
+ "GRAY": {
"ACTIVE": "963361204695334943",
"INACTIVE": "963361200828198952"
}
}
+ },
+ "COLORS": {
+ "RED": "875822912802803754",
+ "ORANGE": "875822913104785418",
+ "YELLOW": "875822913079611402",
+ "GREEN": "875822913213841418",
+ "BLUE": "875822912777637889",
+ "PURPLE": "875822913213841419",
+ "PINK": "875822913088020541",
+ "GRAY": "875822913117368340"
}
}
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index a22045b..80c2c1b 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -31,14 +31,14 @@
await message.fetch();
if (message.embeds.length === 0) return;
const embed = message.embeds[0];
- const newColour = accept ? "Success" : "Danger";
+ const newcolor = accept ? "Success" : "Danger";
const footer = {text: `Suggestion ${accept ? "accepted" : "denied"} by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL()};
const newEmbed = new EmojiEmbed()
.setTitle(embed!.title!)
.setDescription(embed!.description!)
.setFooter(footer)
- .setStatus(newColour);
+ .setStatus(newcolor);
await interaction.update({embeds: [newEmbed], components: []});
}
diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts
index 6805019..0ff04b3 100644
--- a/src/utils/commandRegistration/register.ts
+++ b/src/utils/commandRegistration/register.ts
@@ -6,7 +6,7 @@
import EmojiEmbed from '../generateEmojiEmbed.js';
import getEmojiByName from '../getEmojiByName.js';
-const colours = {
+const colors = {
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
@@ -26,11 +26,11 @@
for (const file of files) {
const last = i === files.length - 1 ? "└" : "├";
if (file.isDirectory()) {
- console.log(`${last}─ ${colours.yellow}Loading subcommands of ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading subcommands of ${file.name}${colors.none}`)
const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`));
commands.push(fetched.command);
} else if (file.name.endsWith(".js")) {
- console.log(`${last}─ ${colours.yellow}Loading command ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading command ${file.name}${colors.none}`)
const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
fetched.command.setDMPermission(fetched.allowedInDMs ?? false)
fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {})
@@ -43,9 +43,9 @@
];
}
i++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
}
- console.log(`${colours.yellow}Loaded ${commands.length} commands, processing...`)
+ console.log(`${colors.yellow}Loaded ${commands.length} commands, processing...`)
const processed = []
for (const subcommand of commands) {
@@ -56,7 +56,7 @@
}
}
- console.log(`${colours.green}Processed ${processed.length} commands${colours.none}`)
+ console.log(`${colors.green}Processed ${processed.length} commands${colors.none}`)
return processed;
};
@@ -73,15 +73,15 @@
const last = i === files.length - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading event ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading event ${file.name}${colors.none}`)
const event = (await import(`../../../${config.eventsFolder}/${file.name}`));
client.on(event.event, event.callback.bind(null, client));
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${files.length}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${files.length}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${files.length}]${colors.none}`)
}
}
console.log(`Loaded ${files.length - errors} events (${errors} failed)`)
@@ -104,7 +104,7 @@
const last = i === totalFiles - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading message context menu ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading message context menu ${file.name}${colors.none}`)
const context = (await import(`../../../${config.messageContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.Message);
context.command.setDMPermission(context.allowedInDMs ?? false)
@@ -113,27 +113,27 @@
client.commands["contextCommands/message/" + context.command.name] = context;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}] | ${e}${colors.none}`)
}
}
for (const file of userFiles) {
const last = i === totalFiles - 1 ? "└" : "├";
i++;
try {
- console.log(`${last}─ ${colours.yellow}Loading user context menu ${file.name}${colours.none}`)
+ console.log(`${last}─ ${colors.yellow}Loading user context menu ${file.name}${colors.none}`)
const context = (await import(`../../../${config.userContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.User);
commands.push(context.command);
client.commands["contextCommands/user/" + context.command.name] = context;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.green}Loaded ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.green}Loaded ${file.name} [${i} / ${totalFiles}]${colors.none}`)
} catch (e) {
errors++;
- console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colours.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colours.none}`)
+ console.log(`${last.replace("└", " ").replace("├", "│")} └─ ${colors.red}Failed to load ${file.name} [${i} / ${totalFiles}]${colors.none}`)
}
}
@@ -210,18 +210,18 @@
if (process.argv.includes("--update-commands")) {
if (config.enableDevelopment) {
const guild = await client.guilds.fetch(config.developmentGuildID);
- console.log(`${colours.purple}Registering commands in ${guild!.name}${colours.none}`)
+ console.log(`${colors.purple}Registering commands in ${guild!.name}${colors.none}`)
await guild.commands.set(commandList);
} else {
- console.log(`${colours.blue}Registering commands in production mode${colours.none}`)
+ console.log(`${colors.blue}Registering commands in production mode${colors.none}`)
await client.application?.commands.set(commandList);
}
}
await registerCommandHandler();
await registerEvents();
- console.log(`${colours.green}Registered commands, events and context menus${colours.none}`)
+ console.log(`${colors.green}Registered commands, events and context menus${colors.none}`)
console.log(
- (config.enableDevelopment ? `${colours.purple}Bot started in Development mode` :
- `${colours.blue}Bot started in Production mode`) + colours.none)
+ (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` :
+ `${colors.blue}Bot started in Production mode`) + colors.none)
// console.log(client.commands)
};
diff --git a/src/utils/commandRegistration/slashCommandBuilder.ts b/src/utils/commandRegistration/slashCommandBuilder.ts
index ef45875..9a72605 100644
--- a/src/utils/commandRegistration/slashCommandBuilder.ts
+++ b/src/utils/commandRegistration/slashCommandBuilder.ts
@@ -6,7 +6,7 @@
import Discord from "discord.js";
-const colours = {
+const colors = {
red: "\x1b[31m",
green: "\x1b[32m",
none: "\x1b[0m"
@@ -23,7 +23,7 @@
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
console.log(`│ ├─ Loading group ${name}`)
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ")
- console.log(`│ │ └─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colours.none}`)
+ console.log(`│ │ └─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands for ${name} (${fetched.errors} failed)${colors.none}`)
return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => {
subcommandGroup
.setName(name)
@@ -54,7 +54,7 @@
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
commandString = "commands/" + (commandString ?? path);
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path);
- console.log(`│ ├─ ${fetched.errors ? colours.red : colours.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colours.none}`)
+ console.log(`│ ├─ ${fetched.errors ? colors.red : colors.green}Loaded ${fetched.subcommands.length} subcommands and ${fetched.subcommandGroups.length} subcommand groups for ${name} (${fetched.errors} failed)${colors.none}`)
// console.log({name: name, description: description})
client.commands[commandString!] = [undefined, { name: name, description: description }]
return (command: SlashCommandBuilder) => {
diff --git a/src/utils/createPageIndicator.ts b/src/utils/createPageIndicator.ts
index 29ea83b..6bc86a4 100644
--- a/src/utils/createPageIndicator.ts
+++ b/src/utils/createPageIndicator.ts
@@ -2,7 +2,7 @@
function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) {
let out = "";
- disabled = disabled ? "GREY." : ""
+ disabled = disabled ? "GRAY." : ""
if (amount === 1) {
out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE"));
} else {
@@ -23,7 +23,7 @@
export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
active = active ? "ACTIVE" : "INACTIVE";
- disabled = disabled ? "GREY." : "";
+ disabled = disabled ? "GRAY." : "";
if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;