feat: add block movement and deletion functionality in interactive editor
This commit is contained in:
618
src/.backup/createDisplayComponent.backup.ts
Normal file
618
src/.backup/createDisplayComponent.backup.ts
Normal file
@@ -0,0 +1,618 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonInteraction,
|
||||
Message,
|
||||
MessageComponentInteraction,
|
||||
MessageFlags,
|
||||
ModalBuilder, TextChannel,
|
||||
TextInputBuilder,
|
||||
TextInputStyle,
|
||||
} from "discord.js";
|
||||
import logger from "../../../core/lib/logger";
|
||||
import {CommandMessage} from "../../../core/types/commands";
|
||||
import {listVariables} from "../../../core/lib/vars";
|
||||
import type Amayo from "../../../core/client";
|
||||
import {BlockState, DisplayComponentUtils, EditorActionRow} from "../../../core/types/displayComponentEditor";
|
||||
import type {DisplayComponentContainer} from "../../../core/types/displayComponents";
|
||||
|
||||
interface EditorData {
|
||||
content?: string;
|
||||
flags?: MessageFlags;
|
||||
display?: DisplayComponentContainer;
|
||||
components?: EditorActionRow[];
|
||||
}
|
||||
|
||||
// Helper para actualizar el editor combinando Display Container dentro de components (tipado)
|
||||
async function updateEditor(message: Message, data: EditorData): Promise<void> {
|
||||
const container = data.display;
|
||||
const rows = Array.isArray(data.components) ? data.components : [];
|
||||
const components = container ? [container, ...rows] : rows;
|
||||
|
||||
const payload: any = { ...data };
|
||||
delete payload.display;
|
||||
payload.components = components;
|
||||
|
||||
if (payload.flags === undefined) {
|
||||
payload.flags = MessageFlags.IsComponentsV2;
|
||||
}
|
||||
|
||||
await message.edit(payload);
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: "crear-embed",
|
||||
type: "message",
|
||||
aliases: ["embed-crear", "nuevo-embed", "blockcreatev2"],
|
||||
cooldown: 20,
|
||||
description: "Crea un nuevo bloque/embedded con editor interactivo (DisplayComponents).",
|
||||
category: "Alianzas",
|
||||
usage: "crear-embed <nombre>",
|
||||
run: async (message, args, client) => {
|
||||
if (!message.member?.permissions.has("Administrator")) {
|
||||
await message.reply("❌ No tienes permisos de Administrador.");
|
||||
return;
|
||||
}
|
||||
|
||||
const blockName = args[0]?.trim();
|
||||
if (!blockName) {
|
||||
await message.reply("Debes proporcionar un nombre. Uso: `!crear-embed <nombre>`");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if block name already exists
|
||||
const existingBlock = await client.prisma.blockV2Config.findFirst({
|
||||
where: {
|
||||
guildId: message.guild!.id,
|
||||
name: blockName
|
||||
}
|
||||
});
|
||||
|
||||
if (existingBlock) {
|
||||
await message.reply("❌ Ya existe un bloque con ese nombre!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Estado inicial
|
||||
let blockState: BlockState = {
|
||||
title: `Editor de Block: ${blockName}`,
|
||||
color: 0x5865f2,
|
||||
coverImage: undefined,
|
||||
components: [
|
||||
{ type: 14, divider: false, spacing: 1 },
|
||||
{ type: 10, content: "Usa los botones para configurar.", thumbnail: null }
|
||||
]
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
const channelSend: If<boolean, GuildTextBasedChannel, TextBasedChannel> = message.channel;
|
||||
if (!channelSend?.isTextBased()) {
|
||||
await message.reply("❌ This command can only be used in a text-based channel.");
|
||||
return;
|
||||
}
|
||||
|
||||
const editorMessage = await channelSend.send({
|
||||
content: "⚠️ **IMPORTANTE:** Prepara tus títulos, descripciones y URLs antes de empezar.\n" +
|
||||
"Este editor usa **modales interactivos** y no podrás ver el chat mientras los usas.\n\n" +
|
||||
"📝 **Recomendaciones:**\n" +
|
||||
"• Ten preparados tus títulos y descripciones\n" +
|
||||
"• Ten las URLs de imágenes listas para copiar\n" +
|
||||
"• Los colores en formato HEX (#FF5733)\n" +
|
||||
"• Las variables de usuario/servidor que necesites\n\n" +
|
||||
"*Iniciando editor en 5 segundos...*"
|
||||
});
|
||||
|
||||
// Esperar 5 segundos para que lean el mensaje
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// Actualizar para mostrar el editor
|
||||
await updateEditor(editorMessage, {
|
||||
content: undefined,
|
||||
flags: MessageFlags.IsComponentsV2,
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, message.member!, message.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await handleEditorInteractions(editorMessage, message, client, blockName, blockState);
|
||||
},
|
||||
};
|
||||
|
||||
async function handleEditorInteractions(
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
client: Amayo,
|
||||
blockName: string,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const collector = editorMessage.createMessageComponentCollector({
|
||||
time: 3600000, // 1 hour
|
||||
filter: (interaction: MessageComponentInteraction) => interaction.user.id === originalMessage.author.id
|
||||
});
|
||||
|
||||
collector.on("collect", async (interaction: ButtonInteraction) => {
|
||||
try {
|
||||
await handleButtonInteraction(
|
||||
interaction,
|
||||
editorMessage,
|
||||
originalMessage,
|
||||
client,
|
||||
blockName,
|
||||
blockState
|
||||
);
|
||||
} catch (error) {
|
||||
//@ts-ignore
|
||||
logger.error("Error handling editor interaction:", error);
|
||||
if (!interaction.replied && !interaction.deferred) {
|
||||
await interaction.reply({
|
||||
content: "❌ Ocurrió un error al procesar la interacción.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
collector.on("end", async (_collected, reason) => {
|
||||
if (reason === "time") {
|
||||
await handleEditorTimeout(editorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handleButtonInteraction(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
client: Amayo,
|
||||
blockName: string,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const { customId } = interaction;
|
||||
|
||||
switch (customId) {
|
||||
case "edit_title":
|
||||
await handleEditTitle(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "edit_description":
|
||||
await handleEditDescription(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "edit_color":
|
||||
await handleEditColor(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "add_content":
|
||||
await handleAddContent(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "add_separator":
|
||||
await handleAddSeparator(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "add_image":
|
||||
await handleAddImage(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "cover_image":
|
||||
await handleCoverImage(interaction, editorMessage, originalMessage, blockState);
|
||||
break;
|
||||
|
||||
case "show_variables":
|
||||
await handleShowVariables(interaction);
|
||||
break;
|
||||
|
||||
case "show_raw":
|
||||
await handleShowRaw(interaction, blockState);
|
||||
break;
|
||||
|
||||
case "save_block":
|
||||
await handleSaveBlock(interaction, client, blockName, blockState, originalMessage.guildId!);
|
||||
break;
|
||||
|
||||
case "cancel_block":
|
||||
await handleCancelBlock(interaction, editorMessage);
|
||||
break;
|
||||
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: `⚠️ Funcionalidad \`${customId}\` en desarrollo.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEditTitle(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("edit_title_modal")
|
||||
.setTitle("Editar Título del Bloque");
|
||||
|
||||
const titleInput = new TextInputBuilder()
|
||||
.setCustomId("title_input")
|
||||
.setLabel("Título")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder("Escribe el título del bloque...")
|
||||
.setValue(blockState.title || "")
|
||||
.setRequired(true)
|
||||
.setMaxLength(256);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(titleInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const newTitle = modalInteraction.fields.getTextInputValue("title_input").trim();
|
||||
|
||||
if (newTitle) {
|
||||
blockState.title = newTitle;
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
}
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Título actualizado correctamente.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
} catch {
|
||||
// Modal timed out or error occurred
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEditDescription(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("edit_description_modal")
|
||||
.setTitle("Editar Descripción del Bloque");
|
||||
|
||||
const descriptionInput = new TextInputBuilder()
|
||||
.setCustomId("description_input")
|
||||
.setLabel("Descripción")
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder("Escribe la descripción del bloque...")
|
||||
.setValue(blockState.description || "")
|
||||
.setRequired(false)
|
||||
.setMaxLength(4000);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(descriptionInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const newDescription = modalInteraction.fields.getTextInputValue("description_input").trim();
|
||||
|
||||
blockState.description = newDescription || undefined;
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Descripción actualizada correctamente.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEditColor(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("edit_color_modal")
|
||||
.setTitle("Editar Color del Bloque");
|
||||
|
||||
const colorInput = new TextInputBuilder()
|
||||
.setCustomId("color_input")
|
||||
.setLabel("Color (formato HEX)")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder("#FF5733 o FF5733")
|
||||
.setValue(blockState.color ? `#${blockState.color.toString(16).padStart(6, '0')}` : "")
|
||||
.setRequired(false)
|
||||
.setMaxLength(7);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(colorInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const colorValue = modalInteraction.fields.getTextInputValue("color_input").trim();
|
||||
|
||||
if (colorValue) {
|
||||
const cleanColor = colorValue.replace('#', '');
|
||||
const colorNumber = parseInt(cleanColor, 16);
|
||||
|
||||
if (!isNaN(colorNumber) && cleanColor.length === 6) {
|
||||
blockState.color = colorNumber;
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Color actualizado correctamente.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
} else {
|
||||
await modalInteraction.reply({
|
||||
content: "❌ Color inválido. Usa formato HEX como #FF5733",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
} else {
|
||||
blockState.color = undefined;
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Color removido.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddContent(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("add_content_modal")
|
||||
.setTitle("Añadir Contenido de Texto");
|
||||
|
||||
const contentInput = new TextInputBuilder()
|
||||
.setCustomId("content_input")
|
||||
.setLabel("Contenido")
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setPlaceholder("Escribe el contenido de texto...")
|
||||
.setRequired(true)
|
||||
.setMaxLength(4000);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(contentInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const content = modalInteraction.fields.getTextInputValue("content_input").trim();
|
||||
|
||||
if (content) {
|
||||
blockState.components.push({
|
||||
type: 10,
|
||||
content,
|
||||
thumbnail: null
|
||||
});
|
||||
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Contenido añadido correctamente.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddSeparator(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
blockState.components.push({
|
||||
type: 14,
|
||||
divider: true,
|
||||
spacing: 1
|
||||
});
|
||||
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await interaction.reply({
|
||||
content: "✅ Separador añadido correctamente.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAddImage(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("add_image_modal")
|
||||
.setTitle("Añadir Imagen");
|
||||
|
||||
const imageInput = new TextInputBuilder()
|
||||
.setCustomId("image_input")
|
||||
.setLabel("URL de la Imagen")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setPlaceholder("https://ejemplo.com/imagen.png")
|
||||
.setRequired(true)
|
||||
.setMaxLength(512);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(imageInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const imageUrl = modalInteraction.fields.getTextInputValue("image_input").trim();
|
||||
|
||||
if (imageUrl && DisplayComponentUtils.isValidUrl(imageUrl)) {
|
||||
blockState.components.push({
|
||||
type: 12,
|
||||
url: imageUrl
|
||||
});
|
||||
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: "✅ Imagen añadida correctamente.",
|
||||
ephemeral: true
|
||||
});
|
||||
} else {
|
||||
await modalInteraction.reply({
|
||||
content: "❌ URL de imagen inválida.",
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCoverImage(
|
||||
interaction: ButtonInteraction,
|
||||
editorMessage: Message,
|
||||
originalMessage: Message,
|
||||
blockState: BlockState
|
||||
): Promise<void> {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("cover_image_modal")
|
||||
.setTitle("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 || "")
|
||||
.setRequired(false)
|
||||
.setMaxLength(512);
|
||||
|
||||
const actionRow = new ActionRowBuilder<TextInputBuilder>().addComponents(coverInput);
|
||||
modal.addComponents(actionRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
|
||||
try {
|
||||
const modalInteraction = await interaction.awaitModalSubmit({ time: 300000 });
|
||||
const coverUrl = modalInteraction.fields.getTextInputValue("cover_input").trim();
|
||||
|
||||
if (coverUrl && DisplayComponentUtils.isValidUrl(coverUrl)) {
|
||||
blockState.coverImage = coverUrl;
|
||||
} else {
|
||||
blockState.coverImage = undefined;
|
||||
}
|
||||
|
||||
await updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false)
|
||||
});
|
||||
|
||||
await modalInteraction.reply({
|
||||
content: coverUrl ? "✅ Imagen de portada actualizada." : "✅ Imagen de portada removida.",
|
||||
ephemeral: true
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleShowVariables(interaction: ButtonInteraction): Promise<void> {
|
||||
const variables = listVariables();
|
||||
await interaction.reply({
|
||||
content: `📋 **Variables disponibles:**\n\`\`\`\n${variables}\`\`\``,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
async function handleShowRaw(interaction: ButtonInteraction, blockState: BlockState): Promise<void> {
|
||||
const rawData = JSON.stringify(blockState, null, 2);
|
||||
await interaction.reply({
|
||||
content: `📊 **Datos del bloque:**\n\`\`\`json\n${rawData.slice(0, 1800)}\`\`\``,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveBlock(
|
||||
interaction: ButtonInteraction,
|
||||
client: Amayo,
|
||||
blockName: string,
|
||||
blockState: BlockState,
|
||||
guildId: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await client.prisma.blockV2Config.create({
|
||||
data: {
|
||||
guildId,
|
||||
name: blockName,
|
||||
config: blockState as any
|
||||
}
|
||||
});
|
||||
|
||||
await interaction.reply({
|
||||
content: `✅ **Bloque guardado exitosamente!**\n\n📄 **Nombre:** \`${blockName}\`\n🎨 **Componentes:** ${blockState.components.length}\n\n🎯 **Uso:** \`!send ${blockName}\``,
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
logger.info(`Block created: ${blockName} in guild ${guildId}`);
|
||||
} catch (error) {
|
||||
//@ts-ignore
|
||||
logger.error("Error saving block:", error);
|
||||
await interaction.reply({
|
||||
content: "❌ Error al guardar el bloque. Inténtalo de nuevo.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancelBlock(interaction: ButtonInteraction, editorMessage: Message): Promise<void> {
|
||||
await interaction.update({
|
||||
content: "❌ **Editor cancelado**\n\nLa creación del bloque ha sido cancelada.",
|
||||
components: [],
|
||||
embeds: []
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEditorTimeout(editorMessage: Message): Promise<void> {
|
||||
try {
|
||||
await editorMessage.edit({
|
||||
content: "⏰ **Editor expirado**\n\nEl editor ha expirado por inactividad. Usa el comando nuevamente para crear un bloque.",
|
||||
components: [],
|
||||
embeds: []
|
||||
});
|
||||
} catch {
|
||||
// message likely deleted
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
MessageFlags,
|
||||
TextChannel,
|
||||
} from "discord.js";
|
||||
import { ComponentType, TextInputStyle } from "discord-api-types/v10";
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from "discord-api-types/v10";
|
||||
import logger from "../../../core/lib/logger";
|
||||
import {CommandMessage} from "../../../core/types/commands";
|
||||
import {listVariables} from "../../../core/lib/vars";
|
||||
@@ -202,6 +202,113 @@ async function handleButtonInteraction(
|
||||
await handleCoverImage(interaction, editorMessage, originalMessage, blockState);
|
||||
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: String(idx),
|
||||
description: c.type === 10 && (c.thumbnail || c.linkButton) ? (c.thumbnail ? 'Con thumbnail' : 'Con botón link') : undefined,
|
||||
}));
|
||||
|
||||
await interaction.reply({
|
||||
flags: MessageFlags.Ephemeral,
|
||||
content: 'Selecciona el bloque que quieres mover:',
|
||||
components: [
|
||||
{ type: 1, components: [ { type: 3, custom_id: 'move_block_select', placeholder: 'Elige un bloque', options } ] },
|
||||
],
|
||||
});
|
||||
const replyMsg = await interaction.fetchReply();
|
||||
// @ts-ignore
|
||||
const selCollector = replyMsg.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === originalMessage.author.id });
|
||||
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
|
||||
const btnCollector = replyMsg.createMessageComponentCollector({ componentType: ComponentType.Button, max: 1, time: 60000, filter: (b: any) => b.user.id === originalMessage.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) {
|
||||
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) {
|
||||
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 updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false),
|
||||
});
|
||||
btnCollector.stop();
|
||||
selCollector.stop();
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "delete_block": {
|
||||
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 interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
// @ts-ignore
|
||||
await interaction.editReply({ content: '❌ No hay elementos para eliminar.' });
|
||||
break;
|
||||
}
|
||||
|
||||
await interaction.reply({
|
||||
flags: MessageFlags.Ephemeral,
|
||||
content: 'Selecciona el elemento que quieres eliminar:',
|
||||
components: [
|
||||
{ type: 1, components: [ { type: 3, custom_id: 'delete_block_select', placeholder: 'Elige un elemento', options } ] },
|
||||
],
|
||||
});
|
||||
const replyMsg = await interaction.fetchReply();
|
||||
// @ts-ignore
|
||||
const selCollector = replyMsg.createMessageComponentCollector({ componentType: ComponentType.StringSelect, max: 1, time: 60000, filter: (it: any) => it.user.id === originalMessage.author.id });
|
||||
selCollector.on('collect', async (sel: any) => {
|
||||
const selectedValue = sel.values[0];
|
||||
if (selectedValue === 'cover_image') {
|
||||
// @ts-ignore
|
||||
blockState.coverImage = null;
|
||||
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 updateEditor(editorMessage, {
|
||||
display: await DisplayComponentUtils.renderPreview(blockState, originalMessage.member!, originalMessage.guild!),
|
||||
components: DisplayComponentUtils.createEditorButtons(false),
|
||||
});
|
||||
selCollector.stop();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "show_variables":
|
||||
await handleShowVariables(interaction);
|
||||
break;
|
||||
@@ -221,7 +328,7 @@ async function handleButtonInteraction(
|
||||
default:
|
||||
await interaction.reply({
|
||||
content: `⚠️ Funcionalidad \`${customId}\` en desarrollo.`,
|
||||
flags: MessageFlags.Ephemeral
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user