import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, Events, ModalBuilder, SlashCommandBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; import { Command } from '../types/index'; import { Items, getItem } from '../lib/rpg/items'; import { formatRecipe } from '../lib/rpg/recipes'; import { craftingStations, getStation } from '../lib/rpg/craftingStations'; import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../lib/db'; export default { data: new SlashCommandBuilder() .setName('recipe') .setDescription('[ADMIN] Manage custom recipes for items') .addSubcommand(sub => sub .setName('create') .setDescription('[ADMIN] Create a custom recipe') ) .setDMPermission(false) .setDefaultMemberPermissions(0), execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ ephemeral: true }); const sub = interaction.options.getSubcommand(true); if (sub === 'create') { const row = new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId(`recipe-create-${interaction.guildId}`).setLabel('I\'ve got my string ready!').setStyle(ButtonStyle.Primary) ); await interaction.followUp({ ephemeral: true, content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/?guild=${interaction.guildId}\nOnce done, click the button below and paste the resulting string in.`, components: [row] }); } }, async onClientReady(bot) { bot.on(Events.InteractionCreate, async (interaction) => { if (!('customId' in interaction)) return; const id = interaction.customId; if (!id.startsWith('recipe-')) return; if (!interaction.member) return; if (id.startsWith('recipe-create-')) { //const guildID = id.split('-')[2]; if (interaction.isMessageComponent()) { const modal = new ModalBuilder() .setCustomId(interaction.customId) .setTitle('Recipe Creator'); const input = new TextInputBuilder() .setCustomId('recipe-create-textbox') .setLabel('Paste in your recipe string here:') .setStyle(TextInputStyle.Paragraph) .setRequired(true); const row = new ActionRowBuilder().addComponents(input); modal.addComponents(row); interaction.showModal(modal); } else if (interaction.isModalSubmit()) { const field = interaction.fields.getField('recipe-create-textbox', ComponentType.TextInput); const recipeString = field.value; await interaction.deferReply({ ephemeral: true }); let parsed; try { parsed = await Promise.all( recipeString .split('|') .map(items => Promise.all( items .split(';') .map(itemStack => itemStack.split(',') ) .map(async ([itemID, quantity]) => ( { item: (await getItem(parseInt(itemID)))!, quantity: parseInt(quantity) } )) ) ) ) as Items[][]; } catch (err) { await interaction.followUp(`This is not a valid string!: \`${(err as Error).message}\``); return; } const inputs = parsed[0] || [], requirements = parsed[1] || [], outputs = parsed[2] || []; const recipe = { inputs, requirements, outputs, station: 'hands', id: 0 }; const components = [ new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .addOptions( ...craftingStations .map(station => ({ label: `${station.emoji} ${station.name}`, value: station.key, description: station.description })) ) .setMinValues(1) .setMaxValues(1) .setCustomId('recipe-select-station') ), new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('recipe-select-done') .setLabel('Done') .setStyle(ButtonStyle.Primary) .setDisabled(true) ) ]; const msg = await interaction.followUp({ content: `${formatRecipe(recipe)}\n_Select a crafting station, and you're good to go!_`, components: components, fetchReply: true }); const selectCollector = msg.createMessageComponentCollector({ componentType: ComponentType.StringSelect, time: 60_000 * 5, }); selectCollector.on('collect', selectInteraction => { const newStation = selectInteraction.values[0]; recipe.station = newStation; components[1].components[0].setDisabled(false); interaction.editReply({ content: `${formatRecipe(recipe)}\n_Select a crafting station, and you're good to go!_`, components: components }); const station = getStation(newStation); selectInteraction.reply({ content: `Set station to ${station?.emoji} **${station?.name}**`, ephemeral: true }); }); selectCollector.on('end', () => { interaction.editReply({ content: msg.content, components: [] }); }); const buttonInteraction = await msg.awaitMessageComponent({ componentType: ComponentType.Button, time: 60_000 * 5 }); selectCollector.stop(); const [customRecipe] = await db('customCraftingRecipes') .insert({ station: recipe.station }) .returning('id'); for (const input of recipe.inputs) { await db('customCraftingRecipeItems') .insert({ id: customRecipe.id, item: input.item.id, quantity: input.quantity, type: 'input' }); } for (const req of recipe.requirements) { await db('customCraftingRecipeItems') .insert({ id: customRecipe.id, item: req.item.id, quantity: req.quantity, type: 'requirement' }); } for (const output of recipe.outputs) { await db('customCraftingRecipeItems') .insert({ id: customRecipe.id, item: output.item.id, quantity: output.quantity, type: 'output' }); } buttonInteraction.reply({ ephemeral: true, content: 'Your recipe has been created 🎉' }); } } }); } } satisfies Command;