-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrcon_service.py
More file actions
159 lines (125 loc) · 5.4 KB
/
rcon_service.py
File metadata and controls
159 lines (125 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
RCON Service - Handles communication with the Minecraft server.
RCON (Remote Console) is Minecraft's built-in protocol for sending commands
to the server remotely. It's like typing commands in the server console,
but from Python code.
This service provides a clean interface to:
- Check if the server is online
- Get the list of current players
- (Future) Get server performance metrics
"""
import asyncio
from typing import Optional
from mcrcon import MCRcon
from config import config
class RCONService:
"""
Service for communicating with Minecraft server via RCON protocol.
RCON requires:
1. Server has RCON enabled in server.properties
2. Correct password
3. Network access to the server on the RCON port (default 25575)
"""
def __init__(self):
"""Initialize RCON connection parameters from config."""
self.host = config.MC_SERVER_HOST
self.port = config.MC_RCON_PORT
self.password = config.MC_RCON_PASSWORD
async def execute_command(self, command: str) -> Optional[str]:
"""
Execute a command on the Minecraft server via RCON.
This is the core method - it connects to the server, sends a command,
and returns the response.
Args:
command: The Minecraft command to execute (e.g., "list", "tps")
Returns:
The server's response as a string, or None if connection failed.
Example:
response = await rcon.execute_command("list")
# Returns: "There are 3 of a max of 20 players online: Steve, Alex, Herobrine"
"""
# Run blocking RCON call in a thread pool to avoid blocking uvloop.
# uvicorn[standard] installs uvloop (libuv-based) which interferes with
# blocking socket timeouts when called directly from the event loop.
return await asyncio.to_thread(self._execute_sync, command)
def _execute_sync(self, command: str) -> Optional[str]:
"""
Synchronous RCON connection and command execution.
This does the actual RCON connection. It's called from the background
polling task, so blocking here doesn't affect HTTP request handling.
How it works:
1. Opens TCP connection to server on RCON port
2. Authenticates with password
3. Sends the command
4. Receives and returns the response
5. Closes connection
"""
try:
# Context manager automatically handles connection and cleanup
with MCRcon(self.host, self.password, port=self.port) as mcr:
response = mcr.command(command)
return response
except Exception as e:
# Log connection errors (server offline, auth failed, network issues, etc.)
print(f"RCON error: {e}")
return None
async def is_server_online(self) -> bool:
"""
Check if the Minecraft server is online and reachable via RCON.
Returns:
True if server responds to RCON, False otherwise.
This is a quick health check - if we can execute any command, server is online.
"""
response = await self.execute_command("list")
return response is not None
async def get_online_players(self) -> list[str]:
"""
Get the list of players currently online.
Returns:
List of player usernames (e.g., ["Steve", "Alex"])
Empty list if server is offline or no players online.
How it works:
1. Sends "list" command to server
2. Parses the response to extract player names
3. Returns them as a Python list
Example response from server:
"There are 3 of a max of 20 players online: Steve, Alex, Herobrine"
"""
response = await self.execute_command("list")
if not response:
# Server offline or command failed
return []
# Parse the response to extract player names
# Response format: "There are X of a max of Y players online: Player1, Player2, ..."
if "online:" in response:
# Split on "online:" and take the part after it
players_part = response.split("online:")[1].strip()
if players_part:
# Split by comma and strip whitespace from each name
return [name.strip() for name in players_part.split(",")]
# No players online or couldn't parse response
return []
async def get_max_players(self) -> int:
"""
Get the maximum number of players allowed on the server.
Returns:
Max player count (e.g., 20), or 0 if unavailable.
Parses the "list" command response to extract the max player count.
Example: "There are 3 of a max of 20 players online: ..." -> returns 20
"""
response = await self.execute_command("list")
if not response:
return 0
try:
# Parse "There are X of a max of Y players online"
# Split on "max of" and take the number before "players"
if "max of" in response:
max_part = response.split("max of")[1].split("players")[0].strip()
return int(max_part)
except (ValueError, IndexError):
# Parsing failed
pass
return 0
# Create a global RCON service instance
# This is reused across requests instead of creating new connections each time
rcon_service = RCONService()