diff --git a/README.md b/README.md index 8bc9f91..0b67ce2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ and only uses the python standard library. Just download and go! # Example Send a command to the Minecraft server via rcon: + ```python from mctools import RCONClient # Import the RCONClient @@ -42,6 +43,7 @@ if rcon.login("password"): resp = rcon.command("broadcast Hello RCON!") ``` + # Installation You can install mctools via pip: @@ -73,7 +75,7 @@ if rcon.login("password"): §6Vault: §fAll commands for Vault §6WorldEdit: §fAll commands for WorldEdit - As you can see, this text is somewhat hard to read. If you told mctools to remove the formatting characters, + As you can see, this text is somewhat hard to read. If you told mctools to remove the formatting characters, then the output will look like this: --------- Help: Index (1/40) -------------------- @@ -88,16 +90,16 @@ if rcon.login("password"): WorldEdit: All commands for WorldEdit Much easier to read and process. mctools handles this operation automatically, so you don't have to. - mctools also handles situations where content is sent in ChatObject notation, and can extract messages from the + mctools also handles situations where content is sent in ChatObject notation, and can extract messages from the player sample list. - To learn more about formatters, and how to create your own, + To learn more about formatters, and how to create your own, then please check out the [formatting documentation](https://mctools.readthedocs.io/en/latest/format.html). # MCLI - mctools Command Line Interface mctools is shipped with a CLI front end called mcli, which you can use to start rcon sessions, get stats - via query/ping, check if a Minecraft server is up, ect. + via query/ping, check if a Minecraft server is up, ect. After installing mctools(through pip or setuptools), you can invoke mcli like so: @@ -140,6 +142,18 @@ if rcon.login("password"): # Changelog +## 1.2.1 + +We now correctly disable length checking in RCONClient if specified by the user. + +The 'set_timeout()' method in BaseProtocol will now work correctly even if the protocol object is not started. +This change applies to all clients, as they all use this method. + +Added a 'DEFAULT_TIMEOUT' constant and moved some protocol attributes to BaseProtocol to prevent redundancy. +Protocol objects now init the parent BaseProtocol object. + +Fixed some spelling errors, added more type hinting, made some more minor changes to the documentation. + ## 1.2.0 Clients (of any type!) can now be started after being stopped, @@ -182,7 +196,7 @@ stopping/starting clients a lot better. - We now raise an exception if the RCON write data is too big, instead of leaving the connection in a unstable state ### Features Added: - + - Users can now specify the format method on a per-call basis - Users can change the timeout value after the object is instantiated - The RCON login check feature can be disabled, meaning that you can attempt to send packets @@ -221,4 +235,4 @@ stopping/starting clients a lot better. mctools offers a pythonic, reliable way to interact with Minecraft servers, without being too complicated. Please check the documentation for more information. More features (hopefully) coming soon! - Thank you for reading! \ No newline at end of file + Thank you for reading! diff --git a/docs/source/client.rst b/docs/source/client.rst index 380861c..ede0553 100644 --- a/docs/source/client.rst +++ b/docs/source/client.rst @@ -38,6 +38,7 @@ All clients have the following methods: 3. stop() 4. raw_send() 5. get_formatter() + 6. set_timeout() is_connected ------------ @@ -125,6 +126,17 @@ which will allow you to fine tune the formatter to your use. More information can be found in the `Formatter Tutorial. `_. +set_timeout +----------- + +This function sets the timeout for network operations: + +.. code-block:: python + + client.set_timeout(10) + +The above statement will set the timeout value to 10 seconds. + Instantiating Clients ===================== diff --git a/mctools/__init__.py b/mctools/__init__.py index 6b14823..a1f3d91 100644 --- a/mctools/__init__.py +++ b/mctools/__init__.py @@ -4,5 +4,5 @@ # Define some metadata here: -__version__ = '1.2.0' +__version__ = '1.2.1' __author__ = 'Owen Cochell' diff --git a/mctools/mclient.py b/mctools/mclient.py index f64d671..7de8bbf 100644 --- a/mctools/mclient.py +++ b/mctools/mclient.py @@ -11,7 +11,7 @@ from typing import Union -from mctools.protocol import BaseProtocol, RCONProtocol, QUERYProtocol, PINGProtocol +from mctools.protocol import BaseProtocol, RCONProtocol, QUERYProtocol, PINGProtocol, DEFAULT_TIMEOUT from mctools.packet import RCONPacket, QUERYPacket, PINGPacket from mctools.formattertools import BaseFormatter, FormatterCollection, DefaultFormatter, QUERYFormatter, PINGFormatter from mctools.errors import RCONAuthenticationError, RCONMalformedPacketError @@ -175,7 +175,7 @@ class RCONClient(BaseClient): :type timeout: int """ - def __init__(self, host, port=25575, reqid=None, format_method=BaseClient.REPLACE, timeout=60): + def __init__(self, host, port=25575, reqid=None, format_method=BaseClient.REPLACE, timeout=DEFAULT_TIMEOUT): self.proto: RCONProtocol = RCONProtocol(host, port, timeout) # RCONProtocol, used for communicating with RCON server self.formatters: FormatterCollection = FormatterCollection() # Formatters instance, formats text from server @@ -346,7 +346,7 @@ def raw_send(self, reqtype: int, payload: str, frag_check: bool=True, length_che return pack - def login(self, password): + def login(self, password) -> bool: """ Authenticates with the RCON server using the given password. If we are already authenticated, then we simply return True. @@ -383,7 +383,7 @@ def login(self, password): return True - def authenticate(self, password): + def authenticate(self, password) -> bool: """ Convenience function, does the same thing that 'login' does, authenticates you with the RCON server. @@ -421,14 +421,14 @@ def command(self, com: str, check_auth: bool=True, format_method: int=None, retu Do so at your own risk! :type frag_check: bool - :param length_check: Determines if we should check and handel outgoing packet length + :param length_check: Determines if we should check and handle outgoing packet length .. warning:: Disabling length checks could lead to instability! Do so at your own risk! - :type legnth_check: bool + :type length_check: bool :return: Response text from server :rtype: str, RCONPacket :raises: @@ -457,7 +457,7 @@ def command(self, com: str, check_auth: bool=True, format_method: int=None, retu # Sending command packet: - pack = self.raw_send(self.proto.COMMAND, com, frag_check=frag_check) + pack = self.raw_send(self.proto.COMMAND, com, frag_check=frag_check, length_check=length_check) # Get the formatted content: @@ -552,7 +552,7 @@ class QUERYClient(BaseClient): :type timeout: int """ - def __init__(self, host: str, port: int=25565, reqid: int=None, format_method: int=BaseClient.REPLACE, timeout: int=60): + def __init__(self, host: str, port: int=25565, reqid: int=None, format_method: int=BaseClient.REPLACE, timeout: int=DEFAULT_TIMEOUT): self.proto: QUERYProtocol = QUERYProtocol(host, port, timeout) # Query protocol instance self.formatters: FormatterCollection = FormatterCollection() # Formatters instance @@ -595,7 +595,7 @@ def is_connected(self) -> bool: :rtype: bool """ - return self.proto.started + return self.proto.connected def raw_send(self, reqtype: int, chall: Union[str, None], packet_type: str) -> QUERYPacket: """ @@ -810,7 +810,7 @@ class PINGClient(BaseClient): :type proto_num: int """ - def __init__(self, host: str, port: int=25565, reqid: int=None, format_method: int=BaseClient.REPLACE, timeout: int=60, proto_num: int=0): + def __init__(self, host: str, port: int=25565, reqid: int=None, format_method: int=BaseClient.REPLACE, timeout: int=DEFAULT_TIMEOUT, proto_num: int=0): self.proto: PINGProtocol = PINGProtocol(host, port, timeout) self.host = host # Host of the Minecraft server diff --git a/mctools/protocol.py b/mctools/protocol.py index 1372af6..bd5831a 100644 --- a/mctools/protocol.py +++ b/mctools/protocol.py @@ -10,6 +10,9 @@ from mctools.encoding import PINGEncoder from mctools.errors import RCONCommunicationError, RCONLengthError, ProtoConnectionClosed +# Default timeout: +DEFAULT_TIMEOUT = 60 + class BaseProtocol(object): """ @@ -23,6 +26,9 @@ def __init__(self) -> None: self.sock: socket.socket + self.timeout = DEFAULT_TIMEOUT # Defines and sets the timeout value + self.connected = False # Value determining if we are connected + def start(self): """ Method to start the connection to remote entity. @@ -78,6 +84,8 @@ def read_tcp(self, length): byts = byts + last + print(byts) + if last == b'': # We received nothing, lets close this connection: @@ -127,11 +135,23 @@ def set_timeout(self, timeout): :param timeout: Value in seconds to set the timeout to :type timeout: int + + .. versionadded:: 1.1.0 + + This method now works before the protocol object is started """ - # Set the timeout: + # First, set the timeout value: + + self.timeout = timeout - self.sock.settimeout(timeout) + # Next, determine if we should set the socket timeout: + + if self.connected: + + # Set the timeout: + + self.sock.settimeout(timeout) def __del__(self): """ @@ -161,14 +181,19 @@ class RCONProtocol(BaseProtocol): def __init__(self, host, port, timeout): + # Init super class + super().__init__() + self.host = host # Host of the RCON server self.port = int(port) # Port of the RCON server self.LOGIN = 3 # Packet type used for logging in self.COMMAND = 2 # Packet type for issuing a command self.RESPONSE = 0 # Packet type for response self.MAX_SIZE = 4096 # Maximum packet size - self.connected = False # Value determining if we are connected - self.timeout = timeout # Global timeout value + + # Finally, set the timeout: + + self.set_timeout(timeout) def start(self): """ @@ -276,32 +301,39 @@ class QUERYProtocol(BaseProtocol): def __init__(self, host, port, timeout): + # Init super class + super().__init__() + self.host = host # Host of the Query server self.port = int(port) # Port of the Query server - self.started = False # Determining if we have started communicating with the Query server - self.timeout = timeout + + # Finally, set the timeout: + + self.set_timeout(timeout) def start(self): """ Sets the protocol object as ready to communicate. """ - self.started = True - # Create an ip4 UPD socket: self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Set the timeout: - + self.sock.settimeout(self.timeout) # Setting timeout value for socket + # Set our connected status: + + self.connected = True + def stop(self): """ Sets the protocol object as not ready to communicate. """ - self.started = False + self.connected = False def send(self, pack): """ @@ -352,10 +384,15 @@ class PINGProtocol(BaseProtocol): def __init__(self, host, port, timeout): + # Init super class + super().__init__() + self.host = host # Host of the Minecraft server self.port = int(port) # Port of the Minecraft server - self.connected = False # Value determining if we are connected - self.timeout = timeout + + # Finally, set the timeout: + + self.set_timeout(timeout) def start(self): """