""" RCON tool for Minecraft server interaction. Provides: - rcon_execute(command) -> send RCON command, return result - get_server_status() -> player list, time, difficulty - get_player_info(player) -> position, health, gamemode """ import re import socket import struct import time from typing import Dict, Any, Optional, List def rcon_send(cmd: str, host: str = '127.0.0.1', port: int = 25577, password: str = 'REDACTED_RCON', timeout: float = 5.0) -> str: """Send a single RCON command and return the response text.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout) try: s.connect((host, int(port))) def pkt(req_id: int, pkt_type: int, payload: str) -> bytes: p = payload.encode('utf-8') + b'\x00\x00' return struct.pack(' Dict[str, Any]: """Execute an RCON command and return structured result.""" result = rcon_send(command, self.host, self.port, self.password) is_error = any(w in result.lower() for w in [ 'unknown', 'incorrect argument', 'expected', 'syntax error', 'error', 'unparseable', 'invalid', ]) return { 'command': command, 'result': result.strip(), 'success': not is_error, } def get_server_status(self) -> Dict[str, Any]: """Get server state: players, time, difficulty.""" players_raw = rcon_send('list', self.host, self.port, self.password) time_raw = rcon_send('time query daytime', self.host, self.port, self.password) diff_raw = rcon_send('difficulty', self.host, self.port, self.password) players = [] m = re.search(r'online:\s*(.*)', players_raw) if m and m.group(1).strip(): players = [p.strip() for p in m.group(1).split(',') if p.strip()] time_m = re.search(r'(\d+)', time_raw) ticks = int(time_m.group(1)) if time_m else 0 diff_m = re.search(r'difficulty is (\w+)', diff_raw) difficulty = diff_m.group(1) if diff_m else 'unknown' return { 'players_online': players, 'player_count': len(players), 'time_ticks': ticks, 'difficulty': difficulty, } def get_player_info(self, player: str) -> Dict[str, Any]: """Get player position, health, gamemode.""" pos_raw = rcon_send(f'data get entity {player} Pos', self.host, self.port, self.password) health_raw = rcon_send(f'data get entity {player} Health', self.host, self.port, self.password) gm_raw = rcon_send(f'data get entity {player} playerGameType', self.host, self.port, self.password) pos = None pos_m = re.findall(r'(-?[\d.]+)d', pos_raw) if pos_m and len(pos_m) >= 3: pos = {'x': float(pos_m[0]), 'y': float(pos_m[1]), 'z': float(pos_m[2])} health_m = re.search(r'([\d.]+)f', health_raw) health = float(health_m.group(1)) if health_m else None gm_m = re.search(r'data:\s*(\d+)', gm_raw) gm_map = {0: 'survival', 1: 'creative', 2: 'adventure', 3: 'spectator'} gamemode = gm_map.get(int(gm_m.group(1)), 'unknown') if gm_m else None return { 'player': player, 'position': pos, 'health': health, 'gamemode': gamemode, 'online': pos is not None, }