Skip to content

Commit

Permalink
Fixed some timeout bugs, bumped version
Browse files Browse the repository at this point in the history
  • Loading branch information
OwenCochell committed Apr 8, 2023
1 parent 7567b14 commit 64ac714
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 29 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -42,6 +43,7 @@ if rcon.login("password"):

resp = rcon.command("broadcast Hello RCON!")
```

# Installation

You can install mctools via pip:
Expand Down Expand Up @@ -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) --------------------
Expand All @@ -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:

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!
Thank you for reading!
12 changes: 12 additions & 0 deletions docs/source/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ All clients have the following methods:
3. stop()
4. raw_send()
5. get_formatter()
6. set_timeout()

is_connected
------------
Expand Down Expand Up @@ -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. <format.html>`_.

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
=====================

Expand Down
2 changes: 1 addition & 1 deletion mctools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

# Define some metadata here:

__version__ = '1.2.0'
__version__ = '1.2.1'
__author__ = 'Owen Cochell'
20 changes: 10 additions & 10 deletions mctools/mclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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
Expand Down
61 changes: 49 additions & 12 deletions mctools/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from mctools.encoding import PINGEncoder
from mctools.errors import RCONCommunicationError, RCONLengthError, ProtoConnectionClosed

# Default timeout:
DEFAULT_TIMEOUT = 60


class BaseProtocol(object):
"""
Expand All @@ -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.
Expand Down Expand Up @@ -78,6 +84,8 @@ def read_tcp(self, length):

byts = byts + last

print(byts)

if last == b'':

# We received nothing, lets close this connection:
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand Down

0 comments on commit 64ac714

Please sign in to comment.