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