const mineflayer = require("mineflayer"); const fs = require("fs"); const path = require("path"); function getRuntimeUsername() { const fromArg = process.argv[2]; if (typeof fromArg === "string" && fromArg.trim()) return fromArg.trim(); const fromEnv = process.env.BOT_USERNAME; if (typeof fromEnv === "string" && fromEnv.trim()) return fromEnv.trim(); return "AFKBot"; } function toSafeFilePart(value) { return String(value).replace(/[^a-zA-Z0-9_-]/g, "_"); } const runtimeUsername = getRuntimeUsername(); // Configuration const BOT_CONFIG = { host: "java.donutsmp.net", username: runtimeUsername, auth: "microsoft", version: "1.21.1", }; const LOG_FILE = path.join( __dirname, `afk-${toSafeFilePart(runtimeUsername)}.log`, ); const TELEPORT_DETECT_REGEX = /you teleported to\b/i; const AFK_RETRY_DELAY_MS = 5000; const AFK_MIN_NUMBER = 1; const AFK_MAX_NUMBER = 50; let hasDetectedTeleport = false; let afkRetryTimer = null; // Logging function log(message) { const timestamp = new Date().toISOString(); const logMessage = `[${timestamp}] ${message}`; console.log(logMessage); fs.appendFileSync(LOG_FILE, logMessage + "\n"); } function getRandomIntInclusive(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function clearAfkRetryTimer() { if (afkRetryTimer) { clearTimeout(afkRetryTimer); afkRetryTimer = null; } } function scheduleAfkRetryCheck() { clearAfkRetryTimer(); afkRetryTimer = setTimeout(() => { if (hasDetectedTeleport) return; const randomNumber = getRandomIntInclusive(AFK_MIN_NUMBER, AFK_MAX_NUMBER); const command = `/afk ${randomNumber}`; log( `No teleport detected after ${AFK_RETRY_DELAY_MS}ms; sending: ${command}`, ); bot.chat(command); // Keep checking every delay window until teleport is detected. scheduleAfkRetryCheck(); }, AFK_RETRY_DELAY_MS); } function toPlainText(value) { if (value == null) return ""; if (typeof value === "string") return value; if ( typeof value.toString === "function" && value.toString !== Object.prototype.toString ) { const asString = value.toString(); if (asString && asString !== "[object Object]") return asString; } if (Array.isArray(value)) { return value.map(toPlainText).join(""); } if (typeof value === "object") { let text = ""; if (typeof value.text === "string") text += value.text; if (typeof value.translate === "string") text += value.translate; if (Array.isArray(value.with)) text += value.with.map(toPlainText).join(""); if (Array.isArray(value.extra)) text += value.extra.map(toPlainText).join(""); return text; } return String(value); } function logIfTeleport(source, text) { const normalized = String(text || "").trim(); if (!normalized) return; if (!TELEPORT_DETECT_REGEX.test(normalized)) return; log(`Teleport detected from ${source}: ${normalized}`); if (!hasDetectedTeleport) { hasDetectedTeleport = true; clearAfkRetryTimer(); log("Teleport confirmed; stopped AFK retry loop"); } } // Create bot const bot = mineflayer.createBot({ host: BOT_CONFIG.host, username: BOT_CONFIG.username, auth: BOT_CONFIG.auth, version: BOT_CONFIG.version, }); // Event handlers bot.on("login", () => { log("Bot logged in"); bot.setControlState("forward", false); }); bot.on("spawn", () => { log("Bot spawned"); hasDetectedTeleport = false; scheduleAfkRetryCheck(); }); bot.on("error", (err) => { log(`Error: ${err.message}`); }); bot.on("kicked", (reason) => { log(`Kicked: ${reason}`); }); bot.on("end", () => { clearAfkRetryTimer(); log("Bot disconnected"); process.exit(0); }); bot.on("chat", (username, message) => { if (username === bot.username) return; log(`Chat [${username}]: ${message}`); logIfTeleport("chat", message); }); bot.on("messagestr", (message, position) => { if (!message) return; if (position === "game_info") { log(`Hotbar: ${message}`); } logIfTeleport(`messagestr${position ? `:${position}` : ""}`, message); }); bot.on("message", (jsonMsg, position) => { const text = toPlainText(jsonMsg); if (!text) return; if (position === "game_info") { log(`Hotbar(json): ${text}`); } logIfTeleport(`message${position ? `:${position}` : ""}`, text); }); // Fallback packet hooks for title/actionbar channels on servers that do not // emit teleport text through regular chat events. bot._client.on("set_action_bar_text", (packet) => { const text = toPlainText(packet?.text); if (!text) return; log(`ActionBar: ${text}`); logIfTeleport("actionbar", text); }); bot._client.on("set_title_text", (packet) => { const text = toPlainText(packet?.text); if (!text) return; log(`Title: ${text}`); logIfTeleport("title", text); }); bot._client.on("set_subtitle_text", (packet) => { const text = toPlainText(packet?.text); if (!text) return; log(`Subtitle: ${text}`); logIfTeleport("subtitle", text); }); // Graceful shutdown process.on("SIGINT", () => { log("Shutdown signal received"); clearAfkRetryTimer(); bot.quit(); }); log("Starting AFK bot...");