Files
amayo/src/commands/messages/game/offerCreate.ts

130 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.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 {}
}