DonutBot/afk.js

211 lines
5.0 KiB
JavaScript

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...");