diff --git a/src/commands/messages/AI/image.ts b/src/commands/messages/AI/image.ts deleted file mode 100644 index 74f8687..0000000 --- a/src/commands/messages/AI/image.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Message, AttachmentBuilder } from 'discord.js'; -import { aiService } from '../../../core/services/AIService'; -import logger from '../../../core/lib/logger'; - -export default { - name: 'image', - aliases: ['imagen', 'img', 'aiimage'], - description: 'Genera una imagen usando IA', - cooldown: 10, - async run(message: Message, args: string[]) { - // Verificar que hay un prompt - if (!args || args.length === 0) { - await message.reply('❌ **Error**: Debes proporcionar una descripción para generar la imagen.\n\n**Ejemplo**: `!image un gato espacial flotando entre estrellas`'); - return; - } - - const prompt = args.join(' ').trim(); - - // Validar longitud del prompt - if (prompt.length < 3) { - await message.reply('❌ **Error**: La descripción debe tener al menos 3 caracteres.'); - return; - } - - if (prompt.length > 1000) { - await message.reply('❌ **Error**: La descripción es demasiado larga (máximo 1000 caracteres).'); - return; - } - - // Mostrar mensaje de "generando..." - const thinkingMessage = await message.reply('🎨 **Generando imagen**... Esto puede tomar unos momentos.'); - - try { - logger.info(`Generando imagen para usuario ${message.author.id}: ${prompt.slice(0, 100)}`); - - // Generar la imagen usando el AIService actualizado - const result = await aiService.generateImage(prompt, { - size: 'square', // Por defecto usar formato cuadrado - mimeType: 'image/jpeg', - numberOfImages: 1, - personGeneration: true - }); - - // Crear attachment para Discord - const attachment = new AttachmentBuilder(result.data, { - name: result.fileName, - description: `Imagen generada: ${prompt.slice(0, 100)}${prompt.length > 100 ? '...' : ''}` - }); - - // Responder con la imagen - await thinkingMessage.edit({ - content: `✅ **Imagen generada** para: *${prompt.slice(0, 150)}${prompt.length > 150 ? '...' : ''}*`, - files: [attachment] - }); - - logger.info(`Imagen generada exitosamente para usuario ${message.author.id}, tamaño: ${result.data.length} bytes`); - - } catch (error) { - logger.error(`Error generando imagen para usuario ${message.author.id}: ${error}`); - - let errorMessage = '❌ **Error generando imagen**: '; - - if (error instanceof Error) { - const errorText = error.message.toLowerCase(); - - if (errorText.includes('no está disponible') || errorText.includes('not found')) { - errorMessage += 'El servicio de generación de imágenes no está disponible en este momento.'; - } else if (errorText.includes('límite') || errorText.includes('quota')) { - errorMessage += 'Se ha alcanzado el límite de generación de imágenes. Intenta más tarde.'; - } else if (errorText.includes('bloqueado') || errorText.includes('safety')) { - errorMessage += 'Tu descripción fue bloqueada por las políticas de seguridad. Intenta con algo diferente.'; - } else if (errorText.includes('inicializado') || errorText.includes('api')) { - errorMessage += 'El servicio no está configurado correctamente.'; - } else { - errorMessage += error.message; - } - } else { - errorMessage += 'Error desconocido. Intenta de nuevo más tarde.'; - } - - await thinkingMessage.edit(errorMessage); - } - } -}; diff --git a/src/commands/messages/admin/areaEliminar.ts b/src/commands/messages/admin/areaEliminar.ts index 9eb283a..bbbf7cb 100644 --- a/src/commands/messages/admin/areaEliminar.ts +++ b/src/commands/messages/admin/areaEliminar.ts @@ -2,6 +2,7 @@ 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 type { TextBasedChannel } from 'discord.js'; export const command: CommandMessage = { name: 'area-eliminar', @@ -13,7 +14,27 @@ export const command: CommandMessage = { run: async (message, args, client: Amayo) => { const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma); if (!allowed) { - await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); + const channel = message.channel as TextBasedChannel & { send: Function }; + await (channel.send as any)({ + 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 + } + }); return; } @@ -21,7 +42,27 @@ export const command: CommandMessage = { const key = args[0]?.trim(); if (!key) { - await message.reply('Uso: \`!area-eliminar \`\nEjemplo: \`!area-eliminar mine.cavern\`'); + const channel = message.channel as TextBasedChannel & { send: Function }; + await (channel.send as any)({ + 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 + } + }); return; } @@ -30,7 +71,27 @@ export const command: CommandMessage = { }); if (!area) { - await message.reply(`❌ No se encontró el área local con key ${key} en este servidor.`); + const channel = message.channel as TextBasedChannel & { send: Function }; + await (channel.send as any)({ + 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 + } + }); return; } @@ -48,9 +109,38 @@ export const command: CommandMessage = { where: { id: area.id } }); - await message.reply( - `✅ Área ${key} eliminada exitosamente.\n` + - `${levelsCount > 0 ? `⚠️ Se eliminaron ${levelsCount} nivel(es) asociado(s).` : ''}` - ); + const channel = message.channel as TextBasedChannel & { send: Function }; + await (channel.send as any)({ + 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 + } + }); } }; diff --git a/src/commands/messages/game/areaCreate.ts b/src/commands/messages/game/areaCreate.ts index 6031425..ffa90d5 100644 --- a/src/commands/messages/game/areaCreate.ts +++ b/src/commands/messages/game/areaCreate.ts @@ -13,6 +13,48 @@ interface AreaState { metadata?: any; } +function createAreaDisplay(state: AreaState, editing: boolean = false) { + const title = editing ? 'Editando Área' : 'Creando Área'; + return { + type: 17, + accent_color: 0x00FF00, + components: [ + { + type: 9, + components: [{ + 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: 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` + }] + } + ] + }; +} + export const command: CommandMessage = { name: 'area-crear', type: 'message', @@ -21,28 +63,102 @@ export const command: CommandMessage = { description: 'Crea una GameArea (mina/laguna/arena/farm) para este servidor con editor.', usage: 'area-crear ', 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) { await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); return; } + if (!allowed) { + await (channel.send as any)({ + 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 + } + }); + return; + } const key = args[0]?.trim(); - if (!key) { await message.reply('Uso: `!area-crear `'); return; } + if (!key) { + await (channel.send as any)({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + 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 + } + }); + return; + } const guildId = message.guild!.id; const exists = await prisma.gameArea.findFirst({ where: { key, guildId } }); - if (exists) { await message.reply('❌ Ya existe un área con esa key en este servidor.'); return; } + if (exists) { + await (channel.send as any)({ + 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.' + }] + }] + }], + message_reference: { + message_id: message.id, + channel_id: message.channel.id, + guild_id: message.guild!.id, + fail_if_not_exists: false + } + }); + return; + } const state: AreaState = { key, config: {}, metadata: {} }; - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - content: `🗺️ Editor de Área: \`${key}\``, - components: [ { 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' }, - ] } ], + const display = createAreaDisplay(state, false); + + const editorMsg = await (channel.send as any)({ + 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' }, + ] + } + ] }); const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); @@ -52,23 +168,49 @@ export const command: CommandMessage = { switch (i.customId) { case 'ga_cancel': await i.deferUpdate(); - await editorMsg.edit({ content: '❌ Editor de Área cancelado.', components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFF0000, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**❌ Editor de Área cancelado.**' + }] + }] + }] + }); collector.stop('cancel'); return; case 'ga_base': - await showBaseModal(i as ButtonInteraction, state); + await showBaseModal(i as ButtonInteraction, state, editorMsg, false); return; case 'ga_config': - await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área'); + await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área', editorMsg, false); return; case 'ga_meta': - await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área'); + await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área', editorMsg, false); return; case 'ga_save': if (!state.name || !state.type) { await i.reply({ content: '❌ Completa Base (nombre/tipo).', flags: MessageFlags.Ephemeral }); return; } 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: `✅ Área \`${state.key}\` creada.`, components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `**✅ Área \`${state.key}\` creada exitosamente.**` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -77,25 +219,102 @@ export const command: CommandMessage = { } }); - collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } }); + collector.on('end', async (_c,r)=> { + if (r==='time') { + try { + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**⏰ Editor expirado.**' + }] + }] + }] + }); + } catch {} + } + }); } }; -async function showBaseModal(i: ButtonInteraction, state: AreaState) { +async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg: Message, editing: boolean) { const modal = { title: 'Base del Área', customId: 'ga_base_modal', components: [ { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, { type: ComponentType.Label, label: 'Tipo (MINE/LAGOON/FIGHT/FARM)', component: { type: ComponentType.TextInput, customId: 'type', style: TextInputStyle.Short, required: true, value: state.type ?? '' } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.type = sub.components.getTextInputValue('type').trim().toUpperCase(); await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + state.name = sub.components.getTextInputValue('name').trim(); + state.type = sub.components.getTextInputValue('type').trim().toUpperCase(); + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + + // Actualizar display + const newDisplay = createAreaDisplay(state, editing); + await editorMsg.edit({ + 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' }, + ] + } + ] + }); + } catch {} } -async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string) { +async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string, editorMsg: Message, editing: boolean) { const current = JSON.stringify(state[field] ?? {}); const modal = { title, customId: `ga_json_${field}`, components: [ { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0,4000) } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const raw = sub.components.getTextInputValue('json'); if (raw) { try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } } else { state[field] = {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('json'); + if (raw) { + try { + state[field] = JSON.parse(raw); + await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); + } catch { + await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); + return; + } + } else { + state[field] = {}; + await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); + } + + // Actualizar display + const newDisplay = createAreaDisplay(state, editing); + await editorMsg.edit({ + 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' }, + ] + } + ] + }); + } catch {} } diff --git a/src/commands/messages/game/areaEdit.ts b/src/commands/messages/game/areaEdit.ts index 86aabbc..1dc36b7 100644 --- a/src/commands/messages/game/areaEdit.ts +++ b/src/commands/messages/game/areaEdit.ts @@ -13,6 +13,48 @@ interface AreaState { metadata?: any; } +function createAreaDisplay(state: AreaState, editing: boolean = false) { + const title = editing ? 'Editando Área' : 'Creando Área'; + return { + type: 17, + accent_color: 0x00FF00, + components: [ + { + type: 9, + components: [{ + 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: 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` + }] + } + ] + }; +} + export const command: CommandMessage = { name: 'area-editar', type: 'message', @@ -21,28 +63,102 @@ export const command: CommandMessage = { description: 'Edita una GameArea de este servidor con un editor interactivo.', usage: 'area-editar ', 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) { await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); return; } + if (!allowed) { + await (channel.send as any)({ + 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 + } + }); + return; + } const key = args[0]?.trim(); - if (!key) { await message.reply('Uso: `!area-editar `'); return; } + if (!key) { + await (channel.send as any)({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + 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 + } + }); + return; + } const guildId = message.guild!.id; const area = await prisma.gameArea.findFirst({ where: { key, guildId } }); - if (!area) { await message.reply('❌ No existe un área con esa key en este servidor.'); return; } + if (!area) { + await (channel.send as any)({ + 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.' + }] + }] + }], + message_reference: { + message_id: message.id, + channel_id: message.channel.id, + guild_id: message.guild!.id, + fail_if_not_exists: false + } + }); + return; + } const state: AreaState = { key, name: area.name, type: area.type, config: area.config ?? {}, metadata: area.metadata ?? {} }; - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - content: `🗺️ Editor de Área (editar): \`${key}\``, - components: [ { 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' }, - ] } ], + const display = createAreaDisplay(state, true); + + const editorMsg = await (channel.send as any)({ + 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' }, + ] + } + ] }); const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id }); @@ -52,23 +168,49 @@ export const command: CommandMessage = { switch (i.customId) { case 'ga_cancel': await i.deferUpdate(); - await editorMsg.edit({ content: '❌ Editor de Área cancelado.', components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFF0000, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**❌ Editor de Área cancelado.**' + }] + }] + }] + }); collector.stop('cancel'); return; case 'ga_base': - await showBaseModal(i as ButtonInteraction, state); + await showBaseModal(i as ButtonInteraction, state, editorMsg, true); return; case 'ga_config': - await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área'); + await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área', editorMsg, true); return; case 'ga_meta': - await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área'); + await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área', editorMsg, true); return; case 'ga_save': if (!state.name || !state.type) { await i.reply({ content: '❌ Completa Base (nombre/tipo).', flags: MessageFlags.Ephemeral }); return; } 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: `✅ Área \`${state.key}\` actualizada.`, components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `**✅ Área \`${state.key}\` actualizada exitosamente.**` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -77,25 +219,102 @@ export const command: CommandMessage = { } }); - collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } }); + collector.on('end', async (_c,r)=> { + if (r==='time') { + try { + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**⏰ Editor expirado.**' + }] + }] + }] + }); + } catch {} + } + }); } }; -async function showBaseModal(i: ButtonInteraction, state: AreaState) { +async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg: Message, editing: boolean) { const modal = { title: 'Base del Área', customId: 'ga_base_modal', components: [ { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, { type: ComponentType.Label, label: 'Tipo (MINE/LAGOON/FIGHT/FARM)', component: { type: ComponentType.TextInput, customId: 'type', style: TextInputStyle.Short, required: true, value: state.type ?? '' } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.type = sub.components.getTextInputValue('type').trim().toUpperCase(); await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + state.name = sub.components.getTextInputValue('name').trim(); + state.type = sub.components.getTextInputValue('type').trim().toUpperCase(); + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + + // Actualizar display + const newDisplay = createAreaDisplay(state, editing); + await editorMsg.edit({ + 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' }, + ] + } + ] + }); + } catch {} } -async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string) { +async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string, editorMsg: Message, editing: boolean) { const current = JSON.stringify(state[field] ?? {}); const modal = { title, customId: `ga_json_${field}`, components: [ { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0,4000) } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const raw = sub.components.getTextInputValue('json'); if (raw) { try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } } else { state[field] = {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + const raw = sub.components.getTextInputValue('json'); + if (raw) { + try { + state[field] = JSON.parse(raw); + await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); + } catch { + await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); + return; + } + } else { + state[field] = {}; + await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); + } + + // Actualizar display + const newDisplay = createAreaDisplay(state, editing); + await editorMsg.edit({ + 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' }, + ] + } + ] + }); + } catch {} } diff --git a/src/commands/messages/game/itemCreate.ts b/src/commands/messages/game/itemCreate.ts index 3321809..a72e139 100644 --- a/src/commands/messages/game/itemCreate.ts +++ b/src/commands/messages/game/itemCreate.ts @@ -26,15 +26,54 @@ export const command: CommandMessage = { category: 'Economía', usage: 'item-crear ', run: async (message: Message, args: string[], client: Amayo) => { + const channel = message.channel as TextBasedChannel & { send: Function }; const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); if (!allowed) { - await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); + await (channel.send as any)({ + 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 + } + }); return; } const key = args[0]?.trim(); if (!key) { - await message.reply('Uso: `!item-crear `'); + await (channel.send as any)({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + type: 10, + content: '⚠️ **Uso Incorrecto**\n└ Uso: `!item-crear `' + }] + }] + }], + message_reference: { + message_id: message.id, + channel_id: message.channel.id, + guild_id: message.guild!.id, + fail_if_not_exists: false + } + }); return; } @@ -42,7 +81,26 @@ export const command: CommandMessage = { const exists = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); if (exists) { - await message.reply('❌ Ya existe un item con esa key en este servidor.'); + await (channel.send as any)({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFF0000, + components: [{ + type: 9, + components: [{ + type: 10, + content: '❌ **Item Ya Existe**\n└ Ya 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 + } + }); return; } @@ -100,10 +158,10 @@ export const command: CommandMessage = { } }); - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - ...createDisplay(), + const editorMsg = await (channel.send as any)({ + flags: 32768, components: [ + createDisplay().display, { 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' }, @@ -172,7 +230,20 @@ export const command: CommandMessage = { }, }); await i.reply({ content: '✅ Item guardado!', flags: MessageFlags.Ephemeral }); - await editorMsg.edit({ content: `✅ Item \`${state.key}\` creado.`, components: [], display: undefined }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `✅ **Item Creado**\n└ Item \`${state.key}\` creado exitosamente.` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -238,7 +309,10 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito } await sub.deferUpdate(); - await editorMsg.edit(createDisplay()); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch {} } @@ -256,7 +330,10 @@ async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, edito const tags = sub.components.getTextInputValue('tags'); state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : []; await sub.deferUpdate(); - await editorMsg.edit(createDisplay()); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch {} } @@ -289,7 +366,11 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit try { const parsed = JSON.parse(raw); state.props = parsed; - await sub.deferUpdate(); await editorMsg.edit(createDisplay()); + await sub.deferUpdate(); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch (e) { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } diff --git a/src/commands/messages/game/itemEdit.ts b/src/commands/messages/game/itemEdit.ts index 2bf6f32..e1c2604 100644 --- a/src/commands/messages/game/itemEdit.ts +++ b/src/commands/messages/game/itemEdit.ts @@ -20,38 +20,100 @@ interface ItemEditorState { export const command: CommandMessage = { name: 'item-editar', type: 'message', - aliases: ['crear-item','itemcreate'], + aliases: ['editar-item','itemedit'], cooldown: 10, - description: 'Crea un EconomyItem para este servidor con un pequeño editor interactivo.', + description: 'Edita un EconomyItem existente del servidor con un pequeño editor interactivo.', category: 'Economía', usage: 'item-editar ', run: async (message: Message, args: string[], client: Amayo) => { + const channel = message.channel as TextBasedChannel & { send: Function }; const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma); if (!allowed) { - await message.reply('❌ No tienes permisos de ManageGuild ni rol de staff.'); + await (channel.send as any)({ + 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 + } + }); return; } const key = args[0]?.trim(); if (!key) { - await message.reply('Uso: `!item-editar `'); + await (channel.send as any)({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFFA500, + components: [{ + type: 9, + components: [{ + 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 + } + }); return; } const guildId = message.guild!.id; - const exists = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); - if (exists) { - await message.reply('❌ Ya existe un item con esa key en este servidor.'); + const existing = await client.prisma.economyItem.findFirst({ where: { key, guildId } }); + if (!existing) { + await (channel.send as any)({ + 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.' + }] + }] + }], + message_reference: { + message_id: message.id, + channel_id: message.channel.id, + guild_id: message.guild!.id, + fail_if_not_exists: false + } + }); return; } const state: ItemEditorState = { key, - tags: [], - stackable: true, - maxPerInventory: null, - props: {}, + name: existing.name, + description: existing.description || undefined, + category: existing.category || undefined, + icon: existing.icon || undefined, + stackable: existing.stackable ?? true, + maxPerInventory: existing.maxPerInventory || null, + tags: existing.tags || [], + props: existing.props || {}, }; // Función para crear display @@ -100,10 +162,10 @@ export const command: CommandMessage = { } }); - const channel = message.channel as TextBasedChannel & { send: Function }; - const editorMsg = await channel.send({ - ...createDisplay(), + const editorMsg = await (channel.send as any)({ + flags: 32768, components: [ + createDisplay().display, { 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' }, @@ -156,11 +218,10 @@ export const command: CommandMessage = { await i.reply({ content: '❌ Falta el nombre del item (configura en Base).', flags: MessageFlags.Ephemeral }); return; } - // Guardar - await client.prisma.economyItem.create({ + // Actualizar + await client.prisma.economyItem.update({ + where: { id: existing.id }, data: { - guildId, - key: state.key, name: state.name!, description: state.description, category: state.category, @@ -171,8 +232,21 @@ export const command: CommandMessage = { props: state.props ?? {}, }, }); - await i.reply({ content: '✅ Item guardado!', flags: MessageFlags.Ephemeral }); - await editorMsg.edit({ content: `✅ Item \`${state.key}\` creado.`, components: [], display: undefined }); + await i.reply({ content: '✅ Item actualizado!', flags: MessageFlags.Ephemeral }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `✅ **Item Actualizado**\n└ Item \`${state.key}\` actualizado exitosamente.` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -238,7 +312,10 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito } await sub.deferUpdate(); - await editorMsg.edit(createDisplay()); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch {} } @@ -256,7 +333,10 @@ async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, edito const tags = sub.components.getTextInputValue('tags'); state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : []; await sub.deferUpdate(); - await editorMsg.edit(createDisplay()); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch {} } @@ -289,7 +369,11 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit try { const parsed = JSON.parse(raw); state.props = parsed; - await sub.deferUpdate(); await editorMsg.edit(createDisplay()); + await sub.deferUpdate(); + await editorMsg.edit({ + flags: 32768, + components: [createDisplay().display] + }); } catch (e) { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } diff --git a/src/commands/messages/game/mobCreate.ts b/src/commands/messages/game/mobCreate.ts index 60698c3..043c57e 100644 --- a/src/commands/messages/game/mobCreate.ts +++ b/src/commands/messages/game/mobCreate.ts @@ -13,6 +13,51 @@ interface MobEditorState { drops?: any; // JSON libre, tabla de recompensas } +function createMobDisplay(state: MobEditorState, editing: boolean = false) { + const title = editing ? 'Editando Mob' : 'Creando Mob'; + const stats = state.stats || {}; + return { + type: 17, + accent_color: 0xFF0000, + components: [ + { + type: 9, + components: [{ + 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` + + `**Categoría:** ${state.category || 'Sin categoría'}\n` + + `**Attack:** ${stats.attack || 0}\n` + + `**HP:** ${stats.hp || 0}\n` + + `**Defense:** ${stats.defense || 0}\n` + + `**Drops:** ${Object.keys(state.drops || {}).length} items` + }] + }, + { type: 14, divider: true }, + { + type: 9, + components: [{ + type: 10, + content: `**🎮 Instrucciones:**\n` + + `• **Base**: Nombre y categoría\n` + + `• **Stats (JSON)**: Estadísticas del mob\n` + + `• **Drops (JSON)**: Items que dropea\n` + + `• **Guardar**: Confirma los cambios\n` + + `• **Cancelar**: Descarta los cambios` + }] + } + ] + }; +} + export const command: CommandMessage = { name: 'mob-crear', type: 'message', @@ -49,15 +94,46 @@ export const command: CommandMessage = { collector.on('collect', async (i: MessageComponentInteraction) => { try { if (!i.isButton()) return; - if (i.customId === 'mb_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; } - if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state); return; } - if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)'); return; } - if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)'); return; } + if (i.customId === 'mb_cancel') { + await i.deferUpdate(); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFF0000, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**❌ Editor cancelado.**' + }] + }] + }] + }); + collector.stop('cancel'); + return; + } + if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state, editorMsg, false); return; } + if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)', editorMsg, false); return; } + if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)', editorMsg, false); return; } if (i.customId === 'mb_save') { if (!state.name) { await i.reply({ content: '❌ Falta el nombre del mob.', flags: MessageFlags.Ephemeral }); return; } await client.prisma.mob.create({ data: { guildId, key: state.key, name: state.name!, category: state.category ?? null, stats: state.stats ?? {}, drops: state.drops ?? {} } }); await i.reply({ content: '✅ Mob guardado!', flags: MessageFlags.Ephemeral }); - await editorMsg.edit({ content: `✅ Mob \`${state.key}\` creado.`, components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `**✅ Mob \`${state.key}\` creado exitosamente.**` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -70,26 +146,79 @@ export const command: CommandMessage = { }, }; -async function showBaseModal(i: ButtonInteraction, state: MobEditorState) { - const modal = { title: 'Configuración base del Mob', customId: 'mb_base_modal', components: [ +async function showBaseModal(i: ButtonInteraction, state: MobEditorState, editorMsg: Message, editing: boolean) { + const modal = { title: 'Base del Mob', customId: 'mb_base_modal', components: [ { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, - { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + { type: ComponentType.Label, label: 'Categoría (opcional)', component: { type: ComponentType.TextInput, customId: 'category', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.category = sub.components.getTextInputValue('cat').trim() || undefined; await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + state.name = sub.components.getTextInputValue('name').trim(); + const cat = sub.components.getTextInputValue('category')?.trim(); + state.category = cat || undefined; + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + + // Refresh display + const newDisplay = createMobDisplay(state, editing); + await editorMsg.edit({ + flags: 32768, + components: [ + newDisplay, + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] + } + ] + }); + } catch {} } -async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', label: string) { - const current = JSON.stringify(state[field] ?? (field==='stats'? { attack: 5 }: {})); - const modal = { title: label, customId: `mb_json_${field}`, components: [ - { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0, 4000) } }, +async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', title: string, editorMsg: Message, editing: boolean) { + const current = JSON.stringify(state[field] ?? {}); + const modal = { title, customId: `mb_json_${field}`, components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0,4000) } }, ] } as const; await i.showModal(modal); try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const raw = sub.components.getTextInputValue('json'); if (raw) { - try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } - } else { state[field] = field==='stats' ? { attack: 5 } : {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } + try { + state[field] = JSON.parse(raw); + await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); + } catch { + await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); + return; + } + } else { + state[field] = {}; + await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); + } + + // Refresh display + const newDisplay = createMobDisplay(state, editing); + await editorMsg.edit({ + flags: 32768, + components: [ + newDisplay, + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] + } + ] + }); } catch {} } diff --git a/src/commands/messages/game/mobEdit.ts b/src/commands/messages/game/mobEdit.ts index ccbf356..7bdfecc 100644 --- a/src/commands/messages/game/mobEdit.ts +++ b/src/commands/messages/game/mobEdit.ts @@ -12,6 +12,50 @@ interface MobEditorState { stats?: any; drops?: any; } +function createMobDisplay(state: MobEditorState, editing: boolean = false) { + const title = editing ? 'Editando Mob' : 'Creando Mob'; + const stats = state.stats || {}; + return { + type: 17, + accent_color: 0xFF0000, + components: [ + { + type: 9, + components: [{ + 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` + + `**Categoría:** ${state.category || 'Sin categoría'}\n` + + `**Attack:** ${stats.attack || 0}\n` + + `**HP:** ${stats.hp || 0}\n` + + `**Defense:** ${stats.defense || 0}\n` + + `**Drops:** ${Object.keys(state.drops || {}).length} items` + }] + }, + { type: 14, divider: true }, + { + type: 9, + components: [{ + type: 10, + content: `**🎮 Instrucciones:**\n` + + `• **Base**: Nombre y categoría\n` + + `• **Stats (JSON)**: Estadísticas del mob\n` + + `• **Drops (JSON)**: Items que dropea\n` + + `• **Guardar**: Confirma los cambios\n` + + `• **Cancelar**: Descarta los cambios` + }] + } + ] + }; +} export const command: CommandMessage = { name: 'mob-editar', @@ -55,15 +99,46 @@ export const command: CommandMessage = { collector.on('collect', async (i: MessageComponentInteraction) => { try { if (!i.isButton()) return; - if (i.customId === 'mb_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; } - if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state); return; } - if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)'); return; } - if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)'); return; } + if (i.customId === 'mb_cancel') { + await i.deferUpdate(); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0xFF0000, + components: [{ + type: 9, + components: [{ + type: 10, + content: '**❌ Editor cancelado.**' + }] + }] + }] + }); + collector.stop('cancel'); + return; + } + if (i.customId === 'mb_base') { await showBaseModal(i as ButtonInteraction, state, editorMsg, true); return; } + if (i.customId === 'mb_stats') { await showJsonModal(i as ButtonInteraction, state, 'stats', 'Stats del Mob (JSON)', editorMsg, true); return; } + if (i.customId === 'mb_drops') { await showJsonModal(i as ButtonInteraction, state, 'drops', 'Drops del Mob (JSON)', editorMsg, true); return; } if (i.customId === 'mb_save') { if (!state.name) { await i.reply({ content: '❌ Falta el nombre del mob.', flags: MessageFlags.Ephemeral }); return; } await client.prisma.mob.update({ where: { id: mob.id }, data: { name: state.name!, category: state.category ?? null, stats: state.stats ?? {}, drops: state.drops ?? {} } }); await i.reply({ content: '✅ Mob actualizado!', flags: MessageFlags.Ephemeral }); - await editorMsg.edit({ content: `✅ Mob \`${state.key}\` actualizado.`, components: [] }); + await editorMsg.edit({ + flags: 32768, + components: [{ + type: 17, + accent_color: 0x00FF00, + components: [{ + type: 9, + components: [{ + type: 10, + content: `**✅ Mob \`${state.key}\` actualizado exitosamente.**` + }] + }] + }] + }); collector.stop('saved'); return; } @@ -76,25 +151,79 @@ export const command: CommandMessage = { }, }; -async function showBaseModal(i: ButtonInteraction, state: MobEditorState) { - const modal = { title: 'Configuración base del Mob', customId: 'mb_base_modal', components: [ +async function showBaseModal(i: ButtonInteraction, state: MobEditorState, editorMsg: Message, editing: boolean) { + const modal = { title: 'Base del Mob', customId: 'mb_base_modal', components: [ { type: ComponentType.Label, label: 'Nombre', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name ?? '' } }, - { type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, + { type: ComponentType.Label, label: 'Categoría (opcional)', component: { type: ComponentType.TextInput, customId: 'category', style: TextInputStyle.Short, required: false, value: state.category ?? '' } }, ] } as const; await i.showModal(modal); - try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.name = sub.components.getTextInputValue('name').trim(); state.category = sub.components.getTextInputValue('cat').trim() || undefined; await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {} + try { + const sub = await i.awaitModalSubmit({ time: 300_000 }); + state.name = sub.components.getTextInputValue('name').trim(); + const cat = sub.components.getTextInputValue('category')?.trim(); + state.category = cat || undefined; + await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); + + // Refresh display + const newDisplay = createMobDisplay(state, editing); + await editorMsg.edit({ + flags: 32768, + components: [ + newDisplay, + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] + } + ] + }); + } catch {} } -async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', label: string) { - const current = JSON.stringify(state[field] ?? (field==='stats'? { attack: 5 }: {})); - const modal = { title: label, customId: `mb_json_${field}`, components: [ - { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0, 4000) } }, +async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field: 'stats'|'drops', title: string, editorMsg: Message, editing: boolean) { + const current = JSON.stringify(state[field] ?? {}); + const modal = { title, customId: `mb_json_${field}`, components: [ + { type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'json', style: TextInputStyle.Paragraph, required: false, value: current.slice(0,4000) } }, ] } as const; await i.showModal(modal); try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const raw = sub.components.getTextInputValue('json'); - if (raw) { try { state[field] = JSON.parse(raw); await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } } - else { state[field] = field==='stats' ? { attack: 5 } : {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); } + if (raw) { + try { + state[field] = JSON.parse(raw); + await sub.reply({ content: '✅ Guardado.', flags: MessageFlags.Ephemeral }); + } catch { + await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); + return; + } + } else { + state[field] = {}; + await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); + } + + // Refresh display + const newDisplay = createMobDisplay(state, editing); + await editorMsg.edit({ + flags: 32768, + components: [ + newDisplay, + { + type: 1, + components: [ + { type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' }, + { type: 2, style: ButtonStyle.Secondary, label: 'Drops (JSON)', custom_id: 'mb_drops' }, + { type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'mb_save' }, + { type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'mb_cancel' }, + ] + } + ] + }); } catch {} }