# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['mcproto',
 'mcproto.packets',
 'mcproto.packets.v757',
 'mcproto.packets.v757.handshaking',
 'mcproto.packets.v757.status',
 'mcproto.protocol',
 'mcproto.utils']

package_data = \
{'': ['*']}

install_requires = \
['asyncio-dgram>=2.1.2,<3.0.0']

setup_kwargs = {
    'name': 'mcproto',
    'version': '0.1.2',
    'description': 'Library providing easy interactions with minecraft sevrers',
    'long_description': '# <img src="https://i.imgur.com/nPCcxts.png" height=25> McProto\n\n[![discord chat](https://img.shields.io/discord/936788458939224094.svg?logo=Discord)](https://discord.gg/C2wX7zduxC)\n![supported python versions](https://img.shields.io/pypi/pyversions/mcproto.svg)\n[![current PyPI version](https://img.shields.io/pypi/v/mcproto.svg)](https://pypi.org/project/mcproto/)\n[![Validation](https://github.com/ItsDrike/mcproto/actions/workflows/validation.yml/badge.svg)](https://github.com/ItsDrike/mcproto/actions/workflows/validation.yml)\n[![Unit tests](https://github.com/ItsDrike/mcproto/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/ItsDrike/mcproto/actions/workflows/unit-tests.yml)\n\nThis is a heavily Work-In-Progress library, which attempts to be a full wrapper around the minecraft protocol, allowing\nfor simple interactions with minecraft servers, and perhaps even for use as a base to a full minecraft server\nimplementation in python (though the speed will very likely be quite terrible, making it probably unusable as any\nreal-world playable server).\n\nCurrently, the library is very limited and doesn\'t yet have any documentation, so while contributions are welcome, fair\nwarning that there is a lot to comprehend in the code-base and it may be challenging to understand it all.\n\n## Installation\n\nMcproto is available on [PyPI](https://pypi.org/project/mcproto/), and can be installed trivially with:\n\n```bash\npython3 -m pip install mcproto\n```\n\nThat said, as mcproto is still in development, the PyPI version will likely go out of date quite soon. This means that\nit may lack some already implemented features, or contain already fixed bugs. For that reason, you can also consider\ninstalling mcproto through git, to get the most recent version. But know that while this will mean you\'ll be getting\nall of the new features, this version is much more likely to contain bugs than the one on PyPI, so make your decision\nwisely. To install the latest version from git, you can use the command below:\n\n```bash\npython3 -m pip install \'mcproto @ git+https://github.com/py-mine/mcproto@main\'\n```\n\nAlternatively, if you want to poke around with mcproto\'s code, you can always include mcproto as a full [git\nsubmodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to your project, or just git clone it directly and play\naround with it from REPL.\n\n## Examples\n\nSince there is no documentation, to satisfy some curious minds that really want to use this library even in this\nheavily unfinished state, here\'s a few simple snippets of it in practice:\n\n### Manual communication with the server\n\nAs sending entire packets is still being worked on, the best solution to communicate with a server is to send the data\nmanually, using our connection reader/writer, and buffers (being readers/writers, but only from bytearrays as opposed\nto using an actual connection).\n\nFair warning: This example is pretty long, but that\'s because it aims to explain the minecraft protocol to people that\nsee it for the first time, and so a lot of explanation comments are included. But the code itself is actually quite\nsimple, due to a bunch of helpful read/write methods the library already provides.\n\n```python\nimport json\nimport asyncio\n\nfrom mcproto.buffer import Buffer\nfrom mcproto.connection import TCPAsyncConnection\nfrom mcproto.protocol.base_io import StructFormat\n\n\nasync def handshake(conn: TCPAsyncConnection, ip: str, port: int = 25565) -> None:\n    # As a simple example, let\'s request status info from a server.\n    # (this is what you see in the multiplayer server list, i.e. the server\'s motd, icon, info\n    # about how many players are connected, etc.)\n\n    # To do this, we first need to understand how are minecraft packets composed, and take a look\n    # at the specific packets that we\'re interested in. Thankfully, there\'s an amazing community\n    # made wiki that documents all of this! You can find it at https://wiki.vg/\n\n    # Alright then, let\'s take a look at the (uncompressed) packet format specification:\n    # https://wiki.vg/Protocol#Packet_format\n    # From the wiki, we can see that a packet is composed of 3 fields:\n    # - Packet length (in bytes), sent as a variable length integer\n    #       combined length of the 2 fields below\n    # - Packet ID, also sent as varint\n    #       each packet has a unique number, that we use to find out which packet it is\n    # - Data, specific to the individual packet\n    #       every packet can hold different kind of data, this will be shown in the packet\'s\n    #       specification (you can find these in wiki.vg)\n\n    # Ok then, with this knowledge, let\'s establish a connection with our server, and request\n    # status. To do this, we fist need to send a handshake packet. Let\'s do it:\n\n    # Let\'s take a look at what data the Handshake packet should contain:\n    # https://wiki.vg/Protocol#Handshake\n    handshake = Buffer()\n    # We use 47 for the protocol version, as it\'s quite old, and will work with almost any server\n    handshake.write_varint(47)\n    handshake.write_utf(ip)\n    handshake.write_value(StructFormat.USHORT, port)\n    handshake.write_varint(1)  # Intention to query status\n\n    # Nice! Now that we have the packet data, let\'s follow the packet format and send it.\n    # Let\'s prepare another buffer that will contain the last 2 fields (packet id and data)\n    # combined. We do this since the first field will require us to know the size of these\n    # two combined, so let\'s just put them into 1 buffer.\n    packet = Buffer()\n    packet.write_varint(0)  # Handshake packet has packet ID of 0\n    packet.write(handshake)  # Full data from our handshake packet\n\n    # And now, it\'s time to send it!\n    await conn.write_varint(len(packet))  # First field (size of packet id + data)\n    await conn.write(packet)  # Second + Third fileds (packet id + data)\n\n\nasync def status(conn: TCPAsyncConnection, ip: str, port: int = 25565) -> dict:\n    # This function will be called right after a handshake\n    # Sending this packet told the server recognize our connection, and since we\'ve specified\n    # the intention to query status, it then moved us to STATUS game state.\n\n    # Different game states have different packets that we can send out, for example there is a\n    # game state for login, that we\'re put into while joining the server, and from it, we tell\n    # the server our username player UUID, etc.\n\n    # The packet IDs are unique to each game state, so since we\'re now in status state, a packet\n    # with ID of 0 is no longer the handshake packet, but rather the status request packet\n    # (precisely what we need).\n    # https://wiki.vg/Protocol#Status_Request\n\n    # The status request packet is empty, and doesn\'t contain any data, it just instructs the\n    # server to send us back a status response packet. Let\'s send it!\n    packet = Buffer()\n    packet.write_varint(0)  # Status request packet ID\n\n    await conn.write_varint(len(packet))\n    await conn.write(packet)\n\n    # Now, let\'s receive the response packet from the server\n    # Remember, the packet format states that we first receive a length, then packet id, then data\n    _response_len = await conn.read_varint()\n    _response = await conn.read(_response_len)  # will give us a bytearray\n\n    # Amazing, we\'ve just received data from the server! But it\'s just bytes, let\'s turn it into\n    # a Buffer object, which includes helpful methods that allow us to read from it\n    response = Buffer(_response)\n    packet_id = response.read_varint()  # Remember, 2nd field is the packet ID\n\n    # Let\'s see it then, what packet did we get?\n    print(packet_id)  # 0\n\n    # Interesting, this packet has an ID of 0, but wasn\'t that the status request packet? We wanted\n    # a response tho. Well, actually, if we take a look at the status response packet at the wiki,\n    # it really has an ID of 0:\n    # https://wiki.vg/Protocol#Status_Response\n    # Aha, so not only are packet ids unique between game states, they\'re also unique between the\n    # direction a server bound packet (sent by client, with server as the destination) can have an\n    # id of 0, while a client bound packet (sent by server, with client as the destination) can\n    # have the same id, and mean something else.\n\n    # Alright then, so we know what we got is a status response packet, let\'s read the wiki a bit\n    # further and see what data it actually contains, and see how we can get it out. Hmmm, it\n    # contains a UTF-8 encoded string that represents JSON data, ok, so let\'s get that string, it\'s\n    # still in our buffer.\n    received_string = response.read_utf()\n\n    # Now, let\'s just use the json module, convert the string into some json object (in this case,\n    # a dict)\n    data = json.loads(received_string)\n    return data\n\nasync def main():\n    # That\'s it, all that\'s left is actually calling our functions now\n\n    ip = "mc.hypixel.net"\n    port = 25565\n\n    async with (await TCPAsyncConnection.make_client((ip, port), 2)) as connection:\n        await handshake(connection, ip, port)\n        data = await status(connection, ip, port)\n\n    # Wohoo, we got the status data! Let\'s see it\n    print(data["players"]["max"])  # This is the server\'s max player amount (slots)\n    print(data["players"]["online"])  # This is how many people are currently online\n    print(data["description"])  # And here\'s the motd\n\n    # There\'s a bunch of other things in this data, try it out, see what you can find!\n\ndef start():\n    # Just some boilerplate code that can run our asynchronous main function\n    asyncio.run(main())\n```\n\n### Using packet classes for communication\n\n#### Obtaining the packet map\n\nThe first thing you\'ll need to understand about packet classes in mcproto is that they\'re versioned depending on the\nprotocol version you\'re using. As we\'ve already seen with minecraft packets, they\'re following a certain format, and\nfor given packet direction and game state, the packet numbers are unique.\n\nThis is how we can detect what packet is being received, but because of the different versions that the library can\nsupport, we will need to use a packet map, that will contain all of the mappings for given protocol version, from\nwhich, knowing the state and direction, we can get a dictionary with packet IDs as keys, and the individual packet\nclasses as values.\n\nThis dictionary is crucial to receiving packets, as it\'s the only thing that tells us which packet class should be\nused, once we receive a packet and learn about the packet id. Otherwise we wouldn\'t know what to do with the data we\nobtained.\n\nThe first game state we\'ll be in, before doing anything will always be the handshaking state, so let\'s see how we could\ngenerate this dictionary for this state, for all of the receiving (client bound) packets.\n\n```python\nfrom mcproto.packets import PACKET_MAP\nfrom mcproto.packets.abc import PacketDirection, GameState\n\nhandshaking_packet_map = PACKET_MAP.make_id_map(\n    protocol_version=757,\n    direction=PacketDirection.CLIENTBOUND,\n    game_state=GameState.HANDSHAKING\n)\n\nprint(handshaking_packet_map)  # {}\n```\n\nNotice that the packet map is actually empty, and this is simply because there (currently) aren\'t any client bound\npackets a server can send out for the handshaking game state. Let\'s see the status gamestate instead:\n\n```python\nstatus_packet_map = PACKET_MAP.make_id_map(\n    protocol_version=757,\n    direction=PacketDirection.CLIENTBOUND,\n    game_state=GameState.STATUS,\n)\n\nprint(status_packet_map)  # Will print:\n# {1: mcproto.packets.v757.status.ping.PingPong, 0: mcproto.packets.v757.status.status.StatusResponse}\n```\n\nCool! These are all of the packets, with their IDs that the server can send in STATUS game state.\n\n#### Creating our own packets\n\n\nNow, we could create a similar packet map for sending out the packets, and just use it to construct our packets,\nhowever this is not the recommended approach, as it\'s kind of hard to remember all of the packet IDs, and (at least\ncurrently) it means not having any typing information about what packet class will we get. For that reason, it\'s\nrecommended to import packets that we want to send out manually, like so:\n\n```python\nfrom mcproto.packets.v757.handshaking.handshake import Handshake, NextState\n\nmy_handshake = Handshake(\n    # Once again, we use an old protocol version so that even older servers will work\n    protocol_version=47,\n    server_address="mc.hypixel.net",\n    server_port=25565,\n    next_state=NextState.STATUS,\n)\n```\n\nThat\'s it! We\'ve now constructed a full handshake packet with all of the data it should contain. You might remember\nfrom the example above, that we originally had to look at the protocol specification, find the handshake packet and\nconstruct it\'s data as a Buffer with all of these variables.\n\nWith these packet classes, you can simply follow your editor\'s function hints to see what this packet requires, pass it\nin and the data will be constructed for you from these attributes, once we\'ll be to sending it.\n\nFor completion, let\'s also construct the status request packet that we were sending to instruct the server to send us\nback the status response packet.\n\n```python\nfrom mcproto.packets.v757.status.status import StatusRequest\n\nmy_status_request = StatusRequest()\n```\n\nThis one was even easier, as the status request packet alone doesn\'t contain any special data, it\'s just a request to\nthe server to send us some data back.\n\n#### Sending packets\n\nTo actually send out a packet to the server, we\'ll need to create a connection, and use the custom functions\nresponsible for sending packets out. Let\'s see it:\n\n```python\nfrom mcproto.packets import async_write_packet\nfrom mcproto.connection import TCPAsyncConnection\n\nasync def main():\n    ip = "mc.hypixel.net"\n    port = 25565\n\n    async with (await TCPAsyncConnection.make_client((ip, port), 2)) as connection:\n        await async_write_packet(connection, my_handshake)  \n        # my_handshake is a packet we\'ve created in the example before\n```\n\nHow quick was that? Now compare this to the manual version.\n\n#### Receiving packets\n\nAlright, we might now know how to send a packet, but how do we receive one? Let\'s see:\n\n```python\nfrom mcproto.packets import PACKET_MAP\n\n# Let\'s prepare the packet map we\'ll be using, say we\'re already in the STATUS game state now\nSTATUS_PACKET_MAP = PACKET_MAP.make_id_map(\n    protocol_version=757,\n    direction=PacketDirection.CLIENTBOUND,\n    game_state=GameState.STATUS\n)\n\n# Let\'s say we already have a connection at this moment, after all, how else would\n# we\'ve gotten into the STATUS game state.\n# Also, let\'s do something different, let\'s say we have a synchronous connection\nfrom mcproto.connection import TCPSyncConnection\nconn: TCPSyncConnection\n\n# With a synchronous connection, comes synchronous reader, so instead of using async_read_packet,\n# we\'ll use sync_read_packet here\nfrom mcproto.packets import sync_read_packet\n\npacket = sync_read_packet(conn, STATUS_PACKET_MAP)\n\n# Cool! We\'ve got back a packet, but what packet is it? Let\'s import the packet classes it could\n# be and check against them\nfrom mcproto.packets.v757.status.status import StatusResponse\nfrom mcproto.packets.v757.status.ping import PingPong\n\nif isinstance(packet, StatusResponse):\n    ...\nelif isinstance(packet, PingPong):\n    ...\nelse:\n    raise Exception("Impossible, there aren\'t other client bound packets in the STATUS game state")\n```\n\n#### Requesting status\n\nNow let\'s actually do something meaningful, and replicate the entire example from the manual version using packets,\nlet\'s see just how much simpler it will be:\n\n```python\nfrom mcproto.connection import TCPAsyncConnection\nfrom mcproto.packets import async_write_packet, async_read_packet, PACKET_MAP\nfrom mcproto.packets.abc import PacketDirection, GameState\nfrom mcproto.packets.v757.handshaking.handshake import Handshake, NextState\nfrom mcproto.packets.v757.status.status import StatusRequest, StatusResponse\n\nSTATUS_PACKET_MAP = PACKET_MAP.make_id_map(\n    protocol_version=757,\n    direction=PacketDirection.CLIENTBOUND,\n    game_state=GameState.STATUS\n)\n\n\nasync def get_status(ip: str, port: int) -> dict:\n    handshake_packet = Handshake(\n        protocol_version=47,\n        server_address=ip,\n        server_port=port,\n        next_state=NextState.STATUS,\n    )\n    status_req_packet = StatusRequest()\n\n    async with (await TCPAsyncConnection.make_client((ip, port), 2)) as connection:\n        # We start out at HANDSHAKING game state\n        await async_write_packet(connection, handshake_packet)  \n        # After sending the handshake, we told the server to now move us into the STATUS game state\n        await async_write_packet(connection, status_req_packet)  \n        # Since we\'re still in STATUS game state, we use the status packet map when reading\n        packet = await async_read_packet(connection, STATUS_PACKET_MAP)\n\n    # Now that we\'ve got back the packet, we no longer need the connection, we won\'t be sending\n    # anything else. Let\'s just make sure it really is the packet we expected\n    if not isinstance(packet, StatusResponse):\n        raise ValueError(f"We\'ve got an unexpected packet back: {packet!r}")\n\n    # Now that we know we\'re dealing with a status response, let\'s get out it\'s data, and return in\n    return packet.data\n```\n\nWell, that wasn\'t so hard, was it?\n',
    'author': 'ItsDrike',
    'author_email': 'itsdrike@protonmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/py-mine/mcproto',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4',
}


setup(**setup_kwargs)
