Persistent RCON — single connection per server, auto-reconnect

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-20 18:25:07 -04:00
parent 0556d0ca16
commit 8a59eff714
+60 -18
View File
@@ -355,26 +355,64 @@ def process_bug_log(player: str, description: str, config):
# RCON # RCON
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def rcon(cmd, host='127.0.0.1', port=25575, password='REDACTED_RCON'): # --- Persistent RCON connection pool ---
try: _rcon_pool = {}
s = socket.socket() _rcon_pool_lock = threading.Lock()
s.settimeout(5)
s.connect((host, port)) class _PersistentRCON:
def pkt(i, t, p): def __init__(self, host, port, password):
p = p.encode() + b'\x00\x00' self.host, self.port, self.password = host, port, password
return struct.pack('<iii', len(p) + 8, i, t) + p self._sock = None
s.sendall(pkt(1, 3, password)) self._lock = threading.Lock()
time.sleep(0.2) self._connected = False
s.recv(4096) self._req_id = 0
s.sendall(pkt(2, 2, cmd))
time.sleep(0.2) def _connect(self):
r = s.recv(4096) if self._sock:
s.close() try: self._sock.close()
return r[12:-2].decode(errors='replace') except: pass
except Exception as e: self._sock = socket.socket()
log.error(f"RCON error executing '{cmd}': {e}") self._sock.settimeout(10)
self._sock.connect((self.host, self.port))
data = self.password.encode() + b'\x00\x00'
self._sock.sendall(struct.pack('<iii', len(data)+8, 1, 3) + data)
time.sleep(0.1)
self._sock.recv(4096)
self._connected = True
def command(self, cmd):
with self._lock:
for attempt in range(2):
try:
if not self._connected or not self._sock:
self._connect()
self._req_id += 1
data = cmd.encode() + b'\x00\x00'
self._sock.sendall(struct.pack('<iii', len(data)+8, self._req_id, 2) + data)
time.sleep(0.05)
raw = self._sock.recv(4096)
if len(raw) < 14:
self._connected = False
if attempt == 0: continue
return ''
return raw[12:-2].decode(errors='replace')
except (OSError, socket.timeout, ConnectionResetError, BrokenPipeError) as e:
self._connected = False
self._sock = None
if attempt == 0:
time.sleep(0.3)
continue
log.error(f"RCON error executing '{cmd}': {e}")
return ''
return '' return ''
def rcon(cmd, host='127.0.0.1', port=25575, password='REDACTED_RCON'):
key = f"{host}:{port}"
with _rcon_pool_lock:
if key not in _rcon_pool:
_rcon_pool[key] = _PersistentRCON(host, port, password)
return _rcon_pool[key].command(cmd)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Server context # Server context
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -1497,6 +1535,10 @@ def write_training_audit(player, mode, user_message, commands_generated,
"server_type": config.get("server_type", "vanilla"), "server_type": config.get("server_type", "vanilla"),
"version": "1.21.x", "version": "1.21.x",
"online_players": context.get("online_players", []), "online_players": context.get("online_players", []),
"player_details": context.get("player_details", {}),
"time_of_day": context.get("time_of_day", "unknown"),
"weather": context.get("weather", "unknown"),
"world_border": context.get("world_border"),
} }
entry = { entry = {
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), "timestamp": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),