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

Fix some bugs in message deserialization #933

Merged
merged 1 commit into from
Dec 31, 2021
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions changes/933.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix some bugs in message deserialization
- Remove case for setting `member` and `referece_message` to `undefined.Undefined` in full message deserialization
- Don't set `message.member` to `undefined.UNDEFINED` on partial message deserialization if message was sent by a webhook
18 changes: 7 additions & 11 deletions hikari/impl/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2148,10 +2148,11 @@ def deserialize_partial_message( # noqa CFQ001 - Function too long
if "guild_id" in payload:
guild_id = snowflakes.Snowflake(payload["guild_id"])

# Webhook messages will never have a member attached to them
if member_pl := payload.get("member"):
assert author is not None, "received message with a member object without a user object"
assert author is not undefined.UNDEFINED, "received message with a member object without a user object"
member = self.deserialize_member(member_pl, user=author, guild_id=guild_id)
else:
elif author is not undefined.UNDEFINED:
member = undefined.UNDEFINED

timestamp: undefined.UndefinedOr[datetime.datetime] = undefined.UNDEFINED
Expand Down Expand Up @@ -2288,14 +2289,12 @@ def deserialize_message( # noqa CFQ001 - Function too long
author = self.deserialize_user(payload["author"])

guild_id: typing.Optional[snowflakes.Snowflake] = None
member: undefined.UndefinedNoneOr[guild_models.Member] = None
member: typing.Optional[guild_models.Member] = None
if "guild_id" in payload:
guild_id = snowflakes.Snowflake(payload["guild_id"])

if member_pl := payload.get("member"):
member = self.deserialize_member(member_pl, user=author, guild_id=guild_id)
else:
member = undefined.UNDEFINED

edited_timestamp: typing.Optional[datetime.datetime] = None
if (raw_edited_timestamp := payload["edited_timestamp"]) is not None:
Expand All @@ -2318,12 +2317,9 @@ def deserialize_message( # noqa CFQ001 - Function too long
if "message_reference" in payload:
message_reference = self._deserialize_message_reference(payload["message_reference"])

referenced_message: undefined.UndefinedNoneOr[message_models.Message] = undefined.UNDEFINED
if "referenced_message" in payload:
if (referenced_message_payload := payload["referenced_message"]) is not None:
referenced_message = self.deserialize_message(referenced_message_payload)
else:
referenced_message = None
referenced_message: typing.Optional[message_models.Message] = None
if referenced_message_payload := payload.get("referenced_message"):
referenced_message = self.deserialize_message(referenced_message_payload)

application: typing.Optional[message_models.MessageApplication] = None
if "application" in payload:
Expand Down
62 changes: 26 additions & 36 deletions hikari/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,17 +862,17 @@ class PartialMessage(snowflakes.Unique):
`type` is `MessageType.REPLY` and `builtins.None`, the message was deleted.
"""

interaction: undefined.UndefinedNoneOr[MessageInteraction] = attr.field(hash=False, repr=False)
interaction: undefined.UndefinedNoneOr[MessageInteraction] = attr.field(hash=False, eq=False, repr=False)
"""Information about the interaction this message was created by."""

application_id: undefined.UndefinedNoneOr[snowflakes.Snowflake] = attr.field(hash=False, repr=False)
application_id: undefined.UndefinedNoneOr[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=False)
"""ID of the application this message was sent by.

!!! note
This will only be provided for interaction messages.
"""

components: undefined.UndefinedOr[typing.Sequence[PartialComponent]] = attr.field(hash=False, repr=False)
components: undefined.UndefinedOr[typing.Sequence[PartialComponent]] = attr.field(hash=False, eq=False, repr=False)
"""Sequence of the components attached to this message."""

def make_link(self, guild: typing.Optional[snowflakes.SnowflakeishOr[guilds.PartialGuild]]) -> str:
Expand Down Expand Up @@ -1536,99 +1536,89 @@ async def remove_all_reactions(
)


@attr.define(hash=True, kw_only=True, weakref_slot=False, auto_attribs=False)
@attr.define(hash=True, kw_only=True, weakref_slot=False)
class Message(PartialMessage):
"""Represents a message with all known details."""

# These are purposely not auto attribs, but instead just specify a
# tighter type bounds (i.e. none are allowed to be undefined.Undefined
# in this model). We use this in cases where we know all information is
# present. DO NOT ADD attr.field TO ANY OF THESE, OR ENABLE auto_attribs
# IN THIS CLASS, the latter will mess up slotting or cause layout conflicts
# and possibly result in large amounts of wasted memory if you get that far.

author: users_.User
author: users_.User = attr.field(hash=False, eq=False, repr=True)
"""The author of this message."""

member: typing.Optional[guilds.Member]
member: typing.Optional[guilds.Member] = attr.field(hash=False, eq=False, repr=False)
"""The member properties for the message's author."""

content: typing.Optional[str]
content: typing.Optional[str] = attr.field(hash=False, eq=False, repr=False)
"""The content of the message."""

timestamp: datetime.datetime
timestamp: datetime.datetime = attr.field(hash=False, eq=False, repr=False)
"""The timestamp that the message was sent at."""

edited_timestamp: typing.Optional[datetime.datetime]
edited_timestamp: typing.Optional[datetime.datetime] = attr.field(hash=False, eq=False, repr=False)
"""The timestamp that the message was last edited at.

Will be `builtins.None` if it wasn't ever edited.
"""

is_tts: bool
is_tts: bool = attr.field(hash=False, eq=False, repr=False)
"""Whether the message is a TTS message."""

mentions: Mentions
"""Who is mentioned in a message."""

attachments: typing.Sequence[Attachment]
attachments: typing.Sequence[Attachment] = attr.field(hash=False, eq=False, repr=False)
"""The message attachments."""

embeds: typing.Sequence[embeds_.Embed]
embeds: typing.Sequence[embeds_.Embed] = attr.field(hash=False, eq=False, repr=False)
"""The message embeds."""

reactions: typing.Sequence[Reaction]
reactions: typing.Sequence[Reaction] = attr.field(hash=False, eq=False, repr=False)
"""The message reactions."""

is_pinned: bool
is_pinned: bool = attr.field(hash=False, eq=False, repr=False)
"""Whether the message is pinned."""

webhook_id: typing.Optional[snowflakes.Snowflake]
webhook_id: typing.Optional[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=False)
"""If the message was generated by a webhook, the webhook's id."""

type: typing.Union[MessageType, int]
type: typing.Union[MessageType, int] = attr.field(hash=False, eq=False, repr=False)
"""The message type."""

activity: typing.Optional[MessageActivity]
activity: typing.Optional[MessageActivity] = attr.field(hash=False, eq=False, repr=False)
"""The message activity.

!!! note
This will only be provided for messages with rich-presence related chat
embeds.
"""

application: typing.Optional[MessageApplication]
application: typing.Optional[MessageApplication] = attr.field(hash=False, eq=False, repr=False)
"""The message application.

!!! note
This will only be provided for messages with rich-presence related chat
embeds.
"""

message_reference: typing.Optional[MessageReference]
message_reference: typing.Optional[MessageReference] = attr.field(hash=False, eq=False, repr=False)
"""The message reference data."""

flags: MessageFlag
flags: MessageFlag = attr.field(hash=False, eq=False, repr=True)
"""The message flags."""

stickers: typing.Sequence[stickers_.PartialSticker]
stickers: typing.Sequence[stickers_.PartialSticker] = attr.field(hash=False, eq=False, repr=False)
"""The stickers sent with this message."""

nonce: typing.Optional[str]
nonce: typing.Optional[str] = attr.field(hash=False, eq=False, repr=False)
"""The message nonce. This is a string used for validating a message was sent."""

referenced_message: typing.Optional[Message]
referenced_message: typing.Optional[Message] = attr.field(hash=False, eq=False, repr=False)
"""The message that was replied to."""

interaction: typing.Optional[MessageInteraction]
interaction: typing.Optional[MessageInteraction] = attr.field(hash=False, eq=False, repr=False)
"""Information about the interaction this message was created by."""

application_id: typing.Optional[snowflakes.Snowflake]
application_id: typing.Optional[snowflakes.Snowflake] = attr.field(hash=False, eq=False, repr=False)
"""ID of the application this message was sent by.

!!! note
This will only be provided for interaction messages.
"""

components: typing.Sequence[PartialComponent]
components: typing.Sequence[PartialComponent] = attr.field(hash=False, eq=False, repr=False)
"""Sequence of the components attached to this message."""
11 changes: 9 additions & 2 deletions tests/hikari/impl/test_entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4083,6 +4083,13 @@ def test_deserialize_partial_message_with_unset_fields(self, entity_factory_impl
assert partial_message.interaction is undefined.UNDEFINED
assert partial_message.components is undefined.UNDEFINED

def test_deserialize_partial_message_with_guild_id_but_no_author(self, entity_factory_impl):
partial_message = entity_factory_impl.deserialize_partial_message(
{"id": 123, "channel_id": 456, "guild_id": 987}
)

assert partial_message.member is None

def test_deserialize_partial_message_deserializes_old_stickers_field(self, entity_factory_impl, message_payload):
message_payload["stickers"] = message_payload["sticker_items"]
del message_payload["sticker_items"]
Expand Down Expand Up @@ -4295,7 +4302,7 @@ def test_deserialize_message_with_null_and_unset_fields(
assert message.activity is None
assert message.application is None
assert message.message_reference is None
assert message.referenced_message is undefined.UNDEFINED
assert message.referenced_message is None
assert message.stickers == []
assert message.nonce is None
assert message.application_id is None
Expand All @@ -4314,7 +4321,7 @@ def test_deserialize_message_with_other_unset_fields(self, entity_factory_impl,
assert message.application.cover_image_hash is None
assert message.application.icon_hash is None
assert message.referenced_message is None
assert message.member is undefined.UNDEFINED
assert message.member is None
davfsa marked this conversation as resolved.
Show resolved Hide resolved

def test_deserialize_message_deserializes_old_stickers_field(self, entity_factory_impl, message_payload):
message_payload["stickers"] = message_payload["sticker_items"]
Expand Down