| |
| import os |
| import sys |
| import time |
| import json |
| import threading |
| import subprocess |
| import signal |
| import socket |
| import struct |
| from datetime import datetime |
| from flask import Flask, jsonify, request |
| from flask_cors import CORS |
| import logging |
|
|
| |
| log = logging.getLogger('werkzeug') |
| log.setLevel(logging.ERROR) |
|
|
| app = Flask(__name__) |
| CORS(app) |
|
|
| |
| SERVER_HOST = "orbitmc.progamer.me" |
| SERVER_PORT = 40675 |
| SERVER_VERSION = "1.21.8" |
| BOT_NAMES = ["Alfha_lag", "Bigboybloom", "Kitcat"] |
| ROTATION_DURATION = 3600 |
|
|
| |
| bots = {} |
| bot_processes = {} |
| current_bot_index = 0 |
| rotation_start_time = None |
| server_status = { |
| "online": False, |
| "players": "0/0", |
| "latency": 0, |
| "last_check": None, |
| "motd": "" |
| } |
|
|
| |
| BOT_SCRIPT = """ |
| const mineflayer = require('mineflayer'); |
| const { Vec3 } = require('vec3'); |
| |
| const botName = process.argv[2]; |
| const host = process.argv[3]; |
| const port = parseInt(process.argv[4]) || 25565; |
| const version = process.argv[5] || false; |
| |
| let isConnected = false; |
| let circleInterval = null; |
| let chatInterval = null; |
| let angle = 0; |
| let centerPos = null; |
| let reconnectTimeout = null; |
| |
| console.log(JSON.stringify({event:'starting', name:botName, time:Date.now()})); |
| |
| function createBot() { |
| const bot = mineflayer.createBot({ |
| host: host, |
| port: port, |
| username: botName, |
| auth: 'offline', |
| version: version === 'false' ? false : version, |
| hideErrors: false, |
| checkTimeoutInterval: 30000, |
| keepAlive: true, |
| skipValidation: true |
| }); |
| |
| function startCircularMovement() { |
| if (circleInterval) clearInterval(circleInterval); |
| |
| setTimeout(() => { |
| if (!bot.entity || !isConnected) return; |
| |
| centerPos = bot.entity.position.clone(); |
| angle = 0; |
| |
| console.log(JSON.stringify({ |
| event:'movement_started', |
| name:botName, |
| center: { |
| x: Math.floor(centerPos.x), |
| y: Math.floor(centerPos.y), |
| z: Math.floor(centerPos.z) |
| } |
| })); |
| |
| circleInterval = setInterval(() => { |
| if (!bot.entity || !isConnected || !centerPos) return; |
| |
| try { |
| const radius = 4; |
| angle += 0.03; |
| |
| const targetX = centerPos.x + Math.cos(angle) * radius; |
| const targetZ = centerPos.z + Math.sin(angle) * radius; |
| |
| const dx = targetX - bot.entity.position.x; |
| const dz = targetZ - bot.entity.position.z; |
| const yaw = Math.atan2(-dx, -dz); |
| |
| bot.look(yaw, 0, true); |
| bot.setControlState('forward', true); |
| |
| // Send position every 2 seconds |
| if (Math.floor(angle * 100) % 200 === 0) { |
| const pos = bot.entity.position; |
| console.log(JSON.stringify({ |
| event:'position', |
| name:botName, |
| x: Math.floor(pos.x), |
| y: Math.floor(pos.y), |
| z: Math.floor(pos.z), |
| health: bot.health, |
| food: bot.food |
| })); |
| } |
| } catch(err) { |
| console.log(JSON.stringify({event:'movement_error', name:botName, error:err.message})); |
| } |
| }, 100); |
| }, 3000); |
| } |
| |
| function stopMovement() { |
| if (circleInterval) { |
| clearInterval(circleInterval); |
| circleInterval = null; |
| } |
| if (chatInterval) { |
| clearInterval(chatInterval); |
| chatInterval = null; |
| } |
| try { |
| bot.setControlState('forward', false); |
| } catch(err) {} |
| } |
| |
| bot.once('spawn', () => { |
| console.log(JSON.stringify({ |
| event:'connected', |
| name:botName, |
| time:Date.now(), |
| gamemode: bot.game.gameMode, |
| dimension: bot.game.dimension |
| })); |
| isConnected = true; |
| startCircularMovement(); |
| |
| // Random chat |
| chatInterval = setInterval(() => { |
| if (isConnected && Math.random() > 0.8) { |
| try { |
| const messages = ['Hello!', 'Im monitoring', 'All good here', ':)', 'Server looking good']; |
| bot.chat(messages[Math.floor(Math.random() * messages.length)]); |
| } catch(err) {} |
| } |
| }, 180000); // Every 3 minutes |
| }); |
| |
| bot.on('respawn', () => { |
| console.log(JSON.stringify({event:'respawned', name:botName, time:Date.now()})); |
| stopMovement(); |
| centerPos = null; |
| startCircularMovement(); |
| }); |
| |
| bot.on('death', () => { |
| console.log(JSON.stringify({event:'death', name:botName, time:Date.now()})); |
| stopMovement(); |
| centerPos = null; |
| }); |
| |
| bot.on('health', () => { |
| if (bot.health <= 0) { |
| console.log(JSON.stringify({event:'died', name:botName})); |
| } |
| }); |
| |
| bot.on('kicked', (reason) => { |
| console.log(JSON.stringify({ |
| event:'kicked', |
| name:botName, |
| reason: JSON.stringify(reason), |
| time:Date.now() |
| })); |
| isConnected = false; |
| stopMovement(); |
| }); |
| |
| bot.on('error', (err) => { |
| console.log(JSON.stringify({ |
| event:'error', |
| name:botName, |
| error:err.message, |
| time:Date.now() |
| })); |
| }); |
| |
| bot.on('end', (reason) => { |
| console.log(JSON.stringify({ |
| event:'disconnected', |
| name:botName, |
| reason: reason || 'unknown', |
| time:Date.now() |
| })); |
| isConnected = false; |
| stopMovement(); |
| |
| // Auto reconnect after 5 seconds |
| if (reconnectTimeout) clearTimeout(reconnectTimeout); |
| reconnectTimeout = setTimeout(() => { |
| console.log(JSON.stringify({event:'reconnecting', name:botName})); |
| createBot(); |
| }, 5000); |
| }); |
| |
| bot.on('message', (message) => { |
| const msg = message.toString(); |
| if (msg.includes(botName)) { |
| console.log(JSON.stringify({event:'mentioned', name:botName, message:msg})); |
| } |
| }); |
| |
| // Heartbeat |
| setInterval(() => { |
| if (isConnected && bot.entity) { |
| console.log(JSON.stringify({ |
| event:'heartbeat', |
| name:botName, |
| health: bot.health || 0, |
| food: bot.food || 0, |
| time:Date.now() |
| })); |
| } |
| }, 30000); |
| |
| process.on('SIGTERM', () => { |
| stopMovement(); |
| bot.quit(); |
| process.exit(0); |
| }); |
| |
| process.on('SIGINT', () => { |
| stopMovement(); |
| bot.quit(); |
| process.exit(0); |
| }); |
| } |
| |
| createBot(); |
| """ |
|
|
| def write_bot_script(): |
| """Write bot.js to disk""" |
| try: |
| with open('/tmp/bot.js', 'w') as f: |
| f.write(BOT_SCRIPT) |
| print("✅ Bot script written to /tmp/bot.js") |
| return True |
| except Exception as e: |
| print(f"❌ Failed to write bot script: {e}") |
| return False |
|
|
| def ping_minecraft_server(): |
| """Ping Minecraft server and get real status""" |
| global server_status |
| |
| try: |
| start_time = time.time() |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| sock.settimeout(5) |
| |
| |
| sock.connect((SERVER_HOST, SERVER_PORT)) |
| |
| |
| handshake = b'\x00\x00' + len(SERVER_HOST).to_bytes(1, 'big') + SERVER_HOST.encode('utf-8') |
| handshake += SERVER_PORT.to_bytes(2, 'big') + b'\x01' |
| handshake = len(handshake).to_bytes(1, 'big') + handshake |
| |
| |
| status_request = b'\x01\x00' |
| |
| sock.send(handshake) |
| sock.send(status_request) |
| |
| |
| response_length = sock.recv(1024) |
| response_data = sock.recv(4096) |
| |
| latency = int((time.time() - start_time) * 1000) |
| |
| sock.close() |
| |
| |
| try: |
| |
| json_start = response_data.find(b'{') |
| if json_start != -1: |
| json_data = response_data[json_start:].decode('utf-8', errors='ignore') |
| server_info = json.loads(json_data) |
| |
| players = server_info.get('players', {}) |
| online = players.get('online', 0) |
| max_players = players.get('max', 0) |
| |
| server_status.update({ |
| "online": True, |
| "players": f"{online}/{max_players}", |
| "latency": latency, |
| "motd": server_info.get('description', {}).get('text', 'Minecraft Server'), |
| "last_check": datetime.now().isoformat() |
| }) |
| else: |
| server_status.update({ |
| "online": True, |
| "players": "?/?", |
| "latency": latency, |
| "last_check": datetime.now().isoformat() |
| }) |
| except: |
| server_status.update({ |
| "online": True, |
| "players": "?/?", |
| "latency": latency, |
| "last_check": datetime.now().isoformat() |
| }) |
| |
| except Exception as e: |
| server_status.update({ |
| "online": False, |
| "players": "0/0", |
| "latency": 0, |
| "motd": str(e), |
| "last_check": datetime.now().isoformat() |
| }) |
|
|
| def start_bot(name): |
| """Start a bot process""" |
| if name in bot_processes: |
| try: |
| proc = bot_processes[name] |
| proc.terminate() |
| proc.wait(timeout=3) |
| except: |
| pass |
| |
| try: |
| proc = subprocess.Popen( |
| ['node', '/tmp/bot.js', name, SERVER_HOST, str(SERVER_PORT), SERVER_VERSION], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| text=True, |
| bufsize=1 |
| ) |
| |
| bot_processes[name] = proc |
| |
| if name not in bots: |
| bots[name] = { |
| 'status': 'connecting', |
| 'start_time': time.time(), |
| 'deaths': 0, |
| 'reconnects': 0, |
| 'position': {'x': 0, 'y': 0, 'z': 0}, |
| 'health': 20, |
| 'food': 20 |
| } |
| else: |
| bots[name]['status'] = 'connecting' |
| bots[name]['start_time'] = time.time() |
| |
| threading.Thread(target=monitor_bot, args=(name,), daemon=True).start() |
| |
| print(f"🚀 Started bot: {name}") |
| return True |
| except Exception as e: |
| print(f"❌ Error starting bot {name}: {e}") |
| return False |
|
|
| def stop_bot(name): |
| """Stop a bot""" |
| if name in bot_processes: |
| try: |
| proc = bot_processes[name] |
| proc.terminate() |
| proc.wait(timeout=3) |
| del bot_processes[name] |
| if name in bots: |
| bots[name]['status'] = 'offline' |
| print(f"⏹️ Stopped bot: {name}") |
| except Exception as e: |
| print(f"⚠️ Error stopping bot {name}: {e}") |
|
|
| def monitor_bot(name): |
| """Monitor bot output""" |
| if name not in bot_processes: |
| return |
| |
| proc = bot_processes[name] |
| |
| try: |
| while proc.poll() is None: |
| line = proc.stdout.readline() |
| if not line: |
| continue |
| |
| try: |
| data = json.loads(line.strip()) |
| event = data.get('event') |
| |
| if name in bots: |
| if event in ['connected', 'heartbeat']: |
| bots[name]['status'] = 'online' |
| bots[name]['health'] = data.get('health', 20) |
| bots[name]['food'] = data.get('food', 20) |
| elif event == 'position': |
| bots[name]['position'] = { |
| 'x': data.get('x', 0), |
| 'y': data.get('y', 0), |
| 'z': data.get('z', 0) |
| } |
| bots[name]['health'] = data.get('health', 20) |
| bots[name]['food'] = data.get('food', 20) |
| elif event in ['death', 'died']: |
| bots[name]['deaths'] += 1 |
| elif event == 'reconnecting': |
| bots[name]['reconnects'] += 1 |
| bots[name]['status'] = 'connecting' |
| elif event in ['disconnected', 'error', 'kicked']: |
| bots[name]['status'] = 'offline' |
| |
| if event in ['connected', 'disconnected', 'kicked', 'error', 'death']: |
| print(f"[{name}] {event}") |
| |
| except json.JSONDecodeError: |
| pass |
| except: |
| pass |
| |
| if name in bots: |
| bots[name]['status'] = 'offline' |
|
|
| def rotation_manager(): |
| """Manage bot rotation""" |
| global current_bot_index, rotation_start_time |
| |
| while True: |
| try: |
| current_bot = BOT_NAMES[current_bot_index] |
| |
| if rotation_start_time is None or time.time() - rotation_start_time >= ROTATION_DURATION: |
| |
| for name in BOT_NAMES: |
| stop_bot(name) |
| |
| time.sleep(3) |
| |
| |
| start_bot(current_bot) |
| rotation_start_time = time.time() |
| print(f"🔄 Rotation: {current_bot} is now active") |
| |
| |
| current_bot_index = (current_bot_index + 1) % len(BOT_NAMES) |
| |
| time.sleep(5) |
| except Exception as e: |
| print(f"⚠️ Rotation error: {e}") |
| time.sleep(10) |
|
|
| def server_ping_loop(): |
| """Continuously ping server""" |
| while True: |
| try: |
| ping_minecraft_server() |
| time.sleep(1) |
| except Exception as e: |
| print(f"⚠️ Server ping error: {e}") |
| time.sleep(2) |
|
|
| def initialize(): |
| """Initialize bots""" |
| for name in BOT_NAMES: |
| bots[name] = { |
| 'status': 'offline', |
| 'start_time': 0, |
| 'deaths': 0, |
| 'reconnects': 0, |
| 'position': {'x': 0, 'y': 0, 'z': 0}, |
| 'health': 20, |
| 'food': 20 |
| } |
|
|
| |
|
|
| @app.route('/') |
| def index(): |
| """Serve HTML""" |
| return open('index.html').read() |
|
|
| @app.route('/api/status') |
| def api_status(): |
| """Get full status""" |
| current_bot = BOT_NAMES[(current_bot_index - 1) % len(BOT_NAMES)] if rotation_start_time else BOT_NAMES[current_bot_index] |
| next_bot = BOT_NAMES[current_bot_index] |
| |
| elapsed = int(time.time() - rotation_start_time) if rotation_start_time else 0 |
| remaining = max(0, ROTATION_DURATION - elapsed) |
| |
| bot_list = [] |
| for name in BOT_NAMES: |
| bot = bots.get(name, {}) |
| uptime = int(time.time() - bot.get('start_time', time.time())) if bot.get('status') == 'online' else 0 |
| |
| bot_list.append({ |
| 'name': name, |
| 'status': bot.get('status', 'offline'), |
| 'is_active': name == current_bot, |
| 'uptime': uptime, |
| 'deaths': bot.get('deaths', 0), |
| 'reconnects': bot.get('reconnects', 0), |
| 'position': bot.get('position', {'x': 0, 'y': 0, 'z': 0}), |
| 'health': bot.get('health', 20), |
| 'food': bot.get('food', 20) |
| }) |
| |
| return jsonify({ |
| 'server_info': { |
| 'host': SERVER_HOST, |
| 'port': SERVER_PORT, |
| 'version': SERVER_VERSION |
| }, |
| 'server_status': server_status, |
| 'rotation': { |
| 'current_bot': current_bot, |
| 'next_bot': next_bot, |
| 'elapsed': elapsed, |
| 'remaining': remaining, |
| 'queue': BOT_NAMES |
| }, |
| 'bots': bot_list |
| }) |
|
|
| @app.route('/api/next_rotation', methods=['POST']) |
| def api_next_rotation(): |
| """Force next rotation""" |
| global rotation_start_time |
| rotation_start_time = 0 |
| return jsonify({'success': True}) |
|
|
| def cleanup(sig=None, frame=None): |
| """Clean shutdown""" |
| print("\n🛑 Shutting down...") |
| for name in BOT_NAMES: |
| stop_bot(name) |
| sys.exit(0) |
|
|
| if __name__ == '__main__': |
| signal.signal(signal.SIGINT, cleanup) |
| signal.signal(signal.SIGTERM, cleanup) |
| |
| print("=" * 60) |
| print("🎮 MINECRAFT BOT MANAGER") |
| print("=" * 60) |
| |
| if not write_bot_script(): |
| print("❌ Failed to write bot script!") |
| sys.exit(1) |
| |
| print(f"📡 Server: {SERVER_HOST}:{SERVER_PORT}") |
| print(f"🤖 Bots: {', '.join(BOT_NAMES)}") |
| print(f"⏱️ Rotation: {ROTATION_DURATION//60} minutes per bot") |
| print("=" * 60) |
| |
| initialize() |
| |
| |
| threading.Thread(target=rotation_manager, daemon=True).start() |
| threading.Thread(target=server_ping_loop, daemon=True).start() |
| |
| print("🌐 Dashboard: http://localhost:7860") |
| print("=" * 60) |
| |
| app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False) |