No mucho solo pequeños detalles
This commit is contained in:
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
@@ -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,34 +975,69 @@ 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") {
|
||||
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');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
404
src/commands/messages/alliaces/displayComponentsDemo.ts
Normal file
404
src/commands/messages/alliaces/displayComponentsDemo.ts
Normal 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
@@ -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 }
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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: embedName,
|
||||
name: blockName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return message.reply(`✅ El embed **${embedName}** fue eliminado con éxito.`);
|
||||
} catch {
|
||||
return message.reply("❌ No encontré un embed con ese nombre.");
|
||||
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
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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`;
|
||||
});
|
||||
|
||||
return {
|
||||
color: 0x5865f2,
|
||||
title: "📚 Centro de Gestión de Bloques",
|
||||
description: blockListText,
|
||||
footer: { text: `Página ${page + 1}/${totalPages} • ${blocks.length} bloques total` }
|
||||
};
|
||||
};
|
||||
|
||||
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: "⚙️" }
|
||||
}));
|
||||
|
||||
rows.push({
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 3,
|
||||
custom_id: "block_actions_select",
|
||||
placeholder: "⚙️ Selecciona un bloque para gestionar...",
|
||||
options: selectOptions
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const separator = new SeparatorBuilder()
|
||||
.setSpacing(SeparatorSpacingSize.Large)
|
||||
.setDivider(false);
|
||||
// Botones de navegación y acciones generales
|
||||
const navigationRow: any = {
|
||||
type: 1,
|
||||
components: []
|
||||
};
|
||||
|
||||
const container = new ContainerBuilder()
|
||||
.setAccentColor(0x49225B)
|
||||
.addTextDisplayComponents(title)
|
||||
.addSeparatorComponents(separator)
|
||||
.addSectionComponents(mainSection); // <--- Añadimos la sección ya completa
|
||||
// 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
|
||||
});
|
||||
|
||||
if (message.channel.type === ChannelType.GuildText) {
|
||||
const channel = message.channel as TextChannel;
|
||||
await channel.send({ components: [container], flags: MessageFlags.IsComponentsV2});
|
||||
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
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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 availableBlocks = await client.prisma.blockV2Config.findMany({
|
||||
where: { guildId: message.guildId! },
|
||||
select: { name: true, id: true, config: true }
|
||||
});
|
||||
|
||||
// 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:`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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: "📝" }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const channelInput = args[0];
|
||||
const blockConfigName = args[1];
|
||||
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.`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Extraer ID del canal
|
||||
let channelId: string;
|
||||
const channelSelectRow = {
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 3,
|
||||
custom_id: "channel_select",
|
||||
placeholder: "📺 Selecciona un canal...",
|
||||
options: textChannels
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Si es una mención de canal (#canal)
|
||||
if (channelInput.startsWith('<#') && channelInput.endsWith('>')) {
|
||||
channelId = channelInput.slice(2, -1);
|
||||
const backRow = {
|
||||
type: 1,
|
||||
components: [
|
||||
{
|
||||
type: 2,
|
||||
style: ButtonStyle.Secondary,
|
||||
label: "↩️ Volver al Inicio",
|
||||
custom_id: "back_to_main"
|
||||
}
|
||||
// Si es solo un ID
|
||||
else if (/^\d+$/.test(channelInput)) {
|
||||
channelId = channelInput;
|
||||
]
|
||||
};
|
||||
|
||||
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>\``
|
||||
}
|
||||
else {
|
||||
return message.reply("❌ Formato de canal inválido. Usa `#canal` o el ID del canal.");
|
||||
]
|
||||
};
|
||||
|
||||
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 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
|
||||
// Verificar que el bloque existe
|
||||
const blockConfig = await client.prisma.blockV2Config.findFirst({
|
||||
where: {
|
||||
guildId: message.guildId,
|
||||
name: blockConfigName
|
||||
name: blockName
|
||||
}
|
||||
});
|
||||
|
||||
if (!blockConfig) {
|
||||
return message.reply(`❌ No se encontró el bloque de configuración \`${blockConfigName}\`. Asegúrate de que exista.`);
|
||||
throw new Error("Bloque no encontrado");
|
||||
}
|
||||
|
||||
// Configurar el canal de alianzas
|
||||
const allianceChannel = await client.prisma.allianceChannel.upsert({
|
||||
// Configurar el canal
|
||||
await client.prisma.allianceChannel.upsert({
|
||||
where: {
|
||||
guildId_channelId: {
|
||||
guildId: message.guildId,
|
||||
guildId: message.guildId!,
|
||||
channelId: channelId
|
||||
}
|
||||
},
|
||||
create: {
|
||||
guildId: message.guildId,
|
||||
guildId: message.guildId!,
|
||||
channelId: channelId,
|
||||
blockConfigName: blockConfigName,
|
||||
blockConfigName: blockName,
|
||||
isActive: true
|
||||
},
|
||||
update: {
|
||||
blockConfigName: blockConfigName,
|
||||
blockConfigName: blockName,
|
||||
isActive: true,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
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.`);
|
||||
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) {
|
||||
console.error('Error configurando canal de alianzas:', error);
|
||||
return message.reply("❌ Ocurrió un error al configurar el canal de alianzas. Inténtalo de nuevo.");
|
||||
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
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
433
src/commands/messages/help.ts
Normal file
433
src/commands/messages/help.ts
Normal 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
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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}\`\`\``)
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
return;
|
||||
}
|
||||
|
||||
const section = new SectionBuilder()
|
||||
.addTextDisplayComponents(sect)
|
||||
//@ts-ignore
|
||||
.setButtonAccessory(button => button
|
||||
.setCustomId('prefixsettings')
|
||||
.setLabel('Prefix')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
const server = await client.prisma.guild.findFirst({
|
||||
where: { id: message.guild!.id }
|
||||
});
|
||||
|
||||
const separator = new SeparatorBuilder()
|
||||
.setSpacing(SeparatorSpacingSize.Large)
|
||||
.setDivider(false);
|
||||
const currentPrefix = server?.prefix || "!";
|
||||
|
||||
const main = new ContainerBuilder()
|
||||
.addTextDisplayComponents(title, description)
|
||||
.addSeparatorComponents(separator)
|
||||
.addSectionComponents(section)
|
||||
// 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]
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
if (message.channel.type === ChannelType.GuildText) {
|
||||
const channel = message.channel as TextChannel;
|
||||
await channel.send({ components: [main], flags: MessageFlags.IsComponentsV2});
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 ?? '')
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user