Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More types #297

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -31,6 +31,11 @@ checks:
return-statements:
enabled: false



exclude_patterns:
- "tests/**"
- ".github/**"
# The scripts directory is only there to provide quick scripts to generate things that are in the actual code
# They don't need testing as they are not meant to be used outside the developpment process
- "scripts/**"
2 changes: 1 addition & 1 deletion changes/285.internal.2.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
- `serialize_to(buf: Buffer)`: Abstract method to write the object to a `Buffer`.
- `validate()`: Validates the object's attributes; can be overridden for custom validation.
- `deserialize(cls, buf: Buffer) -> Self`: Abstract method to construct the object from a `Buffer`.
- **Note**: Use the `dataclass` decorator when adding parameters to subclasses.
- **Note**: Use the :func:`attrs.define` decorator when adding parameters to subclasses.

- Exemple:

17 changes: 17 additions & 0 deletions changes/297.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
- Added more types to the implementation:

- `Angle`: Represents an angle.
- `BitSet`: Represents a set of bits of variable length.
- `FixedBitSet`: Represents a set of bits of fixed length.
- `TextComponent`: Represents a Minecraft text component.
- Renamed `ChatMessage` to `JSONTextComponent`.
- `Identifier`: Represents a Minecraft identifier.
- `Quaternion`: Represents a quaternion.
- `Slot`: Represents an item slot.
- `Vec3`: Represents a 3D vector.
- `Position`: Represents a position with packed integers.
- `EntityMetadata`: Represents metadata for an entity.
> There are **A LOT** of different entity metadata types, so I'm not going to list them all here.

- Removed the `validate` method from most `Serializable` classes.
- Make use of validators and convertors from the `attrs` library instead.
10 changes: 10 additions & 0 deletions docs/api/types/entity_metadata.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Entity Metadata
======================

This is the documentation for the Entity Metadata types used in Minecraft's network protocol.




.. automodule:: mcproto.types.entity
:no-undoc-members:
11 changes: 11 additions & 0 deletions docs/api/types/general.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
General Types
======================

Here are documented the general types used throughout the Minecraft protocol.

.. automodule:: mcproto.types
:no-undoc-members:
:exclude-members: NBTag, StringNBT, CompoundNBT, EndNBT, EntityMetadata, UUID

.. autoclass:: mcproto.types.UUID
:class-doc-from: class
11 changes: 6 additions & 5 deletions docs/api/types/index.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.. api/types documentation master file
.. Types Documentation

=======================
API Types Documentation
=======================
Types Documentation
==================================

Welcome to the API Types documentation! This documentation provides information about the various types used in the API.
This folder contains the documentation for various types used in the project.

.. toctree::
:maxdepth: 2

general.rst
nbt.rst
entity_metadata.rst
3 changes: 3 additions & 0 deletions docs/api/types/nbt.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
NBT Format
==========

This is the documentation for the NBT type used in Minecraft's network protocol.


.. automodule:: mcproto.types.nbt
:members:
:show-inheritance:
16 changes: 16 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -12,10 +12,13 @@
import datetime
import sys
from pathlib import Path
from typing import Any

from packaging.version import parse as parse_version
from typing_extensions import override

from mcproto.types.entity.metadata import DefaultEntityMetadataEntryDeclaration, ProxyEntityMetadataEntryDeclaration

if sys.version_info >= (3, 11):
from tomllib import load as toml_parse
else:
@@ -117,6 +120,19 @@
"exclude-members": "__dict__,__weakref__",
}


def autodoc_skip_member(app: Any, what: str, name: str, obj: Any, skip: bool, options: Any) -> bool:
"""Skip EntityMetadataEntry class fields as they are already documented in the docstring."""
if isinstance(obj, (DefaultEntityMetadataEntryDeclaration, ProxyEntityMetadataEntryDeclaration)):
return True
return skip


def setup(app: Any) -> None:
"""Set up the Sphinx app."""
app.connect("autodoc-skip-member", autodoc_skip_member)


# -- sphinx.ext.autosectionlabel ---------------

# Automatically generate section labels:
2 changes: 1 addition & 1 deletion docs/pages/installation.rst
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ Latest (git) version

Alternatively, you may want to install the latest available version, which is what you currently see in the ``main``
git branch. Although this method will actually work for any branch with a pretty straightforward change. This kind of
installation should only be done when testing new feautes, and it's likely you'll encounter bugs.
installation should only be done when testing new features, and it's likely you'll encounter bugs.

That said, since mcproto is still in development, changes can often be made pretty quickly, and it can sometimes take a
while for these changes to carry over to PyPI. So if you really want to try out that latest feature, this is the method
2 changes: 1 addition & 1 deletion mcproto/multiplayer.py
Original file line number Diff line number Diff line change
@@ -140,7 +140,7 @@ def compute_server_hash(server_id: str, shared_secret: bytes, server_public_key:
need to know the encryption key (``shared_secret``). A proxy can capture this key, as the client
sends it over to the server in :class:`~mcproto.packets.login.login.LoginEncryptionResponse` packet,
however it is sent encrypted. The client performs this encryption with a public key, which it got
from the server, in :class:`mcproto.packets.login.login.LoginEncryptionRequest` packet.
from the server, in :class:`~mcproto.packets.login.login.LoginEncryptionRequest` packet.

That mans that for a proxy to be able to actually obtain this shared secret value, it would need to
be able to capture the encryption response, and decrypt the shared secret value. That means it would
21 changes: 3 additions & 18 deletions mcproto/packets/handshaking/handshake.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from enum import IntEnum
from typing import ClassVar, cast, final
from typing import ClassVar, final

from attrs import define
from typing_extensions import Self, override
@@ -42,19 +42,11 @@ class Handshake(ServerBoundPacket):
protocol_version: int
server_address: str
server_port: int
next_state: NextState | int

@override
def __attrs_post_init__(self) -> None:
if not isinstance(self.next_state, NextState):
self.next_state = NextState(self.next_state)

super().__attrs_post_init__()
next_state: NextState

@override
def serialize_to(self, buf: Buffer) -> None:
"""Serialize the packet."""
self.next_state = cast(NextState, self.next_state) # Handled by the __attrs_post_init__ method
buf.write_varint(self.protocol_version)
buf.write_utf(self.server_address)
buf.write_value(StructFormat.USHORT, self.server_port)
@@ -67,12 +59,5 @@ def _deserialize(cls, buf: Buffer, /) -> Self:
protocol_version=buf.read_varint(),
server_address=buf.read_utf(),
server_port=buf.read_value(StructFormat.USHORT),
LiteApplication marked this conversation as resolved.
Show resolved Hide resolved
next_state=buf.read_varint(),
next_state=NextState(buf.read_varint()),
)

@override
def validate(self) -> None:
if not isinstance(self.next_state, NextState):
rev_lookup = {x.value: x for x in NextState.__members__.values()}
if self.next_state not in rev_lookup:
raise ValueError("No such next_state.")
28 changes: 12 additions & 16 deletions mcproto/packets/login/login.py
Original file line number Diff line number Diff line change
@@ -2,15 +2,15 @@

from typing import ClassVar, cast, final

from attrs import define
from attrs import define, field
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, load_der_public_key
from typing_extensions import Self, override

from mcproto.buffer import Buffer
from mcproto.packets.packet import ClientBoundPacket, GameState, ServerBoundPacket
from mcproto.types.chat import ChatMessage
from mcproto.types.chat import JSONTextComponent
from mcproto.types.uuid import UUID

__all__ = [
@@ -33,7 +33,8 @@ class LoginStart(ServerBoundPacket):
Initialize the LoginStart packet.

:param username: Username of the client who sent the request.
:param uuid: UUID of the player logging in (if the player doesn't have a UUID, this can be ``None``)
:param uuid: UUID of the player logging in (unused by the server)
:type uuid: :class:`~mcproto.types.UUID`
"""

PACKET_ID: ClassVar[int] = 0x00
@@ -70,16 +71,9 @@ class LoginEncryptionRequest(ClientBoundPacket):
PACKET_ID: ClassVar[int] = 0x01
GAME_STATE: ClassVar[GameState] = GameState.LOGIN

public_key: RSAPublicKey
verify_token: bytes
server_id: str | None = None

@override
def __attrs_post_init__(self) -> None:
if self.server_id is None:
self.server_id = " " * 20

super().__attrs_post_init__()
public_key: RSAPublicKey = field()
verify_token: bytes = field()
server_id: str | None = field(default=" " * 20)

@override
def serialize_to(self, buf: Buffer) -> None:
@@ -143,7 +137,9 @@ class LoginSuccess(ClientBoundPacket):
Initialize the LoginSuccess packet.

:param uuid: The UUID of the connecting player/client.
:type uuid: :class:`~mcproto.types.UUID`
:param username: The username of the connecting player/client.
:type username: str
Comment on lines +140 to +142
Copy link
Member

@ItsDrike ItsDrike Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type should be picked up from the type-hint by sphinx autodoc, it shouldn't be necessary to doc it manually.

There's a lot of these in the code, this suggestion applies to all such instances.
(just use grep/rg to find all of them (or awk/tr like a chad) (or your editor's autoreplace, like a non-chad))

Suggested change
:type uuid: :class:`~mcproto.types.UUID`
:param username: The username of the connecting player/client.
:type username: str
:param username: The username of the connecting player/client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They add the type between parenthesis (and links to the actual type) along with saying if it is optional :

Without :type ...: ...

Parameters:

  • item – The item ID of the item in the slot.
  • num – The count of items in the slot.
  • nbt – The NBT data of the item in the slot, None if there is no item or no NBT data.

With it :

Parameters:

  • item (int) – The item ID of the item in the slot.
  • num (int) – The count of items in the slot.
  • nbt (NBTag, optional) – The NBT data of the item in the slot, None if there is no item or no NBT data.

Also these type informations only appear on NamedTuples (without the helpful description next to them), and don't appear at all for regular Serializables.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I think this might be configurable with autosphinx, will need to check though

"""

PACKET_ID: ClassVar[int] = 0x02
@@ -178,7 +174,7 @@ class LoginDisconnect(ClientBoundPacket):
PACKET_ID: ClassVar[int] = 0x00
GAME_STATE: ClassVar[GameState] = GameState.LOGIN

reason: ChatMessage
reason: JSONTextComponent

@override
def serialize_to(self, buf: Buffer) -> None:
@@ -187,7 +183,7 @@ def serialize_to(self, buf: Buffer) -> None:
@override
@classmethod
def _deserialize(cls, buf: Buffer, /) -> Self:
reason = ChatMessage.deserialize(buf)
reason = JSONTextComponent.deserialize(buf)
return cls(reason)


@@ -240,7 +236,7 @@ class LoginPluginResponse(ServerBoundPacket):
GAME_STATE: ClassVar[GameState] = GameState.LOGIN

message_id: int
data: bytes | None
data: bytes | None = None

@override
def serialize_to(self, buf: Buffer) -> None:
17 changes: 7 additions & 10 deletions mcproto/packets/status/status.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import json
from typing import Any, ClassVar, final

from attrs import define
from attrs import Attribute, define, field
from typing_extensions import Self, override

from mcproto.buffer import Buffer
@@ -43,7 +43,12 @@ class StatusResponse(ClientBoundPacket):
PACKET_ID: ClassVar[int] = 0x00
GAME_STATE: ClassVar[GameState] = GameState.STATUS

data: dict[str, Any] # JSON response data sent back to the client.
data: dict[str, Any] = field()

@data.validator # pyright: ignore
def _validate_data(self, _: Attribute[dict[str, Any]], value: dict[str, Any]) -> None:
"""Dump the data as json to check if it's valid."""
json.dumps(value)

@override
def serialize_to(self, buf: Buffer) -> None:
@@ -56,11 +61,3 @@ def _deserialize(cls, buf: Buffer, /) -> Self:
s = buf.read_utf()
data_ = json.loads(s)
return cls(data_)

@override
def validate(self) -> None:
# Ensure the data is serializable to JSON
try:
json.dumps(self.data)
except TypeError as exc:
raise ValueError("Data is not serializable to JSON.") from exc
Loading