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 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 botLastReconnect = new Map(); const serverBotMap = new Map(); // Track one bot per server 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.isManualDisconnect = false; this.startTime = Date.now(); } async connect() { try { // Check if server already has a bot const serverKey = `${this.ip}:${this.port}`; const existingBot = serverBotMap.get(serverKey); if (existingBot && existingBot !== this.botName) { this.status = 'Server already has a bot'; return false; } this.status = 'Connecting...'; this.bot = mineflayer.createBot({ host: this.ip, port: parseInt(this.port), username: this.botName, auth: 'offline', version: this.version, hideErrors: true, checkTimeoutInterval: 30000 }); // Register this bot for the server serverBotMap.set(serverKey, this.botName); this.bot.once('spawn', () => { this.status = 'Connected'; this.disconnectTime = null; console.log(`Bot ${this.botName} spawned on ${this.ip}:${this.port}`); // Start AFK behavior this.startAFK(); }); this.bot.on('death', () => { this.deathCount++; console.log(`Bot ${this.botName} died. Total deaths: ${this.deathCount}`); }); 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'; return false; } } handleDisconnect() { const serverKey = `${this.ip}:${this.port}`; if (serverBotMap.get(serverKey) === this.botName) { serverBotMap.delete(serverKey); } this.status = 'Disconnected'; this.disconnectTime = Date.now(); this.bot = null; if (!this.isManualDisconnect) { 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') return false; const lastReconnect = botLastReconnect.get(this.botName) || 0; const hourAgo = Date.now() - (60 * 60 * 1000); return lastReconnect < hourAgo; } async reconnect() { if (!this.canReconnect()) { return false; } this.isManualDisconnect = false; botLastReconnect.set(this.botName, Date.now()); return await this.connect(); } getInfo() { const uptime = this.status === 'Connected' ? Math.floor((Date.now() - this.startTime) / 1000) : 0; return { botName: this.botName, status: this.status, deathCount: this.deathCount, uptime: uptime, canReconnect: this.canReconnect(), disconnectTime: this.disconnectTime }; } } // 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(); 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); await botManager.connect(); } } // Remove bots that are no longer in the sheet for (const [botName, botManager] of bots.entries()) { if (!activeBots.has(botName)) { botManager.disconnect(); bots.delete(botName); botLastReconnect.delete(botName); } } } // 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