diff --git a/web/server/server.js b/web/server/server.js index 06533f2..ef1abc2 100644 --- a/web/server/server.js +++ b/web/server/server.js @@ -588,6 +588,122 @@ app.post('/api/craft', (req, res) => { } }); +// Recursive craft (multi-step crafting chain) +app.post('/api/recursive-craft', (req, res) => { + try { + const { itemName, count, commandId } = req.body; + + const cached = checkIdempotent(commandId); + if (cached) return res.json(cached); + + if (!itemName || typeof itemName !== 'string' || itemName.length > 200) { + return res.status(400).json({ error: 'Missing or invalid itemName' }); + } + const parsedCount = parseInt(count); + if (!Number.isFinite(parsedCount) || parsedCount < 1 || parsedCount > 100000) { + return res.status(400).json({ error: 'Invalid count (1–100000)' }); + } + + pushCommandToBridge({ type: 'command', action: 'recursive_craft', commandId, itemName, count: parsedCount }); + const result = { success: true, commandId, message: `Recursive craft sent: ${itemName} x${count}` }; + recordCommand(commandId, result); + console.log(`🔨 Recursive craft: ${itemName} x${count}`); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Learn a crafting recipe +app.post('/api/recipes/learn-crafting', (req, res) => { + try { + const { output, count, grid, commandId } = req.body; + + const cached = checkIdempotent(commandId); + if (cached) return res.json(cached); + + if (!output || typeof output !== 'string' || output.length > 200) { + return res.status(400).json({ error: 'Missing or invalid output' }); + } + if (!grid || !Array.isArray(grid) || grid.length !== 9) { + return res.status(400).json({ error: 'grid must be an array of 9 items' }); + } + + pushCommandToBridge({ type: 'command', action: 'learn_crafting_recipe', commandId, output, count: count || 1, grid }); + const result = { success: true, commandId, message: `Learned crafting recipe: ${output}` }; + recordCommand(commandId, result); + console.log(`📖 Learn crafting recipe: ${output}`); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Learn a smelting recipe +app.post('/api/recipes/learn-smelting', (req, res) => { + try { + const { input, result: recipeResult, furnaces, commandId } = req.body; + + const cached = checkIdempotent(commandId); + if (cached) return res.json(cached); + + if (!input || typeof input !== 'string' || input.length > 200) { + return res.status(400).json({ error: 'Missing or invalid input' }); + } + if (!recipeResult || typeof recipeResult !== 'string' || recipeResult.length > 200) { + return res.status(400).json({ error: 'Missing or invalid result' }); + } + + pushCommandToBridge({ type: 'command', action: 'learn_smelting_recipe', commandId, input, result: recipeResult, furnaces }); + const apiResult = { success: true, commandId, message: `Learned smelting recipe: ${input} → ${recipeResult}` }; + recordCommand(commandId, apiResult); + console.log(`📖 Learn smelting recipe: ${input} → ${recipeResult}`); + res.json(apiResult); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Forget a recipe +app.post('/api/recipes/forget', (req, res) => { + try { + const { recipe, commandId } = req.body; + + const cached = checkIdempotent(commandId); + if (cached) return res.json(cached); + + if (!recipe || typeof recipe !== 'string' || recipe.length > 200) { + return res.status(400).json({ error: 'Missing or invalid recipe name' }); + } + + pushCommandToBridge({ type: 'command', action: 'forget_recipe', commandId, recipe }); + const result = { success: true, commandId, message: `Forget recipe: ${recipe}` }; + recordCommand(commandId, result); + console.log(`🗑️ Forget recipe: ${recipe}`); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Sync disabled recipes state +app.post('/api/recipes/sync', (req, res) => { + try { + const { disabledRecipes, smeltingPaused, commandId } = req.body; + + const cached = checkIdempotent(commandId); + if (cached) return res.json(cached); + + pushCommandToBridge({ type: 'command', action: 'sync_disabled_recipes', commandId, disabledRecipes, smeltingPaused }); + const result = { success: true, commandId, message: 'Synced recipe state' }; + recordCommand(commandId, result); + console.log('🔄 Sync disabled recipes'); + res.json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + // ========== Bridge Endpoints (for HTTP polling fallback) ========== // Bridge sends inventory state