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
# ---------------------------------------------------------------------------
def rcon(cmd, host='127.0.0.1', port=25575, password='REDACTED_RCON'):
try:
s = socket.socket()
s.settimeout(5)
s.connect((host, port))
def pkt(i, t, p):
p = p.encode() + b'\x00\x00'
return struct.pack('<iii', len(p) + 8, i, t) + p
s.sendall(pkt(1, 3, password))
time.sleep(0.2)
s.recv(4096)
s.sendall(pkt(2, 2, cmd))
time.sleep(0.2)
r = s.recv(4096)
s.close()
return r[12:-2].decode(errors='replace')
except Exception as e:
log.error(f"RCON error executing '{cmd}': {e}")
# --- Persistent RCON connection pool ---
_rcon_pool = {}
_rcon_pool_lock = threading.Lock()
class _PersistentRCON:
def __init__(self, host, port, password):
self.host, self.port, self.password = host, port, password
self._sock = None
self._lock = threading.Lock()
self._connected = False
self._req_id = 0
def _connect(self):
if self._sock:
try: self._sock.close()
except: pass
self._sock = socket.socket()
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 ''
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
# ---------------------------------------------------------------------------
@@ -1497,6 +1535,10 @@ def write_training_audit(player, mode, user_message, commands_generated,
"server_type": config.get("server_type", "vanilla"),
"version": "1.21.x",
"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 = {
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),