| 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); |
|
|
| |
| const SHEET_ID = '109roJQr-Y4YCLTkCqaK6iwShC-Dr2Jb-hB0qE2phNqQ'; |
| const SHEET_URL = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/export?format=csv`; |
|
|
| |
| const bots = new Map(); |
| const serverBotMap = new Map(); |
|
|
| |
| async function resolveToIP(hostname) { |
| try { |
| |
| if (/^(\d{1,3}\.){3}\d{1,3}$/.test(hostname)) { |
| return hostname; |
| } |
| |
| |
| const addresses = await dns.resolve4(hostname); |
| return addresses[0]; |
| } catch (error) { |
| console.error(`Failed to resolve ${hostname}:`, error.message); |
| return hostname; |
| } |
| } |
|
|
| 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 { |
| |
| this.resolvedIP = await resolveToIP(this.ip); |
| const serverKey = `${this.resolvedIP}:${this.port}`; |
| |
| |
| 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, |
| port: parseInt(this.port), |
| username: this.botName, |
| auth: 'offline', |
| version: this.version, |
| hideErrors: true, |
| checkTimeoutInterval: 30000 |
| }); |
|
|
| |
| 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})`); |
| |
| |
| 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'; |
| |
| |
| 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; |
| |
| |
| let direction = 1; |
| const afkInterval = setInterval(() => { |
| if (!this.bot || this.status !== 'Connected') { |
| clearInterval(afkInterval); |
| return; |
| } |
| |
| |
| 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 |
| }; |
| } |
| } |
|
|
| |
| 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 []; |
| } |
| } |
|
|
| |
| async function updateBots() { |
| const sheetData = await fetchSheetData(); |
| const activeBots = new Set(); |
| |
| |
| 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); |
| |
| |
| 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 { |
| |
| bots.get(botName).inSheet = true; |
| } |
| } |
| |
| |
| 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}`); |
| } |
|
|
| |
| io.on('connection', (socket) => { |
| console.log('Client connected'); |
| |
| |
| 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'); |
| }); |
| }); |
|
|
| |
| app.use(express.static(__dirname)); |
| app.get('/', (req, res) => { |
| res.sendFile(path.join(__dirname, 'index.html')); |
| }); |
|
|
| |
| const PORT = process.env.PORT || 7860; |
| server.listen(PORT, () => { |
| console.log(`Server running on port ${PORT}`); |
| }); |
|
|
| |
| updateBots(); |
| setInterval(updateBots, 30000); |