From ad70adb0f15dc3b0334b583b106b616a927c2684 Mon Sep 17 00:00:00 2001 From: ZareMate <0.zaremate@gmail.com> Date: Fri, 13 Mar 2026 17:55:57 +0100 Subject: [PATCH] working emergency leave --- allowed_players.txt | 1 + index.js | 216 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 allowed_players.txt diff --git a/allowed_players.txt b/allowed_players.txt new file mode 100644 index 0000000..b0135f6 --- /dev/null +++ b/allowed_players.txt @@ -0,0 +1 @@ +ZareMate \ No newline at end of file diff --git a/index.js b/index.js index 6d9866e..9792e1b 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ const http = require("http"); +const https = require("https"); const fs = require("fs"); const path = require("path"); const mineflayer = require("mineflayer"); @@ -13,6 +14,12 @@ const LOOP_DELAY_MS = 10_000; const MAX_ALLOWED_MOVE_BLOCKS = 10; const REJOIN_DELAY_MS = 60_000; const BULB_VERIFY_RETRIES = 3; +const SECURITY_RADIUS = 10; +const EMERGENCY_HOTBAR_SLOT = 0; + +const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL || ""; +const ALLOWED_PLAYERS_FILE = + process.env.ALLOWED_PLAYERS_FILE || "allowed_players.txt"; const ITEM_MODE = normalizeItemMode( process.env.ITEM_MODE || process.env.SPAWNER_MODE || "bones", @@ -27,6 +34,29 @@ let spawnerPosition = null; let reconnectTimer = null; let bot = null; let bothModeStep = 0; +let emergencyTriggered = false; +let securityMonitorInterval = null; + +const allowedPlayers = loadAllowedPlayers(); + +function loadAllowedPlayers() { + const filePath = path.isAbsolute(ALLOWED_PLAYERS_FILE) + ? ALLOWED_PLAYERS_FILE + : path.join(__dirname, ALLOWED_PLAYERS_FILE); + + try { + const data = fs.readFileSync(filePath, "utf8"); + return new Set( + data + .split(/\r?\n/) + .map((line) => line.trim().toLowerCase()) + .filter((line) => line && !line.startsWith("#")), + ); + } catch { + pushLog(`allowed players file not found: ${filePath}`); + return new Set(); + } +} function normalizeItemMode(value) { const input = String(value || "") @@ -126,12 +156,14 @@ async function ensureBulbModeBeforeOpen(mode) { function createBotInstance() { hasJoined = false; joinPosition = null; + spawnerPosition = null; + emergencyTriggered = false; bot = mineflayer.createBot({ - host: "java.donutsmp.net", + host: "localhost", username: "Tayota4420", auth: "microsoft", - version: "1.21.1", + version: "1.21.4", }); bot.loadPlugin(gui); @@ -175,6 +207,148 @@ function pushLog(message) { console.log(message); } +async function sendDiscordWebhook(content) { + if (!DISCORD_WEBHOOK_URL) { + pushLog("DISCORD_WEBHOOK_URL not set; skipping webhook"); + return; + } + + try { + const body = JSON.stringify({ content }); + const url = new URL(DISCORD_WEBHOOK_URL); + + await new Promise((resolve, reject) => { + const req = https.request( + { + method: "POST", + hostname: url.hostname, + path: `${url.pathname}${url.search}`, + port: url.port || 443, + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body), + }, + }, + (res) => { + res.on("data", () => {}); + res.on("end", () => resolve()); + }, + ); + + req.on("error", reject); + req.write(body); + req.end(); + }); + } catch (err) { + pushLog(`failed webhook send: ${err.message}`); + } +} + +function isSpawnerBlock(block) { + if (!block) return false; + return ["chest", "trapped_chest"].includes(block.name); +} + +function getUnauthorizedNearbyPlayers() { + if (!bot || !bot.entity || !bot.entity.position) return []; + + const intruders = []; + for (const [username, player] of Object.entries(bot.players)) { + if (!username || username === bot.username) continue; + if (allowedPlayers.has(username.toLowerCase())) continue; + if (!player || !player.entity || !player.entity.position) continue; + + const distance = player.entity.position.distanceTo(bot.entity.position); + if (distance <= SECURITY_RADIUS) { + intruders.push(`${username} (${distance.toFixed(1)}m)`); + } + } + + return intruders; +} + +async function breakSpawnerUntilGone() { + bot.quickBarSlot = EMERGENCY_HOTBAR_SLOT; + bot.setControlState("sneak", true); + + try { + while (true) { + let targetBlock = null; + + if (spawnerPosition) { + targetBlock = bot.blockAt(spawnerPosition); + } + + if (!isSpawnerBlock(targetBlock)) { + targetBlock = findSpawnerBlock(); + } + + if (!isSpawnerBlock(targetBlock)) { + pushLog("chest no longer present"); + return true; + } + + spawnerPosition = targetBlock.position.clone(); + await bot.lookAt(targetBlock.position.offset(0.5, 0.5, 0.5), true); + + if (!bot.canDigBlock(targetBlock)) { + pushLog("cannot dig chest yet, retrying"); + await sleep(300); + continue; + } + + try { + await bot.dig(targetBlock, true); + } catch (err) { + pushLog(`chest dig failed: ${err.message}`); + await sleep(300); + } + } + } finally { + bot.setControlState("sneak", false); + } +} + +async function triggerSecurityShutdown(reason) { + if (emergencyTriggered) return; + emergencyTriggered = true; + + pushLog(`security trigger: ${reason}`); + stopSpawnerLoop("spawner loop stopped due to security trigger"); + + const spawnerDestroyed = await breakSpawnerUntilGone(); + const status = spawnerDestroyed ? "chest removed" : "chest not removed"; + + await sendDiscordWebhook(`MCBOT ALERT: ${reason} | ${status}`); + + try { + bot.quit("security shutdown"); + } catch { + // ignore disconnect errors + } +} + +function startSecurityMonitor() { + if (securityMonitorInterval) clearInterval(securityMonitorInterval); + + securityMonitorInterval = setInterval(() => { + if (!hasJoined || !bot || emergencyTriggered) return; + + const intruders = getUnauthorizedNearbyPlayers(); + if (intruders.length > 0) { + triggerSecurityShutdown( + `unauthorized player nearby: ${intruders.join(", ")}`, + ); + } + }, 1000); +} + +function stopSecurityMonitor() { + if (!securityMonitorInterval) return; + clearInterval(securityMonitorInterval); + securityMonitorInterval = null; +} + const webPath = path.join(__dirname, "web", "index.html"); const server = http.createServer(async (req, res) => { @@ -289,7 +463,7 @@ function stopSpawnerLoop(reason) { } function findSpawnerBlock() { - const matching = ["spawner", "mob_spawner", "trial_spawner"] + const matching = ["chest", "trapped_chest"] .filter((name) => bot.registry.blocksByName[name] !== undefined) .map((name) => bot.registry.blocksByName[name].id); @@ -313,13 +487,13 @@ async function rotateToSpawner() { if (targetBlock) { spawnerPosition = targetBlock.position.clone(); pushLog( - `spawner position set: ${spawnerPosition.x}, ${spawnerPosition.y}, ${spawnerPosition.z}`, + `chest position set: ${spawnerPosition.x}, ${spawnerPosition.y}, ${spawnerPosition.z}`, ); } } if (!targetBlock) { - pushLog("no spawner found nearby"); + pushLog("no chest found nearby"); return null; } @@ -347,7 +521,7 @@ async function openSpawnerGui(maxAttempts = 2) { for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { const targetBlock = await rotateToSpawner(); if (!targetBlock || targetBlock.name === "air") { - pushLog("unable to target spawner block"); + pushLog("unable to target chest block"); return false; } @@ -361,7 +535,7 @@ async function openSpawnerGui(maxAttempts = 2) { } pushLog( - `no gui opened from block '${targetBlock.name}' (attempt ${attempt}/${maxAttempts})`, + `no gui opened from chest '${targetBlock.name}' (attempt ${attempt}/${maxAttempts})`, ); if (attempt < maxAttempts) { await sleep(700); @@ -535,11 +709,34 @@ function bindBotEvents() { hasJoined = true; if (!bot.entity || !bot.entity.position) return; joinPosition = bot.entity.position.clone(); + startSecurityMonitor(); pushLog( `join position set: ${joinPosition.x.toFixed(1)}, ${joinPosition.y.toFixed(1)}, ${joinPosition.z.toFixed(1)}`, ); }); + bot.on("blockUpdate", (oldBlock, newBlock) => { + if ( + !hasJoined || + emergencyTriggered || + !bot.entity || + !bot.entity.position + ) { + return; + } + + if (!oldBlock || !newBlock) return; + if (oldBlock.name === "air") return; + if (newBlock.name !== "air") return; + + const distance = oldBlock.position.distanceTo(bot.entity.position); + if (distance <= SECURITY_RADIUS) { + triggerSecurityShutdown( + `block broken within ${SECURITY_RADIUS}m at ${oldBlock.position.x},${oldBlock.position.y},${oldBlock.position.z}`, + ); + } + }); + bot.on("physicsTick", () => { if ( !spawnerLoopRunning || @@ -560,6 +757,7 @@ function bindBotEvents() { bot.on("kicked", (reason) => scheduleRejoinIfAlreadyOnline(reason)); bot.on("error", (err) => pushLog(`error: ${err.message}`)); + bot.on("end", () => stopSecurityMonitor()); } function itemToString(item) { @@ -578,3 +776,7 @@ if (BULB_POSITION && BUTTON_POSITION) { } else { pushLog("set BULB_POS and BUTTON_POS in .env to enable mode checking"); } + +pushLog( + `security radius=${SECURITY_RADIUS}m, allowed players file=${ALLOWED_PLAYERS_FILE}`, +);