ni yo se que hice xd
This commit is contained in:
@@ -24,8 +24,7 @@ const btns = (disabled = false) => ([
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🖼️ Portada", disabled, custom_id: "cover_image" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📎 Thumbnail", disabled, custom_id: "edit_thumbnail" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🔗 Crear Botón Link", disabled, custom_id: "edit_link_button" },
|
||||
{ style: ButtonStyle.Primary, type: 2, label: "🔄 Mover", disabled, custom_id: "move_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "🗑️ Eliminar", disabled, custom_id: "delete_block" }
|
||||
{ style: ButtonStyle.Primary, type: 2, label: "🔄 Mover", disabled, custom_id: "move_block" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -42,7 +41,8 @@ const btns = (disabled = false) => ([
|
||||
type: 1,
|
||||
components: [
|
||||
{ style: ButtonStyle.Success, type: 2, label: "💾 Guardar", disabled, custom_id: "save_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "❌ Cancelar", disabled, custom_id: "cancel_block" }
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "❌ Cancelar", disabled, custom_id: "cancel_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "🗑️ Eliminar", disabled, custom_id: "delete_block" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
@@ -272,10 +272,9 @@ export const command: CommandMessage = {
|
||||
await editorMessage.edit({
|
||||
content: null,
|
||||
flags: 4096,
|
||||
components: [
|
||||
await renderPreview(blockState, message.member, message.guild),
|
||||
...btns(false)
|
||||
]
|
||||
// @ts-ignore - display (Display Components)
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
|
||||
const collector = editorMessage.createMessageComponentCollector({
|
||||
@@ -311,16 +310,16 @@ export const command: CommandMessage = {
|
||||
}
|
||||
});
|
||||
await editorMessage.edit({
|
||||
components: [
|
||||
{
|
||||
// @ts-ignore
|
||||
display: {
|
||||
type: 17,
|
||||
accent_color: blockState.color ?? null,
|
||||
components: [
|
||||
{ type: 10, content: `✅ Guardado: ${blockName}` },
|
||||
{ type: 10, content: "Configuración guardada en la base de datos (JSON)." }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
components: []
|
||||
});
|
||||
collector.stop();
|
||||
return;
|
||||
@@ -492,7 +491,9 @@ export const command: CommandMessage = {
|
||||
blockState.coverImage = null;
|
||||
await b.update({ content: "✅ Imagen de portada eliminada.", components: [] });
|
||||
await editorMessage.edit({
|
||||
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
|
||||
// @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
}
|
||||
coverCollector.stop();
|
||||
@@ -603,7 +604,9 @@ export const command: CommandMessage = {
|
||||
}
|
||||
|
||||
await editorMessage.edit({
|
||||
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
|
||||
// @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
|
||||
btnCollector.stop();
|
||||
@@ -690,7 +693,9 @@ export const command: CommandMessage = {
|
||||
}
|
||||
|
||||
await editorMessage.edit({
|
||||
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
|
||||
// @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
|
||||
selCollector.stop();
|
||||
@@ -781,7 +786,9 @@ export const command: CommandMessage = {
|
||||
|
||||
await sel.update({ content: "✅ Elemento duplicado.", components: [] });
|
||||
await editorMessage.edit({
|
||||
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
|
||||
// @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
});
|
||||
break;
|
||||
@@ -1085,7 +1092,9 @@ export const command: CommandMessage = {
|
||||
}
|
||||
|
||||
await editorMessage.edit({
|
||||
components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)]
|
||||
// @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1239,7 +1248,10 @@ export const command: CommandMessage = {
|
||||
try {
|
||||
const messageExists = await editorMessage.fetch().catch(() => null);
|
||||
if (!messageExists) return;
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.code === 10008) {
|
||||
console.log('Mensaje del editor eliminado');
|
||||
@@ -1275,9 +1287,12 @@ export const command: CommandMessage = {
|
||||
const messageExists = await editorMessage.fetch().catch(() => null);
|
||||
if (messageExists) {
|
||||
await editorMessage.edit({
|
||||
components: [
|
||||
{ type: 17, components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }] }
|
||||
]
|
||||
// @ts-ignore
|
||||
display: {
|
||||
type: 17,
|
||||
components: [{ type: 10, content: "⏰ Editor finalizado por inactividad." }]
|
||||
},
|
||||
components: []
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,92 +1,62 @@
|
||||
import { CommandMessage } from "../../../core/types/commands";
|
||||
// @ts-ignore
|
||||
import { ComponentType, ButtonStyle, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, Message, MessageFlags } from "discord.js";
|
||||
import { ComponentType, ButtonStyle, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, MessageFlags } from "discord.js";
|
||||
import { replaceVars, isValidUrlOrVariable, listVariables } from "../../../core/lib/vars";
|
||||
|
||||
/**
|
||||
* Botones de edición - VERSIÓN MEJORADA
|
||||
*/
|
||||
// Botones de edición (máx 5 por fila)
|
||||
const btns = (disabled = false) => ([
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{ type: 1, components: [
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📝 Título", disabled, custom_id: "edit_title" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📄 Descripción", disabled, custom_id: "edit_description" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🎨 Color", disabled, custom_id: "edit_color" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "➕ Contenido", disabled, custom_id: "add_content" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "➖ Separador", disabled, custom_id: "add_separator" }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
]},
|
||||
{ type: 1, components: [
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🖼️ Imagen", disabled, custom_id: "add_image" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🖼️ Portada", disabled, custom_id: "cover_image" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📎 Thumbnail", disabled, custom_id: "edit_thumbnail" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🔗 Crear Botón Link", disabled, custom_id: "edit_link_button" },
|
||||
{ style: ButtonStyle.Primary, type: 2, label: "🔄 Mover", disabled, custom_id: "move_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "🗑️ Eliminar", disabled, custom_id: "delete_block" }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{ style: ButtonStyle.Primary, type: 2, label: "🔄 Mover", disabled, custom_id: "move_block" }
|
||||
]},
|
||||
{ type: 1, components: [
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "🎯 Variables", disabled, custom_id: "show_variables" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📋 Duplicar", disabled, custom_id: "duplicate_block" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📊 Vista Raw", disabled, custom_id: "show_raw" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📥 Importar", disabled, custom_id: "import_json" },
|
||||
{ style: ButtonStyle.Secondary, type: 2, label: "📤 Exportar", disabled, custom_id: "export_json" }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
]},
|
||||
{ type: 1, components: [
|
||||
{ style: ButtonStyle.Success, type: 2, label: "💾 Guardar", disabled, custom_id: "save_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "❌ Cancelar", disabled, custom_id: "cancel_block" }
|
||||
]
|
||||
}
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "❌ Cancelar", disabled, custom_id: "cancel_block" },
|
||||
{ style: ButtonStyle.Danger, type: 2, label: "🗑️ Eliminar", disabled, custom_id: "delete_block" }
|
||||
]}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Validar si una URL es válida o es una variable del sistema
|
||||
*/
|
||||
const isValidUrl = isValidUrlOrVariable;
|
||||
|
||||
/**
|
||||
* Validar y limpiar contenido para Discord
|
||||
*/
|
||||
const validateContent = (content: string): string => {
|
||||
if (!content || typeof content !== 'string') {
|
||||
return "Sin contenido";
|
||||
}
|
||||
if (!content || typeof content !== 'string') return "Sin contenido";
|
||||
const cleaned = content.trim();
|
||||
if (cleaned.length === 0) return "Sin contenido";
|
||||
if (cleaned.length > 4000) return cleaned.substring(0, 3997) + "...";
|
||||
if (!cleaned) return "Sin contenido";
|
||||
if (cleaned.length > 4000) return cleaned.slice(0, 3997) + "...";
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
// Parseo de emoji (Unicode o personalizado <a:name:id>/<:name:id>)
|
||||
const parseEmojiInput = (input?: string): any | null => {
|
||||
if (!input) return null;
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) return null;
|
||||
const match = trimmed.match(/^<(a?):(\w+):(\d+)>$/);
|
||||
if (match) {
|
||||
const animated = match[1] === 'a';
|
||||
const name = match[2];
|
||||
const id = match[3];
|
||||
return { id, name, animated };
|
||||
}
|
||||
if (match) return { id: match[3], name: match[2], animated: match[1] === 'a' };
|
||||
return { name: trimmed };
|
||||
};
|
||||
|
||||
// Construcción de accesorio botón link
|
||||
const buildLinkAccessory = async (link: any, member: any, guild: any) => {
|
||||
if (!link || !link.url) return null;
|
||||
// @ts-ignore
|
||||
const processedUrl = await replaceVars(link.url, member, guild);
|
||||
if (!isValidUrl(processedUrl)) return null;
|
||||
|
||||
const accessory: any = { type: 2, style: ButtonStyle.Link, url: processedUrl };
|
||||
if (link.label && typeof link.label === 'string' && link.label.trim()) accessory.label = link.label.trim().slice(0, 80);
|
||||
if (link.emoji && typeof link.emoji === 'string') {
|
||||
@@ -97,47 +67,35 @@ const buildLinkAccessory = async (link: any, member: any, guild: any) => {
|
||||
return accessory;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generar vista previa
|
||||
*/
|
||||
const renderPreview = async (blockState: any, member: any, guild: any) => {
|
||||
const previewComponents = [] as any[];
|
||||
const previewComponents: any[] = [];
|
||||
|
||||
if (blockState.coverImage && isValidUrl(blockState.coverImage)) {
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const processedCoverUrl = await replaceVars(blockState.coverImage, member, guild);
|
||||
if (isValidUrl(processedCoverUrl)) {
|
||||
previewComponents.push({ type: 12, items: [{ media: { url: processedCoverUrl } }] });
|
||||
}
|
||||
if (isValidUrl(processedCoverUrl)) previewComponents.push({ type: 12, items: [{ media: { url: processedCoverUrl } }] });
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const processedTitle = await replaceVars(blockState.title ?? "Sin título", member, guild);
|
||||
previewComponents.push({ type: 10, content: validateContent(processedTitle) });
|
||||
|
||||
for (const c of blockState.components) {
|
||||
if (c.type === 10) {
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const processedThumbnail = c.thumbnail ? await replaceVars(c.thumbnail, member, guild) : null;
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const processedContent = await replaceVars(c.content || "Sin contenido", member, guild);
|
||||
const validatedContent = validateContent(processedContent);
|
||||
|
||||
let accessory: any = null;
|
||||
if (c.linkButton) accessory = await buildLinkAccessory(c.linkButton, member, guild);
|
||||
if (!accessory && processedThumbnail && isValidUrl(processedThumbnail)) {
|
||||
accessory = { type: 11, media: { url: processedThumbnail } };
|
||||
}
|
||||
|
||||
if (accessory) {
|
||||
previewComponents.push({ type: 9, components: [{ type: 10, content: validatedContent }], accessory });
|
||||
} else {
|
||||
previewComponents.push({ type: 10, content: validatedContent });
|
||||
}
|
||||
if (!accessory && processedThumbnail && isValidUrl(processedThumbnail)) accessory = { type: 11, media: { url: processedThumbnail } };
|
||||
if (accessory) previewComponents.push({ type: 9, components: [{ type: 10, content: validatedContent }], accessory });
|
||||
else previewComponents.push({ type: 10, content: validatedContent });
|
||||
} else if (c.type === 14) {
|
||||
previewComponents.push({ type: 14, divider: c.divider ?? true, spacing: c.spacing ?? 1 });
|
||||
} else if (c.type === 12) {
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const processedImageUrl = await replaceVars(c.url, member, guild);
|
||||
if (isValidUrl(processedImageUrl)) previewComponents.push({ type: 12, items: [{ media: { url: processedImageUrl } }] });
|
||||
}
|
||||
@@ -166,20 +124,19 @@ export const command: CommandMessage = {
|
||||
const existingBlock = await client.prisma.blockV2Config.findFirst({
|
||||
where: { guildId: message.guild!.id, name: blockName }
|
||||
});
|
||||
|
||||
if (!existingBlock) {
|
||||
await message.reply("❌ Block no encontrado. Usa `!blockcreatev2 <nombre>` para crear uno nuevo.");
|
||||
return;
|
||||
}
|
||||
|
||||
let blockState: any = {
|
||||
title: existingBlock.config.title || `Block: ${blockName}`,
|
||||
color: existingBlock.config.color || null,
|
||||
coverImage: existingBlock.config.coverImage || null,
|
||||
components: existingBlock.config.components || []
|
||||
title: existingBlock.config?.title || `Block: ${blockName}`,
|
||||
color: existingBlock.config?.color ?? null,
|
||||
coverImage: existingBlock.config?.coverImage ?? null,
|
||||
components: Array.isArray(existingBlock.config?.components) ? existingBlock.config.components : []
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const editorMessage = await message.channel.send({
|
||||
content: "⚠️ **EDITANDO BLOCK EXISTENTE**\n\n" +
|
||||
"Este editor usa **modales interactivos** y no podrás ver el chat mientras los usas.\n\n" +
|
||||
@@ -191,23 +148,21 @@ export const command: CommandMessage = {
|
||||
"*Iniciando editor en 3 segundos...*"
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
await new Promise(r => setTimeout(r, 3000));
|
||||
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
await editorMessage.edit({
|
||||
content: null,
|
||||
flags: 4096,
|
||||
components: [
|
||||
await renderPreview(blockState, message.member, message.guild),
|
||||
...btns(false)
|
||||
]
|
||||
// @ts-ignore - Display Components
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
|
||||
const collector = editorMessage.createMessageComponentCollector({ time: 3600000 });
|
||||
|
||||
collector.on("collect", async (i: any) => {
|
||||
if (i.user.id !== message.author.id) {
|
||||
await i.reply({ content: "No puedes usar este menú.", flags: 64 });
|
||||
await i.reply({ content: "No puedes usar este menú.", flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,16 +175,16 @@ export const command: CommandMessage = {
|
||||
data: { config: blockState }
|
||||
});
|
||||
await editorMessage.edit({
|
||||
components: [
|
||||
{
|
||||
// @ts-ignore
|
||||
display: {
|
||||
type: 17,
|
||||
accent_color: blockState.color ?? null,
|
||||
components: [
|
||||
{ type: 10, content: `✅ Actualizado: ${blockName}` },
|
||||
{ type: 10, content: "Cambios guardados en la base de datos." }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
components: []
|
||||
});
|
||||
collector.stop();
|
||||
return;
|
||||
@@ -242,12 +197,10 @@ export const command: CommandMessage = {
|
||||
}
|
||||
case "edit_title": {
|
||||
const modal = new ModalBuilder().setCustomId('edit_title_modal').setTitle('📝 Editar Título del Block');
|
||||
const titleInput = new TextInputBuilder()
|
||||
.setCustomId('title_input').setLabel('Nuevo Título').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Escribe el nuevo título aquí...').setValue(blockState.title || '').setMaxLength(256).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(titleInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const titleInput = new TextInputBuilder().setCustomId('title_input').setLabel('Nuevo Título').setStyle(TextInputStyle.Short).setPlaceholder('Escribe el nuevo título aquí...').setValue(blockState.title || '').setMaxLength(256).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(titleInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
@@ -255,197 +208,118 @@ export const command: CommandMessage = {
|
||||
const modal = new ModalBuilder().setCustomId('edit_description_modal').setTitle('📄 Editar Descripción');
|
||||
const descComp = blockState.components.find((c: any) => c.type === 10);
|
||||
const currentDesc = descComp ? descComp.content : '';
|
||||
const descInput = new TextInputBuilder()
|
||||
.setCustomId('description_input').setLabel('Nueva Descripción').setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder('Escribe la nueva descripción aquí...').setValue(currentDesc || '').setMaxLength(2000).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(descInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const descInput = new TextInputBuilder().setCustomId('description_input').setLabel('Nueva Descripción').setStyle(TextInputStyle.Paragraph).setPlaceholder('Escribe la nueva descripción aquí...').setValue(currentDesc || '').setMaxLength(2000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(descInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "edit_color": {
|
||||
const modal = new ModalBuilder().setCustomId('edit_color_modal').setTitle('🎨 Editar Color del Block');
|
||||
const currentColor = blockState.color ? `#${blockState.color.toString(16).padStart(6, '0')}` : '';
|
||||
const colorInput = new TextInputBuilder()
|
||||
.setCustomId('color_input').setLabel('Color en formato HEX').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('#FF5733 o FF5733').setValue(currentColor).setMaxLength(7).setRequired(false);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(colorInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const colorInput = new TextInputBuilder().setCustomId('color_input').setLabel('Color en formato HEX').setStyle(TextInputStyle.Short).setPlaceholder('#FF5733 o FF5733').setValue(currentColor).setMaxLength(7).setRequired(false);
|
||||
const row = new ActionRowBuilder().addComponents(colorInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "add_content": {
|
||||
const modal = new ModalBuilder().setCustomId('add_content_modal').setTitle('➕ Agregar Nuevo Contenido');
|
||||
const contentInput = new TextInputBuilder()
|
||||
.setCustomId('content_input').setLabel('Contenido del Texto').setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder('Escribe el contenido aquí...').setMaxLength(2000).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(contentInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const contentInput = new TextInputBuilder().setCustomId('content_input').setLabel('Contenido del Texto').setStyle(TextInputStyle.Paragraph).setPlaceholder('Escribe el contenido aquí...').setMaxLength(2000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(contentInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "add_image": {
|
||||
const modal = new ModalBuilder().setCustomId('add_image_modal').setTitle('🖼️ Agregar Nueva Imagen');
|
||||
const imageUrlInput = new TextInputBuilder()
|
||||
.setCustomId('image_url_input').setLabel('URL de la Imagen').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com/imagen.png').setMaxLength(2000).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(imageUrlInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const imageUrlInput = new TextInputBuilder().setCustomId('image_url_input').setLabel('URL de la Imagen').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com/imagen.png').setMaxLength(2000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(imageUrlInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "cover_image": {
|
||||
if (blockState.coverImage) {
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Ya tienes una imagen de portada. ¿Qué quieres hacer?",
|
||||
components: [{ type: 1, components: [
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: "Ya tienes una imagen de portada. ¿Qué quieres hacer?", components: [{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Primary, label: "✏️ Editar", custom_id: "edit_cover_modal" },
|
||||
{ type: 2, style: ButtonStyle.Danger, label: "🗑️ Eliminar", custom_id: "delete_cover" }
|
||||
]}],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const coverCollector = reply.createMessageComponentCollector({ componentType: ComponentType.Button, max: 1, time: 60000, filter: (b: any) => b.user.id === message.author.id });
|
||||
coverCollector.on("collect", async (b: any) => {
|
||||
if (b.customId === "edit_cover_modal") {
|
||||
coverCollector.on('collect', async (b: any) => {
|
||||
if (b.customId === 'edit_cover_modal') {
|
||||
const modal = new ModalBuilder().setCustomId('edit_cover_modal').setTitle('🖼️ Editar Imagen de Portada');
|
||||
const coverInput = new TextInputBuilder()
|
||||
.setCustomId('cover_input').setLabel('URL de la Imagen de Portada').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com/portada.png').setValue(blockState.coverImage || '').setMaxLength(2000).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(coverInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const coverInput = new TextInputBuilder().setCustomId('cover_input').setLabel('URL de la Imagen de Portada').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com/portada.png').setValue(blockState.coverImage || '').setMaxLength(2000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(coverInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await b.showModal(modal);
|
||||
} else if (b.customId === "delete_cover") {
|
||||
} else if (b.customId === 'delete_cover') {
|
||||
blockState.coverImage = null;
|
||||
await b.update({ content: "✅ Imagen de portada eliminada.", components: [] });
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await b.update({ content: '✅ Imagen de portada eliminada.', components: [] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
}
|
||||
coverCollector.stop();
|
||||
});
|
||||
} else {
|
||||
const modal = new ModalBuilder().setCustomId('add_cover_modal').setTitle('🖼️ Agregar Imagen de Portada');
|
||||
const coverInput = new TextInputBuilder()
|
||||
.setCustomId('cover_input').setLabel('URL de la Imagen de Portada').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com/portada.png').setMaxLength(2000).setRequired(true);
|
||||
const firstActionRow = new ActionRowBuilder().addComponents(coverInput);
|
||||
modal.addComponents(firstActionRow);
|
||||
//@ts-ignore
|
||||
const coverInput = new TextInputBuilder().setCustomId('cover_input').setLabel('URL de la Imagen de Portada').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com/portada.png').setMaxLength(2000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(coverInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "add_separator": {
|
||||
const modal = new ModalBuilder().setCustomId('add_separator_modal').setTitle('➖ Agregar Separador');
|
||||
const visibleInput = new TextInputBuilder()
|
||||
.setCustomId('separator_visible').setLabel('¿Separador visible? (true/false)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('true o false').setValue('true').setMaxLength(5).setRequired(true);
|
||||
const spacingInput = new TextInputBuilder()
|
||||
.setCustomId('separator_spacing').setLabel('Espaciado (1-3)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('1, 2 o 3').setValue('1').setMaxLength(1).setRequired(false);
|
||||
const firstRow = new ActionRowBuilder().addComponents(visibleInput);
|
||||
const secondRow = new ActionRowBuilder().addComponents(spacingInput);
|
||||
modal.addComponents(firstRow, secondRow);
|
||||
//@ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "edit_thumbnail": {
|
||||
// Listado de TextDisplays para elegir
|
||||
const textDisplays = blockState.components.map((c: any, idx: number) => ({ c, idx })).filter(({ c }: any) => c.type === 10);
|
||||
if (textDisplays.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: "❌ No hay bloques de texto para editar thumbnail." });
|
||||
break;
|
||||
}
|
||||
const options = textDisplays.map(({ c, idx }: any) => ({
|
||||
label: `Texto #${idx + 1}: ${c.content?.slice(0, 30) || '...'}`,
|
||||
value: String(idx),
|
||||
description: c.thumbnail ? 'Con thumbnail' : c.linkButton ? 'Con botón link' : 'Sin accesorio'
|
||||
}));
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Elige el TextDisplay a editar su thumbnail:",
|
||||
components: [{ type: 1, components: [{ type: 3, custom_id: 'choose_text_for_thumbnail', placeholder: 'Selecciona un bloque de texto', options }] }],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === message.author.id });
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const idx = parseInt(sel.values[0]);
|
||||
const textComp = blockState.components[idx];
|
||||
const modal = new ModalBuilder().setCustomId(`edit_thumbnail_modal_${idx}`).setTitle('📎 Editar Thumbnail');
|
||||
const thumbnailInput = new TextInputBuilder()
|
||||
.setCustomId('thumbnail_input').setLabel('URL del Thumbnail').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com/thumbnail.png o dejar vacío para eliminar').setValue(textComp?.thumbnail || '').setMaxLength(2000).setRequired(false);
|
||||
const firstRow = new ActionRowBuilder().addComponents(thumbnailInput);
|
||||
modal.addComponents(firstRow);
|
||||
await sel.update({ content: 'Abriendo modal…', components: [] });
|
||||
//@ts-ignore
|
||||
await i.showModal(modal);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "move_block": {
|
||||
const options = blockState.components.map((c: any, idx: number) => ({
|
||||
label:
|
||||
c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || "..."}`
|
||||
: c.type === 14 ? "Separador"
|
||||
: c.type === 12 ? `Imagen: ${c.url?.slice(-30) || "..."}`
|
||||
: `Componente ${c.type}`,
|
||||
value: idx.toString(),
|
||||
description:
|
||||
c.type === 10 && (c.thumbnail || c.linkButton)
|
||||
? (c.thumbnail ? "Con thumbnail" : "Con botón link")
|
||||
: undefined
|
||||
label: c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || '...'}` : c.type === 14 ? 'Separador' : c.type === 12 ? `Imagen: ${c.url?.slice(-30) || '...'}` : `Componente ${c.type}`,
|
||||
value: String(idx),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? 'Con thumbnail' : 'Con botón link') : undefined
|
||||
}));
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Selecciona el bloque que quieres mover:",
|
||||
components: [{ type: 1, components: [{ type: 3, custom_id: "move_block_select", placeholder: "Elige un bloque", options }] }],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: 'Selecciona el bloque que quieres mover:', components: [{ type: 1, components: [ { type: 3, custom_id: 'move_block_select', placeholder: 'Elige un bloque', options } ] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === message.author.id });
|
||||
selCollector.on("collect", async (sel: any) => {
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const idx = parseInt(sel.values[0]);
|
||||
await sel.update({
|
||||
content: "¿Quieres mover este bloque?",
|
||||
components: [{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: "⬆️ Subir", custom_id: `move_up_${idx}`, disabled: idx === 0 },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: "⬇️ Bajar", custom_id: `move_down_${idx}`, disabled: idx === blockState.components.length - 1 }
|
||||
] }]
|
||||
});
|
||||
//@ts-ignore
|
||||
await sel.update({ content: '¿Quieres mover este bloque?', components: [{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: '⬆️ Subir', custom_id: `move_up_${idx}`, disabled: idx === 0 },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: '⬇️ Bajar', custom_id: `move_down_${idx}`, disabled: idx === blockState.components.length - 1 }
|
||||
] }] });
|
||||
// @ts-ignore
|
||||
const btnCollector = reply.createMessageComponentCollector({ componentType: ComponentType.Button, max: 1, time: 60000, filter: (b: any) => b.user.id === message.author.id });
|
||||
btnCollector.on("collect", async (b: any) => {
|
||||
if (b.customId.startsWith("move_up_")) {
|
||||
const i2 = parseInt(b.customId.replace("move_up_", ""));
|
||||
if (!(i2 <= 0)) {
|
||||
btnCollector.on('collect', async (b: any) => {
|
||||
if (b.customId.startsWith('move_up_')) {
|
||||
const i2 = parseInt(b.customId.replace('move_up_', ''));
|
||||
if (i2 > 0) {
|
||||
const item = blockState.components[i2];
|
||||
blockState.components.splice(i2, 1);
|
||||
blockState.components.splice(i2 - 1, 0, item);
|
||||
}
|
||||
await b.update({ content: "✅ Bloque movido arriba.", components: [] });
|
||||
} else if (b.customId.startsWith("move_down_")) {
|
||||
const i2 = parseInt(b.customId.replace("move_down_", ""));
|
||||
if (!(i2 >= blockState.components.length - 1)) {
|
||||
await b.update({ content: '✅ Bloque movido arriba.', components: [] });
|
||||
} else if (b.customId.startsWith('move_down_')) {
|
||||
const i2 = parseInt(b.customId.replace('move_down_', ''));
|
||||
if (i2 < blockState.components.length - 1) {
|
||||
const item = blockState.components[i2];
|
||||
blockState.components.splice(i2, 1);
|
||||
blockState.components.splice(i2 + 1, 0, item);
|
||||
}
|
||||
await b.update({ content: "✅ Bloque movido abajo.", components: [] });
|
||||
await b.update({ content: '✅ Bloque movido abajo.', components: [] });
|
||||
}
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
btnCollector.stop();
|
||||
selCollector.stop();
|
||||
});
|
||||
@@ -453,153 +327,166 @@ export const command: CommandMessage = {
|
||||
break;
|
||||
}
|
||||
case "delete_block": {
|
||||
const options = [] as any[];
|
||||
if (blockState.coverImage) options.push({ label: "🖼️ Imagen de Portada", value: "cover_image", description: "Imagen principal del bloque" });
|
||||
blockState.components.forEach((c: any, idx: number) => {
|
||||
options.push({
|
||||
label:
|
||||
c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || "..."}`
|
||||
: c.type === 14 ? `Separador ${c.divider ? '(Visible)' : '(Invisible)'}`
|
||||
: c.type === 12 ? `Imagen: ${c.url?.slice(-30) || "..."}`
|
||||
: `Componente ${c.type}`,
|
||||
value: idx.toString(),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? "Con thumbnail" : "Con botón link") : undefined
|
||||
});
|
||||
});
|
||||
const options: any[] = [];
|
||||
if (blockState.coverImage) options.push({ label: '🖼️ Imagen de Portada', value: 'cover_image', description: 'Imagen principal del bloque' });
|
||||
blockState.components.forEach((c: any, idx: number) => options.push({
|
||||
label: c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || '...'}` : c.type === 14 ? `Separador ${c.divider ? '(Visible)' : '(Invisible)'}` : c.type === 12 ? `Imagen: ${c.url?.slice(-30) || '...'}` : `Componente ${c.type}`,
|
||||
value: String(idx),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? 'Con thumbnail' : 'Con botón link') : undefined
|
||||
}));
|
||||
if (options.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: "❌ No hay elementos para eliminar." });
|
||||
// @ts-ignore
|
||||
await i.editReply({ content: '❌ No hay elementos para eliminar.' });
|
||||
break;
|
||||
}
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Selecciona el elemento que quieres eliminar:",
|
||||
components: [{ type: 1, components: [{ type: 3, custom_id: "delete_block_select", placeholder: "Elige un elemento", options }] }],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: 'Selecciona el elemento que quieres eliminar:', components: [{ type: 1, components: [ { type: 3, custom_id: 'delete_block_select', placeholder: 'Elige un elemento', options } ] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === message.author.id });
|
||||
selCollector.on("collect", async (sel: any) => {
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const selectedValue = sel.values[0];
|
||||
if (selectedValue === "cover_image") {
|
||||
if (selectedValue === 'cover_image') {
|
||||
blockState.coverImage = null;
|
||||
await sel.update({ content: "✅ Imagen de portada eliminada.", components: [] });
|
||||
await sel.update({ content: '✅ Imagen de portada eliminada.', components: [] });
|
||||
} else {
|
||||
const idx = parseInt(selectedValue);
|
||||
blockState.components.splice(idx, 1);
|
||||
await sel.update({ content: "✅ Elemento eliminado.", components: [] });
|
||||
await sel.update({ content: '✅ Elemento eliminado.', components: [] });
|
||||
}
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
selCollector.stop();
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "show_variables": {
|
||||
await i.deferReply({ flags: 64 });
|
||||
const vars = listVariables();
|
||||
if (vars.length === 0) {
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: "No hay variables registradas." });
|
||||
break;
|
||||
}
|
||||
const chunks: string[] = [];
|
||||
let buf = "";
|
||||
const chunked: string[] = [];
|
||||
let current = "";
|
||||
for (const v of vars) {
|
||||
const line = `• ${v}\n`;
|
||||
if ((buf + line).length > 1800) { chunks.push(buf); buf = line; } else { buf += line; }
|
||||
if ((current + line).length > 1800) { chunked.push(current); current = line; }
|
||||
else current += line;
|
||||
}
|
||||
if (current) chunked.push(current);
|
||||
if (chunked.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
// @ts-ignore
|
||||
await i.editReply({ content: 'No hay variables registradas.' });
|
||||
} else {
|
||||
// @ts-ignore
|
||||
await i.reply({ flags: 64, content: `📋 **Variables Disponibles:**\n\n${chunked[0]}` });
|
||||
for (let idx = 1; idx < chunked.length; idx++) {
|
||||
// @ts-ignore
|
||||
await i.followUp({ flags: 64, content: chunked[idx] });
|
||||
}
|
||||
if (buf) chunks.push(buf);
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: `📋 **Variables Disponibles:**\n\n${chunks[0]}` });
|
||||
for (let k = 1; k < chunks.length; k++) {
|
||||
//@ts-ignore
|
||||
await i.followUp({ flags: 64, content: chunks[k] });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "duplicate_block": {
|
||||
const options = blockState.components.map((c: any, idx: number) => ({
|
||||
label: c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || "..."}`
|
||||
: c.type === 14 ? "Separador"
|
||||
: c.type === 12 ? `Imagen: ${c.url?.slice(-30) || "..."}`
|
||||
: `Componente ${c.type}`,
|
||||
value: idx.toString(),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? "Con thumbnail" : "Con botón link") : undefined
|
||||
label: c.type === 10 ? `Texto: ${c.content?.slice(0, 30) || '...'}` : c.type === 14 ? 'Separador' : c.type === 12 ? `Imagen: ${c.url?.slice(-30) || '...'}` : `Componente ${c.type}`,
|
||||
value: String(idx),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? 'Con thumbnail' : 'Con botón link') : undefined
|
||||
}));
|
||||
if (options.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: "❌ No hay elementos para duplicar." });
|
||||
// @ts-ignore
|
||||
await i.editReply({ content: '❌ No hay elementos para duplicar.' });
|
||||
break;
|
||||
}
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Selecciona el elemento que quieres duplicar:",
|
||||
components: [{ type: 1, components: [{ type: 3, custom_id: "duplicate_select", placeholder: "Elige un elemento", options }] }],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: 'Selecciona el elemento que quieres duplicar:', components: [{ type: 1, components: [{ type: 3, custom_id: 'duplicate_select', placeholder: 'Elige un elemento', options }] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (sel: any) => sel.user.id === message.author.id });
|
||||
selCollector.on("collect", async (sel: any) => {
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const idx = parseInt(sel.values[0]);
|
||||
const originalComponent = blockState.components[idx];
|
||||
const duplicatedComponent = JSON.parse(JSON.stringify(originalComponent));
|
||||
blockState.components.splice(idx + 1, 0, duplicatedComponent);
|
||||
await sel.update({ content: "✅ Elemento duplicado.", components: [] });
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await sel.update({ content: '✅ Elemento duplicado.', components: [] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "show_raw": {
|
||||
const rawJson = JSON.stringify(blockState, null, 2);
|
||||
const truncated = rawJson.length > 1900 ? rawJson.slice(0, 1900) + "..." : rawJson;
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
await i.reply({ flags: 64, content: `\`\`\`json\n${truncated}\n\`\`\`` });
|
||||
break;
|
||||
}
|
||||
case "import_json": {
|
||||
const modal = new ModalBuilder().setCustomId('import_json_modal').setTitle('📥 Importar JSON');
|
||||
const jsonInput = new TextInputBuilder()
|
||||
.setCustomId('json_input').setLabel('Pega tu configuración JSON aquí').setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder('{"title": "...", "components": [...]}').setMaxLength(4000).setRequired(true);
|
||||
const firstRow = new ActionRowBuilder().addComponents(jsonInput);
|
||||
modal.addComponents(firstRow);
|
||||
//@ts-ignore
|
||||
const jsonInput = new TextInputBuilder().setCustomId('json_input').setLabel('Pega tu configuración JSON aquí').setStyle(TextInputStyle.Paragraph).setPlaceholder('{"title": "...", "components": [...]}').setMaxLength(4000).setRequired(true);
|
||||
const row = new ActionRowBuilder().addComponents(jsonInput);
|
||||
modal.addComponents(row);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "export_json": {
|
||||
const exportJson = JSON.stringify(blockState, null, 2);
|
||||
const truncatedJson = exportJson.length > 1800 ? exportJson.slice(0, 1800) + "\n..." : exportJson;
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
await i.reply({ flags: 64, content: `📤 **JSON Exportado:**\n\`\`\`json\n${truncatedJson}\n\`\`\`\n\n💡 **Tip:** Copia el JSON de arriba manualmente y pégalo donde necesites.` });
|
||||
break;
|
||||
}
|
||||
case "edit_link_button": {
|
||||
// Elegir TextDisplay para botón link
|
||||
case "add_separator": {
|
||||
const modal = new ModalBuilder().setCustomId('add_separator_modal').setTitle('➖ Agregar Separador');
|
||||
const visibleInput = new TextInputBuilder().setCustomId('separator_visible').setLabel('¿Separador visible? (true/false)').setStyle(TextInputStyle.Short).setPlaceholder('true o false').setValue('true').setMaxLength(5).setRequired(true);
|
||||
const spacingInput = new TextInputBuilder().setCustomId('separator_spacing').setLabel('Espaciado (1-3)').setStyle(TextInputStyle.Short).setPlaceholder('1, 2 o 3').setValue('1').setMaxLength(1).setRequired(false);
|
||||
const r1 = new ActionRowBuilder().addComponents(visibleInput);
|
||||
const r2 = new ActionRowBuilder().addComponents(spacingInput);
|
||||
modal.addComponents(r1, r2);
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
break;
|
||||
}
|
||||
case "edit_thumbnail": {
|
||||
const textDisplays = blockState.components.map((c: any, idx: number) => ({ c, idx })).filter(({ c }: any) => c.type === 10);
|
||||
if (textDisplays.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
//@ts-ignore
|
||||
await i.editReply({ content: "❌ Necesitas al menos un componente de texto para añadir un botón link." });
|
||||
// @ts-ignore
|
||||
await i.editReply({ content: '❌ No hay bloques de texto para editar thumbnail.' });
|
||||
break;
|
||||
}
|
||||
const options = textDisplays.map(({ c, idx }: any) => ({
|
||||
label: `Texto #${idx + 1}: ${c.content?.slice(0, 30) || '...'}`,
|
||||
value: String(idx),
|
||||
description: c.linkButton ? 'Con botón link' : c.thumbnail ? 'Con thumbnail' : 'Sin accesorio'
|
||||
}));
|
||||
//@ts-ignore
|
||||
const reply = await i.reply({
|
||||
flags: 64,
|
||||
content: "Elige el TextDisplay donde agregar/editar el botón link:",
|
||||
components: [{ type: 1, components: [{ type: 3, custom_id: 'choose_text_for_linkbtn', placeholder: 'Selecciona un bloque de texto', options }] }],
|
||||
fetchReply: true
|
||||
const options = textDisplays.map(({ c, idx }: any) => ({ label: `Texto #${idx + 1}: ${c.content?.slice(0, 30) || '...'}`, value: String(idx), description: c.thumbnail ? 'Con thumbnail' : c.linkButton ? 'Con botón link' : 'Sin accesorio' }));
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: 'Elige el TextDisplay a editar su thumbnail:', components: [{ type: 1, components: [ { type: 3, custom_id: 'choose_text_for_thumbnail', placeholder: 'Selecciona un bloque de texto', options } ] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === message.author.id });
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const idx = parseInt(sel.values[0]);
|
||||
const textComp = blockState.components[idx];
|
||||
const modal = new ModalBuilder().setCustomId(`edit_thumbnail_modal_${idx}`).setTitle('📎 Editar Thumbnail');
|
||||
const thumbnailInput = new TextInputBuilder().setCustomId('thumbnail_input').setLabel('URL del Thumbnail').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com/thumbnail.png o dejar vacío para eliminar').setValue(textComp?.thumbnail || '').setMaxLength(2000).setRequired(false);
|
||||
const row = new ActionRowBuilder().addComponents(thumbnailInput);
|
||||
modal.addComponents(row);
|
||||
await sel.update({ content: 'Abriendo modal…', components: [] });
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
});
|
||||
//@ts-ignore
|
||||
break;
|
||||
}
|
||||
case "edit_link_button": {
|
||||
const textDisplays = blockState.components.map((c: any, idx: number) => ({ c, idx })).filter(({ c }: any) => c.type === 10);
|
||||
if (textDisplays.length === 0) {
|
||||
await i.deferReply({ flags: 64 });
|
||||
// @ts-ignore
|
||||
await i.editReply({ content: '❌ Necesitas al menos un componente de texto para añadir un botón link.' });
|
||||
break;
|
||||
}
|
||||
const options = textDisplays.map(({ c, idx }: any) => ({ label: `Texto #${idx + 1}: ${c.content?.slice(0, 30) || '...'}`, value: String(idx), description: c.linkButton ? 'Con botón link' : c.thumbnail ? 'Con thumbnail' : 'Sin accesorio' }));
|
||||
// @ts-ignore
|
||||
const reply = await i.reply({ flags: 64, content: 'Elige el TextDisplay donde agregar/editar el botón link:', components: [{ type: 1, components: [ { type: 3, custom_id: 'choose_text_for_linkbtn', placeholder: 'Selecciona un bloque de texto', options } ] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const selCollector = reply.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === message.author.id });
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const idx = parseInt(sel.values[0]);
|
||||
@@ -609,60 +496,46 @@ export const command: CommandMessage = {
|
||||
return;
|
||||
}
|
||||
if (textComp.linkButton) {
|
||||
//@ts-ignore
|
||||
const sub = await i.followUp({
|
||||
flags: 64,
|
||||
content: `Texto #${idx + 1}: ya tiene botón link. ¿Qué deseas hacer?`,
|
||||
components: [{ type: 1, components: [
|
||||
// @ts-ignore
|
||||
const sub = await i.followUp({ flags: 64, content: `Texto #${idx + 1}: ya tiene botón link. ¿Qué deseas hacer?`, components: [{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Primary, label: '✏️ Editar', custom_id: `edit_link_button_modal_${idx}` },
|
||||
{ type: 2, style: ButtonStyle.Danger, label: '🗑️ Eliminar', custom_id: `delete_link_button_${idx}` }
|
||||
] }],
|
||||
fetchReply: true
|
||||
});
|
||||
//@ts-ignore
|
||||
] }], fetchReply: true });
|
||||
// @ts-ignore
|
||||
const btnCollector = sub.createMessageComponentCollector({ componentType: ComponentType.Button, max: 1, time: 60000, filter: (b: any) => b.user.id === message.author.id });
|
||||
btnCollector.on('collect', async (b: any) => {
|
||||
if (b.customId.startsWith('edit_link_button_modal_')) {
|
||||
const modal = new ModalBuilder().setCustomId(`edit_link_button_modal_${idx}`).setTitle('🔗 Editar Botón Link');
|
||||
const urlInput = new TextInputBuilder()
|
||||
.setCustomId('link_url_input').setLabel('URL del botón (obligatoria)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com').setValue(textComp.linkButton?.url || '').setMaxLength(2000).setRequired(true);
|
||||
const labelInput = new TextInputBuilder()
|
||||
.setCustomId('link_label_input').setLabel('Etiqueta (opcional)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Texto del botón o vacío para usar solo emoji').setValue(textComp.linkButton?.label || '').setMaxLength(80).setRequired(false);
|
||||
const emojiInput = new TextInputBuilder()
|
||||
.setCustomId('link_emoji_input').setLabel('Emoji (opcional)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Ej: 🔗 o <:name:id>').setValue(textComp.linkButton?.emoji || '').setMaxLength(64).setRequired(false);
|
||||
const urlInput = new TextInputBuilder().setCustomId('link_url_input').setLabel('URL del botón (obligatoria)').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com').setValue(textComp.linkButton?.url || '').setMaxLength(2000).setRequired(true);
|
||||
const labelInput = new TextInputBuilder().setCustomId('link_label_input').setLabel('Etiqueta (opcional)').setStyle(TextInputStyle.Short).setPlaceholder('Texto del botón o vacío para usar solo emoji').setValue(textComp.linkButton?.label || '').setMaxLength(80).setRequired(false);
|
||||
const emojiInput = new TextInputBuilder().setCustomId('link_emoji_input').setLabel('Emoji (opcional)').setStyle(TextInputStyle.Short).setPlaceholder('Ej: 🔗 o <:name:id>').setValue(textComp.linkButton?.emoji || '').setMaxLength(64).setRequired(false);
|
||||
const r1 = new ActionRowBuilder().addComponents(urlInput);
|
||||
const r2 = new ActionRowBuilder().addComponents(labelInput);
|
||||
const r3 = new ActionRowBuilder().addComponents(emojiInput);
|
||||
modal.addComponents(r1, r2, r3);
|
||||
await b.update({ content: 'Abriendo modal…', components: [] });
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
} else if (b.customId.startsWith('delete_link_button_')) {
|
||||
delete textComp.linkButton;
|
||||
await b.update({ content: '✅ Botón link eliminado.', components: [] });
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const modal = new ModalBuilder().setCustomId(`create_link_button_modal_${idx}`).setTitle('🔗 Crear Botón Link');
|
||||
const urlInput = new TextInputBuilder()
|
||||
.setCustomId('link_url_input').setLabel('URL del botón (obligatoria)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('https://ejemplo.com').setMaxLength(2000).setRequired(true);
|
||||
const labelInput = new TextInputBuilder()
|
||||
.setCustomId('link_label_input').setLabel('Etiqueta (opcional)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Texto del botón o vacío para usar solo emoji').setMaxLength(80).setRequired(false);
|
||||
const emojiInput = new TextInputBuilder()
|
||||
.setCustomId('link_emoji_input').setLabel('Emoji (opcional)').setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder('Ej: 🔗 o <:name:id>').setMaxLength(64).setRequired(false);
|
||||
const urlInput = new TextInputBuilder().setCustomId('link_url_input').setLabel('URL del botón (obligatoria)').setStyle(TextInputStyle.Short).setPlaceholder('https://ejemplo.com').setMaxLength(2000).setRequired(true);
|
||||
const labelInput = new TextInputBuilder().setCustomId('link_label_input').setLabel('Etiqueta (opcional)').setStyle(TextInputStyle.Short).setPlaceholder('Texto del botón o vacío para usar solo emoji').setMaxLength(80).setRequired(false);
|
||||
const emojiInput = new TextInputBuilder().setCustomId('link_emoji_input').setLabel('Emoji (opcional)').setStyle(TextInputStyle.Short).setPlaceholder('Ej: 🔗 o <:name:id>').setMaxLength(64).setRequired(false);
|
||||
const r1 = new ActionRowBuilder().addComponents(urlInput);
|
||||
const r2 = new ActionRowBuilder().addComponents(labelInput);
|
||||
const r3 = new ActionRowBuilder().addComponents(emojiInput);
|
||||
modal.addComponents(r1, r2, r3);
|
||||
await sel.update({ content: 'Abriendo modal…', components: [] });
|
||||
//@ts-ignore
|
||||
// @ts-ignore
|
||||
await i.showModal(modal);
|
||||
}
|
||||
});
|
||||
@@ -670,25 +543,19 @@ export const command: CommandMessage = {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
} catch (updateError: any) {
|
||||
if (updateError.code === 10008) {
|
||||
console.log('Mensaje del editor eliminado');
|
||||
} else {
|
||||
console.error('Error actualizando vista previa:', updateError);
|
||||
}
|
||||
}
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Manejo de modales
|
||||
let modalHandlerActive = true;
|
||||
|
||||
const modalHandler = async (interaction: any) => {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
if (interaction.user.id !== message.author.id) return;
|
||||
if (!modalHandlerActive) return;
|
||||
|
||||
try {
|
||||
const id = interaction.customId as string;
|
||||
if (id === 'edit_title_modal') {
|
||||
@@ -696,15 +563,17 @@ export const command: CommandMessage = {
|
||||
await interaction.reply({ content: '✅ Título actualizado.', flags: 64 });
|
||||
} else if (id === 'edit_description_modal') {
|
||||
const newDescription = interaction.fields.getTextInputValue('description_input');
|
||||
const descComp = blockState.components.find((c: any) => c.type === 10);
|
||||
if (descComp) descComp.content = newDescription; else blockState.components.push({ type: 10, content: newDescription, thumbnail: null });
|
||||
const firstText = blockState.components.find((c: any) => c.type === 10);
|
||||
if (firstText) firstText.content = newDescription; else blockState.components.push({ type: 10, content: newDescription, thumbnail: null });
|
||||
await interaction.reply({ content: '✅ Descripción actualizada.', flags: 64 });
|
||||
} else if (id === 'edit_color_modal') {
|
||||
const colorInput = interaction.fields.getTextInputValue('color_input');
|
||||
if (colorInput.trim() === '') blockState.color = null; else {
|
||||
let hexColor = colorInput.replace('#', '');
|
||||
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)', flags: 64 }); return; }
|
||||
const hexColor = colorInput.replace('#', '');
|
||||
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)', flags: 64 });
|
||||
return;
|
||||
}
|
||||
}
|
||||
await interaction.reply({ content: '✅ Color actualizado.', flags: 64 });
|
||||
} else if (id === 'add_content_modal') {
|
||||
@@ -734,7 +603,7 @@ export const command: CommandMessage = {
|
||||
if (thumbnailUrl.trim() === '') { textComp.thumbnail = null; await interaction.reply({ content: '✅ Thumbnail eliminado.', flags: 64 }); }
|
||||
else if (!isValidUrl(thumbnailUrl)) { await interaction.reply({ content: '❌ URL de thumbnail inválida.', flags: 64 }); return; }
|
||||
else {
|
||||
if (textComp.linkButton) { await interaction.reply({ content: '❌ Este bloque ya tiene un botón link. Elimínalo antes de añadir thumbnail.', flags: 64 }); return; }
|
||||
if (textComp.linkButton) { await interaction.reply({ content: '❌ Este bloque ya tiene un botón link. Elimina el botón antes de añadir thumbnail.', flags: 64 }); return; }
|
||||
textComp.thumbnail = thumbnailUrl; await interaction.reply({ content: '✅ Thumbnail actualizado.', flags: 64 });
|
||||
}
|
||||
} else if (id.startsWith('create_link_button_modal_') || id.startsWith('edit_link_button_modal_')) {
|
||||
@@ -750,63 +619,36 @@ export const command: CommandMessage = {
|
||||
if (textComp.thumbnail) { await interaction.reply({ content: '❌ Este bloque tiene thumbnail. Elimínalo antes de añadir un botón link.', flags: 64 }); return; }
|
||||
textComp.linkButton = { url, label: label || undefined, emoji: emojiStr || undefined };
|
||||
await interaction.reply({ content: '✅ Botón link actualizado.', flags: 64 });
|
||||
} else if (id === 'import_json_modal') {
|
||||
try {
|
||||
const jsonString = interaction.fields.getTextInputValue('json_input');
|
||||
const importedData = JSON.parse(jsonString);
|
||||
if (importedData && typeof importedData === 'object') {
|
||||
blockState = {
|
||||
title: importedData.title || blockState.title,
|
||||
color: importedData.color || blockState.color,
|
||||
coverImage: importedData.coverImage || blockState.coverImage,
|
||||
components: Array.isArray(importedData.components) ? importedData.components : blockState.components
|
||||
};
|
||||
for (const comp of blockState.components) {
|
||||
if (comp?.type === 10 && comp.linkButton && comp.thumbnail) delete comp.thumbnail;
|
||||
}
|
||||
await interaction.reply({ content: '✅ JSON importado correctamente.', flags: 64 });
|
||||
} else { await interaction.reply({ content: '❌ Estructura JSON inválida.', flags: 64 }); return; }
|
||||
} catch { await interaction.reply({ content: '❌ JSON inválido. Verifica el formato.', flags: 64 }); return; }
|
||||
} else { return; }
|
||||
|
||||
setTimeout(async () => {
|
||||
if (!modalHandlerActive) return;
|
||||
try {
|
||||
const messageExists = await editorMessage.fetch().catch(() => null);
|
||||
if (!messageExists) return;
|
||||
await editorMessage.edit({ components: [await renderPreview(blockState, message.member, message.guild), ...btns(false)] });
|
||||
} catch (error: any) {
|
||||
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);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error en modal:', error);
|
||||
try {
|
||||
if (error.code !== 10062 && !interaction.replied && !interaction.deferred) {
|
||||
await interaction.reply({ content: '❌ Error procesando el modal.', flags: 64 });
|
||||
}
|
||||
const exists = await editorMessage.fetch().catch(() => null);
|
||||
if (!exists) return;
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
} catch {}
|
||||
}, 400);
|
||||
} catch {}
|
||||
}
|
||||
};
|
||||
|
||||
client.on('interactionCreate', modalHandler);
|
||||
|
||||
//@ts-ignore
|
||||
collector.on("end", async (_, reason) => {
|
||||
collector.on('end', async (_: any, reason: string) => {
|
||||
modalHandlerActive = false;
|
||||
client.off('interactionCreate', modalHandler);
|
||||
if (reason === "time") {
|
||||
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');
|
||||
const exists = await editorMessage.fetch().catch(() => null);
|
||||
if (exists) {
|
||||
await editorMessage.edit({ // @ts-ignore
|
||||
display: { type: 17, components: [{ type: 10, content: '⏰ Editor finalizado por inactividad.' }] },
|
||||
components: []
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user