Version Estable

This commit is contained in:
2025-09-17 13:33:10 -05:00
commit bf6a7e3024
39 changed files with 2537 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, TextChannel, ChannelType } from "discord.js";
//@ts-ignore
import { ButtonStyle, ComponentType } from "discord.js";
import { replaceVars } from "../../../core/lib/vars";
export const command: CommandMessage = {
name: "embedcreate",
type: "message",
aliases: ["crearembed", "newembed"],
cooldown: 20,
// @ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName: string | null = args[0] ?? null;
if (!embedName) {
return message.reply(
"Debes proporcionar un nombre para el embed. Uso: `!embedcreate <nombre>`"
);
}
const nameIsValid = await client.prisma.embedConfig.findFirst({ where: {
//@ts-ignore
guildId: message.guild.id,
name: embedName
}})
if(nameIsValid) return message.reply("❌ Nombre del embed ya fue tomado!")
// 📌 Estado independiente
let embedState: {
title?: string;
description?: string;
color?: number;
footer?: string;
} = {
title: `Editor de Embed: ${embedName}`,
description:
"Usa los botones de abajo para configurar este embed.\n\n_Ejemplo de variable: `{user.name}`_",
color: 0x5865f2,
footer: "Haz clic en Guardar cuando termines.",
};
// 📌 Función para construir un embed a partir del estado
const renderPreview = async () => {
const preview = new EmbedBuilder()
.setColor(embedState.color ?? 0x5865f2);
if (embedState.title)
preview.setTitle(
//@ts-ignore
await replaceVars(embedState.title, message.member)
);
if (embedState.description)
preview.setDescription(
//@ts-ignore
await replaceVars(embedState.description, message.member)
);
if (embedState.footer)
preview.setFooter({
//@ts-ignore
text: await replaceVars(embedState.footer, message.member),
});
return preview;
};
// 📌 Botones
const generateButtonRows = (disabled = false) => {
const primaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_title")
.setLabel("Título")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_description")
.setLabel("Descripción")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_color")
.setLabel("Color")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled)
);
const secondaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_footer")
.setLabel("Footer")
.setStyle(ButtonStyle.Secondary)
.setDisabled(disabled)
);
const controlRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("save_embed")
.setLabel("Guardar")
.setStyle(ButtonStyle.Success)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("cancel_embed")
.setLabel("Cancelar")
.setStyle(ButtonStyle.Danger)
.setDisabled(disabled)
);
return [primaryRow, secondaryRow, controlRow];
};
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
const editorMessage = await channel.send({
embeds: [await renderPreview()],
components: generateButtonRows(),
});
const collector = editorMessage.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 300000,
});
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id) {
await i.reply({
content: "No puedes usar este menú.",
ephemeral: true,
});
return;
}
await i.deferUpdate();
await editorMessage.edit({ components: generateButtonRows(true) });
// Guardar
if (i.customId === "save_embed") {
try {
const dataForDb = {
title: embedState.title,
description: embedState.description,
color: embedState.color ? `#${embedState.color.toString(16).padStart(6, '0')}` : null,
footerText: embedState.footer,
};
await client.prisma.embedConfig.upsert({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
update: dataForDb,
create: {
name: embedName,
...dataForDb,
// ✅ ESTA ES LA SOLUCIÓN:
// Le decimos a Prisma que se conecte al Guild o lo cree si no existe.
guild: {
connectOrCreate: {
where: { id: message.guildId! },
create: {
id: message.guildId!,
name: message.guild!.name, // Asegura que el nombre del servidor se guarde
},
},
},
},
});
const saved = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle(`✅ Guardado: ${embedName}`)
.setDescription("La configuración se guardó en la base de datos.");
await editorMessage.edit({
embeds: [saved],
components: [],
});
} catch (e) {
const errorEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("❌ Error al Guardar")
.setDescription("No se pudo guardar en la base de datos. Revisa la consola.");
await editorMessage.edit({
embeds: [errorEmbed],
components: [],
});
console.error("Error de Prisma al guardar el embed:", e);
}
collector.stop();
return;
}
// Cancelar
if (i.customId === "cancel_embed") {
await editorMessage.delete();
collector.stop();
return;
}
// Edición
let promptContent = "";
let fieldToEdit: "title" | "description" | "color" | "footer" | null =
null;
switch (i.customId) {
case "edit_title":
promptContent =
"Escribe el nuevo **título** (puedes usar variables como `{user.name}`).";
fieldToEdit = "title";
break;
case "edit_description":
promptContent =
"Escribe la nueva **descripción** (puedes usar variables).";
fieldToEdit = "description";
break;
case "edit_color":
promptContent =
"Escribe el nuevo **color** en formato hexadecimal (ej: `#FF0000`).";
fieldToEdit = "color";
break;
case "edit_footer":
promptContent =
"Escribe el nuevo **texto del footer** (puedes usar variables).";
fieldToEdit = "footer";
break;
}
//@ts-ignore
const promptMessage = await i.channel.send(promptContent);
//@ts-ignore
const messageCollector = i.channel!.createMessageCollector({
//@ts-ignore
filter: (m: Message) => m.author.id === i.user.id,
max: 1,
time: 60000,
});
//@ts-ignore
messageCollector.on("collect", async (collectedMessage) => {
const newValue = collectedMessage.content;
if (fieldToEdit === "title") embedState.title = newValue;
if (fieldToEdit === "description") embedState.description = newValue;
if (fieldToEdit === "footer") embedState.footer = newValue;
if (fieldToEdit === "color") {
try {
const hex = newValue.replace("#", "");
embedState.color = parseInt(hex, 16);
} catch {
embedState.color = 0x5865f2;
}
}
await collectedMessage.delete();
await promptMessage.delete();
await editorMessage.edit({
embeds: [await renderPreview()],
components: generateButtonRows(false),
});
});
//@ts-ignore
messageCollector.on("end", async (collected) => {
if (collected.size === 0) {
await promptMessage.delete();
await editorMessage.edit({
components: generateButtonRows(false),
});
}
});
});
collector.on("end", async (_, reason) => {
if (reason === "time") {
const timeoutEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("Editor finalizado por inactividad.");
await editorMessage.edit({
embeds: [timeoutEmbed],
components: [],
});
}
});
}
},
};

View File

@@ -0,0 +1,276 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, TextChannel, ChannelType } from "discord.js";
//@ts-ignore
import { ButtonStyle, ComponentType } from "discord.js";
import { replaceVars } from "../../../core/lib/vars";
export const command: CommandMessage = {
name: "editembed",
type: "message",
aliases: ["modembed", "updateembed"],
cooldown: 20,
// @ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName: string | null = args[0] ?? null;
if (!embedName) {
return message.reply(
"Debes proporcionar un nombre para el embed. Uso: `!editembed <nombre>`"
);
}
// 📌 Buscar en la base de datos
const existing = await client.prisma.embedConfig.findUnique({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
});
if (!existing) {
return message.reply("❌ No encontré un embed con ese nombre.");
}
// 📌 Estado inicial desde DB
let embedState: {
title?: string;
description?: string;
color?: number;
footer?: string;
} = {
title: existing.title ?? undefined,
description: existing.description ?? undefined,
color: existing.color ? parseInt(existing.color.replace("#", ""), 16) : 0x5865f2,
footer: existing.footerText ?? undefined,
};
// 📌 Función para renderizar preview
const renderPreview = async () => {
const preview = new EmbedBuilder().setColor(embedState.color ?? 0x5865f2);
if (embedState.title)
//@ts-ignore
preview.setTitle(await replaceVars(embedState.title, message.member));
if (embedState.description)
//@ts-ignore
preview.setDescription(await replaceVars(embedState.description, message.member));
if (embedState.footer)
preview.setFooter({
//@ts-ignore
text: await replaceVars(embedState.footer, message.member),
});
return preview;
};
const generateButtonRows = (disabled = false) => {
const primaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_title")
.setLabel("Título")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_description")
.setLabel("Descripción")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("edit_color")
.setLabel("Color")
.setStyle(ButtonStyle.Primary)
.setDisabled(disabled)
);
const secondaryRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("edit_footer")
.setLabel("Footer")
.setStyle(ButtonStyle.Secondary)
.setDisabled(disabled)
);
const controlRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId("save_embed")
.setLabel("Guardar cambios")
.setStyle(ButtonStyle.Success)
.setDisabled(disabled),
new ButtonBuilder()
.setCustomId("cancel_embed")
.setLabel("Cancelar")
.setStyle(ButtonStyle.Danger)
.setDisabled(disabled)
);
return [primaryRow, secondaryRow, controlRow];
};
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
const editorMessage = await channel.send({
embeds: [await renderPreview()],
components: generateButtonRows(),
});
const collector = editorMessage.createMessageComponentCollector({
componentType: ComponentType.Button,
time: 300000,
});
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id) {
await i.reply({
content: "No puedes usar este menú.",
ephemeral: true,
});
return;
}
await i.deferUpdate();
await editorMessage.edit({ components: generateButtonRows(true) });
// Guardar cambios
if (i.customId === "save_embed") {
try {
const dataForDb = {
title: embedState.title,
description: embedState.description,
color: embedState.color ? `#${embedState.color.toString(16).padStart(6, '0')}` : null,
footerText: embedState.footer,
};
await client.prisma.embedConfig.update({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
data: dataForDb,
});
const saved = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle(`✅ Actualizado: ${embedName}`)
.setDescription("Los cambios fueron guardados en la base de datos.");
await editorMessage.edit({
embeds: [saved],
components: [],
});
} catch (e) {
const errorEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("❌ Error al Guardar")
.setDescription("No se pudo guardar en la base de datos. Revisa la consola.");
await editorMessage.edit({
embeds: [errorEmbed],
components: [],
});
console.error("Error de Prisma al actualizar el embed:", e);
}
collector.stop();
return;
}
// Cancelar
if (i.customId === "cancel_embed") {
await editorMessage.delete();
collector.stop();
return;
}
// Edición
let promptContent = "";
let fieldToEdit: "title" | "description" | "color" | "footer" | null =
null;
switch (i.customId) {
case "edit_title":
promptContent = "Escribe el nuevo **título** (puedes usar variables).";
fieldToEdit = "title";
break;
case "edit_description":
promptContent = "Escribe la nueva **descripción**.";
fieldToEdit = "description";
break;
case "edit_color":
promptContent = "Escribe el nuevo **color** en formato hexadecimal (ej: `#FF0000`).";
fieldToEdit = "color";
break;
case "edit_footer":
promptContent = "Escribe el nuevo **texto del footer**.";
fieldToEdit = "footer";
break;
}
//@ts-ignore
const promptMessage = await i.channel.send(promptContent);
//@ts-ignore
const messageCollector = i.channel!.createMessageCollector({
//@ts-ignore
filter: (m: Message) => m.author.id === i.user.id,
max: 1,
time: 60000,
});
//@ts-ignore
messageCollector.on("collect", async (collectedMessage) => {
const newValue = collectedMessage.content;
if (fieldToEdit === "title") embedState.title = newValue;
if (fieldToEdit === "description") embedState.description = newValue;
if (fieldToEdit === "footer") embedState.footer = newValue;
if (fieldToEdit === "color") {
try {
const hex = newValue.replace("#", "");
embedState.color = parseInt(hex, 16);
} catch {
embedState.color = 0x5865f2;
}
}
await collectedMessage.delete();
await promptMessage.delete();
await editorMessage.edit({
embeds: [await renderPreview()],
components: generateButtonRows(false),
});
});
//@ts-ignore
messageCollector.on("end", async (collected) => {
if (collected.size === 0) {
await promptMessage.delete();
await editorMessage.edit({
components: generateButtonRows(false),
});
}
});
});
collector.on("end", async (_, reason) => {
if (reason === "time") {
const timeoutEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle("Editor finalizado por inactividad.");
await editorMessage.edit({
embeds: [timeoutEmbed],
components: [],
});
}
});
}
},
};

View File

@@ -0,0 +1,34 @@
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "embeddelete",
type: "message",
aliases: ["delembed", "removeembed"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embedName = args[0];
if (!embedName) {
return message.reply("Debes proporcionar el nombre del embed a eliminar. Uso: `!embeddelete <nombre>`");
}
try {
await client.prisma.embedConfig.delete({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
});
return message.reply(`✅ El embed **${embedName}** fue eliminado con éxito.`);
} catch {
return message.reply("❌ No encontré un embed con ese nombre.");
}
},
};

View File

@@ -0,0 +1,75 @@
import {CommandMessage} from "../../../core/types/commands";
import {
//@ts-ignore
ChannelType,
ContainerBuilder,
//@ts-ignore
MessageFlags,
SectionBuilder,
SeparatorBuilder,
//@ts-ignore
SeparatorSpacingSize,
TextChannel,
TextDisplayBuilder
} from "discord.js";
export const command: CommandMessage = {
name: "embedlist",
type: "message",
aliases: ["listembeds", "embeds"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embeds = await client.prisma.embedConfig.findMany({
where: { guildId: message.guildId! },
});
if (embeds.length === 0) {
return message.reply("📭 No hay ningún embed guardado en este servidor.");
}
const title = new TextDisplayBuilder()
.setContent('﹒⌒    Embed List    ╰୧﹒');
// Combina la lista de embeds en la misma sección que la miniatura
// para un mejor diseño.
//@ts-ignore
const embedListContent = embeds.map((e, i) => `**${i + 1}.** ${e.name}`).join("\n");
// Obtenemos la URL del icono de forma segura
const guildIconURL = message.guild?.iconURL({ forceStatic: false });
// Creamos la sección que contendrá el texto Y la miniatura
const mainSection = new SectionBuilder()
.addTextDisplayComponents(text => text.setContent(embedListContent)); // <--- Componente principal requerido
// Solo añadimos la miniatura si la URL existe
if (guildIconURL) {
//@ts-ignore
mainSection.setThumbnailAccessory(thumbnail => thumbnail
.setURL(guildIconURL)
.setDescription('Icono del servidor')
);
}
const separator = new SeparatorBuilder()
.setSpacing(SeparatorSpacingSize.Large)
.setDivider(false);
const container = new ContainerBuilder()
.setAccentColor(0x49225B)
.addTextDisplayComponents(title)
.addSeparatorComponents(separator)
.addSectionComponents(mainSection); // <--- Añadimos la sección ya completa
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
await channel.send({ components: [container], flags: MessageFlags.IsComponentsV2});
}
},
};