No mucho solo pequeños detalles

This commit is contained in:
2025-09-20 00:00:39 -05:00
parent 8f86650fa6
commit 97fa4b9acf
11 changed files with 3109 additions and 548 deletions

Binary file not shown.

View File

@@ -1,6 +1,6 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { ComponentType, ButtonStyle, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, Message } from "discord.js";
import { ComponentType, ButtonStyle, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, Message, MessageFlags } from "discord.js";
import { replaceVars } from "../../../core/lib/vars";
/**
@@ -168,8 +168,9 @@ const renderPreview = async (blockState: any, member: any, guild: any) => {
};
export const command: CommandMessage = {
name: "blockcreatev2",
name: "crear-embed",
type: "message",
aliases: ["embed-crear", "nuevo-embed", "blockcreatev2"],
cooldown: 20,
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
@@ -233,7 +234,7 @@ export const command: CommandMessage = {
collector.on("collect", async (i: any) => {
if (i.user.id !== message.author.id) {
await i.reply({ content: "No puedes usar este menú.", ephemeral: true });
await i.reply({ content: "No puedes usar este menú.", flags: MessageFlags.Ephemeral });
return;
}
@@ -841,18 +842,20 @@ export const command: CommandMessage = {
}
});
// Agregar manejo de modales
//@ts-ignore
client.on('interactionCreate', async (interaction) => {
// Agregar manejo de modales mejorado con mejor gestión de errores
let modalHandlerActive = true;
const modalHandler = async (interaction: any) => {
if (!interaction.isModalSubmit()) return;
if (interaction.user.id !== message.author.id) return;
if (!interaction.customId.endsWith('_modal')) return;
if (!modalHandlerActive) return; // Evitar procesar si ya no está activo
try {
switch (interaction.customId) {
case 'edit_title_modal': {
blockState.title = interaction.fields.getTextInputValue('title_input');
await interaction.reply({ content: '✅ Título actualizado.', ephemeral: true });
await interaction.reply({ content: '✅ Título actualizado.', flags: MessageFlags.Ephemeral });
break;
}
case 'edit_description_modal': {
@@ -863,7 +866,7 @@ export const command: CommandMessage = {
} else {
blockState.components.push({ type: 10, content: newDescription, thumbnail: null });
}
await interaction.reply({ content: '✅ Descripción actualizada.', ephemeral: true });
await interaction.reply({ content: '✅ Descripción actualizada.', flags: MessageFlags.Ephemeral });
break;
}
case 'edit_color_modal': {
@@ -875,26 +878,26 @@ export const command: CommandMessage = {
if (/^[0-9A-F]{6}$/i.test(hexColor)) {
blockState.color = parseInt(hexColor, 16);
} else {
await interaction.reply({ content: '❌ Color inválido. Usa formato HEX (#FF5733)', ephemeral: true });
await interaction.reply({ content: '❌ Color inválido. Usa formato HEX (#FF5733)', flags: MessageFlags.Ephemeral });
return;
}
}
await interaction.reply({ content: '✅ Color actualizado.', ephemeral: true });
await interaction.reply({ content: '✅ Color actualizado.', flags: MessageFlags.Ephemeral });
break;
}
case 'add_content_modal': {
const newContent = interaction.fields.getTextInputValue('content_input');
blockState.components.push({ type: 10, content: newContent, thumbnail: null });
await interaction.reply({ content: '✅ Contenido añadido.', ephemeral: true });
await interaction.reply({ content: '✅ Contenido añadido.', flags: MessageFlags.Ephemeral });
break;
}
case 'add_image_modal': {
const imageUrl = interaction.fields.getTextInputValue('image_url_input');
if (isValidUrl(imageUrl)) {
blockState.components.push({ type: 12, url: imageUrl });
await interaction.reply({ content: '✅ Imagen añadida.', ephemeral: true });
await interaction.reply({ content: '✅ Imagen añadida.', flags: MessageFlags.Ephemeral });
} else {
await interaction.reply({ content: '❌ URL de imagen inválida.', ephemeral: true });
await interaction.reply({ content: '❌ URL de imagen inválida.', flags: MessageFlags.Ephemeral });
return;
}
break;
@@ -904,9 +907,9 @@ export const command: CommandMessage = {
const coverUrl = interaction.fields.getTextInputValue('cover_input');
if (isValidUrl(coverUrl)) {
blockState.coverImage = coverUrl;
await interaction.reply({ content: '✅ Imagen de portada actualizada.', ephemeral: true });
await interaction.reply({ content: '✅ Imagen de portada actualizada.', flags: MessageFlags.Ephemeral });
} else {
await interaction.reply({ content: '❌ URL de portada inválida.', ephemeral: true });
await interaction.reply({ content: '❌ URL de portada inválida.', flags: MessageFlags.Ephemeral });
return;
}
break;
@@ -919,7 +922,7 @@ export const command: CommandMessage = {
const spacing = Math.min(3, Math.max(1, parseInt(spacingStr) || 1));
blockState.components.push({ type: 14, divider, spacing });
await interaction.reply({ content: '✅ Separador añadido.', ephemeral: true });
await interaction.reply({ content: '✅ Separador añadido.', flags: MessageFlags.Ephemeral });
break;
}
case 'edit_thumbnail_modal': {
@@ -927,12 +930,18 @@ export const command: CommandMessage = {
const textComp = blockState.components.find((c: any) => c.type === 10);
if (textComp) {
if (thumbnailUrl.trim() === '' || !isValidUrl(thumbnailUrl)) {
if (thumbnailUrl.trim() === '') {
// Si está vacío, eliminar thumbnail
textComp.thumbnail = null;
await interaction.reply({ content: '✅ Thumbnail eliminado.', ephemeral: true });
await interaction.reply({ content: '✅ Thumbnail eliminado.', flags: MessageFlags.Ephemeral });
} else if (!isValidUrl(thumbnailUrl)) {
// Si no es una URL válida, mostrar error
await interaction.reply({ content: '❌ URL de thumbnail inválida.', flags: MessageFlags.Ephemeral });
return;
} else {
// Si es una URL válida, añadir thumbnail
textComp.thumbnail = thumbnailUrl;
await interaction.reply({ content: '✅ Thumbnail actualizado.', ephemeral: true });
await interaction.reply({ content: '✅ Thumbnail actualizado.', flags: MessageFlags.Ephemeral });
}
}
break;
@@ -951,13 +960,13 @@ export const command: CommandMessage = {
components: Array.isArray(importedData.components) ? importedData.components : blockState.components
};
await interaction.reply({ content: '✅ JSON importado correctamente.', ephemeral: true });
await interaction.reply({ content: '✅ JSON importado correctamente.', flags: MessageFlags.Ephemeral });
} else {
await interaction.reply({ content: '❌ Estructura JSON inválida.', ephemeral: true });
await interaction.reply({ content: '❌ Estructura JSON inválida.', flags: MessageFlags.Ephemeral });
return;
}
} catch (error) {
await interaction.reply({ content: '❌ JSON inválido. Verifica el formato.', ephemeral: true });
await interaction.reply({ content: '❌ JSON inválido. Verifica el formato.', flags: MessageFlags.Ephemeral });
return;
}
break;
@@ -966,33 +975,68 @@ export const command: CommandMessage = {
return;
}
// Actualizar la vista previa después de cada cambio en el modal
// Actualizar la vista previa después de cada cambio en el modal con mejor manejo de errores
setTimeout(async () => {
if (!modalHandlerActive) return; // Evitar actualizar si ya no está activo
try {
// Verificar si el mensaje aún existe antes de intentar editarlo
const messageExists = await editorMessage.fetch().catch(() => null);
if (!messageExists) {
console.log('El mensaje del editor ya no existe');
return;
}
await editorMessage.edit({
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
});
} catch (error) {
console.error('Error actualizando preview:', error);
} catch (error: any) {
// Manejar diferentes tipos de errores
if (error.code === 10008) {
console.log('Mensaje del editor eliminado');
} else if (error.code === 10062) {
console.log('Interacción expirada');
} else {
console.error('Error actualizando preview:', error.message || error);
}
}
}, 1000);
} catch (error) {
} catch (error: any) {
console.error('Error en modal:', error);
try {
await interaction.reply({ content: '❌ Error procesando el modal.', ephemeral: true });
} catch {}
// Solo intentar responder si la interacción no ha expirado
if (error.code !== 10062 && !interaction.replied && !interaction.deferred) {
await interaction.reply({ content: '❌ Error procesando el modal.', flags: MessageFlags.Ephemeral });
}
} catch (replyError) {
console.log('No se pudo responder a la interacción (probablemente expirada)');
}
}
});
};
// Registrar el manejador de modales
client.on('interactionCreate', modalHandler);
//@ts-ignore
collector.on("end", async (_, reason) => {
// Desactivar el manejador de modales cuando el collector termine
modalHandlerActive = false;
client.off('interactionCreate', modalHandler);
if (reason === "time") {
await editorMessage.edit({
components: [
{ type: 17, components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }] }
]
});
try {
const messageExists = await editorMessage.fetch().catch(() => null);
if (messageExists) {
await editorMessage.edit({
components: [
{ type: 17, components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }] }
]
});
}
} catch (error) {
console.log('No se pudo actualizar el mensaje final');
}
}
});
}

View File

@@ -0,0 +1,404 @@
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "displaydemo",
type: "message",
aliases: ["ddemo", "componentsdemo"],
cooldown: 10,
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
await message.reply("❌ No tienes permisos de Administrador.");
return;
}
// 🎯 DEMOSTRACIÓN COMPLETA DE DISPLAYCOMPONENTS CON ACCESORIOS
// Panel principal con accessory de thumbnail
const mainPanel = {
type: 17, // Container
accent_color: 0x5865f2,
components: [
{
type: 10, // TextDisplay
content: "🎨 **Demostración de DisplayComponents Avanzados**"
},
{
type: 14, // Separator
divider: true,
spacing: 2
},
// Sección con accessory de botón
{
type: 9, // Section
components: [
{
type: 10,
content: "🔘 **Sección con Botón Accesorio**\n\nEste texto aparece junto a un botón como accesorio. Los accesorios permiten añadir elementos interactivos sin ocupar una fila completa."
}
],
accessory: {
type: 2, // Button
style: 1, // Primary
label: "Acción Rápida",
custom_id: "quick_action",
emoji: { name: "⚡" }
}
},
{
type: 14,
divider: true,
spacing: 1
},
// Sección con accessory de thumbnail
{
type: 9, // Section
components: [
{
type: 10,
content: "🖼️ **Sección con Thumbnail**\n\nAquí se muestra texto con una imagen en miniatura como accesorio. Perfecto para mostrar íconos de servidores, avatares o logotipos."
}
],
accessory: {
type: 11, // Thumbnail
media: {
url: message.guild?.iconURL({ forceStatic: false }) || "https://cdn.discordapp.com/embed/avatars/0.png"
}
}
},
{
type: 14,
divider: true,
spacing: 1
},
// Sección con accessory de link button
{
type: 9, // Section
components: [
{
type: 10,
content: "🔗 **Sección con Botón de Enlace**\n\nEste tipo de accesorio permite enlaces externos directos sin necesidad de interacciones complejas."
}
],
accessory: {
type: 2, // Button
style: 5, // Link
label: "Ir a Discord",
url: "https://discord.com",
emoji: { name: "🚀" }
}
}
]
};
// Fila de botones normales para interacción
const actionRow = {
type: 1, // ActionRow
components: [
{
type: 2,
style: 3, // Success
label: "✨ Más Ejemplos",
custom_id: "show_more_examples"
},
{
type: 2,
style: 2, // Secondary
label: "🔄 Cambiar Estilos",
custom_id: "change_styles"
},
{
type: 2,
style: 4, // Danger
label: "❌ Cerrar",
custom_id: "close_demo"
}
]
};
const demoMessage = await message.reply({
flags: 4096, // SuppressEmbeds
components: [mainPanel, actionRow]
});
const collector = demoMessage.createMessageComponentCollector({
time: 300000, // 5 minutos
filter: (i: any) => i.user.id === message.author.id
});
collector.on("collect", async (interaction: any) => {
switch (interaction.customId) {
case "quick_action":
await interaction.reply({
content: "⚡ **Acción Rápida Ejecutada!**\n\nEste botón estaba como accesorio en una sección.",
flags: 64 // Ephemeral
});
break;
case "show_more_examples":
// Panel con múltiples ejemplos de accesorios
const examplesPanel = {
type: 17,
accent_color: 0xff9500,
components: [
{
type: 10,
content: "🎯 **Más Ejemplos de Accesorios**"
},
{
type: 14,
divider: true,
spacing: 2
},
// Ejemplo con avatar del usuario
{
type: 9,
components: [
{
type: 10,
content: `👤 **Perfil de ${message.author.username}**\n\nEjemplo usando tu avatar como thumbnail accesorio.`
}
],
accessory: {
type: 11,
media: {
url: message.author.displayAvatarURL({ forceStatic: false })
}
}
},
{
type: 14,
divider: false,
spacing: 1
},
// Ejemplo con botón de estilo diferente
{
type: 9,
components: [
{
type: 10,
content: "🎨 **Botones con Diferentes Estilos**\n\nLos accesorios pueden tener distintos estilos y emojis personalizados."
}
],
accessory: {
type: 2,
style: 4, // Danger
label: "Peligro",
custom_id: "danger_button",
emoji: { name: "⚠️" }
}
},
{
type: 14,
divider: false,
spacing: 1
},
// Imagen como accessory
{
type: 9,
components: [
{
type: 10,
content: "🖼️ **Imágenes Personalizadas**\n\nTambién puedes usar imágenes personalizadas, íconos de servidores invitados, etc."
}
],
accessory: {
type: 11,
media: {
url: "https://cdn.discordapp.com/attachments/123/456/discord-logo.png"
}
}
}
]
};
await interaction.update({
components: [examplesPanel, {
type: 1,
components: [
{
type: 2,
style: 2, // Secondary
label: "↩️ Volver",
custom_id: "back_to_main"
}
]
}]
});
break;
case "change_styles":
// Panel mostrando diferentes combinaciones de estilos
const stylesPanel = {
type: 17,
accent_color: 0x57f287,
components: [
{
type: 10,
content: "🎨 **Galería de Estilos**"
},
{
type: 14,
divider: true,
spacing: 2
},
// Botón Primary como accesorio
{
type: 9,
components: [
{
type: 10,
content: "🔵 **Botón Primary (Azul)**\nEstilo: 1 - Para acciones principales"
}
],
accessory: {
type: 2,
style: 1, // Primary
label: "Principal",
custom_id: "style_primary"
}
},
// Botón Secondary como accesorio
{
type: 9,
components: [
{
type: 10,
content: "⚫ **Botón Secondary (Gris)**\nEstilo: 2 - Para acciones secundarias"
}
],
accessory: {
type: 2,
style: 2, // Secondary
label: "Secundario",
custom_id: "style_secondary"
}
},
// Botón Success como accesorio
{
type: 9,
components: [
{
type: 10,
content: "🟢 **Botón Success (Verde)**\nEstilo: 3 - Para confirmar acciones"
}
],
accessory: {
type: 2,
style: 3, // Success
label: "Confirmar",
custom_id: "style_success"
}
},
// Botón Danger como accesorio
{
type: 9,
components: [
{
type: 10,
content: "🔴 **Botón Danger (Rojo)**\nEstilo: 4 - Para acciones destructivas"
}
],
accessory: {
type: 2,
style: 4, // Danger
label: "Eliminar",
custom_id: "style_danger"
}
}
]
};
await interaction.update({
components: [stylesPanel, {
type: 1,
components: [
{
type: 2,
style: 2,
label: "↩️ Volver",
custom_id: "back_to_main"
}
]
}]
});
break;
case "danger_button":
case "style_primary":
case "style_secondary":
case "style_success":
case "style_danger":
await interaction.reply({
content: `🎯 **Botón ${interaction.customId.replace('style_', '').replace('_', ' ')} activado!**\n\nEste botón era un accesorio de una sección.`,
flags: 64 // Ephemeral
});
break;
case "back_to_main":
await interaction.update({
components: [mainPanel, actionRow]
});
break;
case "close_demo":
const closedPanel = {
type: 17,
accent_color: 0x36393f,
components: [
{
type: 10,
content: "✅ **Demostración Finalizada**"
},
{
type: 14,
divider: true,
spacing: 1
},
{
type: 10,
content: "Gracias por probar DisplayComponents con accesorios!\n\n💡 **Recuerda:** Los accesorios son ideales para:\n• Botones de acción rápida\n• Thumbnails e íconos\n• Enlaces externos\n• Elementos decorativos"
}
]
};
await interaction.update({
components: [closedPanel]
});
collector.stop();
break;
}
});
collector.on("end", async (collected: any, reason: string) => {
if (reason === "time") {
try {
const timeoutPanel = {
type: 17,
accent_color: 0x36393f,
components: [
{
type: 10,
content: "⏰ **Demostración Expirada**"
},
{
type: 14,
divider: true,
spacing: 1
},
{
type: 10,
content: "La demostración ha expirado por inactividad.\nUsa `!displaydemo` nuevamente para verla."
}
]
};
await demoMessage.edit({
components: [timeoutPanel]
});
} catch (error) {
// Mensaje eliminado o error de edición
}
}
});
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,217 @@
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "embeddelete",
name: "eliminar-embed",
type: "message",
aliases: ["delembed", "removeembed"],
aliases: ["embed-eliminar", "borrar-embed", "embeddelete"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
run: async (message: any, args: string[], client: any) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
await message.reply("❌ No tienes permisos de Administrador.");
return;
}
const embedName = args[0];
if (!embedName) {
return message.reply("Debes proporcionar el nombre del embed a eliminar. Uso: `!embeddelete <nombre>`");
}
// Obtener todos los bloques del servidor
const blocks = await client.prisma.blockV2Config.findMany({
where: { guildId: message.guildId! },
select: { name: true, id: true }
});
try {
await client.prisma.blockV2Config.delete({
where: {
guildId_name: {
guildId: message.guildId!,
name: embedName,
},
},
if (blocks.length === 0) {
const noBlocksEmbed = {
color: 0xf04747,
title: "🗂️ Panel de Eliminación de Bloques",
description: "📭 **No hay bloques disponibles**\n\nNo se encontraron bloques para eliminar en este servidor.\n\nPuedes crear nuevos bloques usando `!blockcreate`.",
footer: {
text: "Sistema de gestión de bloques • Amayo Bot"
}
};
await message.reply({
embeds: [noBlocksEmbed]
});
return message.reply(`✅ El embed **${embedName}** fue eliminado con éxito.`);
} catch {
return message.reply("❌ No encontré un embed con ese nombre.");
return;
}
// Crear opciones para el select menu
const selectOptions = blocks.slice(0, 25).map((block: any, index: number) => ({
label: block.name,
value: block.name,
description: `ID: ${block.id}`,
emoji: index < 10 ? { name: `${index + 1}️⃣` } : { name: "📄" }
}));
// Crear embed principal de eliminación
const deleteEmbed = {
color: 0xff6b35,
title: "🗑️ Panel de Eliminación de Bloques",
description: `📊 **${blocks.length} bloque(s) encontrado(s)**\n\n⚠ **ADVERTENCIA:** La eliminación es permanente e irreversible.\n\nSelecciona el bloque que deseas eliminar del menú de abajo:`,
footer: {
text: "Selecciona un bloque para eliminar • Timeout: 5 minutos"
}
};
const actionRow = {
type: 1,
components: [
{
type: 3, // StringSelect
custom_id: "delete_block_select",
placeholder: "🗑️ Selecciona un bloque para eliminar...",
min_values: 1,
max_values: 1,
options: selectOptions
}
]
};
const cancelRow = {
type: 1,
components: [
{
type: 2, // Button
style: 4, // Danger
label: "❌ Cancelar",
custom_id: "cancel_delete"
}
]
};
const panelMessage = await message.reply({
embeds: [deleteEmbed],
components: [actionRow, cancelRow]
});
const collector = panelMessage.createMessageComponentCollector({
time: 300000, // 5 minutos
filter: (i: any) => i.user.id === message.author.id
});
collector.on("collect", async (interaction: any) => {
if (interaction.customId === "cancel_delete") {
const canceledEmbed = {
color: 0x36393f,
title: "❌ Operación Cancelada",
description: "La eliminación de bloques ha sido cancelada.\nNingún bloque fue eliminado.",
footer: { text: "Operación cancelada por el usuario" }
};
await interaction.update({
embeds: [canceledEmbed],
components: []
});
collector.stop("cancelled");
return;
}
if (interaction.customId === "delete_block_select" && interaction.isStringSelectMenu()) {
const selectedBlock = interaction.values[0];
const confirmationEmbed = {
color: 0xf04747,
title: "⚠️ CONFIRMAR ELIMINACIÓN",
description: `🗑️ **Bloque a eliminar:** \`${selectedBlock}\`\n\n❗ **ESTA ACCIÓN ES IRREVERSIBLE**\n\nUna vez eliminado, no podrás recuperar:\n• Toda la configuración del bloque\n• Los componentes y contenido\n• Las imágenes y colores personalizados\n\n¿Estás seguro de que quieres continuar?`,
footer: { text: "⚠️ Acción irreversible - Piénsalo bien" }
};
const confirmationRow = {
type: 1,
components: [
{
type: 2,
style: 4, // Danger
label: "🗑️ SÍ, ELIMINAR",
custom_id: `confirm_delete_${selectedBlock}`
},
{
type: 2,
style: 2, // Secondary
label: "↩️ Volver Atrás",
custom_id: "back_to_selection"
}
]
};
await interaction.update({
embeds: [confirmationEmbed],
components: [confirmationRow]
});
return;
}
if (interaction.customId.startsWith("confirm_delete_")) {
const blockName = interaction.customId.replace("confirm_delete_", "");
try {
await client.prisma.blockV2Config.delete({
where: {
guildId_name: {
guildId: message.guildId!,
name: blockName,
},
},
});
const successEmbed = {
color: 0x57f287,
title: "✅ Eliminación Exitosa",
description: `🗑️ **Bloque eliminado:** \`${blockName}\`\n\n✨ El bloque ha sido eliminado permanentemente de la base de datos.\n\n📋 Para ver los bloques restantes, usa: \`!embedlist\`\n📝 Para crear un nuevo bloque, usa: \`!blockcreate\``,
footer: { text: "Bloque eliminado exitosamente" }
};
await interaction.update({
embeds: [successEmbed],
components: []
});
collector.stop("success");
} catch (error) {
const errorEmbed = {
color: 0xf04747,
title: "❌ Error en la Eliminación",
description: `🔍 **Bloque no encontrado:** \`${blockName}\`\n\n💭 Posibles causas:\n• El bloque ya fue eliminado\n• Error de conexión con la base de datos\n• El nombre del bloque cambió\n\n🔄 Intenta refrescar la lista con \`!embedlist\``,
footer: { text: "Error de eliminación" }
};
await interaction.update({
embeds: [errorEmbed],
components: []
});
collector.stop("error");
}
return;
}
if (interaction.customId === "back_to_selection") {
await interaction.update({
embeds: [deleteEmbed],
components: [actionRow, cancelRow]
});
return;
}
});
collector.on("end", async (collected: any, reason: string) => {
if (reason === "time") {
const timeoutEmbed = {
color: 0x36393f,
title: "⏰ Tiempo Agotado",
description: "El panel de eliminación ha expirado por inactividad.\nUsa el comando nuevamente si necesitas eliminar bloques.",
footer: { text: "Panel expirado por inactividad" }
};
try {
await panelMessage.edit({
embeds: [timeoutEmbed],
components: []
});
} catch (error) {
// Mensaje ya eliminado o error de edición
}
}
});
},
};

View File

@@ -1,75 +1,402 @@
import {CommandMessage} from "../../../core/types/commands";
import {
//@ts-ignore
ChannelType,
ContainerBuilder,
//@ts-ignore
MessageFlags,
SectionBuilder,
SeparatorBuilder,
//@ts-ignore
SeparatorSpacingSize,
TextChannel,
TextDisplayBuilder
} from "discord.js";
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: "embedlist",
name: "lista-embeds",
type: "message",
aliases: ["listembeds", "embeds"],
aliases: ["embeds", "ver-embeds", "embedlist"],
cooldown: 10,
//@ts-ignore
run: async (message, args, client) => {
run: async (message: any, args: string[], client: any) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
const embeds = await client.prisma.blockV2Config.findMany({
const blocks = await client.prisma.blockV2Config.findMany({
where: { guildId: message.guildId! },
select: {
name: true,
id: true,
config: true
},
orderBy: { name: 'asc' }
});
if (embeds.length === 0) {
return message.reply("📭 No hay ningún embed guardado en este servidor.");
if (blocks.length === 0) {
const emptyEmbed = {
color: 0x5865f2,
title: "📚 Centro de Gestión de Bloques",
description: "📭 **No hay bloques disponibles**\n\nEste servidor aún no tiene bloques configurados.\n\n🚀 **¿Quieres empezar?**\n• Usa `!crear-embed <nombre>` para crear tu primer bloque\n• Usa `!editar-embed <nombre>` para editar bloques existentes",
footer: { text: "Sistema de gestión de bloques • Amayo Bot" }
};
const createRow = {
type: 1,
components: [
{
type: 2,
style: 3,
label: "📝 Crear Primer Bloque",
custom_id: "show_create_help"
}
]
};
const helpMessage = await message.reply({
embeds: [emptyEmbed],
components: [createRow]
});
const helpCollector = helpMessage.createMessageComponentCollector({
time: 60000,
filter: (i: any) => i.user.id === message.author.id
});
helpCollector.on("collect", async (interaction: any) => {
if (interaction.customId === "show_create_help") {
const helpEmbed = {
color: 0x57f287,
title: "📖 Guía de Creación de Bloques",
description: "🔧 **Comandos disponibles:**\n\n• `!crear-embed <nombre>` - Crear nuevo bloque\n• `!editar-embed <nombre>` - Editar bloque existente\n• `!eliminar-embed <nombre>` - Eliminar bloque\n• `!lista-embeds` - Ver todos los bloques\n\n💡 **Tip:** Los bloques permiten crear interfaces modernas e interactivas.",
footer: { text: "Guía de comandos de creación" }
};
await interaction.update({
embeds: [helpEmbed],
components: []
});
}
});
return;
}
const title = new TextDisplayBuilder()
.setContent('﹒⌒    Embed List    ╰୧﹒');
// Dividir bloques en páginas de 5
const itemsPerPage = 5;
const totalPages = Math.ceil(blocks.length / itemsPerPage);
let currentPage = 0;
// 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");
const generateBlockListEmbed = (page: number) => {
const startIndex = page * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, blocks.length);
const pageBlocks = blocks.slice(startIndex, endIndex);
// Obtenemos la URL del icono de forma segura
const guildIconURL = message.guild?.iconURL({ forceStatic: false });
let blockListText = `📊 **Página ${page + 1} de ${totalPages}** (${blocks.length} total)\n\n`;
// Creamos la sección que contendrá el texto Y la miniatura
const mainSection = new SectionBuilder()
.addTextDisplayComponents(text => text.setContent(embedListContent)); // <--- Componente principal requerido
pageBlocks.forEach((block: any, index: number) => {
const globalIndex = startIndex + index + 1;
const componentsCount = Array.isArray(block.config?.components) ? block.config.components.length : 0;
const hasImage = block.config?.coverImage ? "🖼️" : "";
// Solo añadimos la miniatura si la URL existe
if (guildIconURL) {
//@ts-ignore
mainSection.setThumbnailAccessory(thumbnail => thumbnail
.setURL(guildIconURL)
.setDescription('Icono del servidor')
);
}
blockListText += `**${globalIndex}.** \`${block.name}\` ${hasImage}\n`;
blockListText += `${componentsCount} componente(s) • ID: ${block.id.slice(-8)}\n\n`;
});
const separator = new SeparatorBuilder()
.setSpacing(SeparatorSpacingSize.Large)
.setDivider(false);
return {
color: 0x5865f2,
title: "📚 Centro de Gestión de Bloques",
description: blockListText,
footer: { text: `Página ${page + 1}/${totalPages}${blocks.length} bloques total` }
};
};
const container = new ContainerBuilder()
.setAccentColor(0x49225B)
.addTextDisplayComponents(title)
.addSeparatorComponents(separator)
.addSectionComponents(mainSection); // <--- Añadimos la sección ya completa
const generateActionRows = (page: number) => {
const rows = [];
// Select menu para acciones rápidas
const currentPageBlocks = blocks.slice(page * itemsPerPage, (page + 1) * itemsPerPage);
if (currentPageBlocks.length > 0) {
const selectOptions = currentPageBlocks.map((block: any) => ({
label: block.name,
value: block.name,
description: `${Array.isArray(block.config?.components) ? block.config.components.length : 0} componente(s)`,
emoji: { name: "⚙️" }
}));
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
await channel.send({ components: [container], flags: MessageFlags.IsComponentsV2});
}
rows.push({
type: 1,
components: [
{
type: 3,
custom_id: "block_actions_select",
placeholder: "⚙️ Selecciona un bloque para gestionar...",
options: selectOptions
}
]
});
}
// Botones de navegación y acciones generales
const navigationRow: any = {
type: 1,
components: []
};
// Navegación
if (totalPages > 1) {
navigationRow.components.push({
type: 2,
style: 2,
label: "◀️ Anterior",
custom_id: "prev_page",
disabled: page === 0
});
navigationRow.components.push({
type: 2,
style: 2,
label: `${page + 1}/${totalPages}`,
custom_id: "page_info",
disabled: true
});
navigationRow.components.push({
type: 2,
style: 2,
label: "▶️ Siguiente",
custom_id: "next_page",
disabled: page === totalPages - 1
});
}
// Botón de refrescar
navigationRow.components.push({
type: 2,
style: 1,
label: "🔄 Refrescar",
custom_id: "refresh_list"
});
rows.push(navigationRow);
// Acciones principales
const actionsRow = {
type: 1,
components: [
{
type: 2,
style: 3,
label: "📝 Crear Nuevo",
custom_id: "show_create_commands"
},
{
type: 2,
style: 2,
label: "📋 Exportar Lista",
custom_id: "export_block_list"
},
{
type: 2,
style: 4,
label: "🗑️ Eliminar",
custom_id: "show_delete_commands"
}
]
};
rows.push(actionsRow);
return rows;
};
const panelMessage = await message.reply({
embeds: [generateBlockListEmbed(currentPage)],
components: generateActionRows(currentPage)
});
const collector = panelMessage.createMessageComponentCollector({
time: 600000,
filter: (i: any) => i.user.id === message.author.id
});
collector.on("collect", async (interaction: any) => {
switch (interaction.customId) {
case "prev_page":
if (currentPage > 0) {
currentPage--;
await interaction.update({
embeds: [generateBlockListEmbed(currentPage)],
components: generateActionRows(currentPage)
});
}
break;
case "next_page":
if (currentPage < totalPages - 1) {
currentPage++;
await interaction.update({
embeds: [generateBlockListEmbed(currentPage)],
components: generateActionRows(currentPage)
});
}
break;
case "refresh_list":
// Recargar datos
const refreshedBlocks = await client.prisma.blockV2Config.findMany({
where: { guildId: message.guildId! },
select: {
name: true,
id: true,
config: true
},
orderBy: { name: 'asc' }
});
blocks.length = 0;
blocks.push(...refreshedBlocks);
const newTotalPages = Math.ceil(blocks.length / itemsPerPage);
if (currentPage >= newTotalPages) {
currentPage = Math.max(0, newTotalPages - 1);
}
await interaction.update({
embeds: [generateBlockListEmbed(currentPage)],
components: generateActionRows(currentPage)
});
break;
case "block_actions_select":
if (interaction.isStringSelectMenu()) {
const selectedBlock = interaction.values[0];
const blockActionEmbed = {
color: 0xff9500,
title: `⚙️ Gestión de Bloque: \`${selectedBlock}\``,
description: "Selecciona la acción que deseas realizar con este bloque:",
footer: { text: "Acciones disponibles para el bloque seleccionado" }
};
const blockActionsRow = {
type: 1,
components: [
{
type: 2,
style: 1,
label: "✏️ Editar",
custom_id: `edit_block_${selectedBlock}`
},
{
type: 2,
style: 2,
label: "👁️ Vista Previa",
custom_id: `preview_block_${selectedBlock}`
},
{
type: 2,
style: 2,
label: "📋 Duplicar",
custom_id: `duplicate_block_${selectedBlock}`
},
{
type: 2,
style: 4,
label: "🗑️ Eliminar",
custom_id: `delete_block_${selectedBlock}`
}
]
};
const backRow = {
type: 1,
components: [
{
type: 2,
style: 2,
label: "↩️ Volver a la Lista",
custom_id: "back_to_list"
}
]
};
await interaction.update({
embeds: [blockActionEmbed],
components: [blockActionsRow, backRow]
});
}
break;
case "back_to_list":
await interaction.update({
embeds: [generateBlockListEmbed(currentPage)],
components: generateActionRows(currentPage)
});
break;
case "show_create_commands":
await interaction.reply({
content: `🔧 **Crear nuevos bloques:**\n\n• \`!crear-embed <nombre>\` - Crear bloque básico\n• \`!editar-embed <nombre>\` - Editor avanzado\n\n💡 **Ejemplo:** \`!crear-embed bienvenida\`\n\n📖 **Guía completa:** Los bloques usan DisplayComponents para crear interfaces modernas e interactivas.`,
flags: 64
});
break;
case "show_delete_commands":
await interaction.reply({
content: `⚠️ **Eliminar bloques:**\n\n• \`!eliminar-embed\` - Panel interactivo de eliminación\n• \`!eliminar-embed <nombre>\` - Eliminación directa\n\n❗ **Advertencia:** La eliminación es irreversible.`,
flags: 64
});
break;
case "export_block_list":
const exportText = blocks.map((block: any, index: number) => {
const componentsCount = Array.isArray(block.config?.components) ? block.config.components.length : 0;
return `${index + 1}. ${block.name} (${componentsCount} componentes) - ID: ${block.id}`;
}).join('\n');
await interaction.reply({
content: `📋 **Lista Exportada:**\n\`\`\`\n${exportText}\`\`\``,
flags: 64
});
break;
default:
// Manejar acciones específicas de bloques
if (interaction.customId.startsWith("edit_block_")) {
const blockName = interaction.customId.replace("edit_block_", "");
await interaction.reply({
content: `Usa: \`!editar-embed ${blockName}\``,
flags: 64
});
} else if (interaction.customId.startsWith("delete_block_")) {
const blockName = interaction.customId.replace("delete_block_", "");
await interaction.reply({
content: `Usa: \`!eliminar-embed ${blockName}\` para eliminar este bloque de forma segura.`,
flags: 64
});
} else if (interaction.customId.startsWith("preview_block_")) {
const blockName = interaction.customId.replace("preview_block_", "");
await interaction.reply({
content: `Vista previa de \`${blockName}\` - Funcionalidad en desarrollo`,
flags: 64
});
} else if (interaction.customId.startsWith("duplicate_block_")) {
const blockName = interaction.customId.replace("duplicate_block_", "");
await interaction.reply({
content: `Funcionalidad de duplicación de \`${blockName}\` en desarrollo`,
flags: 64
});
}
break;
}
});
collector.on("end", async (collected: any, reason: string) => {
if (reason === "time") {
const timeoutEmbed = {
color: 0x36393f,
title: "⏰ Panel Expirado",
description: "El panel de gestión ha expirado por inactividad.\n\nUsa `!lista-embeds` para abrir un nuevo panel de gestión.",
footer: { text: "Panel expirado por inactividad" }
};
try {
await panelMessage.edit({
embeds: [timeoutEmbed],
components: []
});
} catch (error) {
// Mensaje eliminado o error de edición
}
}
});
},
};

View File

@@ -1,93 +1,662 @@
import { CommandMessage } from "../../../core/types/commands";
// @ts-ignore
import { ComponentType, ButtonStyle, MessageFlags, ChannelType } from "discord.js";
export const command: CommandMessage = {
name: "setchannel-alliance",
type: "message",
aliases: ["alchannel", "channelally"],
cooldown: 10,
//@ts-ignore
// @ts-ignore
run: async (message, args, client) => {
if (!message.member?.permissions.has("Administrator")) {
return message.reply("❌ No tienes permisos de Administrador.");
}
// Validar argumentos
if (args.length < 2) {
return message.reply("❌ Uso correcto: `!setchannel-alliance <#canal|ID> <blockConfigName>`");
}
// Obtener canales configurados existentes y bloques disponibles
const existingChannels = await client.prisma.allianceChannel.findMany({
where: { guildId: message.guildId! },
include: { blockConfig: true }
});
const channelInput = args[0];
const blockConfigName = args[1];
const availableBlocks = await client.prisma.blockV2Config.findMany({
where: { guildId: message.guildId! },
select: { name: true, id: true, config: true }
});
// Extraer ID del canal
let channelId: string;
// Si es una mención de canal (#canal)
if (channelInput.startsWith('<#') && channelInput.endsWith('>')) {
channelId = channelInput.slice(2, -1);
}
// Si es solo un ID
else if (/^\d+$/.test(channelInput)) {
channelId = channelInput;
}
else {
return message.reply("❌ Formato de canal inválido. Usa `#canal` o el ID del canal.");
}
try {
// Verificar que el canal existe en el servidor
const channel = await message.guild?.channels.fetch(channelId);
if (!channel) {
return message.reply("❌ El canal especificado no existe en este servidor.");
}
// Verificar que el canal es un canal de texto
if (!channel.isTextBased()) {
return message.reply("❌ El canal debe ser un canal de texto.");
}
// Verificar que existe el blockConfig
const blockConfig = await client.prisma.blockV2Config.findFirst({
where: {
guildId: message.guildId,
name: blockConfigName
// Panel principal de configuración
const setupPanel = {
type: 17,
accent_color: 0x00ff88, // Verde alliance
components: [
{
type: 10,
content: "# 🤝 **Centro de Configuración de Alianzas**"
},
{
type: 14,
spacing: 2,
divider: true
},
{
type: 10,
content: `📊 **Estado Actual:**\n` +
`• **${existingChannels.length}** canales configurados\n` +
`• **${availableBlocks.length}** bloques disponibles\n` +
`• **${existingChannels.filter((c: any) => c.isActive).length}** canales activos\n\n` +
`⚙️ Selecciona una acción para continuar:`
}
});
]
};
if (!blockConfig) {
return message.reply(`❌ No se encontró el bloque de configuración \`${blockConfigName}\`. Asegúrate de que exista.`);
}
const mainActionsRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Success,
label: " Configurar Canal",
custom_id: "setup_new_channel",
emoji: { name: "🔧" }
},
{
type: 2,
style: ButtonStyle.Primary,
label: "📋 Ver Configurados",
custom_id: "view_configured_channels",
emoji: { name: "📊" }
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "🧪 Crear Bloque",
custom_id: "help_create_block",
emoji: { name: "📝" }
}
]
};
// Configurar el canal de alianzas
const allianceChannel = await client.prisma.allianceChannel.upsert({
where: {
guildId_channelId: {
guildId: message.guildId,
channelId: channelId
const managementRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Secondary,
label: "🔄 Refrescar",
custom_id: "refresh_status"
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "📖 Ayuda",
custom_id: "show_help"
},
{
type: 2,
style: ButtonStyle.Danger,
label: "🗑️ Gestionar",
custom_id: "manage_channels",
disabled: existingChannels.length === 0
}
]
};
const panelMessage = await message.reply({
flags: MessageFlags.SuppressEmbeds,
components: [setupPanel, mainActionsRow, managementRow]
});
const collector = panelMessage.createMessageComponentCollector({
time: 600000, // 10 minutos
filter: (i) => i.user.id === message.author.id
});
collector.on("collect", async (interaction) => {
switch (interaction.customId) {
case "setup_new_channel":
// Obtener canales de texto disponibles
const textChannels = message.guild!.channels.cache
.filter(channel =>
channel.type === ChannelType.GuildText &&
// @ts-ignore
!existingChannels.some(ec => ec.channelId === channel.id)
)
.map(channel => ({
label: `#${channel.name}`,
value: channel.id,
description: `ID: ${channel.id}`,
emoji: { name: "💬" }
}))
.slice(0, 25);
if (textChannels.length === 0) {
const noChannelsPanel = {
type: 17,
accent_color: 0xffa500,
components: [
{
type: 10,
content: "⚠️ **Sin Canales Disponibles**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: "No hay canales de texto disponibles para configurar.\n\n**Posibles causas:**\n• Todos los canales ya están configurados\n• No hay canales de texto en el servidor\n• Faltan permisos para ver canales"
}
]
};
await interaction.update({ components: [noChannelsPanel] });
return;
}
},
create: {
guildId: message.guildId,
channelId: channelId,
blockConfigName: blockConfigName,
isActive: true
},
update: {
blockConfigName: blockConfigName,
isActive: true,
updatedAt: new Date()
const channelSelectPanel = {
type: 17,
accent_color: 0x5865f2,
components: [
{
type: 10,
content: "📺 **Seleccionar Canal**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `🎯 Selecciona el canal que quieres configurar para alianzas:\n\n💡 **Tip:** Solo se muestran canales de texto que aún no están configurados.`
}
]
};
const channelSelectRow = {
type: 1,
components: [
{
type: 3,
custom_id: "channel_select",
placeholder: "📺 Selecciona un canal...",
options: textChannels
}
]
};
const backRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Secondary,
label: "↩️ Volver al Inicio",
custom_id: "back_to_main"
}
]
};
await interaction.update({
components: [channelSelectPanel, channelSelectRow, backRow]
});
break;
case "channel_select":
if (interaction.isStringSelectMenu()) {
const selectedChannelId = interaction.values[0];
const selectedChannel = message.guild!.channels.cache.get(selectedChannelId);
if (availableBlocks.length === 0) {
const noBlocksPanel = {
type: 17,
accent_color: 0xf04747,
components: [
{
type: 10,
content: "❌ **Sin Bloques Disponibles**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `📺 **Canal seleccionado:** #${selectedChannel?.name}\n\n⚠ **Problema:** No hay bloques de configuración disponibles.\n\n🔧 **Solución:**\n• Crea un bloque usando: \`!blockcreate <nombre>\`\n• Edita bloques usando: \`!blockeditv2 <nombre>\``
}
]
};
const createBlockRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Success,
label: "📝 Ayuda Crear Bloque",
custom_id: "help_create_block"
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "↩️ Volver",
custom_id: "setup_new_channel"
}
]
};
await interaction.update({
components: [noBlocksPanel, createBlockRow]
});
return;
}
// @ts-ignore
const blockOptions = availableBlocks.map(block => ({
label: block.name,
value: `${selectedChannelId}_${block.name}`,
description: `ID: ${block.id}`,
emoji: { name: "🧩" }
}));
const blockSelectPanel = {
type: 17,
accent_color: 0xff9500,
components: [
{
type: 10,
content: "🧩 **Seleccionar Configuración**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `📺 **Canal:** #${selectedChannel?.name}\n\n🎯 Selecciona qué bloque de configuración usar para este canal:\n\n💡 Los bloques definen cómo se verán los mensajes de alianza.`
}
]
};
const blockSelectRow = {
type: 1,
components: [
{
type: 3,
custom_id: "block_select",
placeholder: "🧩 Selecciona una configuración...",
options: blockOptions
}
]
};
await interaction.update({
// @ts-ignore
components: [blockSelectPanel, blockSelectRow, backRow]
});
}
break;
case "block_select":
if (interaction.isStringSelectMenu()) {
const [channelId, blockName] = interaction.values[0].split('_');
const channel = message.guild!.channels.cache.get(channelId);
try {
// Verificar que el bloque existe
const blockConfig = await client.prisma.blockV2Config.findFirst({
where: {
guildId: message.guildId,
name: blockName
}
});
if (!blockConfig) {
throw new Error("Bloque no encontrado");
}
// Configurar el canal
await client.prisma.allianceChannel.upsert({
where: {
guildId_channelId: {
guildId: message.guildId!,
channelId: channelId
}
},
create: {
guildId: message.guildId!,
channelId: channelId,
blockConfigName: blockName,
isActive: true
},
update: {
blockConfigName: blockName,
isActive: true,
updatedAt: new Date()
}
});
const successPanel = {
type: 17,
accent_color: 0x57f287,
components: [
{
type: 10,
content: "✅ **Configuración Exitosa**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `🎉 **Canal configurado correctamente:**\n\n` +
`📺 **Canal:** <#${channelId}>\n` +
`🧩 **Configuración:** \`${blockName}\`\n` +
`🟢 **Estado:** Activo\n\n` +
`🚀 **¡Listo!** Los enlaces de Discord válidos en este canal ahora otorgarán puntos de alianza usando la configuración especificada.`
}
]
};
const successActionsRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Success,
label: "🏠 Volver al Inicio",
custom_id: "back_to_main"
},
{
type: 2,
style: ButtonStyle.Primary,
label: " Configurar Otro",
custom_id: "setup_new_channel"
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "📋 Ver Todos",
custom_id: "view_configured_channels"
}
]
};
await interaction.update({
components: [successPanel, successActionsRow]
});
} catch (error) {
const errorPanel = {
type: 17,
accent_color: 0xf04747,
components: [
{
type: 10,
content: "❌ **Error de Configuración**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `🔍 **Detalles del error:**\n\n` +
`📺 Canal: <#${channelId}>\n` +
`🧩 Bloque: \`${blockName}\`\n\n` +
`💭 **Posibles causas:**\n` +
`• El bloque fue eliminado\n` +
`• Error de base de datos\n` +
`• Permisos insuficientes\n\n` +
`🔄 Intenta nuevamente o contacta al soporte.`
}
]
};
await interaction.update({ components: [errorPanel] });
}
}
break;
case "view_configured_channels":
if (existingChannels.length === 0) {
const emptyListPanel = {
type: 17,
accent_color: 0x36393f,
components: [
{
type: 10,
content: "📋 **Canales Configurados**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: "🗂️ **Lista vacía**\n\nNo hay canales configurados para alianzas en este servidor.\n\n🚀 **¿Quieres empezar?**\n• Usa el botón \"Configurar Canal\" para añadir tu primer canal"
}
]
};
await interaction.update({ components: [emptyListPanel] });
return;
}
let channelListText = `📊 **${existingChannels.length} canal(es) configurado(s)**\n\n`;
// @ts-ignore
existingChannels.forEach((config, index) => {
const channel = message.guild!.channels.cache.get(config.channelId);
const channelName = channel ? `#${channel.name}` : `Canal Eliminado`;
const status = config.isActive ? "🟢 Activo" : "🔴 Inactivo";
channelListText += `**${index + 1}.** ${channelName}\n`;
channelListText += `\`${config.blockConfigName}\`${status}\n\n`;
});
const channelListPanel = {
type: 17,
accent_color: 0x5865f2,
components: [
{
type: 10,
content: "📋 **Canales Configurados**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: channelListText
}
]
};
const listActionsRow = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Secondary,
label: "🏠 Volver al Inicio",
custom_id: "back_to_main"
},
{
type: 2,
style: ButtonStyle.Primary,
label: " Configurar Más",
custom_id: "setup_new_channel"
},
{
type: 2,
style: ButtonStyle.Danger,
label: "🔧 Gestionar",
custom_id: "manage_channels"
}
]
};
await interaction.update({
components: [channelListPanel, listActionsRow]
});
break;
case "help_create_block":
await interaction.reply({
content: `📖 **Guía de Bloques**\n\n🧩 **¿Qué son los bloques?**\nLos bloques son configuraciones que definen cómo se ven los mensajes de alianza.\n\n🔧 **Comandos para gestionar bloques:**\n\n• \`!blockcreate <nombre>\` - Crear nuevo bloque\n• \`!blockeditv2 <nombre>\` - Editor completo\n• \`!embedlist\` - Ver todos los bloques\n• \`!embeddelete\` - Eliminar bloques\n\n💡 **Ejemplo:** \`!blockcreate alianza-general\``,
flags: 64 // Ephemeral
});
break;
case "show_help":
await interaction.reply({
content: `📚 **Ayuda Completa**\n\n🤝 **Sistema de Alianzas:**\nConfigura canales donde los enlaces de Discord válidos otorgan puntos.\n\n🏗 **Proceso de configuración:**\n1. Crear un bloque con \`!blockcreate\`\n2. Configurar canal con este comando\n3. ¡Los usuarios empezarán a ganar puntos!\n\n⚙ **Gestión avanzada:**\n• Usar \`!embedlist\` para ver bloques\n• Usar \`!blockeditv2\` para personalizar\n• Este comando para gestionar canales`,
flags: 64 // Ephemeral
});
break;
case "back_to_main":
case "refresh_status":
// Recargar datos y volver al panel principal
const refreshedChannels = await client.prisma.allianceChannel.findMany({
where: { guildId: message.guildId! },
include: { blockConfig: true }
});
const refreshedBlocks = await client.prisma.blockV2Config.findMany({
where: { guildId: message.guildId! },
select: { name: true, id: true, config: true }
});
const refreshedPanel = {
type: 17,
accent_color: 0x00ff88,
components: [
{
type: 10,
content: "🤝 **Centro de Configuración de Alianzas**"
},
{
type: 14,
divider: true,
spacing: 2
},
{
type: 10,
content: `📊 **Estado Actual:**\n` +
`• **${refreshedChannels.length}** canales configurados\n` +
`• **${refreshedBlocks.length}** bloques disponibles\n` +
// @ts-ignore
`• **${refreshedChannels.filter(c => c.isActive).length}** canales activos\n\n` +
`⚙️ Selecciona una acción para continuar:`
}
]
};
const refreshedMainActions = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Success,
label: " Configurar Canal",
custom_id: "setup_new_channel",
emoji: { name: "🔧" }
},
{
type: 2,
style: ButtonStyle.Primary,
label: "📋 Ver Configurados",
custom_id: "view_configured_channels",
emoji: { name: "📊" }
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "🧪 Crear Bloque",
custom_id: "help_create_block",
emoji: { name: "📝" }
}
]
};
const refreshedManagement = {
type: 1,
components: [
{
type: 2,
style: ButtonStyle.Secondary,
label: "🔄 Refrescar",
custom_id: "refresh_status"
},
{
type: 2,
style: ButtonStyle.Secondary,
label: "📖 Ayuda",
custom_id: "show_help"
},
{
type: 2,
style: ButtonStyle.Danger,
label: "🗑️ Gestionar",
custom_id: "manage_channels",
disabled: refreshedChannels.length === 0
}
]
};
await interaction.update({
components: [refreshedPanel, refreshedMainActions, refreshedManagement]
});
break;
case "manage_channels":
await interaction.reply({
content: `🔧 **Gestión Avanzada**\n\n⚠ **Funciones de gestión avanzada:**\n\n🔄 Activar/desactivar canales\n🗑 Eliminar configuraciones\n✏ Cambiar bloques asignados\n\n💡 **Próximamente:** Panel interactivo completo`,
flags: 64 // Ephemeral
});
break;
}
});
collector.on("end", async (collected, reason) => {
if (reason === "time") {
const timeoutPanel = {
type: 17,
accent_color: 0x36393f,
components: [
{
type: 10,
content: "⏰ **Sesión Expirada**"
},
{
type: 14,
divider: true,
spacing: 1
},
{
type: 10,
content: "El panel de configuración ha expirado.\nUsa el comando nuevamente para continuar."
}
]
};
try {
await panelMessage.edit({
components: [timeoutPanel]
});
} catch (error) {
// Mensaje eliminado o error de edición
}
});
return message.reply(`✅ Canal de alianzas configurado correctamente!\n\n` +
`**Canal:** <#${channelId}>\n` +
`**Configuración:** \`${blockConfigName}\`\n` +
`**Estado:** Activo\n\n` +
`Los enlaces de Discord válidos en este canal ahora otorgarán puntos de alianza.`);
} catch (error) {
console.error('Error configurando canal de alianzas:', error);
return message.reply("❌ Ocurrió un error al configurar el canal de alianzas. Inténtalo de nuevo.");
}
}
});
}
}

View File

@@ -0,0 +1,433 @@
// @ts-ignore
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: 'ayuda',
type: "message",
aliases: ['help', 'comandos', 'cmds'],
cooldown: 5,
run: async (message: any, args: string[], client: any) => {
// Obtener información del servidor para mostrar el prefix actual
const server = await client.prisma.guild.findFirst({
where: { id: message.guild!.id }
});
const prefix = server?.prefix || "!";
// Definir categorías de comandos con nombres modernos
const commandCategories = {
"Alianzas": [
{
name: "crear-embed",
aliases: ["embed-crear", "nuevo-embed"],
description: "Crear nuevos embeds con DisplayComponents modernos",
usage: `${prefix}crear-embed <nombre>`
},
{
name: "editar-embed",
aliases: ["embed-editar", "modificar-embed"],
description: "Editor avanzado de embeds con interfaz interactiva",
usage: `${prefix}editar-embed <nombre>`
},
{
name: "lista-embeds",
aliases: ["embeds", "ver-embeds"],
description: "Centro de gestión de embeds con paginación",
usage: `${prefix}lista-embeds`
},
{
name: "eliminar-embed",
aliases: ["embed-eliminar", "borrar-embed"],
description: "Panel interactivo para eliminar embeds",
usage: `${prefix}eliminar-embed [nombre]`
},
{
name: "canal-alianza",
aliases: ["configurar-canal", "setup-canal"],
description: "Configurar canales para sistema de alianzas",
usage: `${prefix}canal-alianza`
},
{
name: "demo-componentes",
aliases: ["demo", "prueba-componentes"],
description: "Demostración de DisplayComponents con accesorios",
usage: `${prefix}demo-componentes`
}
],
"Red": [
{
name: "ping",
aliases: ["latencia", "pong"],
description: "Verificar latencia y estado del bot",
usage: `${prefix}ping`
}
],
"Configuracion": [
{
name: "configuracion",
aliases: ["config", "ajustes", "settings"],
description: "Panel de configuración del servidor",
usage: `${prefix}configuracion`
}
]
};
// Definir backRow una sola vez fuera de los casos
const backRow = {
type: 1,
components: [
{
type: 2,
style: 2,
label: "↩️ Volver al Menú",
custom_id: "back_to_main"
}
]
};
// Si se especifica un comando específico
if (args.length > 0) {
const searchCommand = args[0].toLowerCase();
let foundCommand = null;
let foundCategory = null;
// Buscar el comando en todas las categorías
for (const [category, commands] of Object.entries(commandCategories)) {
const command = commands.find(cmd =>
cmd.name === searchCommand || cmd.aliases.includes(searchCommand)
);
if (command) {
foundCommand = command;
foundCategory = category;
break;
}
}
if (foundCommand) {
// Panel detallado del comando específico - SIMPLIFICADO
const commandDetailPanel = {
type: 17,
accent_color: 0x5865f2,
components: [
{
type: 10,
content: `### 📖 **Ayuda: \`${foundCommand.name}\`**\n\n**Categoría:** ${foundCategory}\n**Descripción:** ${foundCommand.description}\n**Uso:** ${foundCommand.usage}\n\n**Aliases disponibles:**\n${foundCommand.aliases.map(alias => `\`${prefix}${alias}\``).join(", ")}`
}
]
};
const detailActionsRow = {
type: 1,
components: [
{
type: 2,
style: 2,
label: "📋 Ver Todos",
custom_id: "show_all_commands"
},
{
type: 2,
style: 2,
label: "🔍 Buscar Otro",
custom_id: "search_command"
}
]
};
await message.reply({
flags: 32768,
components: [commandDetailPanel, detailActionsRow]
});
return;
} else {
// Comando no encontrado - SIMPLIFICADO
const notFoundPanel = {
type: 17,
accent_color: 0xf04747,
components: [
{
type: 10,
content: `### ❌ **Comando no encontrado: \`${searchCommand}\`**\n\nNo se encontró ningún comando con el nombre o alias \`${searchCommand}\`.\n\n🔍 **Sugerencias:**\n• Verifica la ortografía\n• Usa \`${prefix}ayuda\` para ver todos los comandos\n• Usa \`${prefix}ayuda <categoría>\` para filtrar`
}
]
};
const notFoundRow = {
type: 1,
components: [
{
type: 2,
style: 1,
label: "📋 Ver Todos",
custom_id: "show_all_commands"
}
]
};
await message.reply({
flags: 32768,
components: [notFoundPanel, notFoundRow]
});
return;
}
}
// Panel principal de ayuda - OPTIMIZADO para no exceder límite de componentes
const helpPanel = {
type: 17,
accent_color: 0x5865f2,
components: [
{
type: 10,
content: `### 📚 **Centro de Ayuda - ${message.guild!.name}**`
},
{
type: 14,
spacing: 1,
divider: true
},
{
type: 10,
content: `**Prefix actual:** \`${prefix}\`\n**Total de comandos:** ${Object.values(commandCategories).flat().length}\n**Categorías disponibles:** ${Object.keys(commandCategories).length}`
},
{
type: 14,
spacing: 2,
divider: false
}
]
};
// Agregar resumen de categorías de forma compacta
for (const [categoryName, commands] of Object.entries(commandCategories)) {
const commandsList = commands.map(cmd => `\`${cmd.name}\``).join(", ");
helpPanel.components.push({
type: 10,
content: `🔹 **${categoryName}** (${commands.length})\n${commandsList}`
});
}
// Botones de navegación
const navigationRow = {
type: 1,
components: [
{
type: 2,
style: 1,
label: "🤝 Alianzas",
custom_id: "category_alliances"
},
{
type: 2,
style: 2,
label: "🌐 Red",
custom_id: "category_network"
},
{
type: 2,
style: 2,
label: "⚙️ Config",
custom_id: "category_settings"
},
{
type: 2,
style: 3,
label: "📋 Exportar",
custom_id: "export_commands"
}
]
};
const panelMessage = await message.reply({
flags: 32768,
components: [helpPanel, navigationRow]
});
const collector = panelMessage.createMessageComponentCollector({
time: 600000,
filter: (i: any) => i.user.id === message.author.id
});
collector.on("collect", async (interaction: any) => {
// Manejar información específica de comandos
if (interaction.customId.startsWith("cmd_info_")) {
const commandName = interaction.customId.replace("cmd_info_", "");
let foundCommand = null;
let foundCategory = null;
for (const [category, commands] of Object.entries(commandCategories)) {
const command = commands.find(cmd => cmd.name === commandName);
if (command) {
foundCommand = command;
foundCategory = category;
break;
}
}
if (foundCommand) {
await interaction.reply({
content: `📖 **${foundCommand.name}**\n\n**Categoría:** ${foundCategory}\n**Descripción:** ${foundCommand.description}\n**Uso:** ${foundCommand.usage}\n**Aliases:** ${foundCommand.aliases.join(", ")}\n\n💡 **Tip:** Usa \`${foundCommand.usage}\` para ejecutar este comando.`,
flags: 64
});
}
return;
}
// Manejar categorías específicas - VERSIÓN COMPACTA
switch (interaction.customId) {
case "category_alliances":
const alliancePanel = {
type: 17,
accent_color: 0x00ff88,
components: [
{
type: 10,
content: "### 🤝 **Comandos de Alianzas**\n\nSistema completo para gestionar alianzas entre servidores:"
},
{
type: 14,
spacing: 2,
divider: true
}
]
};
// Agregar comandos de forma compacta
commandCategories["Alianzas"].forEach(cmd => {
alliancePanel.components.push({
type: 10,
content: `**${cmd.name}**\n${cmd.description}\n\`${cmd.usage}\``
});
});
await interaction.update({
components: [alliancePanel, backRow]
});
break;
case "category_network":
const networkPanel = {
type: 17,
accent_color: 0x5865f2,
components: [
{
type: 10,
content: "### 🌐 **Comandos de Red**"
},
{
type: 14,
spacing: 2,
divider: true
}
]
};
commandCategories["Red"].forEach(cmd => {
networkPanel.components.push({
type: 10,
content: `**${cmd.name}**\n${cmd.description}\n\`${cmd.usage}\``
});
});
await interaction.update({
components: [networkPanel, backRow]
});
break;
case "category_settings":
const settingsPanel = {
type: 17,
accent_color: 0xff9500,
components: [
{
type: 10,
content: "### ⚙️ **Comandos de Configuración**"
},
{
type: 14,
spacing: 2,
divider: true
}
]
};
commandCategories["Configuracion"].forEach(cmd => {
settingsPanel.components.push({
type: 10,
content: `**${cmd.name}**\n${cmd.description}\n\`${cmd.usage}\``
});
});
await interaction.update({
components: [settingsPanel, backRow]
});
break;
case "back_to_main":
await interaction.update({
components: [helpPanel, navigationRow]
});
break;
case "export_commands":
let exportText = `📋 **Lista Completa de Comandos - ${message.guild!.name}**\n\n`;
exportText += `**Prefix:** ${prefix}\n\n`;
for (const [category, commands] of Object.entries(commandCategories)) {
exportText += `**${category}**\n`;
commands.forEach(cmd => {
exportText += `${cmd.name} - ${cmd.description}\n`;
exportText += ` Uso: ${cmd.usage}\n`;
if (cmd.aliases.length > 0) {
exportText += ` Aliases: ${cmd.aliases.join(", ")}\n`;
}
exportText += `\n`;
});
exportText += `\n`;
}
await interaction.reply({
content: `\`\`\`\n${exportText}\`\`\``,
flags: 64
});
break;
default:
if (interaction.customId.startsWith("use_")) {
const commandName = interaction.customId.replace("use_", "");
const foundCmd = Object.values(commandCategories).flat().find(cmd => cmd.name === commandName);
if (foundCmd) {
await interaction.reply({
content: `🚀 **Ejecutar: \`${foundCmd.name}\`**\n\nUsa: \`${foundCmd.usage}\`\n\n💡 **Tip:** Copia y pega el comando en el chat para usarlo.`,
flags: 64
});
}
}
break;
}
});
collector.on("end", async (collected: any, reason: string) => {
if (reason === "time") {
const timeoutPanel = {
type: 17,
accent_color: 0x36393f,
components: [
{
type: 10,
content: `### ⏰ **Panel de Ayuda Expirado**\n\nEl panel de ayuda ha expirado por inactividad.\n\nUsa \`${prefix}ayuda\` para abrir un nuevo panel.`
}
]
};
try {
await panelMessage.edit({
components: [timeoutPanel]
});
} catch (error) {
// Mensaje eliminado o error de edición
}
}
});
}
};

View File

@@ -1,51 +1,351 @@
import {CommandMessage} from "../../../core/types/commands";
//@ts-ignore
import {
ButtonStyle, ChannelType,
ContainerBuilder,
MessageFlags,
SectionBuilder, SeparatorBuilder, SeparatorSpacingSize, TextChannel,
TextDisplayBuilder,
UserSelectMenuBuilder
} from "discord.js";
import { CommandMessage } from "../../../core/types/commands";
export const command: CommandMessage = {
name: 'settings',
name: 'configuracion',
type: "message",
aliases: ['options', 'stts'],
aliases: ['config', 'ajustes', 'settings'],
cooldown: 5,
run: async (message, args, client) => {
const server = await client.prisma.guild.findFirst({ where: { id: message.guild!.id } });
const title = new TextDisplayBuilder()
.setContent("## ﹒⌒    Settings Seɾveɾ    ╰୧﹒")
const description = new TextDisplayBuilder()
.setContent("Panel de Administracion del bot dentro del servidor.")
const sect = new TextDisplayBuilder()
.setContent("**Prefix del bot:** " + ` \`\`\`${server.prefix}\`\`\``)
const section = new SectionBuilder()
.addTextDisplayComponents(sect)
//@ts-ignore
.setButtonAccessory(button => button
.setCustomId('prefixsettings')
.setLabel('Prefix')
.setStyle(ButtonStyle.Primary),
)
const separator = new SeparatorBuilder()
.setSpacing(SeparatorSpacingSize.Large)
.setDivider(false);
const main = new ContainerBuilder()
.addTextDisplayComponents(title, description)
.addSeparatorComponents(separator)
.addSectionComponents(section)
//@ts-ignore
if (message.channel.type === ChannelType.GuildText) {
const channel = message.channel as TextChannel;
await channel.send({ components: [main], flags: MessageFlags.IsComponentsV2});
if (!message.member?.permissions.has("Administrator")) {
await message.reply("❌ No tienes permisos de Administrador.");
return;
}
const server = await client.prisma.guild.findFirst({
where: { id: message.guild!.id }
});
const currentPrefix = server?.prefix || "!";
// Panel de configuración usando DisplayComponents
const settingsPanel = {
type: 17,
accent_color: 6178018, // Color del ejemplo
components: [
{
type: 10,
content: "### <:invisible:1418684224441028608> 梅panel admin📢\n"
},
{
type: 14,
spacing: 1,
divider: false
},
{
type: 10,
content: "Configuracion del Servidor:"
},
{
type: 9, // Section
components: [
{
type: 10,
content: `**Prefix:**<:invisible:1418684224441028608>\`${currentPrefix}\``
}
],
accessory: {
type: 2, // Button
style: 2, // Secondary
emoji: {
name: "⚙️"
},
custom_id: "open_prefix_modal",
label: "Cambiar"
}
},
{
type: 14,
divider: false
}
]
};
const panelMessage = await message.reply({
flags: 32768, // SuppressEmbeds
components: [settingsPanel]
});
const collector = panelMessage.createMessageComponentCollector({
time: 300000, // 5 minutos
filter: (i: any) => i.user.id === message.author.id
});
collector.on("collect", async (interaction: any) => {
if (interaction.customId === "open_prefix_modal") {
// Crear y mostrar modal para cambiar prefix
const prefixModal = {
title: "⚙️ Configurar Prefix del Servidor",
custom_id: "prefix_settings_modal",
components: [
{
type: 1, // ActionRow
components: [
{
type: 4, // TextInput
custom_id: "new_prefix_input",
label: "Nuevo Prefix",
style: 1, // Short
placeholder: `Prefix actual: ${currentPrefix}`,
required: true,
max_length: 10,
min_length: 1,
value: currentPrefix
}
]
},
{
type: 1,
components: [
{
type: 4,
custom_id: "prefix_description",
label: "¿Por qué cambiar el prefix? (Opcional)",
style: 2, // Paragraph
placeholder: "Ej: Evitar conflictos con otros bots...",
required: false,
max_length: 200
}
]
}
]
};
await interaction.showModal(prefixModal);
// Crear un collector específico para este modal
const modalCollector = interaction.awaitModalSubmit({
time: 300000, // 5 minutos
filter: (modalInt: any) => modalInt.customId === "prefix_settings_modal" && modalInt.user.id === message.author.id
});
modalCollector.then(async (modalInteraction: any) => {
const newPrefix = modalInteraction.fields.getTextInputValue("new_prefix_input");
const description = modalInteraction.fields.getTextInputValue("prefix_description") || "Sin descripción";
// Validar prefix
if (!newPrefix || newPrefix.length > 10) {
await modalInteraction.reply({
content: "❌ **Error:** El prefix debe tener entre 1 y 10 caracteres.",
flags: 64 // Ephemeral
});
return;
}
try {
// Actualizar prefix en la base de datos
await client.prisma.guild.upsert({
where: { id: message.guild!.id },
create: {
id: message.guild!.id,
name: message.guild!.name,
prefix: newPrefix
},
update: {
prefix: newPrefix,
name: message.guild!.name
}
});
// Panel de confirmación
const successPanel = {
type: 17,
accent_color: 3066993, // Verde
components: [
{
type: 10,
content: "### ✅ **Prefix Actualizado Exitosamente**"
},
{
type: 14,
spacing: 2,
divider: true
},
{
type: 9,
components: [
{
type: 10,
content: `**Prefix anterior:** \`${currentPrefix}\`\n**Prefix nuevo:** \`${newPrefix}\`\n\n**Motivo:** ${description}`
}
],
accessory: {
type: 2,
style: 3, // Success
label: "✓ Listo",
custom_id: "prefix_confirmed",
emoji: { name: "✅" }
}
},
{
type: 14,
spacing: 1,
divider: false
},
{
type: 10,
content: "🚀 **¡Listo!** Ahora puedes usar los comandos con el nuevo prefix.\n\n💡 **Ejemplo:** `" + newPrefix + "help`, `" + newPrefix + "embedlist`"
}
]
};
// Botón para volver al panel principal
const backToSettingsRow = {
type: 1,
components: [
{
type: 2,
style: 2, // Secondary
label: "↩️ Volver a Configuración",
custom_id: "back_to_settings"
}
]
};
// Actualizar el panel original
await modalInteraction.update({
components: [successPanel, backToSettingsRow]
});
} catch (error) {
const errorPanel = {
type: 17,
accent_color: 15548997, // Rojo
components: [
{
type: 10,
content: "### ❌ **Error al Actualizar Prefix**"
},
{
type: 14,
spacing: 2,
divider: true
},
{
type: 10,
content: `**Error:** No se pudo actualizar el prefix a \`${newPrefix}\`\n\n**Posibles causas:**\n• Error de conexión con la base de datos\n• Prefix contiene caracteres no válidos\n• Permisos insuficientes\n\n🔄 **Solución:** Intenta nuevamente con un prefix diferente.`
}
]
};
const retryRow = {
type: 1,
components: [
{
type: 2,
style: 2,
label: "🔄 Reintentar",
custom_id: "open_prefix_modal"
},
{
type: 2,
style: 4, // Danger
label: "❌ Cancelar",
custom_id: "cancel_prefix_change"
}
]
};
await modalInteraction.update({
components: [errorPanel, retryRow]
});
}
}).catch(async (error: any) => {
// Modal timeout o cancelado
console.log("Modal timeout o error:", error.message);
});
}
// Manejar botones adicionales
if (interaction.customId === "back_to_settings") {
// Volver al panel principal
const updatedServer = await client.prisma.guild.findFirst({
where: { id: message.guild!.id }
});
const newCurrentPrefix = updatedServer?.prefix || "!";
const updatedSettingsPanel = {
type: 17,
accent_color: 6178018,
components: [
{
type: 10,
content: "### <:invisible:1418684224441028608> 梅panel admin📢\n"
},
{
type: 14,
spacing: 1,
divider: false
},
{
type: 10,
content: "Configuracion del Servidor:"
},
{
type: 9,
components: [
{
type: 10,
content: `**Prefix:** \`${newCurrentPrefix}\``
}
],
accessory: {
type: 2,
style: 2,
emoji: { name: "⚙️" },
custom_id: "open_prefix_modal",
label: "Cambiar"
}
},
{
type: 14,
divider: false
}
]
};
await interaction.update({
components: [updatedSettingsPanel]
});
}
if (interaction.customId === "cancel_prefix_change") {
// Volver al panel original sin cambios
await interaction.update({
components: [settingsPanel]
});
}
});
collector.on("end", async (collected: any, reason: string) => {
if (reason === "time") {
const timeoutPanel = {
type: 17,
accent_color: 6178018,
components: [
{
type: 10,
content: "### ⏰ **Panel Expirado**"
},
{
type: 14,
spacing: 1,
divider: true
},
{
type: 10,
content: "El panel de configuración ha expirado por inactividad.\n\nUsa `!settings` para abrir un nuevo panel."
}
]
};
try {
await panelMessage.edit({
components: [timeoutPanel]
});
} catch (error) {
// Mensaje eliminado o error de edición
}
}
});
}
}
};

View File

@@ -6,10 +6,8 @@ export async function replaceVars(text: string, user: User | undefined, guild: G
// Crear inviteObject solo si invite existe y tiene guild
const inviteObject = invite?.guild ? {
guild: {
//@ts-ignore
icon: `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.webp?size=256`
}
name: invite.guild.name,
icon: invite.guild.icon ? `https://cdn.discordapp.com/icons/${invite.guild.id}/${invite.guild.icon}.webp?size=256` : ''
} : null;
return text
@@ -37,7 +35,6 @@ export async function replaceVars(text: string, user: User | undefined, guild: G
/**
* INVITE INFO
*/
.replace(/(invite\.name)/g, invite?.guild?.name ?? "")
.replace(/(invite\.icon)/g, inviteObject?.guild.icon ?? '0')
.replace(/(invite\.name)/g, inviteObject?.name ?? "")
.replace(/(invite\.icon)/g, inviteObject?.icon ?? '')
}

View File

@@ -288,6 +288,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
// Añadir imagen de portada primero si existe
if (config.coverImage && isValidUrl(config.coverImage)) {
// @ts-ignore
const processedCoverUrl = await replaceVars(config.coverImage, user, guild);
if (isValidUrl(processedCoverUrl)) {
previewComponents.push({
@@ -301,6 +302,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
if (config.title) {
previewComponents.push({
type: 10,
// @ts-ignore
content: await replaceVars(config.title, user, guild)
});
}
@@ -310,6 +312,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
for (const c of config.components) {
if (c.type === 10) {
// Componente de texto con thumbnail opcional
// @ts-ignore
const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, user, guild) : null;
if (processedThumbnail && isValidUrl(processedThumbnail)) {
@@ -319,6 +322,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
components: [
{
type: 10,
// @ts-ignore
content: await replaceVars(c.content || " ", user, guild)
}
],
@@ -331,6 +335,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
// Sin thumbnail o thumbnail inválido, componente normal
previewComponents.push({
type: 10,
// @ts-ignore
content: await replaceVars(c.content || " ", user, guild)
});
}
@@ -343,6 +348,7 @@ async function convertConfigToDisplayComponent(config: any, user: any, guild: an
});
} else if (c.type === 12) {
// Imagen - validar URL también
// @ts-ignore
const processedImageUrl = await replaceVars(c.url, user, guild);
if (isValidUrl(processedImageUrl)) {