feat(economy): implement item editing command with enhanced display components and interactive modals
This commit is contained in:
101
src/.backup/areaCreate.ts.backup2
Normal file
101
src/.backup/areaCreate.ts.backup2
Normal file
@@ -0,0 +1,101 @@
|
||||
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 { Message, MessageComponentInteraction, MessageFlags, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
|
||||
interface AreaState {
|
||||
key: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
config?: any;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'area-crear',
|
||||
type: 'message',
|
||||
aliases: ['crear-area','areacreate'],
|
||||
cooldown: 10,
|
||||
description: 'Crea una GameArea (mina/laguna/arena/farm) para este servidor con editor.',
|
||||
usage: 'area-crear <key-única>',
|
||||
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: `!area-crear <key-única>`'); 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; }
|
||||
|
||||
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 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 'ga_cancel':
|
||||
await i.deferUpdate();
|
||||
await editorMsg.edit({ content: '❌ Editor de Área cancelado.', components: [] });
|
||||
collector.stop('cancel');
|
||||
return;
|
||||
case 'ga_base':
|
||||
await showBaseModal(i as ButtonInteraction, state);
|
||||
return;
|
||||
case 'ga_config':
|
||||
await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área');
|
||||
return;
|
||||
case 'ga_meta':
|
||||
await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área');
|
||||
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: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
|
||||
collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
}
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: AreaState) {
|
||||
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 {}
|
||||
}
|
||||
|
||||
async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string) {
|
||||
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 {}
|
||||
}
|
||||
|
||||
101
src/.backup/areaEdit.ts.backup2
Normal file
101
src/.backup/areaEdit.ts.backup2
Normal file
@@ -0,0 +1,101 @@
|
||||
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 { Message, MessageComponentInteraction, MessageFlags, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
|
||||
interface AreaState {
|
||||
key: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
config?: any;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'area-editar',
|
||||
type: 'message',
|
||||
aliases: ['editar-area','areaedit'],
|
||||
cooldown: 10,
|
||||
description: 'Edita una GameArea de este servidor con un editor interactivo.',
|
||||
usage: 'area-editar <key-única>',
|
||||
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: `!area-editar <key-única>`'); 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; }
|
||||
|
||||
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 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 'ga_cancel':
|
||||
await i.deferUpdate();
|
||||
await editorMsg.edit({ content: '❌ Editor de Área cancelado.', components: [] });
|
||||
collector.stop('cancel');
|
||||
return;
|
||||
case 'ga_base':
|
||||
await showBaseModal(i as ButtonInteraction, state);
|
||||
return;
|
||||
case 'ga_config':
|
||||
await showJsonModal(i as ButtonInteraction, state, 'config', 'Config del Área');
|
||||
return;
|
||||
case 'ga_meta':
|
||||
await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta del Área');
|
||||
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: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
|
||||
collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
}
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: AreaState) {
|
||||
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 {}
|
||||
}
|
||||
|
||||
async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'config'|'metadata', title: string) {
|
||||
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 {}
|
||||
}
|
||||
|
||||
227
src/.backup/itemCreate.ts.backup2
Normal file
227
src/.backup/itemCreate.ts.backup2
Normal file
@@ -0,0 +1,227 @@
|
||||
import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||
import logger from '../../../core/lib/logger';
|
||||
import type Amayo from '../../../core/client';
|
||||
|
||||
interface ItemEditorState {
|
||||
key: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
icon?: string;
|
||||
stackable?: boolean;
|
||||
maxPerInventory?: number | null;
|
||||
tags: string[];
|
||||
props?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'item-crear',
|
||||
type: 'message',
|
||||
aliases: ['crear-item','itemcreate'],
|
||||
cooldown: 10,
|
||||
description: 'Crea un EconomyItem para este servidor con un pequeño editor interactivo.',
|
||||
category: 'Economía',
|
||||
usage: 'item-crear <key-única>',
|
||||
run: async (message: Message, args: string[], client: Amayo) => {
|
||||
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.');
|
||||
return;
|
||||
}
|
||||
|
||||
const key = args[0]?.trim();
|
||||
if (!key) {
|
||||
await message.reply('Uso: `!item-crear <key-única>`');
|
||||
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.');
|
||||
return;
|
||||
}
|
||||
|
||||
const state: ItemEditorState = {
|
||||
key,
|
||||
tags: [],
|
||||
stackable: true,
|
||||
maxPerInventory: null,
|
||||
props: {},
|
||||
};
|
||||
|
||||
const channel = message.channel as TextBasedChannel & { send: Function };
|
||||
const editorMsg = await channel.send({
|
||||
content: `🛠️ Editor de Item: \`${key}\`\nUsa los botones para configurar los campos y luego guarda.`,
|
||||
components: [
|
||||
{ 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 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;
|
||||
if (i.customId === 'it_cancel') {
|
||||
await i.deferUpdate();
|
||||
await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] });
|
||||
collector.stop('cancel');
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_base') {
|
||||
await showBaseModal(i as ButtonInteraction, state);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_tags') {
|
||||
await showTagsModal(i as ButtonInteraction, state);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_props') {
|
||||
await showPropsModal(i as ButtonInteraction, state);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_save') {
|
||||
// Validar
|
||||
if (!state.name) {
|
||||
await i.reply({ content: '❌ Falta el nombre del item (configura en Base).', flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
}
|
||||
// Guardar
|
||||
await client.prisma.economyItem.create({
|
||||
data: {
|
||||
guildId,
|
||||
key: state.key,
|
||||
name: state.name!,
|
||||
description: state.description,
|
||||
category: state.category,
|
||||
icon: state.icon,
|
||||
stackable: state.stackable ?? true,
|
||||
maxPerInventory: state.maxPerInventory ?? undefined,
|
||||
tags: state.tags,
|
||||
props: state.props ?? {},
|
||||
},
|
||||
});
|
||||
await i.reply({ content: '✅ Item guardado!', flags: MessageFlags.Ephemeral });
|
||||
await editorMsg.edit({ content: `✅ Item \`${state.key}\` creado.`, components: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'item-crear interaction error');
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
|
||||
collector.on('end', async (_c, r) => {
|
||||
if (r === 'time') {
|
||||
try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const modal = {
|
||||
title: 'Configuración base del Item',
|
||||
customId: 'it_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: 'Descripción', component: { type: ComponentType.TextInput, customId: 'desc', style: TextInputStyle.Paragraph, required: false, value: state.description ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Icon URL', component: { type: ComponentType.TextInput, customId: 'icon', style: TextInputStyle.Short, required: false, value: state.icon ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Stackable y Máx inventario', component: { type: ComponentType.TextInput, customId: 'stack_max', style: TextInputStyle.Short, required: false, placeholder: 'true,10', value: state.stackable !== undefined ? `${state.stackable},${state.maxPerInventory ?? ''}` : '' } },
|
||||
],
|
||||
} as const;
|
||||
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
const name = sub.components.getTextInputValue('name').trim();
|
||||
const desc = sub.components.getTextInputValue('desc').trim();
|
||||
const cat = sub.components.getTextInputValue('cat').trim();
|
||||
const icon = sub.components.getTextInputValue('icon').trim();
|
||||
const stackMax = sub.components.getTextInputValue('stack_max').trim();
|
||||
|
||||
state.name = name;
|
||||
state.description = desc || undefined;
|
||||
state.category = cat || undefined;
|
||||
state.icon = icon || undefined;
|
||||
|
||||
if (stackMax) {
|
||||
const [s, m] = stackMax.split(',');
|
||||
state.stackable = String(s).toLowerCase() !== 'false';
|
||||
const mv = m?.trim();
|
||||
state.maxPerInventory = mv ? Math.max(0, parseInt(mv, 10) || 0) : null;
|
||||
}
|
||||
|
||||
await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function showTagsModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const modal = {
|
||||
title: 'Tags del Item (separados por coma)',
|
||||
customId: 'it_tags_modal',
|
||||
components: [
|
||||
{ type: ComponentType.Label, label: 'Tags', component: { type: ComponentType.TextInput, customId: 'tags', style: TextInputStyle.Paragraph, required: false, value: state.tags.join(', ') } },
|
||||
],
|
||||
} as const;
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
const tags = sub.components.getTextInputValue('tags');
|
||||
state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
||||
await sub.reply({ content: '✅ Tags actualizados.', flags: MessageFlags.Ephemeral });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function showPropsModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({
|
||||
tool: undefined,
|
||||
breakable: undefined,
|
||||
chest: undefined,
|
||||
eventCurrency: undefined,
|
||||
passiveEffects: [],
|
||||
mutationPolicy: undefined,
|
||||
craftingOnly: false,
|
||||
food: undefined,
|
||||
damage: undefined,
|
||||
defense: undefined,
|
||||
maxHpBonus: undefined,
|
||||
});
|
||||
const modal = {
|
||||
title: 'Props (JSON) del Item',
|
||||
customId: 'it_props_modal',
|
||||
components: [
|
||||
{ type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'props', style: TextInputStyle.Paragraph, required: false, value: template.slice(0,4000) } },
|
||||
],
|
||||
} as const;
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
const raw = sub.components.getTextInputValue('props');
|
||||
if (raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
state.props = parsed;
|
||||
await sub.reply({ content: '✅ Props guardados.', flags: MessageFlags.Ephemeral });
|
||||
} catch (e) {
|
||||
await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
} else {
|
||||
state.props = {};
|
||||
await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
143
src/.backup/itemEdit.ts.backup2
Normal file
143
src/.backup/itemEdit.ts.backup2
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||
import logger from '../../../core/lib/logger';
|
||||
import type Amayo from '../../../core/client';
|
||||
|
||||
interface ItemEditorState {
|
||||
key: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
icon?: string;
|
||||
stackable?: boolean;
|
||||
maxPerInventory?: number | null;
|
||||
tags: string[];
|
||||
props?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'item-editar',
|
||||
type: 'message',
|
||||
aliases: ['editar-item','itemedit'],
|
||||
cooldown: 10,
|
||||
description: 'Edita un EconomyItem de este servidor con un editor interactivo.',
|
||||
category: 'Economía',
|
||||
usage: 'item-editar <key-única>',
|
||||
run: async (message: Message, args: string[], client: Amayo) => {
|
||||
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.'); return; }
|
||||
const key = args[0]?.trim();
|
||||
if (!key) { await message.reply('Uso: `!item-editar <key-única>`'); return; }
|
||||
const guildId = message.guild!.id;
|
||||
|
||||
const item = await client.prisma.economyItem.findFirst({ where: { key, guildId } });
|
||||
if (!item) { await message.reply('❌ No existe un item con esa key en este servidor.'); return; }
|
||||
|
||||
const state: ItemEditorState = {
|
||||
key,
|
||||
name: item.name,
|
||||
description: item.description ?? undefined,
|
||||
category: item.category ?? undefined,
|
||||
icon: item.icon ?? undefined,
|
||||
stackable: item.stackable ?? true,
|
||||
maxPerInventory: item.maxPerInventory ?? null,
|
||||
tags: item.tags ?? [],
|
||||
props: item.props ?? {},
|
||||
};
|
||||
|
||||
const channel = message.channel as TextBasedChannel & { send: Function };
|
||||
const editorMsg = await channel.send({
|
||||
content: `🛠️ Editor de Item (editar): \`${key}\``,
|
||||
components: [ { 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 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;
|
||||
if (i.customId === 'it_cancel') { await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor cancelado.', components: [] }); collector.stop('cancel'); return; }
|
||||
if (i.customId === 'it_base') { await showBaseModal(i as ButtonInteraction, state); return; }
|
||||
if (i.customId === 'it_tags') { await showTagsModal(i as ButtonInteraction, state); return; }
|
||||
if (i.customId === 'it_props') { await showPropsModal(i as ButtonInteraction, state); return; }
|
||||
if (i.customId === 'it_save') {
|
||||
if (!state.name) { await i.reply({ content: '❌ Falta el nombre del item.', flags: MessageFlags.Ephemeral }); return; }
|
||||
await client.prisma.economyItem.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
name: state.name!,
|
||||
description: state.description,
|
||||
category: state.category,
|
||||
icon: state.icon,
|
||||
stackable: state.stackable ?? true,
|
||||
maxPerInventory: state.maxPerInventory ?? undefined,
|
||||
tags: state.tags,
|
||||
props: state.props ?? {},
|
||||
},
|
||||
});
|
||||
await i.reply({ content: '✅ Item actualizado!', flags: MessageFlags.Ephemeral });
|
||||
await editorMsg.edit({ content: `✅ Item \`${state.key}\` actualizado.`, components: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'item-editar interaction error');
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
|
||||
collector.on('end', async (_c, r) => { if (r === 'time') { try { await editorMsg.edit({ content: '⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
},
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const modal = {
|
||||
title: 'Configuración base del Item', customId: 'it_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: 'Descripción', component: { type: ComponentType.TextInput, customId: 'desc', style: TextInputStyle.Paragraph, required: false, value: state.description ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Categoría', component: { type: ComponentType.TextInput, customId: 'cat', style: TextInputStyle.Short, required: false, value: state.category ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Icon URL', component: { type: ComponentType.TextInput, customId: 'icon', style: TextInputStyle.Short, required: false, value: state.icon ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Stackable y Máx inventario', component: { type: ComponentType.TextInput, customId: 'stack_max', style: TextInputStyle.Short, required: false, placeholder: 'true,10', value: state.stackable !== undefined ? `${state.stackable},${state.maxPerInventory ?? ''}` : '' } },
|
||||
], } as const;
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
state.name = sub.components.getTextInputValue('name').trim();
|
||||
state.description = sub.components.getTextInputValue('desc').trim() || undefined;
|
||||
state.category = sub.components.getTextInputValue('cat').trim() || undefined;
|
||||
state.icon = sub.components.getTextInputValue('icon').trim() || undefined;
|
||||
const stackMax = sub.components.getTextInputValue('stack_max').trim();
|
||||
if (stackMax) { const [s,m] = stackMax.split(','); state.stackable = String(s).toLowerCase() !== 'false'; const mv = m?.trim(); state.maxPerInventory = mv ? Math.max(0, parseInt(mv,10)||0) : null; }
|
||||
await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function showTagsModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const modal = { title: 'Tags del Item (separados por coma)', customId: 'it_tags_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Tags', component: { type: ComponentType.TextInput, customId: 'tags', style: TextInputStyle.Paragraph, required: false, value: state.tags.join(', ') } },
|
||||
], } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const tags = sub.components.getTextInputValue('tags'); state.tags = tags ? tags.split(',').map(t=>t.trim()).filter(Boolean) : []; await sub.reply({ content: '✅ Tags actualizados.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
|
||||
async function showPropsModal(i: ButtonInteraction, state: ItemEditorState) {
|
||||
const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({});
|
||||
const modal = { title: 'Props (JSON) del Item', customId: 'it_props_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'JSON', component: { type: ComponentType.TextInput, customId: 'props', style: TextInputStyle.Paragraph, required: false, value: template.slice(0,4000) } },
|
||||
], } as const;
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
const raw = sub.components.getTextInputValue('props');
|
||||
if (raw) { try { state.props = JSON.parse(raw); await sub.reply({ content: '✅ Props guardados.', flags: MessageFlags.Ephemeral }); } catch { await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral }); } }
|
||||
else { state.props = {}; await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral }); }
|
||||
} catch {}
|
||||
}
|
||||
95
src/.backup/mobCreate.ts.backup2
Normal file
95
src/.backup/mobCreate.ts.backup2
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||
import logger from '../../../core/lib/logger';
|
||||
import type Amayo from '../../../core/client';
|
||||
|
||||
interface MobEditorState {
|
||||
key: string;
|
||||
name?: string;
|
||||
category?: string;
|
||||
stats?: any; // JSON libre, ej: { attack, hp, defense }
|
||||
drops?: any; // JSON libre, tabla de recompensas
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'mob-crear',
|
||||
type: 'message',
|
||||
aliases: ['crear-mob','mobcreate'],
|
||||
cooldown: 10,
|
||||
description: 'Crea un Mob (enemigo) para este servidor con editor interactivo.',
|
||||
category: 'Minijuegos',
|
||||
usage: 'mob-crear <key-única>',
|
||||
run: async (message: Message, args: string[], client: Amayo) => {
|
||||
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.'); return; }
|
||||
const key = args[0]?.trim();
|
||||
if (!key) { await message.reply('Uso: `!mob-crear <key-única>`'); return; }
|
||||
|
||||
const guildId = message.guild!.id;
|
||||
const exists = await client.prisma.mob.findFirst({ where: { key, guildId } });
|
||||
if (exists) { await message.reply('❌ Ya existe un mob con esa key.'); return; }
|
||||
|
||||
const state: MobEditorState = { key, stats: { attack: 5 }, drops: {} };
|
||||
|
||||
const channel = message.channel as TextBasedChannel & { send: Function };
|
||||
const editorMsg = await channel.send({
|
||||
content: `👾 Editor de Mob: \`${key}\``,
|
||||
components: [ { 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' },
|
||||
] } ],
|
||||
});
|
||||
|
||||
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;
|
||||
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_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: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, 'mob-crear');
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
},
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: MobEditorState) {
|
||||
const modal = { title: 'Configuración 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 ?? '' } },
|
||||
] } 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 {}
|
||||
}
|
||||
|
||||
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) } },
|
||||
] } 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 }); }
|
||||
} catch {}
|
||||
}
|
||||
100
src/.backup/mobEdit.ts.backup2
Normal file
100
src/.backup/mobEdit.ts.backup2
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Message, MessageFlags, MessageComponentInteraction, ButtonInteraction, TextBasedChannel } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
|
||||
import logger from '../../../core/lib/logger';
|
||||
import type Amayo from '../../../core/client';
|
||||
|
||||
interface MobEditorState {
|
||||
key: string;
|
||||
name?: string;
|
||||
category?: string;
|
||||
stats?: any;
|
||||
drops?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'mob-editar',
|
||||
type: 'message',
|
||||
aliases: ['editar-mob','mobedit'],
|
||||
cooldown: 10,
|
||||
description: 'Edita un Mob (enemigo) de este servidor con editor interactivo.',
|
||||
category: 'Minijuegos',
|
||||
usage: 'mob-editar <key-única>',
|
||||
run: async (message: Message, args: string[], client: Amayo) => {
|
||||
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.'); return; }
|
||||
const key = args[0]?.trim();
|
||||
if (!key) { await message.reply('Uso: `!mob-editar <key-única>`'); return; }
|
||||
const guildId = message.guild!.id;
|
||||
|
||||
const mob = await client.prisma.mob.findFirst({ where: { key, guildId } });
|
||||
if (!mob) { await message.reply('❌ No existe un mob con esa key en este servidor.'); return; }
|
||||
|
||||
const state: MobEditorState = {
|
||||
key,
|
||||
name: mob.name,
|
||||
category: mob.category ?? undefined,
|
||||
stats: mob.stats ?? {},
|
||||
drops: mob.drops ?? {},
|
||||
};
|
||||
|
||||
const channel = message.channel as TextBasedChannel & { send: Function };
|
||||
const editorMsg = await channel.send({
|
||||
content: `👾 Editor de Mob (editar): \`${key}\``,
|
||||
components: [ { 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' },
|
||||
] } ],
|
||||
});
|
||||
|
||||
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;
|
||||
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_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: [] });
|
||||
collector.stop('saved');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({err}, 'mob-editar');
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
},
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: MobEditorState) {
|
||||
const modal = { title: 'Configuración 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 ?? '' } },
|
||||
] } 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 {}
|
||||
}
|
||||
|
||||
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) } },
|
||||
] } 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 }); }
|
||||
} catch {}
|
||||
}
|
||||
129
src/.backup/offerCreate.ts.backup2
Normal file
129
src/.backup/offerCreate.ts.backup2
Normal file
@@ -0,0 +1,129 @@
|
||||
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 { Message, MessageComponentInteraction, MessageFlags, ButtonInteraction } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
|
||||
interface OfferState {
|
||||
itemKey?: string;
|
||||
enabled?: boolean;
|
||||
price?: any;
|
||||
startAt?: string;
|
||||
endAt?: string;
|
||||
perUserLimit?: number | null;
|
||||
stock?: number | null;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'offer-crear',
|
||||
type: 'message',
|
||||
aliases: ['crear-oferta','ofertacreate'],
|
||||
cooldown: 10,
|
||||
description: 'Crea una ShopOffer para este servidor con editor interactivo (price/ventanas/stock/limit).',
|
||||
usage: 'offer-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 guildId = message.guild!.id;
|
||||
const state: OfferState = { enabled: true, price: {}, perUserLimit: null, stock: null, metadata: {} };
|
||||
|
||||
const editorMsg = await (message.channel as any).send({
|
||||
content: `🛒 Editor de Oferta (crear)`,
|
||||
components: [
|
||||
{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'of_base' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Precio (JSON)', custom_id: 'of_price' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Ventana', custom_id: 'of_window' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Límites', custom_id: 'of_limits' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'of_meta' },
|
||||
]},
|
||||
{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'of_save' },
|
||||
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'of_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 'of_cancel': await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor de Oferta cancelado.', components: [] }); collector.stop('cancel'); return;
|
||||
case 'of_base': await showBaseModal(i as ButtonInteraction, state); return;
|
||||
case 'of_price': await showJsonModal(i as ButtonInteraction, state, 'price', 'Precio'); return;
|
||||
case 'of_window': await showWindowModal(i as ButtonInteraction, state); return;
|
||||
case 'of_limits': await showLimitsModal(i as ButtonInteraction, state); return;
|
||||
case 'of_meta': await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta'); return;
|
||||
case 'of_save':
|
||||
if (!state.itemKey) { await i.reply({ content: '❌ Falta itemKey en Base.', flags: MessageFlags.Ephemeral }); return; }
|
||||
const item = await prisma.economyItem.findFirst({ where: { key: state.itemKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||
if (!item) { await i.reply({ content: '❌ Item no encontrado por key.', flags: MessageFlags.Ephemeral }); return; }
|
||||
try {
|
||||
await prisma.shopOffer.create({
|
||||
data: {
|
||||
guildId,
|
||||
itemId: item.id,
|
||||
enabled: state.enabled ?? true,
|
||||
price: state.price ?? {},
|
||||
startAt: state.startAt ? new Date(state.startAt) : null,
|
||||
endAt: state.endAt ? new Date(state.endAt) : null,
|
||||
perUserLimit: state.perUserLimit ?? null,
|
||||
stock: state.stock ?? null,
|
||||
metadata: state.metadata ?? {},
|
||||
}
|
||||
});
|
||||
await i.reply({ content: '✅ Oferta guardada.', flags: MessageFlags.Ephemeral });
|
||||
await editorMsg.edit({ content: `✅ Oferta creada para ${state.itemKey}.`, components: [] });
|
||||
collector.stop('saved');
|
||||
} catch (err: any) {
|
||||
await i.reply({ content: `❌ Error al guardar: ${err?.message ?? err}`, flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
collector.on('end', async (_c,r)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
}
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Base de Oferta', customId: 'of_base_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Item Key', component: { type: ComponentType.TextInput, customId: 'itemKey', style: TextInputStyle.Short, required: true, value: state.itemKey ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Habilitada? (true/false)', component: { type: ComponentType.TextInput, customId: 'enabled', style: TextInputStyle.Short, required: false, value: String(state.enabled ?? true) } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.itemKey = sub.components.getTextInputValue('itemKey').trim(); const en = sub.components.getTextInputValue('enabled').trim(); state.enabled = en ? (en.toLowerCase() !== 'false') : (state.enabled ?? true); await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
|
||||
async function showJsonModal(i: ButtonInteraction, state: OfferState, field: 'price'|'metadata', title: string) {
|
||||
const current = JSON.stringify(state[field] ?? {});
|
||||
const modal = { title, customId: `of_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 {}
|
||||
}
|
||||
|
||||
async function showWindowModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Ventana', customId: 'of_window_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Inicio (ISO opcional)', component: { type: ComponentType.TextInput, customId: 'start', style: TextInputStyle.Short, required: false, value: state.startAt ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Fin (ISO opcional)', component: { type: ComponentType.TextInput, customId: 'end', style: TextInputStyle.Short, required: false, value: state.endAt ?? '' } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const s = sub.components.getTextInputValue('start').trim(); const e = sub.components.getTextInputValue('end').trim(); state.startAt = s || ''; state.endAt = e || ''; await sub.reply({ content: '✅ Ventana actualizada.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
|
||||
async function showLimitsModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Límites', customId: 'of_limits_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Límite por usuario (vacío = sin límite)', component: { type: ComponentType.TextInput, customId: 'limit', style: TextInputStyle.Short, required: false, value: state.perUserLimit != null ? String(state.perUserLimit) : '' } },
|
||||
{ type: ComponentType.Label, label: 'Stock global (vacío = ilimitado)', component: { type: ComponentType.TextInput, customId: 'stock', style: TextInputStyle.Short, required: false, value: state.stock != null ? String(state.stock) : '' } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const lim = sub.components.getTextInputValue('limit').trim(); const st = sub.components.getTextInputValue('stock').trim(); state.perUserLimit = lim ? Math.max(0, parseInt(lim,10)||0) : null; state.stock = st ? Math.max(0, parseInt(st,10)||0) : null; await sub.reply({ content: '✅ Límites actualizados.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
148
src/.backup/offerEdit.ts.backup2
Normal file
148
src/.backup/offerEdit.ts.backup2
Normal file
@@ -0,0 +1,148 @@
|
||||
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 { Message, MessageComponentInteraction, MessageFlags, ButtonInteraction } from 'discord.js';
|
||||
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
|
||||
|
||||
interface OfferState {
|
||||
offerId: string;
|
||||
itemKey?: string;
|
||||
enabled?: boolean;
|
||||
price?: any;
|
||||
startAt?: string;
|
||||
endAt?: string;
|
||||
perUserLimit?: number | null;
|
||||
stock?: number | null;
|
||||
metadata?: any;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'offer-editar',
|
||||
type: 'message',
|
||||
aliases: ['editar-oferta','offeredit'],
|
||||
cooldown: 10,
|
||||
description: 'Edita una ShopOffer por ID con editor interactivo (price/ventanas/stock/limit).',
|
||||
usage: 'offer-editar <offerId>',
|
||||
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 offerId = args[0]?.trim();
|
||||
if (!offerId) { await message.reply('Uso: `!offer-editar <offerId>`'); return; }
|
||||
|
||||
const guildId = message.guild!.id;
|
||||
const offer = await prisma.shopOffer.findUnique({ where: { id: offerId } });
|
||||
if (!offer || offer.guildId !== guildId) { await message.reply('❌ Oferta no encontrada para este servidor.'); return; }
|
||||
|
||||
const item = await prisma.economyItem.findUnique({ where: { id: offer.itemId } });
|
||||
|
||||
const state: OfferState = {
|
||||
offerId,
|
||||
itemKey: item?.key,
|
||||
enabled: offer.enabled,
|
||||
price: offer.price ?? {},
|
||||
startAt: offer.startAt ? new Date(offer.startAt).toISOString() : '',
|
||||
endAt: offer.endAt ? new Date(offer.endAt).toISOString() : '',
|
||||
perUserLimit: offer.perUserLimit ?? null,
|
||||
stock: offer.stock ?? null,
|
||||
metadata: offer.metadata ?? {},
|
||||
};
|
||||
|
||||
const editorMsg = await (message.channel as any).send({
|
||||
content: `🛒 Editor de Oferta (editar): ${offerId}`,
|
||||
components: [
|
||||
{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'of_base' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Precio (JSON)', custom_id: 'of_price' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Ventana', custom_id: 'of_window' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Límites', custom_id: 'of_limits' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'of_meta' },
|
||||
] },
|
||||
{ type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'of_save' },
|
||||
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'of_cancel' },
|
||||
] },
|
||||
],
|
||||
});
|
||||
|
||||
const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i: MessageComponentInteraction)=> i.user.id === message.author.id });
|
||||
collector.on('collect', async (i: MessageComponentInteraction) => {
|
||||
try {
|
||||
if (!i.isButton()) return;
|
||||
switch (i.customId) {
|
||||
case 'of_cancel': await i.deferUpdate(); await editorMsg.edit({ content: '❌ Editor de Oferta cancelado.', components: [] }); collector.stop('cancel'); return;
|
||||
case 'of_base': await showBaseModal(i as ButtonInteraction, state); return;
|
||||
case 'of_price': await showJsonModal(i as ButtonInteraction, state, 'price', 'Precio'); return;
|
||||
case 'of_window': await showWindowModal(i as ButtonInteraction, state); return;
|
||||
case 'of_limits': await showLimitsModal(i as ButtonInteraction, state); return;
|
||||
case 'of_meta': await showJsonModal(i as ButtonInteraction, state, 'metadata', 'Meta'); return;
|
||||
case 'of_save':
|
||||
if (!state.itemKey) { await i.reply({ content: '❌ Falta itemKey en Base.', flags: MessageFlags.Ephemeral }); return; }
|
||||
const it = await prisma.economyItem.findFirst({ where: { key: state.itemKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||
if (!it) { await i.reply({ content: '❌ Item no encontrado por key.', flags: MessageFlags.Ephemeral }); return; }
|
||||
try {
|
||||
await prisma.shopOffer.update({
|
||||
where: { id: state.offerId },
|
||||
data: {
|
||||
itemId: it.id,
|
||||
enabled: state.enabled ?? true,
|
||||
price: state.price ?? {},
|
||||
startAt: state.startAt ? new Date(state.startAt) : null,
|
||||
endAt: state.endAt ? new Date(state.endAt) : null,
|
||||
perUserLimit: state.perUserLimit ?? null,
|
||||
stock: state.stock ?? null,
|
||||
metadata: state.metadata ?? {},
|
||||
}
|
||||
});
|
||||
await i.reply({ content: '✅ Oferta actualizada.', flags: MessageFlags.Ephemeral });
|
||||
await editorMsg.edit({ content: `✅ Oferta ${state.offerId} actualizada.`, components: [] });
|
||||
collector.stop('saved');
|
||||
} catch (err: any) {
|
||||
await i.reply({ content: `❌ Error al guardar: ${err?.message ?? err}`, flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!i.deferred && !i.replied) await i.reply({ content: '❌ Error procesando la acción.', flags: MessageFlags.Ephemeral });
|
||||
}
|
||||
});
|
||||
collector.on('end', async (_c: any,r: string)=> { if (r==='time') { try { await editorMsg.edit({ content:'⏰ Editor expirado.', components: [] }); } catch {} } });
|
||||
}
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Base de Oferta', customId: 'of_base_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Item Key', component: { type: ComponentType.TextInput, customId: 'itemKey', style: TextInputStyle.Short, required: true, value: state.itemKey ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Habilitada? (true/false)', component: { type: ComponentType.TextInput, customId: 'enabled', style: TextInputStyle.Short, required: false, value: String(state.enabled ?? true) } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); state.itemKey = sub.components.getTextInputValue('itemKey').trim(); const en = sub.components.getTextInputValue('enabled').trim(); state.enabled = en ? (en.toLowerCase() !== 'false') : (state.enabled ?? true); await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
|
||||
async function showJsonModal(i: ButtonInteraction, state: OfferState, field: 'price'|'metadata', title: string) {
|
||||
const current = JSON.stringify(state[field] ?? {});
|
||||
const modal = { title, customId: `of_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 {}
|
||||
}
|
||||
|
||||
async function showWindowModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Ventana', customId: 'of_window_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Inicio (ISO opcional)', component: { type: ComponentType.TextInput, customId: 'start', style: TextInputStyle.Short, required: false, value: state.startAt ?? '' } },
|
||||
{ type: ComponentType.Label, label: 'Fin (ISO opcional)', component: { type: ComponentType.TextInput, customId: 'end', style: TextInputStyle.Short, required: false, value: state.endAt ?? '' } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const s = sub.components.getTextInputValue('start').trim(); const e = sub.components.getTextInputValue('end').trim(); state.startAt = s || ''; state.endAt = e || ''; await sub.reply({ content: '✅ Ventana actualizada.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
|
||||
async function showLimitsModal(i: ButtonInteraction, state: OfferState) {
|
||||
const modal = { title: 'Límites', customId: 'of_limits_modal', components: [
|
||||
{ type: ComponentType.Label, label: 'Límite por usuario (vacío = sin límite)', component: { type: ComponentType.TextInput, customId: 'limit', style: TextInputStyle.Short, required: false, value: state.perUserLimit != null ? String(state.perUserLimit) : '' } },
|
||||
{ type: ComponentType.Label, label: 'Stock global (vacío = ilimitado)', component: { type: ComponentType.TextInput, customId: 'stock', style: TextInputStyle.Short, required: false, value: state.stock != null ? String(state.stock) : '' } },
|
||||
] } as const;
|
||||
await i.showModal(modal);
|
||||
try { const sub = await i.awaitModalSubmit({ time: 300_000 }); const lim = sub.components.getTextInputValue('limit').trim(); const st = sub.components.getTextInputValue('stock').trim(); state.perUserLimit = lim ? Math.max(0, parseInt(lim,10)||0) : null; state.stock = st ? Math.max(0, parseInt(st,10)||0) : null; await sub.reply({ content: '✅ Límites actualizados.', flags: MessageFlags.Ephemeral }); } catch {}
|
||||
}
|
||||
215
src/.backup/player.ts.backup
Normal file
215
src/.backup/player.ts.backup
Normal file
@@ -0,0 +1,215 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { prisma } from '../../../core/database/prisma';
|
||||
import { getOrCreateWallet } from '../../../game/economy/service';
|
||||
import { getEquipment, getEffectiveStats } from '../../../game/combat/equipmentService';
|
||||
import type { ItemProps } from '../../../game/economy/types';
|
||||
|
||||
function parseItemProps(json: unknown): ItemProps {
|
||||
if (!json || typeof json !== 'object') return {};
|
||||
return json as ItemProps;
|
||||
}
|
||||
|
||||
function fmtTool(props: ItemProps) {
|
||||
const t = props.tool;
|
||||
if (!t) return '';
|
||||
const icon = t.type === 'pickaxe' ? '⛏️' : t.type === 'rod' ? '🎣' : t.type === 'sword' ? '🗡️' : t.type === 'bow' ? '🏹' : t.type === 'halberd' ? '⚔️' : t.type === 'net' ? '🕸️' : '🔧';
|
||||
const tier = t.tier != null ? ` T${t.tier}` : '';
|
||||
return `${icon}${tier}`;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'player',
|
||||
type: 'message',
|
||||
aliases: ['perfil', 'profile', 'yo', 'me'],
|
||||
cooldown: 5,
|
||||
description: 'Muestra toda tu información de jugador: stats, equipo, progreso y últimas actividades.',
|
||||
usage: 'player [@usuario]',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
// Permitir ver perfil de otros usuarios mencionándolos
|
||||
const targetUser = message.mentions.users.first() || message.author;
|
||||
const userId = targetUser.id;
|
||||
const guildId = message.guild!.id;
|
||||
|
||||
// Obtener datos del jugador
|
||||
const wallet = await getOrCreateWallet(userId, guildId);
|
||||
const { eq, weapon, armor, cape } = await getEquipment(userId, guildId);
|
||||
const stats = await getEffectiveStats(userId, guildId);
|
||||
const playerState = await prisma.playerState.findUnique({ where: { userId_guildId: { userId, guildId } } });
|
||||
|
||||
// Progreso por áreas
|
||||
const progress = await prisma.playerProgress.findMany({
|
||||
where: { userId, guildId },
|
||||
include: { area: true },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
take: 5,
|
||||
});
|
||||
|
||||
// Últimas actividades de minijuegos
|
||||
const recentRuns = await prisma.minigameRun.findMany({
|
||||
where: { userId, guildId },
|
||||
include: { area: true },
|
||||
orderBy: { startedAt: 'desc' },
|
||||
take: 5,
|
||||
});
|
||||
|
||||
// Conteo de items en inventario
|
||||
const inventoryCount = await prisma.inventoryEntry.count({
|
||||
where: { userId, guildId, quantity: { gt: 0 } },
|
||||
});
|
||||
|
||||
// Total de items (cantidad sumada)
|
||||
const inventorySum = await prisma.inventoryEntry.aggregate({
|
||||
where: { userId, guildId },
|
||||
_sum: { quantity: true },
|
||||
});
|
||||
|
||||
// Compras totales
|
||||
const purchaseCount = await prisma.shopPurchase.count({
|
||||
where: { userId, guildId },
|
||||
});
|
||||
|
||||
// Cooldowns activos
|
||||
const activeCooldowns = await prisma.actionCooldown.findMany({
|
||||
where: { userId, guildId, until: { gt: new Date() } },
|
||||
orderBy: { until: 'asc' },
|
||||
take: 5,
|
||||
});
|
||||
|
||||
// Construir el mensaje
|
||||
const lines: string[] = [];
|
||||
|
||||
// Header
|
||||
lines.push(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
||||
lines.push(`👤 **Perfil de ${targetUser.username}**`);
|
||||
lines.push(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
||||
|
||||
// 📊 Stats Básicos
|
||||
lines.push(`📊 **ESTADÍSTICAS**`);
|
||||
lines.push(`❤️ HP: ${stats.hp}/${stats.maxHp}`);
|
||||
lines.push(`⚔️ ATK: ${stats.damage}`);
|
||||
lines.push(`🛡️ DEF: ${stats.defense}`);
|
||||
lines.push(`💰 Monedas: ${wallet.coins}`);
|
||||
lines.push(``);
|
||||
|
||||
// 🎒 Inventario
|
||||
lines.push(`🎒 **INVENTARIO**`);
|
||||
lines.push(`📦 Items únicos: ${inventoryCount}`);
|
||||
lines.push(`🔢 Total items: ${inventorySum._sum.quantity ?? 0}`);
|
||||
lines.push(`🛒 Compras totales: ${purchaseCount}`);
|
||||
lines.push(``);
|
||||
|
||||
// 🧰 Equipamiento
|
||||
lines.push(`🧰 **EQUIPAMIENTO**`);
|
||||
if (weapon) {
|
||||
const wProps = parseItemProps(weapon.props);
|
||||
const wTool = fmtTool(wProps);
|
||||
const wDmg = wProps.damage ? ` +${wProps.damage} ATK` : '';
|
||||
lines.push(`🗡️ Arma: ${weapon.name || weapon.key}${wTool ? ` ${wTool}` : ''}${wDmg}`);
|
||||
} else {
|
||||
lines.push(`🗡️ Arma: -`);
|
||||
}
|
||||
|
||||
if (armor) {
|
||||
const aProps = parseItemProps(armor.props);
|
||||
const aDef = aProps.defense ? ` +${aProps.defense} DEF` : '';
|
||||
lines.push(`🛡️ Armadura: ${armor.name || armor.key}${aDef}`);
|
||||
} else {
|
||||
lines.push(`🛡️ Armadura: -`);
|
||||
}
|
||||
|
||||
if (cape) {
|
||||
const cProps = parseItemProps(cape.props);
|
||||
const cHp = cProps.maxHpBonus ? ` +${cProps.maxHpBonus} HP` : '';
|
||||
lines.push(`🧥 Capa: ${cape.name || cape.key}${cHp}`);
|
||||
} else {
|
||||
lines.push(`🧥 Capa: -`);
|
||||
}
|
||||
lines.push(``);
|
||||
|
||||
// 🗺️ Progreso por Áreas
|
||||
if (progress.length > 0) {
|
||||
lines.push(`🗺️ **PROGRESO EN ÁREAS**`);
|
||||
for (const p of progress) {
|
||||
const areaIcon = p.area.type === 'MINE' ? '⛏️' : p.area.type === 'LAGOON' ? '🎣' : p.area.type === 'FIGHT' ? '⚔️' : p.area.type === 'FARM' ? '🌾' : '🗺️';
|
||||
lines.push(`${areaIcon} ${p.area.name}: Nivel ${p.highestLevel}`);
|
||||
}
|
||||
lines.push(``);
|
||||
}
|
||||
|
||||
// 📜 Últimas Actividades
|
||||
if (recentRuns.length > 0) {
|
||||
lines.push(`📜 **ÚLTIMAS ACTIVIDADES**`);
|
||||
for (const run of recentRuns.slice(0, 3)) {
|
||||
const result = run.result as any;
|
||||
const areaIcon = run.area.type === 'MINE' ? '⛏️' : run.area.type === 'LAGOON' ? '🎣' : run.area.type === 'FIGHT' ? '⚔️' : run.area.type === 'FARM' ? '🌾' : '🗺️';
|
||||
const timestamp = run.startedAt;
|
||||
const relativeTime = getRelativeTime(timestamp);
|
||||
const rewardsCount = result.rewards?.length ?? 0;
|
||||
lines.push(`${areaIcon} ${run.area.name} (Nv.${run.level}) - ${rewardsCount} recompensas - ${relativeTime}`);
|
||||
}
|
||||
lines.push(``);
|
||||
}
|
||||
|
||||
// ⏱️ Cooldowns Activos
|
||||
if (activeCooldowns.length > 0) {
|
||||
lines.push(`⏱️ **COOLDOWNS ACTIVOS**`);
|
||||
for (const cd of activeCooldowns) {
|
||||
const remaining = Math.max(0, Math.ceil((cd.until.getTime() - Date.now()) / 1000));
|
||||
const cdName = formatCooldownKey(cd.key);
|
||||
lines.push(`⏳ ${cdName}: ${formatDuration(remaining)}`);
|
||||
}
|
||||
lines.push(``);
|
||||
}
|
||||
|
||||
// Stats adicionales del PlayerState
|
||||
if (playerState?.stats) {
|
||||
const additionalStats = playerState.stats as any;
|
||||
if (Object.keys(additionalStats).length > 0) {
|
||||
lines.push(`🎯 **STATS ADICIONALES**`);
|
||||
if (additionalStats.attack != null) lines.push(`⚔️ Ataque Base: ${additionalStats.attack}`);
|
||||
if (additionalStats.defense != null) lines.push(`🛡️ Defensa Base: ${additionalStats.defense}`);
|
||||
if (additionalStats.strength != null) lines.push(`💪 Fuerza: ${additionalStats.strength}`);
|
||||
if (additionalStats.luck != null) lines.push(`🍀 Suerte: ${additionalStats.luck}`);
|
||||
lines.push(``);
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
lines.push(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
||||
lines.push(`Usa \`!inventario\` para ver tus items completos`);
|
||||
|
||||
await message.reply(lines.join('\n'));
|
||||
},
|
||||
};
|
||||
|
||||
// Helpers
|
||||
function getRelativeTime(date: Date): string {
|
||||
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||
if (seconds < 60) return 'Hace un momento';
|
||||
if (seconds < 3600) return `Hace ${Math.floor(seconds / 60)}m`;
|
||||
if (seconds < 86400) return `Hace ${Math.floor(seconds / 3600)}h`;
|
||||
if (seconds < 604800) return `Hace ${Math.floor(seconds / 86400)}d`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const mins = Math.floor((seconds % 3600) / 60);
|
||||
return `${hours}h ${mins}m`;
|
||||
}
|
||||
|
||||
function formatCooldownKey(key: string): string {
|
||||
// Convertir keys como "minigame:mine.starter" a "Mina"
|
||||
if (key.startsWith('minigame:')) {
|
||||
const areaKey = key.split(':')[1];
|
||||
if (areaKey?.includes('mine')) return 'Mina';
|
||||
if (areaKey?.includes('lagoon')) return 'Laguna';
|
||||
if (areaKey?.includes('fight')) return 'Pelea';
|
||||
if (areaKey?.includes('farm')) return 'Granja';
|
||||
return areaKey || key;
|
||||
}
|
||||
return key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
||||
}
|
||||
Reference in New Issue
Block a user