pull/11/head
TheCodedProf 3 years ago
parent baee2c19da
commit 46518a4bb5

@ -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;
};

@ -349,7 +349,7 @@
"TOP": { "TOP": {
"ACTIVE": "963122664648630293", "ACTIVE": "963122664648630293",
"INACTIVE": "963122659862917140", "INACTIVE": "963122659862917140",
"GREY": { "GRAY": {
"ACTIVE": "963123505052934144", "ACTIVE": "963123505052934144",
"INACTIVE": "963123495221469194" "INACTIVE": "963123495221469194"
} }
@ -357,7 +357,7 @@
"MIDDLE": { "MIDDLE": {
"ACTIVE": "963122679332880384", "ACTIVE": "963122679332880384",
"INACTIVE": "963122673246937199", "INACTIVE": "963122673246937199",
"GREY": { "GRAY": {
"ACTIVE": "963123517702955018", "ACTIVE": "963123517702955018",
"INACTIVE": "963123511927390329" "INACTIVE": "963123511927390329"
} }
@ -365,7 +365,7 @@
"BOTTOM": { "BOTTOM": {
"ACTIVE": "963122691752218624", "ACTIVE": "963122691752218624",
"INACTIVE": "963122685691453552", "INACTIVE": "963122685691453552",
"GREY": { "GRAY": {
"ACTIVE": "963123529988059187", "ACTIVE": "963123529988059187",
"INACTIVE": "963123523742748742" "INACTIVE": "963123523742748742"
} }
@ -374,10 +374,20 @@
"SINGLE": { "SINGLE": {
"ACTIVE": "963361162215424060", "ACTIVE": "963361162215424060",
"INACTIVE": "963361431758176316", "INACTIVE": "963361431758176316",
"GREY": { "GRAY": {
"ACTIVE": "963361204695334943", "ACTIVE": "963361204695334943",
"INACTIVE": "963361200828198952" "INACTIVE": "963361200828198952"
} }
} }
},
"COLORS": {
"RED": "875822912802803754",
"ORANGE": "875822913104785418",
"YELLOW": "875822913079611402",
"GREEN": "875822913213841418",
"BLUE": "875822912777637889",
"PURPLE": "875822913213841419",
"PINK": "875822913088020541",
"GRAY": "875822913117368340"
} }
} }

@ -31,14 +31,14 @@ async function modifySuggestion(interaction: Discord.MessageComponentInteraction
await message.fetch(); await message.fetch();
if (message.embeds.length === 0) return; if (message.embeds.length === 0) return;
const embed = message.embeds[0]; 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 footer = {text: `Suggestion ${accept ? "accepted" : "denied"} by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL()};
const newEmbed = new EmojiEmbed() const newEmbed = new EmojiEmbed()
.setTitle(embed!.title!) .setTitle(embed!.title!)
.setDescription(embed!.description!) .setDescription(embed!.description!)
.setFooter(footer) .setFooter(footer)
.setStatus(newColour); .setStatus(newcolor);
await interaction.update({embeds: [newEmbed], components: []}); await interaction.update({embeds: [newEmbed], components: []});
} }

@ -6,7 +6,7 @@ import fs from "fs";
import EmojiEmbed from '../generateEmojiEmbed.js'; import EmojiEmbed from '../generateEmojiEmbed.js';
import getEmojiByName from '../getEmojiByName.js'; import getEmojiByName from '../getEmojiByName.js';
const colours = { const colors = {
red: "\x1b[31m", red: "\x1b[31m",
green: "\x1b[32m", green: "\x1b[32m",
yellow: "\x1b[33m", yellow: "\x1b[33m",
@ -26,11 +26,11 @@ async function registerCommands() {
for (const file of files) { for (const file of files) {
const last = i === files.length - 1 ? "└" : "├"; const last = i === files.length - 1 ? "└" : "├";
if (file.isDirectory()) { 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`)); const fetched = (await import(`../../../${config.commandsFolder}/${file.name}/_meta.js`));
commands.push(fetched.command); commands.push(fetched.command);
} else if (file.name.endsWith(".js")) { } 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}`)); const fetched = (await import(`../../../${config.commandsFolder}/${file.name}`));
fetched.command.setDMPermission(fetched.allowedInDMs ?? false) fetched.command.setDMPermission(fetched.allowedInDMs ?? false)
fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {}) fetched.command.setNameLocalizations(fetched.nameLocalizations ?? {})
@ -43,9 +43,9 @@ async function registerCommands() {
]; ];
} }
i++; 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 = [] const processed = []
for (const subcommand of commands) { for (const subcommand of commands) {
@ -56,7 +56,7 @@ async function registerCommands() {
} }
} }
console.log(`${colours.green}Processed ${processed.length} commands${colours.none}`) console.log(`${colors.green}Processed ${processed.length} commands${colors.none}`)
return processed; return processed;
}; };
@ -73,15 +73,15 @@ async function registerEvents() {
const last = i === files.length - 1 ? "└" : "├"; const last = i === files.length - 1 ? "└" : "├";
i++; i++;
try { 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}`)); const event = (await import(`../../../${config.eventsFolder}/${file.name}`));
client.on(event.event, event.callback.bind(null, client)); 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) { } catch (e) {
errors++; 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)`) console.log(`Loaded ${files.length - errors} events (${errors} failed)`)
@ -104,7 +104,7 @@ async function registerContextMenus() {
const last = i === totalFiles - 1 ? "└" : "├"; const last = i === totalFiles - 1 ? "└" : "├";
i++; i++;
try { 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}`)); const context = (await import(`../../../${config.messageContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.Message); context.command.setType(ApplicationCommandType.Message);
context.command.setDMPermission(context.allowedInDMs ?? false) context.command.setDMPermission(context.allowedInDMs ?? false)
@ -113,27 +113,27 @@ async function registerContextMenus() {
client.commands["contextCommands/message/" + context.command.name] = context; 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) { } catch (e) {
errors++; 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) { for (const file of userFiles) {
const last = i === totalFiles - 1 ? "└" : "├"; const last = i === totalFiles - 1 ? "└" : "├";
i++; i++;
try { 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}`)); const context = (await import(`../../../${config.userContextFolder}/${file.name}`));
context.command.setType(ApplicationCommandType.User); context.command.setType(ApplicationCommandType.User);
commands.push(context.command); commands.push(context.command);
client.commands["contextCommands/user/" + context.command.name] = context; 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) { } catch (e) {
errors++; 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 @@ export default async function register() {
if (process.argv.includes("--update-commands")) { if (process.argv.includes("--update-commands")) {
if (config.enableDevelopment) { if (config.enableDevelopment) {
const guild = await client.guilds.fetch(config.developmentGuildID); 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); await guild.commands.set(commandList);
} else { } 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 client.application?.commands.set(commandList);
} }
} }
await registerCommandHandler(); await registerCommandHandler();
await registerEvents(); 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( console.log(
(config.enableDevelopment ? `${colours.purple}Bot started in Development mode` : (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` :
`${colours.blue}Bot started in Production mode`) + colours.none) `${colors.blue}Bot started in Production mode`) + colors.none)
// console.log(client.commands) // console.log(client.commands)
}; };

@ -6,7 +6,7 @@ import client from "../client.js";
import Discord from "discord.js"; import Discord from "discord.js";
const colours = { const colors = {
red: "\x1b[31m", red: "\x1b[31m",
green: "\x1b[32m", green: "\x1b[32m",
none: "\x1b[0m" none: "\x1b[0m"
@ -23,7 +23,7 @@ export async function group(
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
console.log(`│ ├─ Loading group ${name}`) console.log(`│ ├─ Loading group ${name}`)
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + path, "│ ") 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) => { return (subcommandGroup: SlashCommandSubcommandGroupBuilder) => {
subcommandGroup subcommandGroup
.setName(name) .setName(name)
@ -54,7 +54,7 @@ export async function command(
// If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString // If the name of the command does not match the path (e.g. attachment.ts has /attachments), use commandString
commandString = "commands/" + (commandString ?? path); commandString = "commands/" + (commandString ?? path);
const fetched = await getSubcommandsInFolder(config.commandsFolder + "/" + 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}) // console.log({name: name, description: description})
client.commands[commandString!] = [undefined, { name: name, description: description }] client.commands[commandString!] = [undefined, { name: name, description: description }]
return (command: SlashCommandBuilder) => { return (command: SlashCommandBuilder) => {

@ -2,7 +2,7 @@ import getEmojiByName from "./getEmojiByName.js";
function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) { function pageIndicator(amount: number, selected: number, showDetails?: boolean, disabled?: boolean | string) {
let out = ""; let out = "";
disabled = disabled ? "GREY." : "" disabled = disabled ? "GRAY." : ""
if (amount === 1) { if (amount === 1) {
out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE")); out += getEmojiByName("TRACKS.SINGLE." + (disabled) + (selected === 0 ? "ACTIVE" : "INACTIVE"));
} else { } else {
@ -23,7 +23,7 @@ function pageIndicator(amount: number, selected: number, showDetails?: boolean,
export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => { export const verticalTrackIndicator = (position: number, active: string | boolean, size: number, disabled: string | boolean) => {
active = active ? "ACTIVE" : "INACTIVE"; active = active ? "ACTIVE" : "INACTIVE";
disabled = disabled ? "GREY." : ""; disabled = disabled ? "GRAY." : "";
if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active; if (position === 0 && size === 1) return "TRACKS.SINGLE." + disabled + active;
if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active; if (position === size - 1) return "TRACKS.VERTICAL.BOTTOM." + disabled + active;
if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active; if (position === 0) return "TRACKS.VERTICAL.TOP." + disabled + active;

Loading…
Cancel
Save