MIneLine / server.js
Tokipo's picture
Update server.js
3b2ae1c verified
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mineflayer = require('mineflayer');
const fetch = require('node-fetch');
const { parse } = require('csv-parse/sync');
const path = require('path');
const dns = require('dns').promises;
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
// Google Sheets configuration
const SHEET_ID = '109roJQr-Y4YCLTkCqaK6iwShC-Dr2Jb-hB0qE2phNqQ';
const SHEET_URL = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/export?format=csv`;
// Bot management
const bots = new Map();
const serverBotMap = new Map(); // Track one bot per server
// Function to resolve domain to IP
async function resolveToIP(hostname) {
try {
// Check if it's already an IP address
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname)) {
return hostname;
}
// Resolve domain to IP
const addresses = await dns.resolve4(hostname);
return addresses[0]; // Return first IP
} catch (error) {
console.error(`Failed to resolve ${hostname}:`, error.message);
return hostname; // Return original if resolution fails
}
}
class BotManager {
constructor(botName, ip, port, version) {
this.botName = botName;
this.ip = ip;
this.port = port;
this.version = version || '1.20.1';
this.bot = null;
this.status = 'Disconnected';
this.deathCount = 0;
this.disconnectTime = null;
this.lastReconnectTime = null;
this.isManualDisconnect = false;
this.startTime = Date.now();
this.connectedTime = null;
this.inSheet = true;
this.resolvedIP = null;
}
async connect() {
try {
// Resolve domain to IP for comparison
this.resolvedIP = await resolveToIP(this.ip);
const serverKey = `${this.resolvedIP}:${this.port}`;
// Check if server already has a bot
const existingBot = serverBotMap.get(serverKey);
if (existingBot && existingBot !== this.botName) {
this.status = 'Server already has a bot';
console.log(`Bot ${this.botName} blocked: Server ${serverKey} already has bot ${existingBot}`);
return false;
}
this.status = 'Connecting...';
this.bot = mineflayer.createBot({
host: this.ip, // Use original hostname/IP for connection
port: parseInt(this.port),
username: this.botName,
auth: 'offline',
version: this.version,
hideErrors: true,
checkTimeoutInterval: 30000
});
// Register this bot for the server using resolved IP
serverBotMap.set(serverKey, this.botName);
console.log(`Registering bot ${this.botName} for server ${serverKey}`);
this.bot.once('spawn', () => {
this.status = 'Connected';
this.connectedTime = Date.now();
this.disconnectTime = null;
console.log(`Bot ${this.botName} spawned on ${this.ip}:${this.port} (resolved: ${this.resolvedIP})`);
// Start AFK behavior
this.startAFK();
});
this.bot.on('death', () => {
this.deathCount++;
this.status = 'Dead';
console.log(`Bot ${this.botName} died. Total deaths: ${this.deathCount}`);
this.handleDisconnect();
});
this.bot.on('kicked', (reason) => {
console.log(`Bot ${this.botName} was kicked: ${reason}`);
this.handleDisconnect();
});
this.bot.on('error', (err) => {
console.error(`Bot ${this.botName} error:`, err.message);
});
this.bot.on('end', () => {
this.handleDisconnect();
});
return true;
} catch (error) {
console.error(`Failed to connect bot ${this.botName}:`, error);
this.status = 'Connection Failed';
// Clean up server registration if connection failed
if (this.resolvedIP) {
const serverKey = `${this.resolvedIP}:${this.port}`;
if (serverBotMap.get(serverKey) === this.botName) {
serverBotMap.delete(serverKey);
}
}
return false;
}
}
handleDisconnect() {
if (this.resolvedIP) {
const serverKey = `${this.resolvedIP}:${this.port}`;
if (serverBotMap.get(serverKey) === this.botName) {
serverBotMap.delete(serverKey);
console.log(`Unregistering bot ${this.botName} from server ${serverKey}`);
}
}
if (this.status !== 'Dead') {
this.status = 'Disconnected';
}
this.disconnectTime = Date.now();
this.connectedTime = null;
this.bot = null;
console.log(`Bot ${this.botName} disconnected from ${this.ip}:${this.port}`);
}
disconnect() {
this.isManualDisconnect = true;
if (this.bot) {
this.bot.quit();
}
this.handleDisconnect();
}
startAFK() {
if (!this.bot) return;
// Simple AFK movement
let direction = 1;
const afkInterval = setInterval(() => {
if (!this.bot || this.status !== 'Connected') {
clearInterval(afkInterval);
return;
}
// Walk forward and backward
this.bot.setControlState('forward', direction > 0);
this.bot.setControlState('back', direction < 0);
setTimeout(() => {
if (this.bot) {
this.bot.clearControlStates();
}
}, 1000);
direction *= -1;
}, 5000);
}
canReconnect() {
if (this.status === 'Connected' || this.status === 'Connecting...') return false;
if (!this.inSheet) return false;
if (!this.lastReconnectTime) return true;
const hourAgo = Date.now() - (60 * 60 * 1000);
return this.lastReconnectTime < hourAgo;
}
async reconnect() {
if (!this.canReconnect()) {
return false;
}
this.lastReconnectTime = Date.now();
this.isManualDisconnect = false;
return await this.connect();
}
getTimeUntilReconnect() {
if (!this.lastReconnectTime) return 0;
const timeElapsed = Date.now() - this.lastReconnectTime;
const hourInMs = 60 * 60 * 1000;
const timeRemaining = Math.max(0, hourInMs - timeElapsed);
return Math.ceil(timeRemaining / 1000);
}
getConnectedDuration() {
if (!this.connectedTime || this.status !== 'Connected') return 0;
return Math.floor((Date.now() - this.connectedTime) / 1000);
}
getInfo() {
return {
botName: this.botName,
status: this.status,
deathCount: this.deathCount,
connectedDuration: this.getConnectedDuration(),
canReconnect: this.canReconnect(),
disconnectTime: this.disconnectTime,
timeUntilReconnect: this.getTimeUntilReconnect(),
inSheet: this.inSheet
};
}
}
// Fetch and parse Google Sheets data
async function fetchSheetData() {
try {
const response = await fetch(SHEET_URL);
const csvText = await response.text();
const records = parse(csvText, {
columns: true,
skip_empty_lines: true
});
return records;
} catch (error) {
console.error('Error fetching sheet data:', error);
return [];
}
}
// Update bots based on sheet data
async function updateBots() {
const sheetData = await fetchSheetData();
const activeBots = new Set();
// Mark all existing bots as not in sheet initially
for (const [botName, botManager] of bots.entries()) {
botManager.inSheet = false;
}
for (const row of sheetData) {
const botName = row['BOT NAME']?.trim();
const ip = row['IP']?.trim();
const port = row['PORT']?.trim();
const version = row['Version']?.trim() || '1.20.1';
if (!botName || !ip || !port) continue;
activeBots.add(botName);
// Add new bot if it doesn't exist
if (!bots.has(botName)) {
const botManager = new BotManager(botName, ip, port, version);
bots.set(botName, botManager);
console.log(`Added new bot from sheet: ${botName} for ${ip}:${port}`);
} else {
// Mark existing bot as still in sheet
bots.get(botName).inSheet = true;
}
}
// Remove bots that are no longer in the sheet
for (const [botName, botManager] of bots.entries()) {
if (!botManager.inSheet) {
console.log(`Removing bot ${botName} - no longer in sheet`);
botManager.disconnect();
bots.delete(botName);
}
}
console.log(`Total bots: ${bots.size}, Active servers: ${serverBotMap.size}`);
}
// Socket.IO events
io.on('connection', (socket) => {
console.log('Client connected');
// Send initial bot data
const sendBotData = () => {
const botData = Array.from(bots.values()).map(bot => bot.getInfo());
socket.emit('botUpdate', botData);
};
sendBotData();
const updateInterval = setInterval(sendBotData, 2000);
socket.on('reconnectBot', async (botName) => {
const botManager = bots.get(botName);
if (botManager) {
const success = await botManager.reconnect();
socket.emit('reconnectResult', { botName, success });
}
});
socket.on('refreshSheet', async () => {
await updateBots();
sendBotData();
});
socket.on('disconnect', () => {
clearInterval(updateInterval);
console.log('Client disconnected');
});
});
// Serve static files
app.use(express.static(__dirname));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// Start server
const PORT = process.env.PORT || 7860;
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
// Initial load and periodic updates
updateBots();
setInterval(updateBots, 30000); // Check sheet every 30 seconds