Skip to content

Commit c31d13f

Browse files
committed
Replaced the method in rcon.py with a class named RconClient
1 parent a235196 commit c31d13f

File tree

2 files changed

+90
-57
lines changed

2 files changed

+90
-57
lines changed

mojang/api/net/rcon.py

Lines changed: 82 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import struct
22
import enum
33
import socket
4-
import time
4+
import select
55

66
class RconPacketTypes(enum.IntEnum):
77
SERVERDATA_AUTH = 3
@@ -10,53 +10,96 @@ class RconPacketTypes(enum.IntEnum):
1010
SERVERDATA_RESPONSE_VALUE = 0
1111

1212

13-
def _request_id():
14-
return int(time.time())
13+
class RconPacket:
14+
__id__ = 0
15+
id: int
16+
type: int
17+
payload: str
1518

16-
def _write_packet(sock: socket.socket, _type: int, payload: str):
17-
# Prepare packet
18-
req_id = _request_id()
19-
packet = struct.pack('<ii{}s2s'.format(len(payload)), req_id, _type, payload.encode('ascii'), b'\0\0')
20-
header = struct.pack('<i', len(packet))
19+
def __new__(cls, *args, **kwargs):
20+
obj = object.__new__(cls)
21+
if len(args) == 1:
22+
req_id, _type, payload = struct.unpack_from('<ii{}s'.format(len(args[0]) - 10), args[0])
23+
setattr(obj, 'id', req_id)
24+
setattr(obj, 'type', _type)
25+
setattr(obj, 'payload', payload.decode('ascii'))
26+
elif len(kwargs) >= 2:
27+
setattr(obj, 'id', kwargs.get('id', cls.get_id()))
28+
setattr(obj, 'type', kwargs['type'])
29+
setattr(obj, 'payload', kwargs['payload'])
30+
else:
31+
raise Exception('Wrong arguments')
2132

22-
# Send packet
23-
data_sent = sock.send(header + packet)
24-
return req_id, data_sent
33+
return obj
2534

26-
def _read_packet(sock: socket.socket):
27-
# Recv data
28-
length_bytes = sock.recv(4)
29-
length = struct.unpack('<i', length_bytes)[0]
30-
data = bytes()
35+
@classmethod
36+
def get_id(cls):
37+
cls.__id__ += 1
38+
return cls.__id__
3139

32-
# Ensure that all the data was read
33-
while length > len(data):
34-
data += sock.recv(length - len(data))
40+
@property
41+
def data(self):
42+
data = struct.pack('<ii{}s2s'.format(len(self.payload)), self.id, self.type, self.payload.encode('ascii'), b'\0\0')
43+
return data
3544

36-
# Parse data
37-
r_req_id, r_type, payload = struct.unpack('<ii{}s'.format(len(data) - 8), data)
38-
return r_req_id, r_type, payload
3945

40-
def authenticate(sock: socket.socket, password: str):
41-
req_id = _write_packet(sock, RconPacketTypes.SERVERDATA_AUTH, password)[0]
46+
class RconClient:
4247

43-
r_req_id, r_type, _ = _read_packet(sock)
48+
def __init__(self, host: str, port: int, password: str):
49+
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
50+
self.__host = (host, port)
51+
self.__password = password
4452

45-
if r_type != RconPacketTypes.SERVERDATA_AUTH_RESPONSE or r_req_id == -1:
46-
raise Exception('Authentication failed')
53+
def _recv(self):
54+
if select.select([self.__sock], [], [], 2)[0]:
55+
with self.__sock.makefile('rb') as stream:
56+
packet_length = struct.unpack('<i', stream.read(4))[0]
57+
58+
data = b''
59+
while len(data) < packet_length:
60+
_data = stream.read(packet_length - len(data))
61+
if _data:
62+
data += _data
4763

48-
def run_command(sock: socket.socket, command: str):
49-
req_id = _write_packet(sock, RconPacketTypes.SERVERDATA_EXECOMMAND, command)[0]
64+
return RconPacket(data)
5065

51-
fpayload = bytes()
52-
try:
53-
# Receive message until no more
54-
while 1:
55-
r_req_id, r_type, payload = _read_packet(sock)
66+
def _send(self, packet: RconPacket):
67+
if select.select([], [self.__sock], [], 2)[1]:
68+
with self.__sock.makefile('wb') as stream:
69+
stream.write(struct.pack('<i', len(packet.data)))
70+
stream.write(packet.data)
71+
72+
def connect(self):
73+
self.__sock.connect(self.__host)
74+
self.__sock.setblocking(False)
75+
self._authenticate()
76+
77+
def _authenticate(self):
78+
packet = RconPacket(type=RconPacketTypes.SERVERDATA_AUTH, payload=self.__password)
79+
self._send(packet)
5680

57-
if r_type != RconPacketTypes.SERVERDATA_RESPONSE_VALUE or r_req_id != req_id:
58-
raise Exception('Error occured while executing a command')
81+
r_packet = self._recv()
5982

60-
fpayload += payload
61-
finally:
62-
return fpayload.decode('ascii')
83+
if r_packet.type != RconPacketTypes.SERVERDATA_AUTH_RESPONSE or r_packet.id == -1:
84+
raise Exception('Authentication Failed !')
85+
86+
def run_cmd(self, cmd: str):
87+
packet = RconPacket(type=RconPacketTypes.SERVERDATA_EXECOMMAND, payload=cmd)
88+
self._send(packet)
89+
90+
response = ''
91+
try:
92+
while 1:
93+
r_packet = self._recv()
94+
if r_packet is None:
95+
break
96+
97+
if r_packet.type != RconPacketTypes.SERVERDATA_RESPONSE_VALUE or r_packet.id != packet.id:
98+
raise Exception('Error while getting the response')
99+
100+
response += r_packet.payload
101+
finally:
102+
return response
103+
104+
def close(self):
105+
self.__sock.close()

mojang/utils/server.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,30 @@
11
import socket
2-
from ..api.net import query, rcon, slp
2+
from ..api.net import query, slp
3+
from ..api.net.rcon import RconClient
34

45
class Server:
56

67
def __init__(self, hostname: str, port: int, rcon_port=None, rcon_password=None, query_port=None):
78
self.__hostname = hostname
89
self.__port = port
9-
self.__rcon_password = rcon_password
10-
self.__rcon_port = rcon_port
10+
self.__rcon_client = RconClient(self.__hostname, rcon_port, rcon_password)
1111
self.__query_port = query_port
1212

13-
self.__rcon_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
14-
self.__rcon_sock.settimeout(2)
15-
1613
def __enter__(self):
1714
return self
1815

1916
def __exit__(self, *args):
2017
self.close()
2118

19+
def close(self):
20+
self.__rcon_client.close()
21+
2222
# Rcon
2323
def connect_rcon(self):
24-
if not self.__rcon_password or not self.__rcon_port:
25-
raise Exception("Missing rcon port or rcon password")
26-
27-
self.__rcon_sock.connect((self.__hostname, self.__rcon_port))
28-
rcon.authenticate(self.__rcon_sock, self.__rcon_password)
29-
30-
def _close_rcon(self):
31-
self.__rcon_sock.close()
24+
self.__rcon_client.connect()
3225

3326
def run_cmd(self, command: str):
34-
return rcon.run_command(self.__rcon_sock, command)
27+
return self.__rcon_client.run_cmd(command)
3528

3629
# Query
3730
def get_stats(self, full=False):
@@ -53,6 +46,3 @@ def get_stats(self, full=False):
5346
def ping(self):
5447
return slp.ping(self.__hostname, self.__port)
5548

56-
57-
def close(self):
58-
self._close_rcon()

0 commit comments

Comments
 (0)