You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

98 lines
2.9 KiB

"""Synchronous client."""
from socket import socket
from typing import Optional
from rcon.exceptions import RequestIdMismatch, WrongPassword
from rcon.proto import Packet, Type
__all__ = ['Client']
class Client:
"""An RCON client."""
__slots__ = ('_socket', 'host', 'port', 'passwd')
def __init__(
self, host: str, port: int, *,
timeout: Optional[float] = None,
passwd: Optional[str] = None
):
"""Initializes the base client with the SOCK_STREAM socket type."""
self._socket = socket()
self.host = host
self.port = port
self.timeout = timeout
self.passwd = passwd
def __enter__(self):
"""Attempts an auto-login if a password is set."""
self._socket.__enter__()
self.connect(login=True)
return self
def __exit__(self, typ, value, traceback):
"""Delegates to the underlying socket's exit method."""
return self._socket.__exit__(typ, value, traceback)
@property
def timeout(self) -> float:
"""Returns the socket timeout."""
return self._socket.gettimeout()
@timeout.setter
def timeout(self, timeout: float):
"""Sets the socket timeout."""
self._socket.settimeout(timeout)
def connect(self, login: bool = False) -> None:
"""Connects the socket and attempts a
login if wanted and a password is set.
"""
self._socket.connect((self.host, self.port))
if login and self.passwd is not None:
self.login(self.passwd)
def close(self) -> None:
"""Closes the socket connection."""
self._socket.close()
def communicate(self, packet: Packet) -> Packet:
"""Sends and receives a packet."""
with self._socket.makefile('wb') as file:
file.write(bytes(packet))
return self.read()
def read(self) -> Packet:
"""Reads a packet."""
with self._socket.makefile('rb') as file:
return Packet.read(file)
def login(self, passwd: str, *, encoding: str = 'utf-8') -> bool:
"""Performs a login."""
request = Packet.make_login(passwd, encoding=encoding)
response = self.communicate(request)
# Wait for SERVERDATA_AUTH_RESPONSE according to:
# https://developer.valvesoftware.com/wiki/Source_RCON_Protocol
while response.type != Type.SERVERDATA_AUTH_RESPONSE:
response = self.read()
if response.id == -1:
raise WrongPassword()
return True
def run(self, command: str, *args: str, encoding: str = 'utf-8') -> str:
"""Runs a command."""
request = Packet.make_command(command, *args, encoding=encoding)
response = self.communicate(request)
if response.id != request.id:
raise RequestIdMismatch(request.id, response.id)
return response.payload.decode(encoding)