import asyncio import websockets import os from http import HTTPStatus # --- Configuration --- HTTP_PORT = 7860 WEBSOCKET_PORT = 8765 MINECRAFT_RAM = "12G" PLAYIT_SECRET = os.environ.get('PLAYIT_SECRET') # A set to keep track of all connected WebSocket clients connected_clients = set() async def start_subprocess(command): """Starts a subprocess and returns the process object.""" print(f"Starting subprocess: {' '.join(command)}") process = await asyncio.create_subprocess_exec( *command, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) return process async def broadcast(message): """Sends a message to all connected clients.""" if connected_clients: await asyncio.wait([client.send(message) for client in connected_clients]) async def read_stream(stream, prefix=""): """Reads lines from a stream and broadcasts them.""" while True: line = await stream.readline() if not line: break output = line.decode('utf-8', errors='replace').strip() print(f"{prefix}{output}") await broadcast(f"{prefix}{output}") async def websocket_handler(websocket, path): """Handles WebSocket connections.""" global mc_process print("Client connected to WebSocket.") connected_clients.add(websocket) try: # Keep the connection open and listen for commands from the client async for message in websocket: print(f"Received command: {message}") if mc_process and not mc_process.stdin.is_closing(): # Add a newline character, as Minecraft expects command = message.strip() + '\n' mc_process.stdin.write(command.encode('utf-8')) await mc_process.stdin.drain() else: print("Minecraft process is not running or stdin is closed.") await websocket.send("Error: Minecraft server is not running.") except websockets.exceptions.ConnectionClosed: print("Client disconnected.") finally: connected_clients.remove(websocket) async def http_handler(reader, writer): """Handles basic HTTP requests to serve the HTML file.""" try: request_line = await reader.readline() if not request_line: return # Simple request parsing method, path, _ = request_line.decode().split(' ') if method == "GET" and path == "/": writer.write(b"HTTP/1.1 200 OK\r\n") writer.write(b"Content-Type: text/html\r\n") writer.write(b"\r\n") with open("index.html", "rb") as f: writer.write(f.read()) else: writer.write(b"HTTP/1.1 404 Not Found\r\n\r\n") writer.write(b"Not Found") await writer.drain() except Exception as e: print(f"HTTP Server Error: {e}") finally: writer.close() async def main(): global mc_process # 1. Start playit.gg if PLAYIT_SECRET: playit_command = ['playit', '--secret', PLAYIT_SECRET] playit_process = await start_subprocess(playit_command) # Create tasks to forward playit's output to the console logs asyncio.create_task(read_stream(playit_process.stdout, "[playit] ")) asyncio.create_task(read_stream(playit_process.stderr, "[playit/err] ")) else: print("Warning: PLAYIT_SECRET not set. Tunnel will not be started.") # 2. Start Minecraft server mc_command = [ 'java', f'-Xmx{MINECRAFT_RAM}', '-Xms1G', '-jar', 'server.jar', 'nogui' ] mc_process = await start_subprocess(mc_command) # Create tasks to forward Minecraft's output to websockets asyncio.create_task(read_stream(mc_process.stdout, "")) asyncio.create_task(read_stream(mc_process.stderr, "[mc/err] ")) # 3. Start WebSocket and HTTP servers websocket_server = await websockets.serve(websocket_handler, "0.0.0.0", WEBSOCKET_PORT) print(f"WebSocket server started on port {WEBSOCKET_PORT}") http_server = await asyncio.start_server(http_handler, "0.0.0.0", HTTP_PORT) print(f"HTTP server started on port {HTTP_PORT}") # Wait for servers to close (which they won't, unless an error occurs) await asyncio.gather( websocket_server.wait_closed(), http_server.wait_closed() ) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: print("Server shutting down.")