jillo-bot/src/commands/recipe.ts

207 lines
7.4 KiB
TypeScript

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<ButtonBuilder>().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<TextInputBuilder>().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<StringSelectMenuBuilder>().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<ButtonBuilder>().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<CustomCraftingRecipe>('customCraftingRecipes')
.insert({
station: recipe.station
})
.returning('id');
for (const input of recipe.inputs) {
await db<CustomCraftingRecipeItem>('customCraftingRecipeItems')
.insert({
id: customRecipe.id,
item: input.item.id,
quantity: input.quantity,
type: 'input'
});
}
for (const req of recipe.requirements) {
await db<CustomCraftingRecipeItem>('customCraftingRecipeItems')
.insert({
id: customRecipe.id,
item: req.item.id,
quantity: req.quantity,
type: 'requirement'
});
}
for (const output of recipe.outputs) {
await db<CustomCraftingRecipeItem>('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;