From 3b9c66ebfd0cb75b333c9934cf3e0e6d9aee9c6d Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 14:59:29 +0300 Subject: [PATCH] recipe creator FINISH --- customCraftingRecipes | 0 deploy-commands.cjs | 2 +- migrations/20231117173052_craftingRecipes.js | 12 +- src/commands/recipe.ts | 185 ++++++++++++++++++- src/commands/survey.ts | 1 + src/lib/db.ts | 10 + 6 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 customCraftingRecipes diff --git a/customCraftingRecipes b/customCraftingRecipes new file mode 100644 index 0000000..e69de29 diff --git a/deploy-commands.cjs b/deploy-commands.cjs index f7e04a0..7053d6d 100644 --- a/deploy-commands.cjs +++ b/deploy-commands.cjs @@ -17,7 +17,7 @@ rest const commandFiles = fs.readdirSync("./dist/commands").filter((file) => file.endsWith(".js") && !file.startsWith('.')); for (const file of commandFiles) { - const command = require(`./dist/commands/${file}`); + const command = require(`./dist/commands/${file}`).default; commands.push(command); } diff --git a/migrations/20231117173052_craftingRecipes.js b/migrations/20231117173052_craftingRecipes.js index 1bb6ae4..3c9bd11 100644 --- a/migrations/20231117173052_craftingRecipes.js +++ b/migrations/20231117173052_craftingRecipes.js @@ -4,12 +4,12 @@ */ exports.up = function(knex) { return knex.schema - .createTable('craftingRecipes', (table) => { + .createTable('customCraftingRecipes', (table) => { table.increments('id'); - table.string('table'); + table.string('station'); }) - .createTable('craftingRecipeItems', (table) => { - table.integer('id').references('id').inTable('craftingRecipes').notNullable(); + .createTable('customCraftingRecipeItems', (table) => { + table.integer('id').references('id').inTable('customCraftingRecipes').notNullable(); table.integer('item').notNullable(); table.integer('quantity').defaultTo(1); table.enum('type', ['input', 'output', 'requirement']); @@ -22,6 +22,6 @@ exports.up = function(knex) { */ exports.down = function(knex) { return knex.schema - .dropTable('craftingRecipes') - .dropTable('craftingRecipeItems'); + .dropTable('customCraftingRecipes') + .dropTable('customCraftingRecipes'); }; diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 1527fc1..ef196e3 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -1,5 +1,9 @@ -import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; +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() @@ -16,15 +20,188 @@ export default { execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; - interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); const sub = interaction.options.getSubcommand(true); if (sub === 'create') { - interaction.reply({ + 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.` + 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; \ No newline at end of file diff --git a/src/commands/survey.ts b/src/commands/survey.ts index fae14c0..47912e8 100644 --- a/src/commands/survey.ts +++ b/src/commands/survey.ts @@ -733,6 +733,7 @@ export default { }); bot.on(Events.InteractionCreate, interaction => { if (!interaction.isModalSubmit()) return; + if (!interaction.customId.startsWith('survey-')) return; if (!interaction.member) return; const member = interaction.member as GuildMember; diff --git a/src/lib/db.ts b/src/lib/db.ts index 110a2a1..d9bbca1 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -68,4 +68,14 @@ export interface CraftingStationCooldown { station: string, user: string, usedAt: number +} +export interface CustomCraftingRecipe { + id: number, + station: string +} +export interface CustomCraftingRecipeItem { + id: number, + item: number, + quantity: number, + type: 'input' | 'output' | 'requirement' } \ No newline at end of file