blob: 2cdd1f83f9f6e2bef4dc4d488f97f57a49aeeea1 [file] [log] [blame]
import type Discord from "discord.js";
import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, Message, ModalBuilder, RoleSelectMenuBuilder, RoleSelectMenuInteraction, StringSelectMenuBuilder, StringSelectMenuInteraction, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import { LoadingEmbed } from "../../utils/defaults.js";
import client from "../../utils/client.js";
import getEmojiByName from "../../utils/getEmojiByName.js";
import createPageIndicator from "../../utils/createPageIndicator.js";
import { configToDropdown } from "../../actions/roleMenu.js";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("rolemenu")
.setDescription("rolemenu")
interface ObjectSchema {
name: string;
description: string;
min: number;
max: number;
options: {
name: string;
description: string | null;
role: string;
}[];
}
const editNameDescription = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => {
let {name, description} = data;
const modal = new ModalBuilder()
.setTitle("Edit Name and Description")
.setCustomId("editNameDescription")
.addComponents(
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setCustomId("name")
.setPlaceholder(name ?? "")
.setStyle(TextInputStyle.Short)
.setRequired(true),
new TextInputBuilder()
.setCustomId("description")
.setPlaceholder(description ?? "")
.setStyle(TextInputStyle.Short)
)
)
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setLabel("Back")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
)
return [name, description]
}
const ellipsis = (str: string, max: number): string => {
if (str.length <= max) return str;
return str.slice(0, max - 3) + "...";
}
const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema) => {
if (!data) data = {
name: "Role Menu Page",
description: "A new role menu page",
min: 0,
max: 0,
options: []
};
const buttons = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setLabel("Back")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("edit")
.setLabel("Edit")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("addRole")
.setLabel("Add Role")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
);
let back = false
do {
const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options});
const embed = new EmojiEmbed()
.setTitle(`${data.name}`)
.setStatus("Success")
.setDescription(
`**Description:**\n> ${data.description}\n\n` +
`**Min:** ${data.min}` + (data.min === 0 ? " (Members will be given a skip button)" : "") + "\n" +
`**Max:** ${data.max}\n`
)
interaction.editReply({embeds: [embed], components: [previewSelect, buttons]});
let i: StringSelectMenuInteraction | ButtonInteraction;
try {
i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
} catch (e) {
back = true;
break;
}
if (i.isStringSelectMenu()) {
if(i.customId === "roles") {
await i.deferUpdate();
await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0]));
}
} else if (i.isButton()) {
await i.deferUpdate();
switch (i.customId) {
case "back":
back = true;
break;
case "edit":
let [name, description] = await editNameDescription(interaction, m, data);
data.name = name ? name : data.name;
data.description = description ? description : data.description;
break;
case "addRole":
data.options.push(await createRoleMenuOptionPage(interaction, m));
break;
}
}
} while (!back);
return data;
}
const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: {name: string; description: string | null; role: string}) => {
const { renderRole} = client.logger;
if (!data) data = {
name: "Role Menu Option",
description: null,
role: "No role set"
};
let back = false;
const buttons = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setLabel("Back")
.setStyle(ButtonStyle.Secondary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("edit")
.setLabel("Edit Details")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji)
);
do {
const roleSelect = new RoleSelectMenuBuilder().setCustomId("role").setPlaceholder(data.role ? "Set role to" : "Set the role");
const embed = new EmojiEmbed()
.setTitle(`${data.name ?? "New Role Menu Option"}`)
.setStatus("Success")
.setDescription(
`**Description:**\n> ${data.description ?? "No description set"}\n\n` +
`**Role:** ${renderRole((await interaction.guild!.roles.fetch(data.role))!) ?? "No role set"}\n`
)
interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<RoleSelectMenuBuilder>().addComponents(roleSelect), buttons]});
let i: RoleSelectMenuInteraction | ButtonInteraction;
try {
i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | RoleSelectMenuInteraction;
} catch (e) {
back = true;
break;
}
if (i.isRoleSelectMenu()) {
if(i.customId === "role") {
await i.deferUpdate();
data.role = (i as RoleSelectMenuInteraction).values[0]!;
}
} else if (i.isButton()) {
await i.deferUpdate();
switch (i.customId) {
case "back":
back = true;
break;
case "edit":
await i.deferUpdate();
let [name, description] = await editNameDescription(interaction, m, data as {name: string; description: string});
data.name = name ? name : data.name;
data.description = description ? description : data.description;
break;
}
}
} while (!back);
return data;
}
const callback = async (interaction: CommandInteraction): Promise<void> => {
if (!interaction.guild) return;
const m = await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true});
let page = 0;
let closed = false;
const config = await client.database.guilds.read(interaction.guild.id);
let currentObject: ObjectSchema[] = config.roleMenu.options;
let modified = false;
do {
const embed = new EmojiEmbed()
.setTitle("Role Menu Settings")
.setEmoji("GUILD.GREEN")
.setStatus("Success");
const noRoleMenus = currentObject.length === 0;
let current: ObjectSchema;
const pageSelect = new StringSelectMenuBuilder()
.setCustomId("page")
.setPlaceholder("Select a Role Menu page to manage");
const actionSelect = new StringSelectMenuBuilder()
.setCustomId("action")
.setPlaceholder("Perform an action")
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Edit")
.setDescription("Edit this page")
.setValue("edit")
.setEmoji(getEmojiByName("ICONS.EDIT", "id") as APIMessageComponentEmoji),
new StringSelectMenuOptionBuilder()
.setLabel("Delete")
.setDescription("Delete this page")
.setValue("delete")
.setEmoji(getEmojiByName("TICKETS.ISSUE", "id") as APIMessageComponentEmoji)
);
const buttonRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("back")
.setStyle(ButtonStyle.Primary)
.setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)
.setDisabled(page === 0),
new ButtonBuilder()
.setCustomId("next")
.setEmoji(getEmojiByName("CONTROL.RIGHT", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Primary)
.setDisabled(page === Object.keys(currentObject).length - 1),
new ButtonBuilder()
.setCustomId("add")
.setLabel("New Page")
.setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Secondary)
.setDisabled(Object.keys(currentObject).length >= 24),
new ButtonBuilder()
.setCustomId("reorder")
.setLabel("Reorder Pages")
.setEmoji(getEmojiByName("ICONS.SHUFFLE", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Secondary)
.setDisabled(Object.keys(currentObject).length <= 1),
new ButtonBuilder()
.setCustomId("save")
.setLabel("Save")
.setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
.setStyle(ButtonStyle.Success)
.setDisabled(!modified),
);
if(noRoleMenus) {
embed.setDescription("No role menu page have been set up yet. Use the button below to add one.\n\n" +
createPageIndicator(1, 1, undefined, true)
);
pageSelect.setDisabled(true);
actionSelect.setDisabled(true);
pageSelect.addOptions(new StringSelectMenuOptionBuilder()
.setLabel("No role menu pages")
.setValue("none")
);
} else {
page = Math.min(page, Object.keys(currentObject).length - 1);
current = currentObject[page]!;
embed.setDescription(`**Currently Editing:** ${current.name}\n\n` +
`**Description:** \`${current.description}\`\n` +
`\n\n${createPageIndicator(Object.keys(config.roleMenu.options).length, page)}`
);
pageSelect.addOptions(
currentObject.map((key: ObjectSchema, index) => {
return new StringSelectMenuOptionBuilder()
.setLabel(ellipsis(key.name, 50))
.setDescription(ellipsis(key.description, 50))
.setValue(index.toString());
})
);
}
await interaction.editReply({embeds: [embed], components: [new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(actionSelect), new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(pageSelect), buttonRow]});
let i: StringSelectMenuInteraction | ButtonInteraction;
try {
i = await m.awaitMessageComponent({ time: 300000, filter: (i) => i.user.id === interaction.user.id && i.message.id === m.id && i.channelId === interaction.channelId}) as ButtonInteraction | StringSelectMenuInteraction;
} catch (e) {
closed = true;
break;
}
await i.deferUpdate();
if (i.isButton()) {
switch (i.customId) {
case "back":
page--;
break;
case "next":
page++;
break;
case "add":
currentObject.push(await createRoleMenuPage(i, m));
page = currentObject.length - 1;
break;
case "reorder":
break;
case "save":
client.database.guilds.write(interaction.guild.id, {"roleMenu.options": currentObject});
modified = false;
break;
}
} else if (i.isStringSelectMenu()) {
switch (i.customId) {
case "action":
switch(i.values[0]) {
case "edit":
currentObject[page] = await createRoleMenuPage(i, m, current!);
modified = true;
break;
case "delete":
currentObject.splice(page, 1);
break;
}
break;
case "page":
page = parseInt(i.values[0]!);
break;
}
}
} while (!closed)
};
const check = (interaction: CommandInteraction, _partial: boolean = false) => {
const member = interaction.member as Discord.GuildMember;
if (!member.permissions.has("ManageRoles"))
return "You must have the *Manage Roles* permission to use this command";
return true;
};
export { command };
export { callback };
export { check };