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:
2025-10-08 23:34:55 -05:00
parent 03f66f2f82
commit 44aad43df6
4 changed files with 1246 additions and 531 deletions

View File

@@ -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 ? '' : '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 ? "" : "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 {}
}