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