refactor(componentsV2): migrate all admin and game message commands to new display components API

This commit is contained in:
2025-10-05 18:09:15 -05:00
parent b37261e73b
commit f317b6d3bf
11 changed files with 522 additions and 810 deletions

View File

@@ -3,6 +3,7 @@ import type Amayo from '../../../core/client';
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
import { prisma } from '../../../core/database/prisma';
import type { TextBasedChannel } from 'discord.js';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'area-eliminar',
@@ -12,28 +13,18 @@ export const command: CommandMessage = {
description: 'Eliminar un área del servidor',
usage: 'area-eliminar <key>',
run: async (message, args, client: Amayo) => {
const channel = message.channel as TextBasedChannel & { send: Function };
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma);
if (!allowed) {
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
components: [
buildDisplay(0xFF0000, [
textBlock('❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.')
])
],
reply: { messageReference: message.id }
});
return;
}
@@ -42,26 +33,17 @@ export const command: CommandMessage = {
const key = args[0]?.trim();
if (!key) {
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-eliminar <key>`\n└ Ejemplo: `!area-eliminar mine.cavern`'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
components: [
buildDisplay(0xFFA500, [
textBlock('⚠️ **Uso Incorrecto**'),
dividerBlock(),
textBlock('└ Uso: `!area-eliminar <key>`\n└ Ejemplo: `!area-eliminar mine.cavern`')
])
],
reply: { messageReference: message.id }
});
return;
}
@@ -71,26 +53,17 @@ export const command: CommandMessage = {
});
if (!area) {
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: `❌ **Área No Encontrada**\n└ No se encontró el área local con key \`${key}\` en este servidor.`
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
components: [
buildDisplay(0xFF0000, [
textBlock(`❌ **Área No Encontrada**`),
dividerBlock(),
textBlock(`└ No se encontró el área local con key \`${key}\` en este servidor.`)
])
],
reply: { messageReference: message.id }
});
return;
}
@@ -109,38 +82,23 @@ export const command: CommandMessage = {
where: { id: area.id }
});
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0x00FF00,
components: [
{
type: 9,
components: [{
type: 10,
content: `✅ **Área Eliminada Exitosamente**\n└ Key: \`${key}\``
}]
},
...(levelsCount > 0 ? [{
type: 14,
divider: true
}, {
type: 9,
components: [{
type: 10,
content: `⚠️ Se eliminaron ${levelsCount} nivel(es) asociado(s).`
}]
}] : [])
buildDisplay(0x00FF00, [
textBlock(`✅ **Área Eliminada Exitosamente**`),
dividerBlock(),
textBlock(`└ Key: \`${key}\``),
...(levelsCount > 0
? [
dividerBlock(),
textBlock(`⚠️ 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
}
: []),
])
],
reply: { messageReference: message.id }
});
}
};

View File

@@ -3,6 +3,7 @@ import type Amayo from '../../../core/client';
import { prisma } from '../../../core/database/prisma';
import { ComponentType, ButtonStyle } from 'discord-api-types/v10';
import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'areas-lista',
@@ -34,29 +35,27 @@ export const command: CommandMessage = {
const totalPages = Math.ceil(total / perPage);
const display = {
type: 17,
accent_color: 0x00FF00,
components: [
{
type: 9,
components: [{
type: 10,
content: `**🗺️ Lista de Áreas**\nPágina ${page}/${totalPages} • Total: ${total}`
}]
},
{ type: 14, divider: true },
...areas.map(area => ({
type: 9,
components: [{
type: 10,
content: `**${area.name || area.key}**\n` +
`└ Key: \`${area.key}\`\n` +
`${area.guildId === guildId ? '📍 Local' : '🌐 Global'}`
}]
}))
]
};
const displayBlocks = [
textBlock(`# 🗺️ Lista de Áreas`),
dividerBlock(),
textBlock(`Página ${page}/${totalPages} • Total: ${total}`),
dividerBlock({ divider: false, spacing: 2 }),
...areas.flatMap((area, index) => {
const lines = [
`**${area.name || area.key}**`,
`└ Key: \`${area.key}\``,
`${area.guildId === guildId ? '📍 Local' : '🌐 Global'}`,
].join('\n');
const blocks = [textBlock(lines)];
if (index < areas.length - 1) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
}
return blocks;
})
];
const display = buildDisplay(0x00FF00, displayBlocks);
const buttons: any[] = [];
@@ -80,7 +79,9 @@ export const command: CommandMessage = {
const channel = message.channel as TextBasedChannel & { send: Function };
const msg = await (channel.send as any)({
content: null,
flags: 32768,
reply: { messageReference: message.id },
components: [
display,
...(buttons.length > 0 ? [{

View File

@@ -2,6 +2,7 @@ import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { prisma } from '../../../core/database/prisma';
import type { TextBasedChannel } from 'discord.js';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'item-ver',
@@ -34,70 +35,42 @@ export const command: CommandMessage = {
const props = item.props as any || {};
const tags = item.tags || [];
const display = {
type: 17,
accent_color: 0x00D9FF,
components: [
{
type: 9,
components: [{
type: 10,
content: `**🛠️ ${item.name || item.key}**`
}]
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**Key:** \`${item.key}\`\n` +
`**Nombre:** ${item.name || '*Sin nombre*'}\n` +
`**Descripción:** ${item.description || '*Sin descripción*'}\n` +
`**Categoría:** ${item.category || '*Sin categoría*'}\n` +
`**Stackable:** ${item.stackable ? 'Sí' : 'No'}\n` +
`**Máx. Inventario:** ${item.maxPerInventory || 'Ilimitado'}\n` +
`**Ámbito:** ${item.guildId ? '📍 Local del servidor' : '🌐 Global'}`
}]
}
]
};
const blocks = [
textBlock(`# 🛠️ ${item.name || item.key}`),
dividerBlock(),
textBlock([
`**Key:** \`${item.key}\``,
`**Nombre:** ${item.name || '*Sin nombre*'}`,
`**Descripción:** ${item.description || '*Sin descripción*'}`,
`**Categoría:** ${item.category || '*Sin categoría*'}`,
`**Stackable:** ${item.stackable ? 'Sí' : 'No'}`,
`**Máx. Inventario:** ${item.maxPerInventory ?? 'Ilimitado'}`,
`**Ámbito:** ${item.guildId ? '📍 Local del servidor' : '🌐 Global'}`,
].join('\n')),
];
if (tags.length > 0) {
display.components.push({ type: 14, divider: true });
display.components.push({
type: 9,
components: [{
type: 10,
content: `**Tags:** ${tags.join(', ')}`
}]
});
blocks.push(dividerBlock());
blocks.push(textBlock(`**Tags:** ${tags.join(', ')}`));
}
if (item.icon) {
display.components.push({ type: 14, divider: true });
display.components.push({
type: 9,
components: [{
type: 10,
content: `**Icon URL:** ${item.icon}`
}]
});
blocks.push(dividerBlock());
blocks.push(textBlock(`**Icon URL:** ${item.icon}`));
}
if (Object.keys(props).length > 0) {
display.components.push({ type: 14, divider: true });
display.components.push({
type: 9,
components: [{
type: 10,
content: `**Props (JSON):**\n\`\`\`json\n${JSON.stringify(props, null, 2)}\n\`\`\``
}]
});
blocks.push(dividerBlock());
blocks.push(textBlock(`**Props (JSON):**\n\`\`\`json\n${JSON.stringify(props, null, 2)}\n\`\`\``));
}
const display = buildDisplay(0x00D9FF, blocks);
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
display,
content: null,
flags: 32768,
components: [display],
reply: { messageReference: message.id }
});
}

View File

@@ -3,6 +3,7 @@ import type Amayo from '../../../core/client';
import { prisma } from '../../../core/database/prisma';
import { ComponentType, ButtonStyle } from 'discord-api-types/v10';
import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'items-lista',
@@ -34,32 +35,28 @@ export const command: CommandMessage = {
const totalPages = Math.ceil(total / perPage);
const display = {
type: 17,
accent_color: 0x00D9FF,
components: [
{
type: 9,
components: [{
type: 10,
content: `**🛠️ Lista de Items**\nPágina ${page}/${totalPages} • Total: ${total}`
}]
},
{ type: 14, divider: true },
...items.map(item => ({
type: 9,
components: [{
type: 10,
content: `**${item.name || item.key}**\n` +
`└ Key: \`${item.key}\`\n` +
`└ Categoría: ${item.category || '*Sin categoría*'}\n` +
`${item.stackable ? '📚 Apilable' : '🔒 No apilable'}` +
(item.maxPerInventory ? ` (Máx: ${item.maxPerInventory})` : '') +
(item.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global')
}]
}))
]
};
const displayBlocks = [
textBlock(`# 🛠️ Lista de Items`),
dividerBlock(),
textBlock(`Página ${page}/${totalPages} • Total: ${total}`),
dividerBlock({ divider: false, spacing: 2 }),
...items.flatMap((item, index) => {
const lines = [
`**${item.name || item.key}**`,
`└ Key: \`${item.key}\``,
`└ Categoría: ${item.category || '*Sin categoría*'}`,
`${item.stackable ? '📚 Apilable' : '🔒 No apilable'}${item.maxPerInventory ? ` (Máx: ${item.maxPerInventory})` : ''}${item.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global'}`,
].join('\n');
const blocks = [textBlock(lines)];
if (index < items.length - 1) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
}
return blocks;
})
];
const display = buildDisplay(0x00D9FF, displayBlocks);
const buttons: any[] = [];
@@ -90,7 +87,9 @@ export const command: CommandMessage = {
const channel = message.channel as TextBasedChannel & { send: Function };
const msg = await (channel.send as any)({
content: null,
flags: 32768,
reply: { messageReference: message.id },
components: [
display,
...(buttons.length > 0 ? [{

View File

@@ -2,7 +2,8 @@ import type {CommandMessage} from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import {hasManageGuildOrStaff} from '../../../core/lib/permissions';
import {prisma} from '../../../core/database/prisma';
import {ButtonStyle, ComponentType, MessageFlags, TextInputStyle} from 'discord-api-types/v10';
import {ButtonStyle, ComponentType, TextInputStyle} from 'discord-api-types/v10';
import {buildDisplay, dividerBlock, textBlock} from '../../../core/lib/componentsV2';
import type {ButtonInteraction, MessageComponentInteraction, TextBasedChannel} from 'discord.js';
interface AchievementState {
@@ -53,25 +54,12 @@ export const command: CommandMessage = {
rewards: { coins: 100 }
};
// Crear mensaje con DisplayComponents
const displayMessage = createDisplay(state);
const channel = message.channel as TextBasedChannel & { send: Function };
const editorMsg = await channel.send({
...displayMessage,
flags: 32768, // MessageFlags.IsComponentsV2
components: [
{
type: ComponentType.ActionRow,
components: [
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ach_base' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'ach_req' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'ach_reward' },
{ type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ach_save' },
{ type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ach_cancel' }
]
}
]
const editorMsg = await (channel.send as any)({
content: null,
flags: 32768,
reply: { messageReference: message.id },
components: buildEditorComponents(state)
});
const collector = editorMsg.createMessageComponentCollector({
@@ -87,19 +75,13 @@ export const command: CommandMessage = {
case 'ach_cancel':
await i.deferUpdate();
await editorMsg.edit({
display: {
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '**❌ Creación de logro cancelada.**'
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0xFF0000, [
textBlock('**❌ Creación de logro cancelada.**')
])
]
});
collector.stop('cancel');
return;
@@ -139,19 +121,13 @@ export const command: CommandMessage = {
await i.reply({ content: '✅ Logro creado exitosamente!', flags: 64 });
await editorMsg.edit({
display: {
type: 17,
accent_color: 0x00FF00,
components: [{
type: 9,
components: [{
type: 10,
content: `**✅ Logro \`${state.key}\` creado exitosamente.**`
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0x00FF00, [
textBlock(`**✅ Logro \`${state.key}\` creado exitosamente.**`)
])
]
});
collector.stop('saved');
return;
@@ -168,19 +144,13 @@ export const command: CommandMessage = {
if (r === 'time') {
try {
await editorMsg.edit({
display: {
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '**⏰ Editor expirado.**'
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0xFFA500, [
textBlock('**⏰ Editor expirado.**')
])
]
});
} catch {}
}
@@ -188,54 +158,41 @@ export const command: CommandMessage = {
}
};
function createDisplay(state: AchievementState) {
return {
display: {
type: 17, // Container
accent_color: 0xFFD700,
components: [
{
type: 9, // Section
components: [
{
type: 10, // Text Display
content: `**🏆 Creando Logro: \`${state.key}\`**`
function buildEditorDisplay(state: AchievementState) {
const baseInfo = [
`**Nombre:** ${state.name || '*Sin definir*'}`,
`**Descripción:** ${state.description || '*Sin definir*'}`,
`**Categoría:** ${state.category || 'economy'}`,
`**Icono:** ${state.icon || '🏆'}`,
`**Puntos:** ${state.points ?? 10}`,
`**Oculto:** ${state.hidden ? 'Sí' : 'No'}`,
].join('\n');
return buildDisplay(0xFFD700, [
textBlock(`# 🏆 Creando Logro: \`${state.key}\``),
dividerBlock(),
textBlock(baseInfo),
dividerBlock(),
textBlock(`**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``),
dividerBlock(),
textBlock(`**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``),
]);
}
]
},
{ type: 14, divider: true }, // Separator
function buildEditorComponents(state: AchievementState) {
return [
buildEditorDisplay(state),
{
type: 9,
type: ComponentType.ActionRow,
components: [
{
type: 10,
content: `**Nombre:** ${state.name || '*Sin definir*'}\n**Descripción:** ${state.description || '*Sin definir*'}\n**Categoría:** ${state.category || 'economy'}\n**Icono:** ${state.icon || '🏆'}\n**Puntos:** ${state.points || 10}\n**Oculto:** ${state.hidden ? 'Sí' : 'No'}`
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``
}
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ach_base' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'ach_req' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'ach_reward' },
{ type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ach_save' },
{ type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ach_cancel' }
]
}
]
}
};
];
}
async function showBaseModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) {
@@ -316,7 +273,11 @@ async function showBaseModal(i: ButtonInteraction, state: AchievementState, edit
state.points = parseInt(submit.components.getTextInputValue('points')) || 10;
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
}
async function showRequirementsModal(i: ButtonInteraction, state: AchievementState, editorMsg: any) {
@@ -351,7 +312,11 @@ async function showRequirementsModal(i: ButtonInteraction, state: AchievementSta
try {
state.requirements = JSON.parse(submit.components.getTextInputValue('requirements'));
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
} catch (e) {
await submit.reply({ content: '❌ JSON inválido en requisitos.', flags: 64 });
}
@@ -389,7 +354,11 @@ async function showRewardsModal(i: ButtonInteraction, state: AchievementState, e
try {
state.rewards = JSON.parse(submit.components.getTextInputValue('rewards'));
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
} catch (e) {
await submit.reply({ content: '❌ JSON inválido en recompensas.', flags: 64 });
}

View File

@@ -2,7 +2,7 @@ import type { CommandMessage } from '../../../core/types/commands';
import type Amayo from '../../../core/client';
import { prisma } from '../../../core/database/prisma';
import type { TextBasedChannel } from 'discord.js';
import { ComponentType, ButtonStyle } from 'discord-api-types/v10';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'logro-ver',
@@ -50,78 +50,40 @@ export const command: CommandMessage = {
const req = achievement.requirements as any;
const rew = achievement.rewards as any;
const display = {
type: 17,
accent_color: 0xFFD700,
components: [
{
type: 9,
components: [
{
type: 10,
content: `${achievement.icon || '🏆'} **${achievement.name}**`
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**Descripción:** ${achievement.description}\n` +
`**Key:** \`${achievement.key}\`\n` +
`**Categoría:** ${achievement.category}\n` +
`**Puntos:** ${achievement.points} pts\n` +
`**Visibilidad:** ${achievement.hidden ? '🔒 Oculto' : '👁️ Visible'}\n` +
`**Ámbito:** ${achievement.guildId ? '📍 Local del servidor' : '🌐 Global'}\n` +
`**Desbloqueados:** ${unlockedCount} jugadores`
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**📋 Requisitos:**\n\`\`\`json\n${JSON.stringify(req, null, 2)}\n\`\`\``
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**🎁 Recompensas:**\n\`\`\`json\n${JSON.stringify(rew, null, 2)}\n\`\`\``
}
]
}
]
};
const blocks = [
textBlock(`${achievement.icon || '🏆'} **${achievement.name}**`),
dividerBlock(),
textBlock([
`**Descripción:** ${achievement.description}`,
`**Key:** \`${achievement.key}\``,
`**Categoría:** ${achievement.category}`,
`**Puntos:** ${achievement.points} pts`,
`**Visibilidad:** ${achievement.hidden ? '🔒 Oculto' : '👁️ Visible'}`,
`**Ámbito:** ${achievement.guildId ? '📍 Local del servidor' : '🌐 Global'}`,
`**Desbloqueados:** ${unlockedCount} jugadores`,
].join('\n')),
dividerBlock(),
textBlock(`**📋 Requisitos:**\n\`\`\`json\n${JSON.stringify(req, null, 2)}\n\`\`\``),
dividerBlock(),
textBlock(`**🎁 Recompensas:**\n\`\`\`json\n${JSON.stringify(rew, null, 2)}\n\`\`\``),
];
if (achievement.unlocked.length > 0) {
display.components.push({ type: 14, divider: true });
display.components.push({
type: 9,
components: [
{
type: 10,
content: `**🏆 Últimos Desbloqueados:**\n` +
achievement.unlocked.slice(0, 5).map(pa =>
`• <@${pa.userId}> - ${pa.unlockedAt ? new Date(pa.unlockedAt).toLocaleDateString() : 'N/A'}`
).join('\n')
}
]
});
const unlockedLines = achievement.unlocked.slice(0, 5)
.map(pa => `• <@${pa.userId}> - ${pa.unlockedAt ? new Date(pa.unlockedAt).toLocaleDateString() : 'N/A'}`)
.join('\n');
blocks.push(dividerBlock());
blocks.push(textBlock(`**🏆 Últimos Desbloqueados:**\n${unlockedLines}`));
}
const display = buildDisplay(0xFFD700, blocks);
const channel = message.channel as TextBasedChannel & { send: Function };
await (channel.send as any)({
content: null,
flags: 32768,
components: [display]
components: [display],
reply: { messageReference: message.id }
});
}
};

View File

@@ -4,6 +4,7 @@ import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
import { prisma } from '../../../core/database/prisma';
import { ComponentType, ButtonStyle } from 'discord-api-types/v10';
import type { MessageComponentInteraction, TextBasedChannel } from 'discord.js';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
export const command: CommandMessage = {
name: 'logros-lista',
@@ -35,36 +36,29 @@ export const command: CommandMessage = {
const totalPages = Math.ceil(total / perPage);
const display = {
type: 17,
accent_color: 0xFFD700,
components: [
{
type: 9,
components: [
{
type: 10,
content: `**🏆 Lista de Logros**\nPágina ${page}/${totalPages} • Total: ${total}`
const displayBlocks = [
textBlock(`# 🏆 Lista de Logros`),
dividerBlock(),
textBlock(`Página ${page}/${totalPages} • Total: ${total}`),
dividerBlock({ divider: false, spacing: 2 }),
...achievements.flatMap((ach, index) => {
const lines = [
`${ach.icon || '🏆'} **${ach.name}** (${ach.points} pts)`,
`└ Key: \`${ach.key}\``,
`└ Categoría: ${ach.category}`,
`${ach.description}`,
`${ach.hidden ? '🔒 Oculto' : '👁️ Visible'}${ach.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global'}`,
].join('\n');
const blocks = [textBlock(lines)];
if (index < achievements.length - 1) {
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
}
]
},
{ type: 14, divider: true },
...achievements.map(ach => ({
type: 9,
components: [
{
type: 10,
content: `${ach.icon || '🏆'} **${ach.name}** (${ach.points} pts)\n` +
`└ Key: \`${ach.key}\`\n` +
`└ Categoría: ${ach.category}\n` +
`${ach.description}\n` +
`${ach.hidden ? '🔒 Oculto' : '👁️ Visible'}` +
(ach.guildId === guildId ? ' • 📍 Local' : ' • 🌐 Global')
}
]
}))
]
};
return blocks;
})
];
const display = buildDisplay(0xFFD700, displayBlocks);
const buttons: any[] = [];
@@ -95,7 +89,9 @@ export const command: CommandMessage = {
const channel = message.channel as TextBasedChannel & { send: Function };
const msg = await (channel.send as any)({
content: null,
flags: 32768,
reply: { messageReference: message.id },
components: [
display,
...(buttons.length > 0 ? [{

View File

@@ -3,6 +3,7 @@ import type Amayo from '../../../core/client';
import { hasManageGuildOrStaff } from '../../../core/lib/permissions';
import { prisma } from '../../../core/database/prisma';
import { ComponentType, TextInputStyle, ButtonStyle } from 'discord-api-types/v10';
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
import type { ButtonInteraction, MessageComponentInteraction, TextBasedChannel } from 'discord.js';
interface QuestState {
@@ -53,24 +54,12 @@ export const command: CommandMessage = {
rewards: { coins: 500 }
};
const displayMessage = createDisplay(state);
const channel = message.channel as TextBasedChannel & { send: Function };
const editorMsg = await channel.send({
...displayMessage,
const editorMsg = await (channel.send as any)({
content: null,
flags: 32768,
components: [
{
type: ComponentType.ActionRow,
components: [
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'quest_base' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'quest_req' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'quest_reward' },
{ type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'quest_save' },
{ type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'quest_cancel' }
]
}
]
reply: { messageReference: message.id },
components: buildEditorComponents(state)
});
const collector = editorMsg.createMessageComponentCollector({
@@ -86,19 +75,13 @@ export const command: CommandMessage = {
case 'quest_cancel':
await i.deferUpdate();
await editorMsg.edit({
display: {
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '**❌ Creación de misión cancelada.**'
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0xFF0000, [
textBlock('**❌ Creación de misión cancelada.**')
])
]
});
collector.stop('cancel');
return;
@@ -139,19 +122,13 @@ export const command: CommandMessage = {
await i.reply({ content: '✅ Misión creada exitosamente.', flags: 64 });
await editorMsg.edit({
display: {
type: 17,
accent_color: 0x00FF00,
components: [{
type: 9,
components: [{
type: 10,
content: `**✅ Misión \`${state.key}\` creada exitosamente.**`
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0x00FF00, [
textBlock(`**✅ Misión \`${state.key}\` creada exitosamente.**`)
])
]
});
collector.stop('saved');
return;
@@ -168,19 +145,13 @@ export const command: CommandMessage = {
if (r === 'time') {
try {
await editorMsg.edit({
display: {
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '**⏰ Editor expirado.**'
}]
}]
},
content: null,
flags: 32768,
components: []
components: [
buildDisplay(0xFFA500, [
textBlock('**⏰ Editor expirado.**')
])
]
});
} catch {}
}
@@ -188,7 +159,7 @@ export const command: CommandMessage = {
}
};
function createDisplay(state: QuestState) {
function buildEditorDisplay(state: QuestState) {
const typeEmojis: Record<string, string> = {
daily: '📅',
weekly: '📆',
@@ -196,53 +167,40 @@ function createDisplay(state: QuestState) {
event: '🎉'
};
return {
display: {
type: 17, // Container
accent_color: 0x5865F2,
components: [
{
type: 9, // Section
components: [
{
type: 10, // Text Display
content: `**📜 Creando Misión: \`${state.key}\`**`
const baseInfo = [
`**Nombre:** ${state.name || '*Sin definir*'}`,
`**Descripción:** ${state.description || '*Sin definir*'}`,
`**Categoría:** ${state.category || 'mining'}`,
`**Tipo:** ${typeEmojis[state.type || 'daily']} ${state.type || 'daily'}`,
`**Icono:** ${state.icon || '📋'}`,
`**Repetible:** ${state.repeatable ? 'Sí' : 'No'}`
].join('\n');
return buildDisplay(0x5865F2, [
textBlock(`# 📜 Creando Misión: \`${state.key}\``),
dividerBlock(),
textBlock(baseInfo),
dividerBlock(),
textBlock(`**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``),
dividerBlock(),
textBlock(`**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``)
]);
}
]
},
{ type: 14, divider: true }, // Separator
function buildEditorComponents(state: QuestState) {
return [
buildEditorDisplay(state),
{
type: 9,
type: ComponentType.ActionRow,
components: [
{
type: 10,
content: `**Nombre:** ${state.name || '*Sin definir*'}\n**Descripción:** ${state.description || '*Sin definir*'}\n**Categoría:** ${state.category || 'mining'}\n**Tipo:** ${typeEmojis[state.type || 'daily']} ${state.type || 'daily'}\n**Icono:** ${state.icon || '📋'}\n**Repetible:** ${state.repeatable ? 'Sí' : 'No'}`
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**Requisitos:**\n\`\`\`json\n${JSON.stringify(state.requirements, null, 2)}\n\`\`\``
}
]
},
{ type: 14, divider: true },
{
type: 9,
components: [
{
type: 10,
content: `**Recompensas:**\n\`\`\`json\n${JSON.stringify(state.rewards, null, 2)}\n\`\`\``
}
{ type: ComponentType.Button, style: ButtonStyle.Primary, label: 'Base', custom_id: 'quest_base' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Requisitos', custom_id: 'quest_req' },
{ type: ComponentType.Button, style: ButtonStyle.Secondary, label: 'Recompensas', custom_id: 'quest_reward' },
{ type: ComponentType.Button, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'quest_save' },
{ type: ComponentType.Button, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'quest_cancel' }
]
}
]
}
};
];
}
async function showBaseModal(i: ButtonInteraction, state: QuestState, editorMsg: any) {
@@ -323,7 +281,11 @@ async function showBaseModal(i: ButtonInteraction, state: QuestState, editorMsg:
state.icon = submit.components.getTextInputValue('icon') || '📋';
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
}
async function showRequirementsModal(i: ButtonInteraction, state: QuestState, editorMsg: any) {
@@ -358,7 +320,11 @@ async function showRequirementsModal(i: ButtonInteraction, state: QuestState, ed
try {
state.requirements = JSON.parse(submit.components.getTextInputValue('requirements'));
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
} catch (e) {
await submit.reply({ content: '❌ JSON inválido en requisitos.', flags: 64 });
}
@@ -396,7 +362,11 @@ async function showRewardsModal(i: ButtonInteraction, state: QuestState, editorM
try {
state.rewards = JSON.parse(submit.components.getTextInputValue('rewards'));
await submit.deferUpdate();
await editorMsg.edit(createDisplay(state));
await editorMsg.edit({
content: null,
flags: 32768,
components: buildEditorComponents(state)
});
} catch (e) {
await submit.reply({ content: '❌ JSON inválido en recompensas.', flags: 64 });
}

View File

@@ -13,48 +13,61 @@ interface AreaState {
metadata?: any;
}
function createAreaDisplay(state: AreaState, editing: boolean = false) {
function buildAreaDisplay(state: AreaState, editing: boolean = false) {
const title = editing ? 'Editando Área' : 'Creando Área';
const statusText = [
'**📋 Estado Actual:**',
`**Nombre:** ${state.name || '❌ No configurado'}`,
`**Tipo:** ${state.type || '❌ No configurado'}`,
`**Config:** ${Object.keys(state.config || {}).length} campos`,
`**Metadata:** ${Object.keys(state.metadata || {}).length} campos`
].join('\n');
const instructionsText = [
'**🎮 Instrucciones:**',
'• **Base**: Configura nombre y tipo',
'• **Config (JSON)**: Configuración técnica',
'• **Meta (JSON)**: Metadatos adicionales',
'• **Guardar**: Confirma los cambios',
'• **Cancelar**: Descarta los cambios'
].join('\n');
return {
type: 17,
accent_color: 0x00FF00,
components: [
{
type: 9,
components: [{
type: 10,
content: `🗺️ **${title}: \`${state.key}\`**`
}]
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`
}]
content: statusText
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**🎮 Instrucciones:**\n` +
`• **Base**: Configura nombre y tipo\n` +
`• **Config (JSON)**: Configuración técnica\n` +
`• **Meta (JSON)**: Metadatos adicionales\n` +
`• **Guardar**: Confirma los cambios\n` +
`• **Cancelar**: Descarta los cambios`
}]
content: instructionsText
}
]
};
}
const buildEditorComponents = (state: AreaState, editing: boolean = false) => [
buildAreaDisplay(state, editing),
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
];
export const command: CommandMessage = {
name: 'area-crear',
type: 'message',
@@ -67,24 +80,17 @@ export const command: CommandMessage = {
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma);
if (!allowed) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -92,24 +98,17 @@ export const command: CommandMessage = {
const key = args[0]?.trim();
if (!key) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-crear <key-única>`'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -118,47 +117,28 @@ export const command: CommandMessage = {
const exists = await prisma.gameArea.findFirst({ where: { key, guildId } });
if (exists) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Área Ya Existe**\n└ Ya existe un área con esa key en este servidor.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
const state: AreaState = { key, config: {}, metadata: {} };
const display = createAreaDisplay(state, false);
const editorMsg = await (channel.send as any)({
content: null,
flags: 32768,
components: [
display,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, false),
reply: { messageReference: message.id }
});
const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id });
@@ -169,18 +149,16 @@ export const command: CommandMessage = {
case 'ga_cancel':
await i.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '**❌ Editor de Área cancelado.**'
}]
}]
}]
});
collector.stop('cancel');
return;
@@ -198,18 +176,16 @@ export const command: CommandMessage = {
await prisma.gameArea.create({ data: { guildId, key: state.key, name: state.name!, type: state.type!, config: state.config ?? {}, metadata: state.metadata ?? {} } });
await i.reply({ content: '✅ Área guardada.', flags: MessageFlags.Ephemeral });
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0x00FF00,
components: [{
type: 9,
components: [{
type: 10,
content: `**✅ Área \`${state.key}\` creada exitosamente.**`
}]
}]
}]
});
collector.stop('saved');
return;
@@ -223,18 +199,16 @@ export const command: CommandMessage = {
if (r==='time') {
try {
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '**⏰ Editor expirado.**'
}]
}]
}]
});
} catch {}
}
@@ -255,22 +229,10 @@ async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg:
await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral });
// Actualizar display
const newDisplay = createAreaDisplay(state, editing);
await editorMsg.edit({
content: null,
flags: 32768,
components: [
newDisplay,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, editing)
});
} catch {}
}
@@ -298,22 +260,10 @@ async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'con
}
// Actualizar display
const newDisplay = createAreaDisplay(state, editing);
await editorMsg.edit({
content: null,
flags: 32768,
components: [
newDisplay,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, editing)
});
} catch {}
}

View File

@@ -13,48 +13,61 @@ interface AreaState {
metadata?: any;
}
function createAreaDisplay(state: AreaState, editing: boolean = false) {
function buildAreaDisplay(state: AreaState, editing: boolean = false) {
const title = editing ? 'Editando Área' : 'Creando Área';
const statusText = [
'**📋 Estado Actual:**',
`**Nombre:** ${state.name || '❌ No configurado'}`,
`**Tipo:** ${state.type || '❌ No configurado'}`,
`**Config:** ${Object.keys(state.config || {}).length} campos`,
`**Metadata:** ${Object.keys(state.metadata || {}).length} campos`
].join('\n');
const instructionsText = [
'**🎮 Instrucciones:**',
'• **Base**: Configura nombre y tipo',
'• **Config (JSON)**: Configuración técnica',
'• **Meta (JSON)**: Metadatos adicionales',
'• **Guardar**: Confirma los cambios',
'• **Cancelar**: Descarta los cambios'
].join('\n');
return {
type: 17,
accent_color: 0x00FF00,
components: [
{
type: 9,
components: [{
type: 10,
content: `🗺️ **${title}: \`${state.key}\`**`
}]
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`
}]
content: statusText
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**🎮 Instrucciones:**\n` +
`• **Base**: Configura nombre y tipo\n` +
`• **Config (JSON)**: Configuración técnica\n` +
`• **Meta (JSON)**: Metadatos adicionales\n` +
`• **Guardar**: Confirma los cambios\n` +
`• **Cancelar**: Descarta los cambios`
}]
content: instructionsText
}
]
};
}
const buildEditorComponents = (state: AreaState, editing: boolean = false) => [
buildAreaDisplay(state, editing),
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
];
export const command: CommandMessage = {
name: 'area-editar',
type: 'message',
@@ -67,24 +80,17 @@ export const command: CommandMessage = {
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, prisma);
if (!allowed) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -92,24 +98,17 @@ export const command: CommandMessage = {
const key = args[0]?.trim();
if (!key) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '⚠️ **Uso Incorrecto**\n└ Uso: `!area-editar <key-única>`'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -118,47 +117,28 @@ export const command: CommandMessage = {
const area = await prisma.gameArea.findFirst({ where: { key, guildId } });
if (!area) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Área No Encontrada**\n└ No existe un área con esa key en este servidor.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
const state: AreaState = { key, name: area.name, type: area.type, config: area.config ?? {}, metadata: area.metadata ?? {} };
const display = createAreaDisplay(state, true);
const editorMsg = await (channel.send as any)({
content: null,
flags: 32768,
components: [
display,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, true),
reply: { messageReference: message.id }
});
const collector = editorMsg.createMessageComponentCollector({ time: 30*60_000, filter: (i)=> i.user.id === message.author.id });
@@ -169,18 +149,16 @@ export const command: CommandMessage = {
case 'ga_cancel':
await i.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '**❌ Editor de Área cancelado.**'
}]
}]
}]
});
collector.stop('cancel');
return;
@@ -198,18 +176,16 @@ export const command: CommandMessage = {
await prisma.gameArea.update({ where: { id: area.id }, data: { name: state.name!, type: state.type!, config: state.config ?? {}, metadata: state.metadata ?? {} } });
await i.reply({ content: '✅ Área actualizada.', flags: MessageFlags.Ephemeral });
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0x00FF00,
components: [{
type: 9,
components: [{
type: 10,
content: `**✅ Área \`${state.key}\` actualizada exitosamente.**`
}]
}]
}]
});
collector.stop('saved');
return;
@@ -223,18 +199,16 @@ export const command: CommandMessage = {
if (r==='time') {
try {
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '**⏰ Editor expirado.**'
}]
}]
}]
});
} catch {}
}
@@ -255,22 +229,10 @@ async function showBaseModal(i: ButtonInteraction, state: AreaState, editorMsg:
await sub.reply({ content: '✅ Base actualizada.', flags: MessageFlags.Ephemeral });
// Actualizar display
const newDisplay = createAreaDisplay(state, editing);
await editorMsg.edit({
content: null,
flags: 32768,
components: [
newDisplay,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, editing)
});
} catch {}
}
@@ -298,22 +260,10 @@ async function showJsonModal(i: ButtonInteraction, state: AreaState, field: 'con
}
// Actualizar display
const newDisplay = createAreaDisplay(state, editing);
await editorMsg.edit({
content: null,
flags: 32768,
components: [
newDisplay,
{
type: 1,
components: [
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'ga_base' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Config (JSON)', custom_id: 'ga_config' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Meta (JSON)', custom_id: 'ga_meta' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'ga_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'ga_cancel' },
]
}
]
components: buildEditorComponents(state, editing)
});
} catch {}
}

View File

@@ -30,24 +30,17 @@ export const command: CommandMessage = {
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
if (!allowed) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -55,24 +48,17 @@ export const command: CommandMessage = {
const key = args[0]?.trim();
if (!key) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '⚠️ **Uso Incorrecto**\n└ Uso: `!item-editar <key-única>`'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -82,24 +68,17 @@ export const command: CommandMessage = {
const existing = await client.prisma.economyItem.findFirst({ where: { key, guildId } });
if (!existing) {
await (channel.send as any)({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '❌ **Item No Encontrado**\n└ No existe un item con esa key en este servidor.'
}]
}]
}],
message_reference: {
message_id: message.id,
channel_id: message.channel.id,
guild_id: message.guild!.id,
fail_if_not_exists: false
}
reply: { messageReference: message.id }
});
return;
}
@@ -116,64 +95,65 @@ export const command: CommandMessage = {
props: existing.props || {},
};
// Función para crear display
const createDisplay = () => ({
display: {
const buildEditorDisplay = () => {
const baseInfo = [
`**Nombre:** ${state.name || '*Sin definir*'}`,
`**Descripción:** ${state.description || '*Sin definir*'}`,
`**Categoría:** ${state.category || '*Sin definir*'}`,
`**Icon URL:** ${state.icon || '*Sin definir*'}`,
`**Stackable:** ${state.stackable ? 'Sí' : 'No'}`,
`**Máx. Inventario:** ${state.maxPerInventory ?? 'Ilimitado'}`,
].join('\n');
const tagsInfo = `**Tags:** ${state.tags.length > 0 ? state.tags.join(', ') : '*Ninguno*'}`;
const propsJson = JSON.stringify(state.props ?? {}, null, 2);
return {
type: 17,
accent_color: 0x00D9FF,
components: [
{
type: 9,
components: [{
type: 10,
content: `**🛠️ Editando Item: \`${key}\`**`
}]
content: `# 🛠️ Editando Item: \`${key}\``
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**Nombre:** ${state.name || '*Sin definir*'}\n` +
`**Descripción:** ${state.description || '*Sin definir*'}\n` +
`**Categoría:** ${state.category || '*Sin definir*'}\n` +
`**Icon URL:** ${state.icon || '*Sin definir*'}\n` +
`**Stackable:** ${state.stackable ? 'Sí' : 'No'}\n` +
`**Máx. Inventario:** ${state.maxPerInventory || 'Ilimitado'}`
}]
content: baseInfo
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**Tags:** ${state.tags.length > 0 ? state.tags.join(', ') : '*Ninguno*'}`
}]
content: tagsInfo
},
{ type: 14, divider: true },
{
type: 9,
components: [{
type: 10,
content: `**Props (JSON):**\n\`\`\`json\n${JSON.stringify(state.props, null, 2)}\n\`\`\``
}]
content: `**Props (JSON):**\n\`\`\`json\n${propsJson}\n\`\`\``
}
]
}
});
};
};
const editorMsg = await (channel.send as any)({
flags: 32768,
const buildEditorComponents = () => [
buildEditorDisplay(),
{
type: 1,
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' },
{ type: 2, style: ButtonStyle.Secondary, label: 'Props (JSON)', custom_id: 'it_props' },
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'it_save' },
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'it_cancel' },
]},
],
]
}
];
const editorMsg = await (channel.send as any)({
content: null,
flags: 32768,
components: buildEditorComponents(),
reply: { messageReference: message.id }
});
const collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id });
@@ -184,32 +164,30 @@ export const command: CommandMessage = {
if (i.customId === 'it_cancel') {
await i.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFF0000,
components: [{
type: 9,
components: [{
type: 10,
content: '**❌ Editor cancelado.**'
}]
}]
}]
});
collector.stop('cancel');
return;
}
if (i.customId === 'it_base') {
await showBaseModal(i as ButtonInteraction, state, editorMsg, createDisplay);
await showBaseModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
return;
}
if (i.customId === 'it_tags') {
await showTagsModal(i as ButtonInteraction, state, editorMsg, createDisplay);
await showTagsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
return;
}
if (i.customId === 'it_props') {
await showPropsModal(i as ButtonInteraction, state, editorMsg, createDisplay);
await showPropsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
return;
}
if (i.customId === 'it_save') {
@@ -234,18 +212,16 @@ export const command: CommandMessage = {
});
await i.reply({ content: '✅ Item actualizado!', flags: MessageFlags.Ephemeral });
await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0x00FF00,
components: [{
type: 9,
components: [{
type: 10,
content: `✅ **Item Actualizado**\n└ Item \`${state.key}\` actualizado exitosamente.`
}]
}]
}]
});
collector.stop('saved');
return;
@@ -259,25 +235,23 @@ export const command: CommandMessage = {
collector.on('end', async (_c, r) => {
if (r === 'time') {
try { await editorMsg.edit({
content: null,
flags: 32768,
components: [{
type: 17,
accent_color: 0xFFA500,
components: [{
type: 9,
components: [{
type: 10,
content: '**⏰ Editor expirado.**'
}]
}]
}]
}); } catch {}
}
});
},
};
async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) {
async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) {
const modal = {
title: 'Configuración base del Item',
customId: 'it_base_modal',
@@ -313,13 +287,14 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito
await sub.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [createDisplay().display]
components: buildComponents()
});
} catch {}
}
async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) {
async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) {
const modal = {
title: 'Tags del Item (separados por coma)',
customId: 'it_tags_modal',
@@ -334,13 +309,14 @@ async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, edito
state.tags = tags ? tags.split(',').map((t) => t.trim()).filter(Boolean) : [];
await sub.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [createDisplay().display]
components: buildComponents()
});
} catch {}
}
async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, createDisplay: Function) {
async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) {
const template = state.props && Object.keys(state.props).length ? JSON.stringify(state.props) : JSON.stringify({
tool: undefined,
breakable: undefined,
@@ -371,8 +347,9 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit
state.props = parsed;
await sub.deferUpdate();
await editorMsg.edit({
content: null,
flags: 32768,
components: [createDisplay().display]
components: buildComponents()
});
} catch (e) {
await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral });
@@ -380,6 +357,13 @@ async function showPropsModal(i: ButtonInteraction, state: ItemEditorState, edit
} else {
state.props = {};
await sub.reply({ content: ' Props limpiados.', flags: MessageFlags.Ephemeral });
try {
await editorMsg.edit({
content: null,
flags: 32768,
components: buildComponents()
});
} catch {}
}
} catch {}
}