Refactor modal handling for editDisplayComponent: improve error handling, logging, and input validation
This commit is contained in:
@@ -3,6 +3,7 @@ import { MessageFlags } from "discord.js";
|
||||
import { ComponentType, ButtonStyle, TextInputStyle } from "discord-api-types/v10";
|
||||
import { replaceVars, isValidUrlOrVariable, listVariables } from "../../../core/lib/vars";
|
||||
import { hasManageGuildOrStaff } from "../../../core/lib/permissions";
|
||||
import logger from "../../../core/lib/logger";
|
||||
|
||||
// Botones de edición (máx 5 por fila)
|
||||
const btns = (disabled = false) => ([
|
||||
@@ -824,69 +825,123 @@ export const command: CommandMessage = {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
if (interaction.user.id !== message.author.id) return;
|
||||
if (!modalHandlerActive) return;
|
||||
const sendResponse = async (content: string) => {
|
||||
try {
|
||||
if (!interaction.deferred && !interaction.replied) {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
|
||||
if (interaction.deferred) {
|
||||
await interaction.editReply({ content });
|
||||
} else {
|
||||
await interaction.reply({ content, flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Fallo al enviar respuesta del modal en editar-embed');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const id = interaction.customId as string;
|
||||
|
||||
if (id === 'edit_title_modal') {
|
||||
blockState.title = interaction.fields.getTextInputValue('title_input');
|
||||
await interaction.reply({ content: '✅ Título actualizado.', flags: 64 });
|
||||
const newTitle = interaction.components.getTextInputValue('title_input').trim();
|
||||
blockState.title = newTitle.length > 0 ? newTitle : blockState.title;
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Título actualizado mediante modal.');
|
||||
await sendResponse('✅ Título actualizado.');
|
||||
} else if (id === 'edit_description_modal') {
|
||||
const newDescription = interaction.fields.getTextInputValue('description_input').trim();
|
||||
const newDescription = interaction.components.getTextInputValue('description_input').trim();
|
||||
blockState.description = newDescription.length > 0 ? newDescription : undefined;
|
||||
await interaction.reply({ content: '✅ Descripción actualizada.', flags: 64 });
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Descripción actualizada mediante modal.');
|
||||
await sendResponse('✅ Descripción actualizada.');
|
||||
} else if (id === 'edit_color_modal') {
|
||||
const colorInput = interaction.fields.getTextInputValue('color_input');
|
||||
if (colorInput.trim() === '') blockState.color = null; else {
|
||||
const colorInput = interaction.components.getTextInputValue('color_input').trim();
|
||||
if (colorInput === '') {
|
||||
blockState.color = null;
|
||||
await sendResponse('✅ Color eliminado.');
|
||||
} else {
|
||||
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 });
|
||||
if (/^[0-9A-F]{6}$/i.test(hexColor)) {
|
||||
blockState.color = parseInt(hexColor, 16);
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id, color: blockState.color }, 'Color actualizado mediante modal.');
|
||||
await sendResponse('✅ Color actualizado.');
|
||||
} else {
|
||||
await sendResponse('❌ Color inválido. Usa formato HEX (#FF5733)');
|
||||
return;
|
||||
}
|
||||
}
|
||||
await interaction.reply({ content: '✅ Color actualizado.', flags: 64 });
|
||||
} else if (id === 'add_content_modal') {
|
||||
const newContent = interaction.fields.getTextInputValue('content_input');
|
||||
const newContent = interaction.components.getTextInputValue('content_input').trim();
|
||||
if (!newContent) {
|
||||
await sendResponse('❌ Debes ingresar contenido para añadir.');
|
||||
return;
|
||||
}
|
||||
blockState.components.push({ type: 10, content: newContent, thumbnail: null });
|
||||
await interaction.reply({ content: '✅ Contenido añadido.', flags: 64 });
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Contenido añadido mediante modal.');
|
||||
await sendResponse('✅ Contenido añadido.');
|
||||
} else if (id === '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.', flags: 64 }); }
|
||||
else { await interaction.reply({ content: '❌ URL de imagen inválida.', flags: 64 }); return; }
|
||||
const imageUrl = interaction.components.getTextInputValue('image_url_input').trim();
|
||||
if (!isValidUrl(imageUrl)) {
|
||||
await sendResponse('❌ URL de imagen inválida.');
|
||||
return;
|
||||
}
|
||||
blockState.components.push({ type: 12, url: imageUrl });
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Imagen añadida mediante modal.');
|
||||
await sendResponse('✅ Imagen añadida.');
|
||||
} else if (id === 'add_cover_modal' || id === 'edit_cover_modal') {
|
||||
const coverUrl = interaction.fields.getTextInputValue('cover_input');
|
||||
if (isValidUrl(coverUrl)) { blockState.coverImage = coverUrl; await interaction.reply({ content: '✅ Imagen de portada actualizada.', flags: 64 }); }
|
||||
else { await interaction.reply({ content: '❌ URL de portada inválida.', flags: 64 }); return; }
|
||||
const coverUrl = interaction.components.getTextInputValue('cover_input').trim();
|
||||
if (!isValidUrl(coverUrl)) {
|
||||
await sendResponse('❌ URL de portada inválida.');
|
||||
return;
|
||||
}
|
||||
blockState.coverImage = coverUrl;
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Portada actualizada mediante modal.');
|
||||
await sendResponse('✅ Imagen de portada actualizada.');
|
||||
} else if (id === 'add_separator_modal') {
|
||||
const visibleStr = interaction.fields.getTextInputValue('separator_visible').toLowerCase();
|
||||
const spacingStr = interaction.fields.getTextInputValue('separator_spacing') || '1';
|
||||
const visibleStr = interaction.components.getTextInputValue('separator_visible').toLowerCase();
|
||||
const spacingStr = interaction.components.getTextInputValue('separator_spacing') || '1';
|
||||
const divider = visibleStr === 'true' || visibleStr === '1' || visibleStr === 'si' || visibleStr === 'sí';
|
||||
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.', flags: 64 });
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id, divider, spacing }, 'Separador añadido mediante modal.');
|
||||
await sendResponse('✅ Separador añadido.');
|
||||
} else if (id.startsWith('edit_thumbnail_modal_')) {
|
||||
const idx = parseInt(id.replace('edit_thumbnail_modal_', ''));
|
||||
const textComp = blockState.components[idx];
|
||||
if (!textComp || textComp.type !== 10) return;
|
||||
const thumbnailUrl = interaction.fields.getTextInputValue('thumbnail_input');
|
||||
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. Elimina el botón antes de añadir thumbnail.', flags: 64 }); return; }
|
||||
textComp.thumbnail = thumbnailUrl; await interaction.reply({ content: '✅ Thumbnail actualizado.', flags: 64 });
|
||||
const thumbnailUrl = interaction.components.getTextInputValue('thumbnail_input').trim();
|
||||
if (thumbnailUrl === '') {
|
||||
textComp.thumbnail = null;
|
||||
await sendResponse('✅ Thumbnail eliminado.');
|
||||
} else if (!isValidUrl(thumbnailUrl)) {
|
||||
await sendResponse('❌ URL de thumbnail inválida.');
|
||||
return;
|
||||
} else {
|
||||
if (textComp.linkButton) {
|
||||
await sendResponse('❌ Este bloque ya tiene un botón link. Elimínalo antes de añadir thumbnail.');
|
||||
return;
|
||||
}
|
||||
textComp.thumbnail = thumbnailUrl;
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Thumbnail actualizado mediante modal.');
|
||||
await sendResponse('✅ Thumbnail actualizado.');
|
||||
}
|
||||
} else if (id.startsWith('create_link_button_modal_') || id.startsWith('edit_link_button_modal_')) {
|
||||
const idx = parseInt(id.replace('create_link_button_modal_', '').replace('edit_link_button_modal_', ''));
|
||||
const textComp = blockState.components[idx];
|
||||
if (!textComp || textComp.type !== 10) return;
|
||||
const url = interaction.fields.getTextInputValue('link_url_input');
|
||||
const label = (interaction.fields.getTextInputValue('link_label_input') || '').trim();
|
||||
const emojiStr = (interaction.fields.getTextInputValue('link_emoji_input') || '').trim();
|
||||
if (!isValidUrl(url)) { await interaction.reply({ content: '❌ URL inválida para el botón.', flags: 64 }); return; }
|
||||
const url = interaction.components.getTextInputValue('link_url_input').trim();
|
||||
const label = (interaction.components.getTextInputValue('link_label_input') || '').trim();
|
||||
const emojiStr = (interaction.components.getTextInputValue('link_emoji_input') || '').trim();
|
||||
if (!isValidUrl(url)) { await sendResponse('❌ URL inválida para el botón.'); return; }
|
||||
const parsedEmoji = parseEmojiInput(emojiStr || undefined);
|
||||
if (!label && !parsedEmoji) { await interaction.reply({ content: '❌ Debes proporcionar al menos una etiqueta o un emoji.', flags: 64 }); return; }
|
||||
if (textComp.thumbnail) { await interaction.reply({ content: '❌ Este bloque tiene thumbnail. Elimínalo antes de añadir un botón link.', flags: 64 }); return; }
|
||||
if (!label && !parsedEmoji) { await sendResponse('❌ Debes proporcionar al menos una etiqueta o un emoji.'); return; }
|
||||
if (textComp.thumbnail) { await sendResponse('❌ Este bloque tiene thumbnail. Elimínalo antes de añadir un botón link.'); return; }
|
||||
textComp.linkButton = { url, label: label || undefined, emoji: emojiStr || undefined };
|
||||
await interaction.reply({ content: '✅ Botón link actualizado.', flags: 64 });
|
||||
} else { return; }
|
||||
logger.info({ modalId: id, guildId: message.guildId, userId: interaction.user.id }, 'Botón link actualizado mediante modal.');
|
||||
await sendResponse('✅ Botón link actualizado.');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
if (!modalHandlerActive) return;
|
||||
@@ -897,9 +952,20 @@ export const command: CommandMessage = {
|
||||
display: await renderPreview(blockState, message.member, message.guild),
|
||||
components: btns(false)
|
||||
});
|
||||
} catch {}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error actualizando editor tras procesar modal.');
|
||||
}
|
||||
}, 400);
|
||||
} catch {}
|
||||
} catch (error) {
|
||||
logger.error({ err: error, userId: interaction.user.id, modalId: interaction.customId }, 'Error procesando modal en editar-embed');
|
||||
if (!interaction.deferred && !interaction.replied) {
|
||||
try {
|
||||
await interaction.reply({ content: '❌ Error procesando el modal. Revisa los logs.', flags: MessageFlags.Ephemeral });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Fallo al responder con error tras excepción en modal.');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.on('interactionCreate', modalHandler);
|
||||
|
||||
Reference in New Issue
Block a user