working emergency leave
This commit is contained in:
parent
2241108f97
commit
ad70adb0f1
1
allowed_players.txt
Normal file
1
allowed_players.txt
Normal file
@ -0,0 +1 @@
|
||||
ZareMate
|
||||
216
index.js
216
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}`,
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user