From f317b6d3bfaf408d1b9d444b145c2f30dd7e5d3c Mon Sep 17 00:00:00 2001 From: shni Date: Sun, 5 Oct 2025 18:09:15 -0500 Subject: [PATCH] refactor(componentsV2): migrate all admin and game message commands to new display components API --- src/commands/messages/admin/areaEliminar.ts | 126 +++++--------- src/commands/messages/admin/areasLista.ts | 47 ++--- src/commands/messages/admin/itemVer.ts | 77 +++------ src/commands/messages/admin/itemsLista.ts | 51 +++--- src/commands/messages/admin/logroCrear.ts | 175 ++++++++----------- src/commands/messages/admin/logroVer.ts | 94 +++------- src/commands/messages/admin/logrosLista.ts | 56 +++--- src/commands/messages/admin/misionCrear.ts | 170 ++++++++---------- src/commands/messages/game/areaCreate.ts | 182 +++++++------------- src/commands/messages/game/areaEdit.ts | 182 +++++++------------- src/commands/messages/game/itemEdit.ts | 172 +++++++++--------- 11 files changed, 522 insertions(+), 810 deletions(-) diff --git a/src/commands/messages/admin/areaEliminar.ts b/src/commands/messages/admin/areaEliminar.ts index bbbf7cb..b22f6c7 100644 --- a/src/commands/messages/admin/areaEliminar.ts +++ b/src/commands/messages/admin/areaEliminar.ts @@ -3,6 +3,7 @@ import type Amayo from '../../../core/client'; import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; import { prisma } from '../../../core/database/prisma'; import type { TextBasedChannel } from 'discord.js'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'area-eliminar', @@ -12,28 +13,18 @@ export const command: CommandMessage = { description: 'Eliminar un área del servidor', usage: 'area-eliminar ', run: async (message, args, client: Amayo) => { + const channel = message.channel as TextBasedChannel & { send: Function }; const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma); if (!allowed) { - const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ + content: null, flags: 32768, - components: [{ - type: 17, - accent_color: 0xFF0000, - components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' - }] - }] - }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + components: [ + buildDisplay(0xFF0000, [ + textBlock('❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.') + ]) + ], + reply: { messageReference: message.id } }); return; } @@ -42,26 +33,17 @@ export const command: CommandMessage = { const key = args[0]?.trim(); if (!key) { - const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ + content: null, flags: 32768, - components: [{ - type: 17, - accent_color: 0xFFA500, - components: [{ - type: 9, - components: [{ - type: 10, - content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-eliminar `\n└ Ejemplo: `!area-eliminar mine.cavern`' - }] - }] - }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + components: [ + buildDisplay(0xFFA500, [ + textBlock('⚠️ **Uso Incorrecto**'), + dividerBlock(), + textBlock('└ Uso: `!area-eliminar `\n└ Ejemplo: `!area-eliminar mine.cavern`') + ]) + ], + reply: { messageReference: message.id } }); return; } @@ -71,26 +53,17 @@ export const command: CommandMessage = { }); if (!area) { - const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ + content: null, flags: 32768, - components: [{ - type: 17, - accent_color: 0xFF0000, - components: [{ - type: 9, - components: [{ - type: 10, - content: `❌ **Área No Encontrada**\n└ No se encontró el área local con key \`${key}\` en este servidor.` - }] - }] - }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + components: [ + buildDisplay(0xFF0000, [ + textBlock(`❌ **Área No Encontrada**`), + dividerBlock(), + textBlock(`└ No se encontró el área local con key \`${key}\` en este servidor.`) + ]) + ], + reply: { messageReference: message.id } }); return; } @@ -109,38 +82,23 @@ export const command: CommandMessage = { where: { id: area.id } }); - const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ + content: null, flags: 32768, - components: [{ - type: 17, - accent_color: 0x00FF00, - components: [ - { - type: 9, - components: [{ - type: 10, - content: `✅ **Área Eliminada Exitosamente**\n└ Key: \`${key}\`` - }] - }, - ...(levelsCount > 0 ? [{ - type: 14, - divider: true - }, { - type: 9, - components: [{ - type: 10, - content: `⚠️ Se eliminaron ${levelsCount} nivel(es) asociado(s).` - }] - }] : []) - ] - }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + components: [ + buildDisplay(0x00FF00, [ + textBlock(`✅ **Área Eliminada Exitosamente**`), + dividerBlock(), + textBlock(`└ Key: \`${key}\``), + ...(levelsCount > 0 + ? [ + dividerBlock(), + textBlock(`⚠️ Se eliminaron ${levelsCount} nivel(es) asociado(s).`), + ] + : []), + ]) + ], + reply: { messageReference: message.id } }); } }; diff --git a/src/commands/messages/admin/areasLista.ts b/src/commands/messages/admin/areasLista.ts index b882e09..0280232 100644 --- a/src/commands/messages/admin/areasLista.ts +++ b/src/commands/messages/admin/areasLista.ts @@ -3,6 +3,7 @@ import type Amayo from '../../../core/client'; import { prisma } from '../../../core/database/prisma'; import { ComponentType, ButtonStyle } from 'discord-api-types/v10'; import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'areas-lista', @@ -34,29 +35,27 @@ export const command: CommandMessage = { const totalPages = Math.ceil(total / perPage); - const display = { - type: 17, - accent_color: 0x00FF00, - components: [ - { - type: 9, - components: [{ - type: 10, - content: `**🗺️ Lista de Áreas**\nPágina ${page}/${totalPages} • Total: ${total}` - }] - }, - { type: 14, divider: true }, - ...areas.map(area => ({ - type: 9, - components: [{ - type: 10, - content: `**${area.name || area.key}**\n` + - `└ Key: \`${area.key}\`\n` + - `└ ${area.guildId === guildId ? '📍 Local' : '🌐 Global'}` - }] - })) - ] - }; + const displayBlocks = [ + textBlock(`# 🗺️ Lista de Áreas`), + dividerBlock(), + textBlock(`Página ${page}/${totalPages} • Total: ${total}`), + dividerBlock({ divider: false, spacing: 2 }), + ...areas.flatMap((area, index) => { + const lines = [ + `**${area.name || area.key}**`, + `└ Key: \`${area.key}\``, + `└ ${area.guildId === guildId ? '📍 Local' : '🌐 Global'}`, + ].join('\n'); + + const blocks = [textBlock(lines)]; + if (index < areas.length - 1) { + blocks.push(dividerBlock({ divider: false, spacing: 1 })); + } + return blocks; + }) + ]; + + const display = buildDisplay(0x00FF00, displayBlocks); const buttons: any[] = []; @@ -80,7 +79,9 @@ export const command: CommandMessage = { const channel = message.channel as TextBasedChannel & { send: Function }; const msg = await (channel.send as any)({ + content: null, flags: 32768, + reply: { messageReference: message.id }, components: [ display, ...(buttons.length > 0 ? [{ diff --git a/src/commands/messages/admin/itemVer.ts b/src/commands/messages/admin/itemVer.ts index 421dd31..5eba10d 100644 --- a/src/commands/messages/admin/itemVer.ts +++ b/src/commands/messages/admin/itemVer.ts @@ -2,6 +2,7 @@ import type { CommandMessage } from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import { prisma } from '../../../core/database/prisma'; import type { TextBasedChannel } from 'discord.js'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'item-ver', @@ -34,70 +35,42 @@ export const command: CommandMessage = { const props = item.props as any || {}; const tags = item.tags || []; - const display = { - type: 17, - accent_color: 0x00D9FF, - components: [ - { - type: 9, - components: [{ - type: 10, - content: `**🛠️ ${item.name || item.key}**` - }] - }, - { type: 14, divider: true }, - { - type: 9, - components: [{ - type: 10, - content: `**Key:** \`${item.key}\`\n` + - `**Nombre:** ${item.name || '*Sin nombre*'}\n` + - `**Descripción:** ${item.description || '*Sin descripción*'}\n` + - `**Categoría:** ${item.category || '*Sin categoría*'}\n` + - `**Stackable:** ${item.stackable ? 'Sí' : 'No'}\n` + - `**Máx. Inventario:** ${item.maxPerInventory || 'Ilimitado'}\n` + - `**Ámbito:** ${item.guildId ? '📍 Local del servidor' : '🌐 Global'}` - }] - } - ] - }; + const blocks = [ + textBlock(`# 🛠️ ${item.name || item.key}`), + dividerBlock(), + textBlock([ + `**Key:** \`${item.key}\``, + `**Nombre:** ${item.name || '*Sin nombre*'}`, + `**Descripción:** ${item.description || '*Sin descripción*'}`, + `**Categoría:** ${item.category || '*Sin categoría*'}`, + `**Stackable:** ${item.stackable ? 'Sí' : 'No'}`, + `**Máx. Inventario:** ${item.maxPerInventory ?? 'Ilimitado'}`, + `**Ámbito:** ${item.guildId ? '📍 Local del servidor' : '🌐 Global'}`, + ].join('\n')), + ]; if (tags.length > 0) { - display.components.push({ type: 14, divider: true }); - display.components.push({ - type: 9, - components: [{ - type: 10, - content: `**Tags:** ${tags.join(', ')}` - }] - }); + blocks.push(dividerBlock()); + blocks.push(textBlock(`**Tags:** ${tags.join(', ')}`)); } if (item.icon) { - display.components.push({ type: 14, divider: true }); - display.components.push({ - type: 9, - components: [{ - type: 10, - content: `**Icon URL:** ${item.icon}` - }] - }); + blocks.push(dividerBlock()); + blocks.push(textBlock(`**Icon URL:** ${item.icon}`)); } if (Object.keys(props).length > 0) { - display.components.push({ type: 14, divider: true }); - display.components.push({ - type: 9, - components: [{ - type: 10, - content: `**Props (JSON):**\n\`\`\`json\n${JSON.stringify(props, null, 2)}\n\`\`\`` - }] - }); + blocks.push(dividerBlock()); + blocks.push(textBlock(`**Props (JSON):**\n\`\`\`json\n${JSON.stringify(props, null, 2)}\n\`\`\``)); } + const display = buildDisplay(0x00D9FF, blocks); + const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ - display, + content: null, + flags: 32768, + components: [display], reply: { messageReference: message.id } }); } diff --git a/src/commands/messages/admin/itemsLista.ts b/src/commands/messages/admin/itemsLista.ts index db54e50..a7d2fc4 100644 --- a/src/commands/messages/admin/itemsLista.ts +++ b/src/commands/messages/admin/itemsLista.ts @@ -3,6 +3,7 @@ import type Amayo from '../../../core/client'; import { prisma } from '../../../core/database/prisma'; import { ComponentType, ButtonStyle } from 'discord-api-types/v10'; import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'items-lista', @@ -34,32 +35,28 @@ export const command: CommandMessage = { const totalPages = Math.ceil(total / perPage); - const display = { - type: 17, - accent_color: 0x00D9FF, - components: [ - { - type: 9, - components: [{ - type: 10, - content: `**🛠️ Lista de Items**\nPágina ${page}/${totalPages} • Total: ${total}` - }] - }, - { type: 14, divider: true }, - ...items.map(item => ({ - type: 9, - components: [{ - type: 10, - content: `**${item.name || item.key}**\n` + - `└ Key: \`${item.key}\`\n` + - `└ Categoría: ${item.category || '*Sin categoría*'}\n` + - `└ ${item.stackable ? '📚 Apilable' : '🔒 No apilable'}` + - (item.maxPerInventory ? ` (Máx: ${item.maxPerInventory})` : '') + - (item.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global') - }] - })) - ] - }; + const displayBlocks = [ + textBlock(`# 🛠️ Lista de Items`), + dividerBlock(), + textBlock(`Página ${page}/${totalPages} • Total: ${total}`), + dividerBlock({ divider: false, spacing: 2 }), + ...items.flatMap((item, index) => { + const lines = [ + `**${item.name || item.key}**`, + `└ Key: \`${item.key}\``, + `└ Categoría: ${item.category || '*Sin categoría*'}`, + `└ ${item.stackable ? '📚 Apilable' : '🔒 No apilable'}${item.maxPerInventory ? ` (Máx: ${item.maxPerInventory})` : ''}${item.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global'}`, + ].join('\n'); + + const blocks = [textBlock(lines)]; + if (index < items.length - 1) { + blocks.push(dividerBlock({ divider: false, spacing: 1 })); + } + return blocks; + }) + ]; + + const display = buildDisplay(0x00D9FF, displayBlocks); const buttons: any[] = []; @@ -90,7 +87,9 @@ export const command: CommandMessage = { const channel = message.channel as TextBasedChannel & { send: Function }; const msg = await (channel.send as any)({ + content: null, flags: 32768, + reply: { messageReference: message.id }, components: [ display, ...(buttons.length > 0 ? [{ diff --git a/src/commands/messages/admin/logroCrear.ts b/src/commands/messages/admin/logroCrear.ts index 5b0f5a7..8b7c179 100644 --- a/src/commands/messages/admin/logroCrear.ts +++ b/src/commands/messages/admin/logroCrear.ts @@ -2,7 +2,8 @@ import type {CommandMessage} from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import {hasManageGuildOrStaff} from '../../../core/lib/permissions'; import {prisma} from '../../../core/database/prisma'; -import {ButtonStyle, ComponentType, MessageFlags, TextInputStyle} from 'discord-api-types/v10'; +import {ButtonStyle, ComponentType, TextInputStyle} from 'discord-api-types/v10'; +import {buildDisplay, dividerBlock, textBlock} from '../../../core/lib/componentsV2'; import type {ButtonInteraction, MessageComponentInteraction, TextBasedChannel} from 'discord.js'; interface AchievementState { @@ -53,25 +54,12 @@ export const command: CommandMessage = { rewards: { coins: 100 } }; - // Crear mensaje con DisplayComponents - const displayMessage = createDisplay(state); - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - ...displayMessage, - flags: 32768, // MessageFlags.IsComponentsV2 - components: [ - { - type: ComponentType.ActionRow, - components: [ - { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ach_base' }, - { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'ach_req' }, - { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'ach_reward' }, - { type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ach_save' }, - { type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ach_cancel' } - ] - } - ] + const editorMsg = await (channel.send as any)({ + content: null, + flags: 32768, + reply: { messageReference: message.id }, + components: buildEditorComponents(state) }); const collector = editorMsg.createMessageComponentCollector({ @@ -87,19 +75,13 @@ export const command: CommandMessage = { case 'ach_cancel': await i.deferUpdate(); await editorMsg.edit({ - display: { - type: 17, - accent_color: 0xFF0000, - components: [{ - type: 9, - components: [{ - type: 10, - content: '**❌ Creación de logro cancelada.**' - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0xFF0000, [ + textBlock('**❌ Creación de logro cancelada.**') + ]) + ] }); collector.stop('cancel'); return; @@ -139,19 +121,13 @@ export const command: CommandMessage = { await i.reply({ content: '✅ Logro creado exitosamente!', flags: 64 }); await editorMsg.edit({ - display: { - type: 17, - accent_color: 0x00FF00, - components: [{ - type: 9, - components: [{ - type: 10, - content: `**✅ Logro \`${state.key}\` creado exitosamente.**` - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0x00FF00, [ + textBlock(`**✅ Logro \`${state.key}\` creado exitosamente.**`) + ]) + ] }); collector.stop('saved'); return; @@ -168,19 +144,13 @@ export const command: CommandMessage = { if (r === 'time') { try { await editorMsg.edit({ - display: { - type: 17, - accent_color: 0xFFA500, - components: [{ - type: 9, - components: [{ - type: 10, - content: '**⏰ Editor expirado.**' - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0xFFA500, [ + textBlock('**⏰ Editor expirado.**') + ]) + ] }); } catch {} } @@ -188,54 +158,41 @@ export const command: CommandMessage = { } }; -function createDisplay(state: AchievementState) { - return { - display: { - type: 17, // Container - accent_color: 0xFFD700, +function buildEditorDisplay(state: AchievementState) { + const baseInfo = [ + `**Nombre:** ${state.name || '*Sin definir*'}`, + `**Descripción:** ${state.description || '*Sin definir*'}`, + `**Categoría:** ${state.category || 'economy'}`, + `**Icono:** ${state.icon || '🏆'}`, + `**Puntos:** ${state.points ?? 10}`, + `**Oculto:** ${state.hidden ? 'Sí' : 'No'}`, + ].join('\n'); + + return buildDisplay(0xFFD700, [ + textBlock(`# 🏆 Creando Logro: \`${state.key}\``), + dividerBlock(), + textBlock(baseInfo), + dividerBlock(), + textBlock(`**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``), + dividerBlock(), + textBlock(`**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``), + ]); +} + +function buildEditorComponents(state: AchievementState) { + return [ + buildEditorDisplay(state), + { + type: ComponentType.ActionRow, components: [ - { - type: 9, // Section - components: [ - { - type: 10, // Text Display - content: `**🏆 Creando Logro: \`${state.key}\`**` - } - ] - }, - { type: 14, divider: true }, // Separator - { - type: 9, - components: [ - { - type: 10, - content: `**Nombre:** ${state.name || '*Sin definir*'}\n**Descripción:** ${state.description || '*Sin definir*'}\n**Categoría:** ${state.category || 'economy'}\n**Icono:** ${state.icon || '🏆'}\n**Puntos:** ${state.points || 10}\n**Oculto:** ${state.hidden ? 'Sí' : 'No'}` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\`` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\`` - } - ] - } + { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ach_base' }, + { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'ach_req' }, + { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'ach_reward' }, + { type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ach_save' }, + { type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ach_cancel' } ] } - }; + ]; } async function showBaseModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) { @@ -316,7 +273,11 @@ async function showBaseModal(i: ButtonInteraction, state: AchievementState, edit state.points = parseInt(submit.components.getTextInputValue('points')) || 10; await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } async function showRequirementsModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) { @@ -351,7 +312,11 @@ async function showRequirementsModal(i: ButtonInteraction, state: AchievementSta try { state.requirements = JSON.parse(submit.components.getTextInputValue('requirements')); await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } catch (e) { await submit.reply({ content: '❌ JSON inválido en requisitos.', flags: 64 }); } @@ -389,7 +354,11 @@ async function showRewardsModal(i: ButtonInteraction, state: AchievementState, e try { state.rewards = JSON.parse(submit.components.getTextInputValue('rewards')); await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } catch (e) { await submit.reply({ content: '❌ JSON inválido en recompensas.', flags: 64 }); } diff --git a/src/commands/messages/admin/logroVer.ts b/src/commands/messages/admin/logroVer.ts index 969cf5d..3206175 100644 --- a/src/commands/messages/admin/logroVer.ts +++ b/src/commands/messages/admin/logroVer.ts @@ -2,7 +2,7 @@ import type { CommandMessage } from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import { prisma } from '../../../core/database/prisma'; import type { TextBasedChannel } from 'discord.js'; -import { ComponentType, ButtonStyle } from 'discord-api-types/v10'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'logro-ver', @@ -50,78 +50,40 @@ export const command: CommandMessage = { const req = achievement.requirements as any; const rew = achievement.rewards as any; - const display = { - type: 17, - accent_color: 0xFFD700, - components: [ - { - type: 9, - components: [ - { - type: 10, - content: `${achievement.icon || '🏆'} **${achievement.name}**` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**Descripción:** ${achievement.description}\n` + - `**Key:** \`${achievement.key}\`\n` + - `**Categoría:** ${achievement.category}\n` + - `**Puntos:** ${achievement.points} pts\n` + - `**Visibilidad:** ${achievement.hidden ? '🔒 Oculto' : '👁️ Visible'}\n` + - `**Ámbito:** ${achievement.guildId ? '📍 Local del servidor' : '🌐 Global'}\n` + - `**Desbloqueados:** ${unlockedCount} jugadores` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**📋 Requisitos:**\n\`\`\`json\n${JSON.stringify(req, null, 2)}\n\`\`\`` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**🎁 Recompensas:**\n\`\`\`json\n${JSON.stringify(rew, null, 2)}\n\`\`\`` - } - ] - } - ] - }; + const blocks = [ + textBlock(`${achievement.icon || '🏆'} **${achievement.name}**`), + dividerBlock(), + textBlock([ + `**Descripción:** ${achievement.description}`, + `**Key:** \`${achievement.key}\``, + `**Categoría:** ${achievement.category}`, + `**Puntos:** ${achievement.points} pts`, + `**Visibilidad:** ${achievement.hidden ? '🔒 Oculto' : '👁️ Visible'}`, + `**Ámbito:** ${achievement.guildId ? '📍 Local del servidor' : '🌐 Global'}`, + `**Desbloqueados:** ${unlockedCount} jugadores`, + ].join('\n')), + dividerBlock(), + textBlock(`**📋 Requisitos:**\n\`\`\`json\n${JSON.stringify(req, null, 2)}\n\`\`\``), + dividerBlock(), + textBlock(`**🎁 Recompensas:**\n\`\`\`json\n${JSON.stringify(rew, null, 2)}\n\`\`\``), + ]; if (achievement.unlocked.length > 0) { - display.components.push({ type: 14, divider: true }); - display.components.push({ - type: 9, - components: [ - { - type: 10, - content: `**🏆 Últimos Desbloqueados:**\n` + - achievement.unlocked.slice(0, 5).map(pa => - `• <@${pa.userId}> - ${pa.unlockedAt ? new Date(pa.unlockedAt).toLocaleDateString() : 'N/A'}` - ).join('\n') - } - ] - }); + const unlockedLines = achievement.unlocked.slice(0, 5) + .map(pa => `• <@${pa.userId}> - ${pa.unlockedAt ? new Date(pa.unlockedAt).toLocaleDateString() : 'N/A'}`) + .join('\n'); + blocks.push(dividerBlock()); + blocks.push(textBlock(`**🏆 Últimos Desbloqueados:**\n${unlockedLines}`)); } + const display = buildDisplay(0xFFD700, blocks); + const channel = message.channel as TextBasedChannel & { send: Function }; await (channel.send as any)({ + content: null, flags: 32768, - components: [display] + components: [display], + reply: { messageReference: message.id } }); } }; diff --git a/src/commands/messages/admin/logrosLista.ts b/src/commands/messages/admin/logrosLista.ts index d900a92..abf0b04 100644 --- a/src/commands/messages/admin/logrosLista.ts +++ b/src/commands/messages/admin/logrosLista.ts @@ -4,6 +4,7 @@ import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; import { prisma } from '../../../core/database/prisma'; import { ComponentType, ButtonStyle } from 'discord-api-types/v10'; import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; export const command: CommandMessage = { name: 'logros-lista', @@ -35,36 +36,29 @@ export const command: CommandMessage = { const totalPages = Math.ceil(total / perPage); - const display = { - type: 17, - accent_color: 0xFFD700, - components: [ - { - type: 9, - components: [ - { - type: 10, - content: `**🏆 Lista de Logros**\nPágina ${page}/${totalPages} • Total: ${total}` - } - ] - }, - { type: 14, divider: true }, - ...achievements.map(ach => ({ - type: 9, - components: [ - { - type: 10, - content: `${ach.icon || '🏆'} **${ach.name}** (${ach.points} pts)\n` + - `└ Key: \`${ach.key}\`\n` + - `└ Categoría: ${ach.category}\n` + - `└ ${ach.description}\n` + - `└ ${ach.hidden ? '🔒 Oculto' : '👁️ Visible'}` + - (ach.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global') - } - ] - })) - ] - }; + const displayBlocks = [ + textBlock(`# 🏆 Lista de Logros`), + dividerBlock(), + textBlock(`Página ${page}/${totalPages} • Total: ${total}`), + dividerBlock({ divider: false, spacing: 2 }), + ...achievements.flatMap((ach, index) => { + const lines = [ + `${ach.icon || '🏆'} **${ach.name}** (${ach.points} pts)`, + `└ Key: \`${ach.key}\``, + `└ Categoría: ${ach.category}`, + `└ ${ach.description}`, + `└ ${ach.hidden ? '🔒 Oculto' : '👁️ Visible'}${ach.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global'}`, + ].join('\n'); + + const blocks = [textBlock(lines)]; + if (index < achievements.length - 1) { + blocks.push(dividerBlock({ divider: false, spacing: 1 })); + } + return blocks; + }) + ]; + + const display = buildDisplay(0xFFD700, displayBlocks); const buttons: any[] = []; @@ -95,7 +89,9 @@ export const command: CommandMessage = { const channel = message.channel as TextBasedChannel & { send: Function }; const msg = await (channel.send as any)({ + content: null, flags: 32768, + reply: { messageReference: message.id }, components: [ display, ...(buttons.length > 0 ? [{ diff --git a/src/commands/messages/admin/misionCrear.ts b/src/commands/messages/admin/misionCrear.ts index 48d6442..5794564 100644 --- a/src/commands/messages/admin/misionCrear.ts +++ b/src/commands/messages/admin/misionCrear.ts @@ -3,6 +3,7 @@ import type Amayo from '../../../core/client'; import { hasManageGuildOrStaff } from '../../../core/lib/permissions'; import { prisma } from '../../../core/database/prisma'; import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; +import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2'; import type { ButtonInteraction, MessageComponentInteraction, TextBasedChannel } from 'discord.js'; interface QuestState { @@ -53,24 +54,12 @@ export const command: CommandMessage = { rewards: { coins: 500 } }; - const displayMessage = createDisplay(state); - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - ...displayMessage, + const editorMsg = await (channel.send as any)({ + content: null, flags: 32768, - components: [ - { - type: ComponentType.ActionRow, - components: [ - { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'quest_base' }, - { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'quest_req' }, - { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'quest_reward' }, - { type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'quest_save' }, - { type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'quest_cancel' } - ] - } - ] + reply: { messageReference: message.id }, + components: buildEditorComponents(state) }); const collector = editorMsg.createMessageComponentCollector({ @@ -86,19 +75,13 @@ export const command: CommandMessage = { case 'quest_cancel': await i.deferUpdate(); await editorMsg.edit({ - display: { - type: 17, - accent_color: 0xFF0000, - components: [{ - type: 9, - components: [{ - type: 10, - content: '**❌ Creación de misión cancelada.**' - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0xFF0000, [ + textBlock('**❌ Creación de misión cancelada.**') + ]) + ] }); collector.stop('cancel'); return; @@ -139,19 +122,13 @@ export const command: CommandMessage = { await i.reply({ content: '✅ Misión creada exitosamente.', flags: 64 }); await editorMsg.edit({ - display: { - type: 17, - accent_color: 0x00FF00, - components: [{ - type: 9, - components: [{ - type: 10, - content: `**✅ Misión \`${state.key}\` creada exitosamente.**` - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0x00FF00, [ + textBlock(`**✅ Misión \`${state.key}\` creada exitosamente.**`) + ]) + ] }); collector.stop('saved'); return; @@ -168,19 +145,13 @@ export const command: CommandMessage = { if (r === 'time') { try { await editorMsg.edit({ - display: { - type: 17, - accent_color: 0xFFA500, - components: [{ - type: 9, - components: [{ - type: 10, - content: '**⏰ Editor expirado.**' - }] - }] - }, + content: null, flags: 32768, - components: [] + components: [ + buildDisplay(0xFFA500, [ + textBlock('**⏰ Editor expirado.**') + ]) + ] }); } catch {} } @@ -188,7 +159,7 @@ export const command: CommandMessage = { } }; -function createDisplay(state: QuestState) { +function buildEditorDisplay(state: QuestState) { const typeEmojis: Record = { daily: '📅', weekly: '📆', @@ -196,53 +167,40 @@ function createDisplay(state: QuestState) { event: '🎉' }; - return { - display: { - type: 17, // Container - accent_color: 0x5865F2, + const baseInfo = [ + `**Nombre:** ${state.name || '*Sin definir*'}`, + `**Descripción:** ${state.description || '*Sin definir*'}`, + `**Categoría:** ${state.category || 'mining'}`, + `**Tipo:** ${typeEmojis[state.type || 'daily']} ${state.type || 'daily'}`, + `**Icono:** ${state.icon || '📋'}`, + `**Repetible:** ${state.repeatable ? 'Sí' : 'No'}` + ].join('\n'); + + return buildDisplay(0x5865F2, [ + textBlock(`# 📜 Creando Misión: \`${state.key}\``), + dividerBlock(), + textBlock(baseInfo), + dividerBlock(), + textBlock(`**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``), + dividerBlock(), + textBlock(`**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``) + ]); +} + +function buildEditorComponents(state: QuestState) { + return [ + buildEditorDisplay(state), + { + type: ComponentType.ActionRow, components: [ - { - type: 9, // Section - components: [ - { - type: 10, // Text Display - content: `**📜 Creando Misión: \`${state.key}\`**` - } - ] - }, - { type: 14, divider: true }, // Separator - { - type: 9, - components: [ - { - type: 10, - content: `**Nombre:** ${state.name || '*Sin definir*'}\n**Descripción:** ${state.description || '*Sin definir*'}\n**Categoría:** ${state.category || 'mining'}\n**Tipo:** ${typeEmojis[state.type || 'daily']} ${state.type || 'daily'}\n**Icono:** ${state.icon || '📋'}\n**Repetible:** ${state.repeatable ? 'Sí' : 'No'}` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\`` - } - ] - }, - { type: 14, divider: true }, - { - type: 9, - components: [ - { - type: 10, - content: `**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\`` - } - ] - } + { type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'quest_base' }, + { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'quest_req' }, + { type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'quest_reward' }, + { type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'quest_save' }, + { type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'quest_cancel' } ] } - }; + ]; } async function showBaseModal(i: ButtonInteraction, state: QuestState, editorMsg: any) { @@ -323,7 +281,11 @@ async function showBaseModal(i: ButtonInteraction, state: QuestState, editorMsg: state.icon = submit.components.getTextInputValue('icon') || '📋'; await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } async function showRequirementsModal(i: ButtonInteraction, state: QuestState, editorMsg: any) { @@ -358,7 +320,11 @@ async function showRequirementsModal(i: ButtonInteraction, state: QuestState, ed try { state.requirements = JSON.parse(submit.components.getTextInputValue('requirements')); await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } catch (e) { await submit.reply({ content: '❌ JSON inválido en requisitos.', flags: 64 }); } @@ -396,7 +362,11 @@ async function showRewardsModal(i: ButtonInteraction, state: QuestState, editorM try { state.rewards = JSON.parse(submit.components.getTextInputValue('rewards')); await submit.deferUpdate(); - await editorMsg.edit(createDisplay(state)); + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildEditorComponents(state) + }); } catch (e) { await submit.reply({ content: '❌ JSON inválido en recompensas.', flags: 64 }); } diff --git a/src/commands/messages/game/areaCreate.ts b/src/commands/messages/game/areaCreate.ts index ffa90d5..ab5c0bc 100644 --- a/src/commands/messages/game/areaCreate.ts +++ b/src/commands/messages/game/areaCreate.ts @@ -13,48 +13,61 @@ interface AreaState { metadata?: any; } -function createAreaDisplay(state: AreaState, editing: boolean = false) { +function buildAreaDisplay(state: AreaState, editing: boolean = false) { const title = editing ? 'Editando Área' : 'Creando Área'; + const statusText = [ + '**📋 Estado Actual:**', + `**Nombre:** ${state.name || '❌ No configurado'}`, + `**Tipo:** ${state.type || '❌ No configurado'}`, + `**Config:** ${Object.keys(state.config || {}).length} campos`, + `**Metadata:** ${Object.keys(state.metadata || {}).length} campos` + ].join('\n'); + + const instructionsText = [ + '**🎮 Instrucciones:**', + '• **Base**: Configura nombre y tipo', + '• **Config (JSON)**: Configuración técnica', + '• **Meta (JSON)**: Metadatos adicionales', + '• **Guardar**: Confirma los cambios', + '• **Cancelar**: Descarta los cambios' + ].join('\n'); + return { type: 17, accent_color: 0x00FF00, components: [ { - type: 9, - components: [{ - type: 10, - content: `🗺️ **${title}: \`${state.key}\`**` - }] + type: 10, + content: `# 🗺️ ${title}: \`${state.key}\`` }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**📋 Estado Actual:**\n` + - `**Nombre:** ${state.name || '❌ No configurado'}\n` + - `**Tipo:** ${state.type || '❌ No configurado'}\n` + - `**Config:** ${Object.keys(state.config || {}).length} campos\n` + - `**Metadata:** ${Object.keys(state.metadata || {}).length} campos` - }] + type: 10, + content: statusText }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**🎮 Instrucciones:**\n` + - `• **Base**: Configura nombre y tipo\n` + - `• **Config (JSON)**: Configuración técnica\n` + - `• **Meta (JSON)**: Metadatos adicionales\n` + - `• **Guardar**: Confirma los cambios\n` + - `• **Cancelar**: Descarta los cambios` - }] + type: 10, + content: instructionsText } ] }; } +const buildEditorComponents = (state: AreaState, editing: boolean = false) => [ + buildAreaDisplay(state, editing), + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, + ] + } +]; + export const command: CommandMessage = { name: 'area-crear', type: 'message', @@ -67,24 +80,17 @@ export const command: CommandMessage = { const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma); if (!allowed) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' - }] + type: 10, + content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -92,24 +98,17 @@ export const command: CommandMessage = { const key = args[0]?.trim(); if (!key) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-crear `' - }] + type: 10, + content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-crear `' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -118,47 +117,28 @@ export const command: CommandMessage = { const exists = await prisma.gameArea.findFirst({ where: { key, guildId } }); if (exists) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Área Ya Existe**\n└ Ya existe un área con esa key en este servidor.' - }] + type: 10, + content: '❌ **Área Ya Existe**\n└ Ya existe un área con esa key en este servidor.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } const state: AreaState = { key, config: {}, metadata: {} }; - const display = createAreaDisplay(state, false); - const editorMsg = await (channel.send as any)({ + content: null, flags: 32768, - components: [ - display, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, false), + reply: { messageReference: message.id } }); const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); @@ -169,16 +149,14 @@ export const command: CommandMessage = { case 'ga_cancel': await i.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '**❌ Editor de Área cancelado.**' - }] + type: 10, + content: '**❌ Editor de Área cancelado.**' }] }] }); @@ -198,16 +176,14 @@ export const command: CommandMessage = { await prisma.gameArea.create({ data: { guildId, key: state.key, name: state.name!, type: state.type!, config: state.config ?? {}, metadata: state.metadata ?? {} } }); await i.reply({ content: '✅ Área guardada.', flags: MessageFlags.Ephemeral }); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0x00FF00, components: [{ - type: 9, - components: [{ - type: 10, - content: `**✅ Área \`${state.key}\` creada exitosamente.**` - }] + type: 10, + content: `**✅ Área \`${state.key}\` creada exitosamente.**` }] }] }); @@ -223,16 +199,14 @@ export const command: CommandMessage = { if (r==='time') { try { await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '**⏰ Editor expirado.**' - }] + type: 10, + content: '**⏰ Editor expirado.**' }] }] }); @@ -255,22 +229,10 @@ async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg: await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); // Actualizar display - const newDisplay = createAreaDisplay(state, editing); await editorMsg.edit({ + content: null, flags: 32768, - components: [ - newDisplay, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, editing) }); } catch {} } @@ -298,22 +260,10 @@ async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'con } // Actualizar display - const newDisplay = createAreaDisplay(state, editing); await editorMsg.edit({ + content: null, flags: 32768, - components: [ - newDisplay, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, editing) }); } catch {} } diff --git a/src/commands/messages/game/areaEdit.ts b/src/commands/messages/game/areaEdit.ts index 1dc36b7..425baa5 100644 --- a/src/commands/messages/game/areaEdit.ts +++ b/src/commands/messages/game/areaEdit.ts @@ -13,48 +13,61 @@ interface AreaState { metadata?: any; } -function createAreaDisplay(state: AreaState, editing: boolean = false) { +function buildAreaDisplay(state: AreaState, editing: boolean = false) { const title = editing ? 'Editando Área' : 'Creando Área'; + const statusText = [ + '**📋 Estado Actual:**', + `**Nombre:** ${state.name || '❌ No configurado'}`, + `**Tipo:** ${state.type || '❌ No configurado'}`, + `**Config:** ${Object.keys(state.config || {}).length} campos`, + `**Metadata:** ${Object.keys(state.metadata || {}).length} campos` + ].join('\n'); + + const instructionsText = [ + '**🎮 Instrucciones:**', + '• **Base**: Configura nombre y tipo', + '• **Config (JSON)**: Configuración técnica', + '• **Meta (JSON)**: Metadatos adicionales', + '• **Guardar**: Confirma los cambios', + '• **Cancelar**: Descarta los cambios' + ].join('\n'); + return { type: 17, accent_color: 0x00FF00, components: [ { - type: 9, - components: [{ - type: 10, - content: `🗺️ **${title}: \`${state.key}\`**` - }] + type: 10, + content: `# 🗺️ ${title}: \`${state.key}\`` }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**📋 Estado Actual:**\n` + - `**Nombre:** ${state.name || '❌ No configurado'}\n` + - `**Tipo:** ${state.type || '❌ No configurado'}\n` + - `**Config:** ${Object.keys(state.config || {}).length} campos\n` + - `**Metadata:** ${Object.keys(state.metadata || {}).length} campos` - }] + type: 10, + content: statusText }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**🎮 Instrucciones:**\n` + - `• **Base**: Configura nombre y tipo\n` + - `• **Config (JSON)**: Configuración técnica\n` + - `• **Meta (JSON)**: Metadatos adicionales\n` + - `• **Guardar**: Confirma los cambios\n` + - `• **Cancelar**: Descarta los cambios` - }] + type: 10, + content: instructionsText } ] }; } +const buildEditorComponents = (state: AreaState, editing: boolean = false) => [ + buildAreaDisplay(state, editing), + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, + ] + } +]; + export const command: CommandMessage = { name: 'area-editar', type: 'message', @@ -67,24 +80,17 @@ export const command: CommandMessage = { const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma); if (!allowed) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' - }] + type: 10, + content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -92,24 +98,17 @@ export const command: CommandMessage = { const key = args[0]?.trim(); if (!key) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-editar `' - }] + type: 10, + content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-editar `' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -118,47 +117,28 @@ export const command: CommandMessage = { const area = await prisma.gameArea.findFirst({ where: { key, guildId } }); if (!area) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Área No Encontrada**\n└ No existe un área con esa key en este servidor.' - }] + type: 10, + content: '❌ **Área No Encontrada**\n└ No existe un área con esa key en este servidor.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } const state: AreaState = { key, name: area.name, type: area.type, config: area.config ?? {}, metadata: area.metadata ?? {} }; - const display = createAreaDisplay(state, true); - const editorMsg = await (channel.send as any)({ + content: null, flags: 32768, - components: [ - display, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, true), + reply: { messageReference: message.id } }); const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); @@ -169,16 +149,14 @@ export const command: CommandMessage = { case 'ga_cancel': await i.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '**❌ Editor de Área cancelado.**' - }] + type: 10, + content: '**❌ Editor de Área cancelado.**' }] }] }); @@ -198,16 +176,14 @@ export const command: CommandMessage = { await prisma.gameArea.update({ where: { id: area.id }, data: { name: state.name!, type: state.type!, config: state.config ?? {}, metadata: state.metadata ?? {} } }); await i.reply({ content: '✅ Área actualizada.', flags: MessageFlags.Ephemeral }); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0x00FF00, components: [{ - type: 9, - components: [{ - type: 10, - content: `**✅ Área \`${state.key}\` actualizada exitosamente.**` - }] + type: 10, + content: `**✅ Área \`${state.key}\` actualizada exitosamente.**` }] }] }); @@ -223,16 +199,14 @@ export const command: CommandMessage = { if (r==='time') { try { await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '**⏰ Editor expirado.**' - }] + type: 10, + content: '**⏰ Editor expirado.**' }] }] }); @@ -255,22 +229,10 @@ async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg: await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); // Actualizar display - const newDisplay = createAreaDisplay(state, editing); await editorMsg.edit({ + content: null, flags: 32768, - components: [ - newDisplay, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, editing) }); } catch {} } @@ -298,22 +260,10 @@ async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'con } // Actualizar display - const newDisplay = createAreaDisplay(state, editing); await editorMsg.edit({ + content: null, flags: 32768, - components: [ - newDisplay, - { - type: 1, - components: [ - { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' }, - { type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' }, - { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' }, - { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' }, - ] - } - ] + components: buildEditorComponents(state, editing) }); } catch {} } diff --git a/src/commands/messages/game/itemEdit.ts b/src/commands/messages/game/itemEdit.ts index e1c2604..c3319bb 100644 --- a/src/commands/messages/game/itemEdit.ts +++ b/src/commands/messages/game/itemEdit.ts @@ -30,24 +30,17 @@ export const command: CommandMessage = { const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); if (!allowed) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' - }] + type: 10, + content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -55,24 +48,17 @@ export const command: CommandMessage = { const key = args[0]?.trim(); if (!key) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '⚠️ **Uso Incorrecto**\n└ Uso: `!item-editar `' - }] + type: 10, + content: '⚠️ **Uso Incorrecto**\n└ Uso: `!item-editar `' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -82,24 +68,17 @@ export const command: CommandMessage = { const existing = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); if (!existing) { await (channel.send as any)({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '❌ **Item No Encontrado**\n└ No existe un item con esa key en este servidor.' - }] + type: 10, + content: '❌ **Item No Encontrado**\n└ No existe un item con esa key en este servidor.' }] }], - message_reference: { - message_id: message.id, - channel_id: message.channel.id, - guild_id: message.guild!.id, - fail_if_not_exists: false - } + reply: { messageReference: message.id } }); return; } @@ -116,64 +95,65 @@ export const command: CommandMessage = { props: existing.props || {}, }; - // Función para crear display - const createDisplay = () => ({ - display: { + const buildEditorDisplay = () => { + const baseInfo = [ + `**Nombre:** ${state.name || '*Sin definir*'}`, + `**Descripción:** ${state.description || '*Sin definir*'}`, + `**Categoría:** ${state.category || '*Sin definir*'}`, + `**Icon URL:** ${state.icon || '*Sin definir*'}`, + `**Stackable:** ${state.stackable ? 'Sí' : 'No'}`, + `**Máx. Inventario:** ${state.maxPerInventory ?? 'Ilimitado'}`, + ].join('\n'); + + const tagsInfo = `**Tags:** ${state.tags.length > 0 ? state.tags.join(', ') : '*Ninguno*'}`; + const propsJson = JSON.stringify(state.props ?? {}, null, 2); + + return { type: 17, accent_color: 0x00D9FF, components: [ { - type: 9, - components: [{ - type: 10, - content: `**🛠️ Editando Item: \`${key}\`**` - }] + type: 10, + content: `# 🛠️ Editando Item: \`${key}\`` }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**Nombre:** ${state.name || '*Sin definir*'}\n` + - `**Descripción:** ${state.description || '*Sin definir*'}\n` + - `**Categoría:** ${state.category || '*Sin definir*'}\n` + - `**Icon URL:** ${state.icon || '*Sin definir*'}\n` + - `**Stackable:** ${state.stackable ? 'Sí' : 'No'}\n` + - `**Máx. Inventario:** ${state.maxPerInventory || 'Ilimitado'}` - }] + type: 10, + content: baseInfo }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**Tags:** ${state.tags.length > 0 ? state.tags.join(', ') : '*Ninguno*'}` - }] + type: 10, + content: tagsInfo }, { type: 14, divider: true }, { - type: 9, - components: [{ - type: 10, - content: `**Props (JSON):**\n\`\`\`json\n${JSON.stringify(state.props, null, 2)}\n\`\`\`` - }] + type: 10, + content: `**Props (JSON):**\n\`\`\`json\n${propsJson}\n\`\`\`` } ] - } - }); + }; + }; - const editorMsg = await (channel.send as any)({ - flags: 32768, - components: [ - createDisplay().display, - { type: 1, components: [ + const buildEditorComponents = () => [ + buildEditorDisplay(), + { + type: 1, + components: [ { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'it_base' }, { type: 2, style: ButtonStyle.Secondary, label: 'Tags', custom_id: 'it_tags' }, { type: 2, style: ButtonStyle.Secondary, label: 'Props (JSON)', custom_id: 'it_props' }, { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'it_save' }, { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'it_cancel' }, - ]}, - ], + ] + } + ]; + + const editorMsg = await (channel.send as any)({ + content: null, + flags: 32768, + components: buildEditorComponents(), + reply: { messageReference: message.id } }); const collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id }); @@ -184,16 +164,14 @@ export const command: CommandMessage = { if (i.customId === 'it_cancel') { await i.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFF0000, components: [{ - type: 9, - components: [{ - type: 10, - content: '**❌ Editor cancelado.**' - }] + type: 10, + content: '**❌ Editor cancelado.**' }] }] }); @@ -201,15 +179,15 @@ export const command: CommandMessage = { return; } if (i.customId === 'it_base') { - await showBaseModal(i as ButtonInteraction, state, editorMsg, createDisplay); + await showBaseModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents); return; } if (i.customId === 'it_tags') { - await showTagsModal(i as ButtonInteraction, state, editorMsg, createDisplay); + await showTagsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents); return; } if (i.customId === 'it_props') { - await showPropsModal(i as ButtonInteraction, state, editorMsg, createDisplay); + await showPropsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents); return; } if (i.customId === 'it_save') { @@ -234,16 +212,14 @@ export const command: CommandMessage = { }); await i.reply({ content: '✅ Item actualizado!', flags: MessageFlags.Ephemeral }); await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0x00FF00, components: [{ - type: 9, - components: [{ - type: 10, - content: `✅ **Item Actualizado**\n└ Item \`${state.key}\` actualizado exitosamente.` - }] + type: 10, + content: `✅ **Item Actualizado**\n└ Item \`${state.key}\` actualizado exitosamente.` }] }] }); @@ -259,16 +235,14 @@ export const command: CommandMessage = { collector.on('end', async (_c, r) => { if (r === 'time') { try { await editorMsg.edit({ + content: null, flags: 32768, components: [{ type: 17, accent_color: 0xFFA500, components: [{ - type: 9, - components: [{ - type: 10, - content: '**⏰ Editor expirado.**' - }] + type: 10, + content: '**⏰ Editor expirado.**' }] }] }); } catch {} @@ -277,7 +251,7 @@ export const command: CommandMessage = { }, }; -async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) { +async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) { const modal = { title: 'Configuración base del Item', customId: 'it_base_modal', @@ -313,13 +287,14 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito await sub.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, - components: [createDisplay().display] + components: buildComponents() }); } catch {} } -async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) { +async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) { const modal = { title: 'Tags del Item (separados por coma)', customId: 'it_tags_modal', @@ -334,13 +309,14 @@ async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, edito state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : []; await sub.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, - components: [createDisplay().display] + components: buildComponents() }); } catch {} } -async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) { +async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) { const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({ tool: undefined, breakable: undefined, @@ -371,8 +347,9 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit state.props = parsed; await sub.deferUpdate(); await editorMsg.edit({ + content: null, flags: 32768, - components: [createDisplay().display] + components: buildComponents() }); } catch (e) { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); @@ -380,6 +357,13 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit } else { state.props = {}; await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral }); + try { + await editorMsg.edit({ + content: null, + flags: 32768, + components: buildComponents() + }); + } catch {} } } catch {} }