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 { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10'; import type { ButtonInteraction, MessageComponentInteraction, TextBasedChannel } from 'discord.js'; interface AchievementState { key: string; name?: string; description?: string; category?: string; icon?: string; requirements?: any; rewards?: any; points?: number; hidden?: boolean; } export const command: CommandMessage = { name: 'logro-crear', type: 'message', aliases: ['crear-logro', 'achievement-create'], cooldown: 10, description: 'Crea un logro para el servidor con editor interactivo', usage: 'logro-crear ', 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.'); return; } const key = args[0]?.trim(); if (!key) { await message.reply('Uso: `!logro-crear `\nEjemplo: `!logro-crear master_fisher`'); return; } const guildId = message.guild!.id; const exists = await prisma.achievement.findFirst({ where: { key, guildId } }); if (exists) { await message.reply('❌ Ya existe un logro con esa key en este servidor.'); return; } const state: AchievementState = { key, category: 'economy', points: 10, hidden: false, requirements: { type: 'mine_count', value: 1 }, 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, 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 collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id }); collector.on('collect', async (i: MessageComponentInteraction) => { try { if (!i.isButton()) return; switch (i.customId) { case 'ach_cancel': await i.deferUpdate(); await editorMsg.edit({ content: '❌ Creación de logro cancelada.', components: [], display: undefined }); collector.stop('cancel'); return; case 'ach_base': await showBaseModal(i as ButtonInteraction, state, editorMsg); return; case 'ach_req': await showRequirementsModal(i as ButtonInteraction, state, editorMsg); return; case 'ach_reward': await showRewardsModal(i as ButtonInteraction, state, editorMsg); return; case 'ach_save': if (!state.name || !state.description) { await i.reply({ content: '❌ Completa al menos el nombre y descripción.', flags: 64 }); return; } await prisma.achievement.create({ data: { guildId, key: state.key, name: state.name!, description: state.description!, category: state.category || 'economy', icon: state.icon, requirements: state.requirements as any || {}, rewards: state.rewards as any || {}, points: state.points || 10, hidden: state.hidden || false } }); await i.reply({ content: '✅ Logro creado exitosamente.', flags: 64 }); await editorMsg.edit({ content: `✅ Logro \`${state.key}\` creado.`, components: [], display: undefined }); collector.stop('saved'); return; } } catch (e: any) { console.error('Error en editor de logros:', e); if (!i.deferred && !i.replied) { await i.reply({ content: '❌ Error procesando la acción.', flags: 64 }); } } }); collector.on('end', async (_c, r) => { if (r === 'time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [], display: undefined }); } catch {} } }); } }; function createDisplay(state: AchievementState) { return { display: { type: 17, // Container accent_color: 0xFFD700, 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\`\`\`` } ] } ] } }; } async function showBaseModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) { const modal = { title: 'Información Base del Logro', customId: 'ach_base_modal', components: [ { type: ComponentType.Label, label: 'Nombre del logro', component: { type: ComponentType.TextInput, customId: 'name', style: TextInputStyle.Short, required: true, value: state.name || '', placeholder: 'Ej: Maestro Pescador' } }, { type: ComponentType.Label, label: 'Descripción', component: { type: ComponentType.TextInput, customId: 'description', style: TextInputStyle.Paragraph, required: true, value: state.description || '', placeholder: 'Ej: Pesca 100 veces' } }, { type: ComponentType.Label, label: 'Categoría (mining/fishing/combat/economy/crafting)', component: { type: ComponentType.TextInput, customId: 'category', style: TextInputStyle.Short, required: false, value: state.category || 'economy' } }, { type: ComponentType.Label, label: 'Icono (emoji)', component: { type: ComponentType.TextInput, customId: 'icon', style: TextInputStyle.Short, required: false, value: state.icon || '🏆' } }, { type: ComponentType.Label, label: 'Puntos (número)', component: { type: ComponentType.TextInput, customId: 'points', style: TextInputStyle.Short, required: false, value: String(state.points || 10) } } ] } as const; await i.showModal(modal); const submit = await i.awaitModalSubmit({ time: 5 * 60_000 }).catch(() => null); if (!submit) return; state.name = submit.components.getTextInputValue('name'); state.description = submit.components.getTextInputValue('description'); state.category = submit.components.getTextInputValue('category') || 'economy'; state.icon = submit.components.getTextInputValue('icon') || '🏆'; state.points = parseInt(submit.components.getTextInputValue('points')) || 10; await submit.deferUpdate(); await editorMsg.edit(createDisplay(state)); } async function showRequirementsModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) { const modal = { title: 'Requisitos del Logro', customId: 'ach_req_modal', components: [ { type: ComponentType.TextDisplay, content: 'Formato JSON con "type" y "value"' }, { type: ComponentType.Label, label: 'Requisitos (JSON)', component: { type: ComponentType.TextInput, customId: 'requirements', style: TextInputStyle.Paragraph, required: true, value: JSON.stringify(state.requirements, null, 2), placeholder: '{"type": "mine_count", "value": 100}' } } ] } as const; await i.showModal(modal); const submit = await i.awaitModalSubmit({ time: 5 * 60_000 }).catch(() => null); if (!submit) return; try { state.requirements = JSON.parse(submit.components.getTextInputValue('requirements')); await submit.deferUpdate(); await editorMsg.edit(createDisplay(state)); } catch (e) { await submit.reply({ content: '❌ JSON inválido en requisitos.', flags: 64 }); } } async function showRewardsModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) { const modal = { title: 'Recompensas del Logro', customId: 'ach_reward_modal', components: [ { type: ComponentType.TextDisplay, content: 'Formato JSON con coins, items, etc.' }, { type: ComponentType.Label, label: 'Recompensas (JSON)', component: { type: ComponentType.TextInput, customId: 'rewards', style: TextInputStyle.Paragraph, required: true, value: JSON.stringify(state.rewards, null, 2), placeholder: '{"coins": 1000, "items": [{"key": "item.key", "quantity": 1}]}' } } ] } as const; await i.showModal(modal); const submit = await i.awaitModalSubmit({ time: 5 * 60_000 }).catch(() => null); if (!submit) return; try { state.rewards = JSON.parse(submit.components.getTextInputValue('rewards')); await submit.deferUpdate(); await editorMsg.edit(createDisplay(state)); } catch (e) { await submit.reply({ content: '❌ JSON inválido en recompensas.', flags: 64 }); } }