Refactor economy service and types for improved readability and functionality
- Reformatted code for consistent styling and indentation in service.ts - Enhanced item reward structure in types.ts to include probability for chest rewards - Added randomization modes for chest rewards: 'all', 'single', and 'roll-each' - Updated functions in service.ts to handle new reward structures and improve error handling - Ensured better organization and clarity in function parameters and return types
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
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';
|
||||
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;
|
||||
@@ -21,32 +31,44 @@ interface ItemEditorState {
|
||||
ingredients: Array<{ itemKey: string; quantity: number }>;
|
||||
productQuantity: number;
|
||||
};
|
||||
// Derivado de props.global (solo owner puede establecerlo)
|
||||
isGlobal?: boolean;
|
||||
}
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'item-crear',
|
||||
type: 'message',
|
||||
aliases: ['crear-item','itemcreate'],
|
||||
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>',
|
||||
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 channel = message.channel as TextBasedChannel & { send: Function };
|
||||
const allowed = await hasManageGuildOrStaff(message.member, message.guild!.id, client.prisma);
|
||||
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: 10,
|
||||
content: '❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.'
|
||||
}]
|
||||
}],
|
||||
reply: { messageReference: message.id }
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0xff0000,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content:
|
||||
"❌ **Error de Permisos**\n└ No tienes permisos de ManageGuild ni rol de staff.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
reply: { messageReference: message.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -56,35 +78,47 @@ export const command: CommandMessage = {
|
||||
await (channel.send as any)({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: [{
|
||||
type: 17,
|
||||
accent_color: 0xFFA500,
|
||||
components: [{
|
||||
type: 10,
|
||||
content: '⚠️ **Uso Incorrecto**\n└ Uso: `!item-crear <key-única>`'
|
||||
}]
|
||||
}],
|
||||
reply: { messageReference: message.id }
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0xffa500,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content:
|
||||
"⚠️ **Uso Incorrecto**\n└ Uso: `!item-crear <key-única>`",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
reply: { messageReference: message.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const guildId = message.guild!.id;
|
||||
|
||||
const exists = await client.prisma.economyItem.findFirst({ where: { key, guildId } });
|
||||
const exists = await client.prisma.economyItem.findFirst({
|
||||
where: { key, guildId },
|
||||
});
|
||||
if (exists) {
|
||||
await (channel.send as any)({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: [{
|
||||
type: 17,
|
||||
accent_color: 0xFF0000,
|
||||
components: [{
|
||||
type: 10,
|
||||
content: '❌ **Item Ya Existe**\n└ Ya existe un item con esa key en este servidor.'
|
||||
}]
|
||||
}],
|
||||
reply: { messageReference: message.id }
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0xff0000,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content:
|
||||
"❌ **Item Ya Existe**\n└ Ya existe un item con esa key en este servidor.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
reply: { messageReference: message.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -98,55 +132,59 @@ export const command: CommandMessage = {
|
||||
recipe: {
|
||||
enabled: false,
|
||||
ingredients: [],
|
||||
productQuantity: 1
|
||||
}
|
||||
productQuantity: 1,
|
||||
},
|
||||
isGlobal: false,
|
||||
};
|
||||
|
||||
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');
|
||||
`**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"}`,
|
||||
`**Global:** ${state.isGlobal ? "Sí" : "No"}`,
|
||||
].join("\n");
|
||||
|
||||
const tagsInfo = `**Tags:** ${state.tags.length > 0 ? state.tags.join(', ') : '*Ninguno*'}`;
|
||||
const tagsInfo = `**Tags:** ${
|
||||
state.tags.length > 0 ? state.tags.join(", ") : "*Ninguno*"
|
||||
}`;
|
||||
const propsJson = JSON.stringify(state.props ?? {}, null, 2);
|
||||
const recipeInfo = state.recipe?.enabled
|
||||
const recipeInfo = state.recipe?.enabled
|
||||
? `**Receta:** Habilitada (${state.recipe.ingredients.length} ingredientes → ${state.recipe.productQuantity} unidades)`
|
||||
: `**Receta:** Deshabilitada`;
|
||||
|
||||
return {
|
||||
type: 17,
|
||||
accent_color: 0x00D9FF,
|
||||
accent_color: 0x00d9ff,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: `# 🛠️ Editor de Item: \`${key}\``
|
||||
content: `# 🛠️ Editor de Item: \`${key}\``,
|
||||
},
|
||||
{ type: 14, divider: true },
|
||||
{
|
||||
type: 10,
|
||||
content: baseInfo
|
||||
content: baseInfo,
|
||||
},
|
||||
{ type: 14, divider: true },
|
||||
{
|
||||
type: 10,
|
||||
content: tagsInfo
|
||||
content: tagsInfo,
|
||||
},
|
||||
{ type: 14, divider: true },
|
||||
{
|
||||
type: 10,
|
||||
content: recipeInfo
|
||||
content: recipeInfo,
|
||||
},
|
||||
{ type: 14, divider: true },
|
||||
{
|
||||
type: 10,
|
||||
content: `**Props (JSON):**\n\`\`\`json\n${propsJson}\n\`\`\``
|
||||
}
|
||||
]
|
||||
content: `**Props (JSON):**\n\`\`\`json\n${propsJson}\n\`\`\``,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -155,77 +193,165 @@ export const command: CommandMessage = {
|
||||
{
|
||||
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: 'Receta', custom_id: 'it_recipe' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Props (JSON)', custom_id: 'it_props' },
|
||||
]
|
||||
{
|
||||
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: "Receta",
|
||||
custom_id: "it_recipe",
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
style: ButtonStyle.Secondary,
|
||||
label: "Props (JSON)",
|
||||
custom_id: "it_props",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 1,
|
||||
components: [
|
||||
{ type: 2, style: ButtonStyle.Success, label: 'Guardar', custom_id: 'it_save' },
|
||||
{ type: 2, style: ButtonStyle.Danger, label: 'Cancelar', custom_id: 'it_cancel' },
|
||||
]
|
||||
}
|
||||
{
|
||||
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 }
|
||||
reply: { messageReference: message.id },
|
||||
});
|
||||
|
||||
const collector = editorMsg.createMessageComponentCollector({ time: 30 * 60_000, filter: (i) => i.user.id === message.author.id });
|
||||
const collector = editorMsg.createMessageComponentCollector({
|
||||
time: 30 * 60_000,
|
||||
filter: (i) => i.user.id === message.author.id,
|
||||
});
|
||||
|
||||
collector.on('collect', async (i: MessageComponentInteraction) => {
|
||||
collector.on("collect", async (i: MessageComponentInteraction) => {
|
||||
try {
|
||||
if (!i.isButton()) return;
|
||||
if (i.customId === 'it_cancel') {
|
||||
if (i.customId === "it_cancel") {
|
||||
await i.deferUpdate();
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: [{
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0xFF0000,
|
||||
components: [{
|
||||
type: 10,
|
||||
content: '**❌ Editor cancelado.**'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
collector.stop('cancel');
|
||||
accent_color: 0xff0000,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "**❌ Editor cancelado.**",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
collector.stop("cancel");
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_base') {
|
||||
await showBaseModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
|
||||
if (i.customId === "it_base") {
|
||||
await showBaseModal(
|
||||
i as ButtonInteraction,
|
||||
state,
|
||||
editorMsg,
|
||||
buildEditorComponents
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_tags') {
|
||||
await showTagsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
|
||||
if (i.customId === "it_tags") {
|
||||
await showTagsModal(
|
||||
i as ButtonInteraction,
|
||||
state,
|
||||
editorMsg,
|
||||
buildEditorComponents
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_recipe') {
|
||||
await showRecipeModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents, client);
|
||||
if (i.customId === "it_recipe") {
|
||||
await showRecipeModal(
|
||||
i as ButtonInteraction,
|
||||
state,
|
||||
editorMsg,
|
||||
buildEditorComponents,
|
||||
client
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_props') {
|
||||
await showPropsModal(i as ButtonInteraction, state, editorMsg, buildEditorComponents);
|
||||
if (i.customId === "it_props") {
|
||||
await showPropsModal(
|
||||
i as ButtonInteraction,
|
||||
state,
|
||||
editorMsg,
|
||||
buildEditorComponents
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (i.customId === 'it_save') {
|
||||
if (i.customId === "it_save") {
|
||||
// Validar
|
||||
if (!state.name) {
|
||||
await i.reply({ content: '❌ Falta el nombre del item (configura en Base).', flags: MessageFlags.Ephemeral });
|
||||
await i.reply({
|
||||
content: "❌ Falta el nombre del item (configura en Base).",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Revisar bandera global en props (puede haberse puesto manualmente en JSON)
|
||||
state.isGlobal = !!state.props?.global;
|
||||
const BOT_OWNER_ID = "327207082203938818";
|
||||
if (state.isGlobal && i.user.id !== BOT_OWNER_ID) {
|
||||
await i.reply({
|
||||
content:
|
||||
"❌ No puedes crear ítems globales. Solo el owner del bot.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Si es global, usar guildId = null y verificar que no exista ya global con esa key
|
||||
let targetGuildId: string | null = message.guild!.id;
|
||||
if (state.isGlobal) {
|
||||
const existsGlobal = await client.prisma.economyItem.findFirst({
|
||||
where: { key: state.key, guildId: null },
|
||||
});
|
||||
if (existsGlobal) {
|
||||
await i.reply({
|
||||
content: "❌ Ya existe un ítem global con esa key.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
return;
|
||||
}
|
||||
targetGuildId = null;
|
||||
}
|
||||
|
||||
// Guardar item
|
||||
const createdItem = await client.prisma.economyItem.create({
|
||||
data: {
|
||||
guildId,
|
||||
guildId: targetGuildId,
|
||||
key: state.key,
|
||||
name: state.name!,
|
||||
description: state.description,
|
||||
@@ -237,106 +363,192 @@ export const command: CommandMessage = {
|
||||
props: state.props ?? {},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// Guardar receta si está habilitada
|
||||
if (state.recipe?.enabled && state.recipe.ingredients.length > 0) {
|
||||
try {
|
||||
// Resolver itemIds de los ingredientes
|
||||
const ingredientsData: Array<{ itemId: string; quantity: number }> = [];
|
||||
const ingredientsData: Array<{
|
||||
itemId: string;
|
||||
quantity: number;
|
||||
}> = [];
|
||||
for (const ing of state.recipe.ingredients) {
|
||||
const item = await client.prisma.economyItem.findFirst({
|
||||
where: {
|
||||
key: ing.itemKey,
|
||||
OR: [{ guildId }, { guildId: null }]
|
||||
OR: [{ guildId }, { guildId: null }],
|
||||
},
|
||||
orderBy: [{ guildId: 'desc' }]
|
||||
orderBy: [{ guildId: "desc" }],
|
||||
});
|
||||
if (!item) {
|
||||
throw new Error(`Ingrediente no encontrado: ${ing.itemKey}`);
|
||||
}
|
||||
ingredientsData.push({
|
||||
itemId: item.id,
|
||||
quantity: ing.quantity
|
||||
quantity: ing.quantity,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Crear la receta
|
||||
await client.prisma.itemRecipe.create({
|
||||
data: {
|
||||
productItemId: createdItem.id,
|
||||
productQuantity: state.recipe.productQuantity,
|
||||
ingredients: {
|
||||
create: ingredientsData
|
||||
}
|
||||
}
|
||||
create: ingredientsData,
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
logger.warn({ err }, 'Error creando receta para item');
|
||||
await i.followUp({ content: `⚠️ Item creado pero falló la receta: ${err.message}`, flags: MessageFlags.Ephemeral });
|
||||
logger.warn({ err }, "Error creando receta para item");
|
||||
await i.followUp({
|
||||
content: `⚠️ Item creado pero falló la receta: ${err.message}`,
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
}
|
||||
await i.reply({ content: '✅ Item guardado!', flags: MessageFlags.Ephemeral });
|
||||
await editorMsg.edit({
|
||||
await i.reply({
|
||||
content: "✅ Item guardado!",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: [{
|
||||
type: 17,
|
||||
accent_color: 0x00FF00,
|
||||
components: [{
|
||||
type: 10,
|
||||
content: `✅ **Item Creado**\n└ Item \`${state.key}\` creado exitosamente.`
|
||||
}]
|
||||
}]
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0x00ff00,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: `✅ **Item Creado**\n└ Item \`${
|
||||
state.key
|
||||
}\` creado exitosamente.${
|
||||
state.isGlobal ? " (Global)" : ""
|
||||
}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
collector.stop('saved');
|
||||
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 });
|
||||
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({
|
||||
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: 10,
|
||||
content: '**⏰ Editor expirado.**'
|
||||
}]
|
||||
}]
|
||||
}); } catch {}
|
||||
components: [
|
||||
{
|
||||
type: 17,
|
||||
accent_color: 0xffa500,
|
||||
components: [
|
||||
{
|
||||
type: 10,
|
||||
content: "**⏰ Editor expirado.**",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) {
|
||||
async function showBaseModal(
|
||||
i: ButtonInteraction,
|
||||
state: ItemEditorState,
|
||||
editorMsg: any,
|
||||
buildComponents: () => any[]
|
||||
) {
|
||||
const modal = {
|
||||
title: 'Configuración base del Item',
|
||||
customId: 'it_base_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 ?? ''}` : '' } },
|
||||
{
|
||||
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();
|
||||
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;
|
||||
@@ -344,8 +556,8 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito
|
||||
state.icon = icon || undefined;
|
||||
|
||||
if (stackMax) {
|
||||
const [s, m] = stackMax.split(',');
|
||||
state.stackable = String(s).toLowerCase() !== 'false';
|
||||
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;
|
||||
}
|
||||
@@ -354,162 +566,226 @@ async function showBaseModal(i: ButtonInteraction, state: ItemEditorState, edito
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: buildComponents()
|
||||
components: buildComponents(),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function showTagsModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[]) {
|
||||
async function showTagsModal(
|
||||
i: ButtonInteraction,
|
||||
state: ItemEditorState,
|
||||
editorMsg: any,
|
||||
buildComponents: () => any[]
|
||||
) {
|
||||
const modal = {
|
||||
title: 'Tags del Item (separados por coma)',
|
||||
customId: 'it_tags_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(', ') } },
|
||||
{
|
||||
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) : [];
|
||||
const tags = sub.components.getTextInputValue("tags");
|
||||
state.tags = tags
|
||||
? tags
|
||||
.split(",")
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
await sub.deferUpdate();
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: buildComponents()
|
||||
components: buildComponents(),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
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,
|
||||
chest: undefined,
|
||||
eventCurrency: undefined,
|
||||
passiveEffects: [],
|
||||
mutationPolicy: undefined,
|
||||
craftingOnly: false,
|
||||
food: undefined,
|
||||
damage: undefined,
|
||||
defense: undefined,
|
||||
maxHpBonus: undefined,
|
||||
});
|
||||
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,
|
||||
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',
|
||||
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) } },
|
||||
{
|
||||
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');
|
||||
const raw = sub.components.getTextInputValue("props");
|
||||
if (raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
state.props = parsed;
|
||||
await sub.deferUpdate();
|
||||
await sub.deferUpdate();
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: buildComponents()
|
||||
components: buildComponents(),
|
||||
});
|
||||
} catch (e) {
|
||||
await sub.reply({ content: '❌ JSON inválido.', flags: MessageFlags.Ephemeral });
|
||||
await sub.reply({
|
||||
content: "❌ JSON inválido.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
state.props = {};
|
||||
await sub.reply({ content: 'ℹ️ Props limpiados.', flags: MessageFlags.Ephemeral });
|
||||
await sub.reply({
|
||||
content: "ℹ️ Props limpiados.",
|
||||
flags: MessageFlags.Ephemeral,
|
||||
});
|
||||
try {
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: buildComponents()
|
||||
components: buildComponents(),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function showRecipeModal(i: ButtonInteraction, state: ItemEditorState, editorMsg: any, buildComponents: () => any[], client: Amayo) {
|
||||
const currentRecipe = state.recipe || { enabled: false, ingredients: [], productQuantity: 1 };
|
||||
const ingredientsStr = currentRecipe.ingredients.map(ing => `${ing.itemKey}:${ing.quantity}`).join(', ');
|
||||
|
||||
async function showRecipeModal(
|
||||
i: ButtonInteraction,
|
||||
state: ItemEditorState,
|
||||
editorMsg: any,
|
||||
buildComponents: () => any[],
|
||||
client: Amayo
|
||||
) {
|
||||
const currentRecipe = state.recipe || {
|
||||
enabled: false,
|
||||
ingredients: [],
|
||||
productQuantity: 1,
|
||||
};
|
||||
const ingredientsStr = currentRecipe.ingredients
|
||||
.map((ing) => `${ing.itemKey}:${ing.quantity}`)
|
||||
.join(", ");
|
||||
|
||||
const modal = {
|
||||
title: 'Receta de Crafteo',
|
||||
customId: 'it_recipe_modal',
|
||||
title: "Receta de Crafteo",
|
||||
customId: "it_recipe_modal",
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: 'Habilitar receta? (true/false)',
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: 'enabled',
|
||||
style: TextInputStyle.Short,
|
||||
required: false,
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: "Habilitar receta? (true/false)",
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: "enabled",
|
||||
style: TextInputStyle.Short,
|
||||
required: false,
|
||||
value: String(currentRecipe.enabled),
|
||||
placeholder: 'true o false'
|
||||
}
|
||||
placeholder: "true o false",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: 'Cantidad que produce',
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: 'quantity',
|
||||
style: TextInputStyle.Short,
|
||||
required: false,
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: "Cantidad que produce",
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: "quantity",
|
||||
style: TextInputStyle.Short,
|
||||
required: false,
|
||||
value: String(currentRecipe.productQuantity),
|
||||
placeholder: '1'
|
||||
}
|
||||
placeholder: "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: 'Ingredientes (itemKey:qty, ...)',
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: 'ingredients',
|
||||
style: TextInputStyle.Paragraph,
|
||||
required: false,
|
||||
{
|
||||
type: ComponentType.Label,
|
||||
label: "Ingredientes (itemKey:qty, ...)",
|
||||
component: {
|
||||
type: ComponentType.TextInput,
|
||||
customId: "ingredients",
|
||||
style: TextInputStyle.Paragraph,
|
||||
required: false,
|
||||
value: ingredientsStr,
|
||||
placeholder: 'iron_ingot:3, wood_plank:1'
|
||||
}
|
||||
placeholder: "iron_ingot:3, wood_plank:1",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
||||
await i.showModal(modal);
|
||||
try {
|
||||
const sub = await i.awaitModalSubmit({ time: 300_000 });
|
||||
const enabledStr = sub.components.getTextInputValue('enabled').trim().toLowerCase();
|
||||
const quantityStr = sub.components.getTextInputValue('quantity').trim();
|
||||
const ingredientsInput = sub.components.getTextInputValue('ingredients').trim();
|
||||
const enabledStr = sub.components
|
||||
.getTextInputValue("enabled")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const quantityStr = sub.components.getTextInputValue("quantity").trim();
|
||||
const ingredientsInput = sub.components
|
||||
.getTextInputValue("ingredients")
|
||||
.trim();
|
||||
|
||||
const enabled = enabledStr === 'true';
|
||||
const enabled = enabledStr === "true";
|
||||
const productQuantity = parseInt(quantityStr, 10) || 1;
|
||||
|
||||
|
||||
// Parsear ingredientes
|
||||
const ingredients: Array<{ itemKey: string; quantity: number }> = [];
|
||||
if (ingredientsInput && enabled) {
|
||||
const parts = ingredientsInput.split(',').map(p => p.trim()).filter(Boolean);
|
||||
const parts = ingredientsInput
|
||||
.split(",")
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
for (const part of parts) {
|
||||
const [itemKey, qtyStr] = part.split(':').map(s => s.trim());
|
||||
const [itemKey, qtyStr] = part.split(":").map((s) => s.trim());
|
||||
const qty = parseInt(qtyStr, 10);
|
||||
if (itemKey && qty > 0) {
|
||||
ingredients.push({ itemKey, quantity: qty });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
state.recipe = { enabled, ingredients, productQuantity };
|
||||
|
||||
await sub.deferUpdate();
|
||||
await editorMsg.edit({
|
||||
content: null,
|
||||
flags: 32768,
|
||||
components: buildComponents()
|
||||
components: buildComponents(),
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user