Repository: hikari-py/hikari
Pull request #1015 (2022-02-09 00:32:39)

Add attachment command option type support
### Summary
<!-- Small summary of the merge request -->

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/changes/1015.feature.md b/changes/1015.feature.md
new file mode 100644
index 00000000..86e6fb83
--- /dev/null
+++ b/changes/1015.feature.md
@@ -0,0 +1,1 @@
+Added attachment command option type support.
diff --git a/hikari/commands.py b/hikari/commands.py
index 55a7ba31..6fa77cb3 100644
--- a/hikari/commands.py
+++ b/hikari/commands.py
@@ -104,6 +104,9 @@ class OptionType(int, enums.Enum):
     This is range limited between -2^53 and 2^53.
     """
 
+    ATTACHMENT = 11
+    """Denotes a command option where the value will be an attachment."""
+
 
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 1d36158a..72aecf19 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1931,12 +1931,19 @@ def _deserialize_resolved_option_data(
         else:
             messages = {}
 
+        if raw_attachments := payload.get("attachments"):
+            attachments = {a.id: a for a in map(self._deserialize_message_attachment, raw_attachments.values())}
+
+        else:
+            attachments = {}
+
         return command_interactions.ResolvedOptionData(
+            attachments=attachments,
             channels=channels,
             members=members,
-            users=users,
-            roles=roles,
             messages=messages,
+            roles=roles,
+            users=users,
         )
 
     def deserialize_command_interaction(
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index 29d8e9f5..e39f0b0c 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -92,20 +92,23 @@ class InteractionChannel(channels.PartialChannel):
 class ResolvedOptionData:
     """Represents the resolved objects of entities referenced in a command's options."""
 
-    users: typing.Mapping[snowflakes.Snowflake, users_.User] = attr.field(repr=False)
-    """Mapping of snowflake IDs to the resolved option user objects."""
+    attachments: typing.Mapping[snowflakes.Snowflake, messages_.Attachment] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the attachment objects."""
+
+    channels: typing.Mapping[snowflakes.Snowflake, InteractionChannel] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the resolved option partial channel objects."""
 
     members: typing.Mapping[snowflakes.Snowflake, base_interactions.InteractionMember] = attr.field(repr=False)
     """Mapping of snowflake IDs to the resolved option member objects."""
 
+    messages: typing.Mapping[snowflakes.Snowflake, messages_.Message]
+    """Mapping of snowflake IDs to the resolved option partial message objects."""
+
     roles: typing.Mapping[snowflakes.Snowflake, guilds.Role] = attr.field(repr=False)
     """Mapping of snowflake IDs to the resolved option role objects."""
 
-    channels: typing.Mapping[snowflakes.Snowflake, InteractionChannel] = attr.field(repr=False)
-    """Mapping of snowflake iDs to the resolved option partial channel objects."""
-
-    messages: typing.Mapping[snowflakes.Snowflake, messages_.Message]
-    """Mapping of snowflake iDs to the resolved option partial message objects."""
+    users: typing.Mapping[snowflakes.Snowflake, users_.User] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the resolved option user objects."""
 
 
 @attr_extensions.with_copy


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index d6d31fd3..fab2d7a3 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2970,6 +2970,7 @@ def test__deserialize_resolved_option_data(
         self,
         entity_factory_impl,
         interaction_resolved_data_payload,
+        attachment_payload,
         user_payload,
         guild_role_payload,
         interaction_member_payload,
@@ -2979,7 +2980,6 @@ def test__deserialize_resolved_option_data(
             interaction_resolved_data_payload, guild_id=123321
         )
 
-        # ResolvedOptionData
         assert len(resolved.channels) == 1
         channel = resolved.channels[695382395666300958]
         assert channel.type is channel_models.ChannelType.GUILD_TEXT
@@ -2993,6 +2993,9 @@ def test__deserialize_resolved_option_data(
             interaction_member_payload, guild_id=123321, user=entity_factory_impl.deserialize_user(user_payload)
         )
 
+        assert resolved.attachments == {
+            690922406474154014: entity_factory_impl._deserialize_message_attachment(attachment_payload)
+        }
         assert resolved.roles == {
             41771983423143936: entity_factory_impl.deserialize_role(guild_role_payload, guild_id=123321)
         }
@@ -3004,6 +3007,7 @@ def test__deserialize_resolved_option_data(
     def test__deserialize_resolved_option_data_with_empty_resolved_resources(self, entity_factory_impl):
         resolved = entity_factory_impl._deserialize_resolved_option_data({})
 
+        assert resolved.attachments == {}
         assert resolved.channels == {}
         assert resolved.members == {}
         assert resolved.roles == {}
@@ -3012,9 +3016,10 @@ def test__deserialize_resolved_option_data_with_empty_resolved_resources(self, e
 
     @pytest.fixture()
     def interaction_resolved_data_payload(
-        self, interaction_member_payload, guild_role_payload, user_payload, message_payload
+        self, interaction_member_payload, attachment_payload, guild_role_payload, user_payload, message_payload
     ):
         return {
+            "attachments": {"690922406474154014": attachment_payload},
             "channels": {
                 "695382395666300958": {
                     "id": "695382395666300958",
@@ -4087,6 +4092,20 @@ def referenced_message(self, user_payload):
             "flags": "222",
         }
 
+    @pytest.fixture()
+    def attachment_payload(self):
+        return {
+            "id": "690922406474154014",
+            "filename": "IMG.jpg",
+            "content_type": "image/png",
+            "size": 660521,
+            "url": "https://somewhere.com/attachments/123/456/IMG.jpg",
+            "proxy_url": "https://media.somewhere.com/attachments/123/456/IMG.jpg",
+            "width": 1844,
+            "height": 2638,
+            "ephemeral": True,
+        }
+
     @pytest.fixture()
     def message_payload(
         self,
@@ -4098,6 +4117,7 @@ def message_payload(
         referenced_message,
         action_row_payload,
         partial_sticker_payload,
+        attachment_payload,
     ):
         member_payload = member_payload.copy()
         del member_payload["user"]
@@ -4117,19 +4137,7 @@ def message_payload(
             ],
             "mention_roles": ["987"],
             "mention_channels": [{"id": "456", "guild_id": "678", "type": 1, "name": "hikari-testing"}],
-            "attachments": [
-                {
-                    "id": "690922406474154014",
-                    "filename": "IMG.jpg",
-                    "content_type": "image/png",
-                    "size": 660521,
-                    "url": "https://somewhere.com/attachments/123/456/IMG.jpg",
-                    "proxy_url": "https://media.somewhere.com/attachments/123/456/IMG.jpg",
-                    "width": 1844,
-                    "height": 2638,
-                    "ephemeral": True,
-                }
-            ],
+            "attachments": [attachment_payload],
             "embeds": [embed_payload],
             "reactions": [{"emoji": custom_emoji_payload, "count": 100, "me": True}],
             "pinned": True,
@@ -4151,6 +4159,51 @@ def message_payload(
             "components": [action_row_payload, {"type": 1000000000}],
         }
 
+    def test__deserialize_message_attachment(self, entity_factory_impl, attachment_payload):
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.id == 690922406474154014
+        assert attachment.filename == "IMG.jpg"
+        assert attachment.size == 660521
+        assert attachment.media_type == "image/png"
+        assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
+        assert attachment.proxy_url == "https://media.somewhere.com/attachments/123/456/IMG.jpg"
+        assert attachment.width == 1844
+        assert attachment.height == 2638
+        assert attachment.is_ephemeral is True
+        assert isinstance(attachment, message_models.Attachment)
+
+    def test__deserialize_message_attachment_with_null_fields(
+        self,
+        entity_factory_impl,
+        attachment_payload,
+    ):
+        attachment_payload["height"] = None
+        attachment_payload["width"] = None
+
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.height is None
+        assert attachment.width is None
+        assert isinstance(attachment, message_models.Attachment)
+
+    def test__deserialize_message_attachment_with_unset_fields(
+        self,
+        entity_factory_impl,
+        attachment_payload,
+    ):
+        del attachment_payload["content_type"]
+        del attachment_payload["height"]
+        del attachment_payload["width"]
+        del attachment_payload["ephemeral"]
+
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.media_type is None
+        assert attachment.height is None
+        assert attachment.width is None
+        assert attachment.is_ephemeral is False
+
     def test_deserialize_partial_message(
         self,
         entity_factory_impl,
@@ -4163,6 +4216,7 @@ def test_deserialize_partial_message(
         embed_payload,
         referenced_message,
         action_row_payload,
+        attachment_payload,
     ):
         partial_message = entity_factory_impl.deserialize_partial_message(message_payload)
 
@@ -4186,19 +4240,7 @@ def test_deserialize_partial_message(
         assert partial_message.mentions.user_ids == [5678]
         assert partial_message.mentions.role_ids == [987]
         assert partial_message.mentions.channels_ids == [456]
-        # Attachment
-        assert len(partial_message.attachments) == 1
-        attachment = partial_message.attachments[0]
-        assert attachment.id == 690922406474154014
-        assert attachment.filename == "IMG.jpg"
-        assert attachment.size == 660521
-        assert attachment.media_type == "image/png"
-        assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
-        assert attachment.proxy_url == "https://media.somewhere.com/attachments/123/456/IMG.jpg"
-        assert attachment.width == 1844
-        assert attachment.height == 2638
-        assert attachment.is_ephemeral is True
-        assert isinstance(attachment, message_models.Attachment)
+        assert partial_message.attachments == [entity_factory_impl._deserialize_message_attachment(attachment_payload)]
 
         expected_embed = entity_factory_impl.deserialize_embed(embed_payload)
         assert partial_message.embeds == [expected_embed]
@@ -4443,10 +4485,6 @@ def test_deserialize_message(
     def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, message_payload):
         del message_payload["application"]["cover_image"]
         del message_payload["application"]["primary_sku_id"]
-        del message_payload["attachments"][0]["content_type"]
-        del message_payload["attachments"][0]["height"]
-        del message_payload["attachments"][0]["width"]
-        del message_payload["attachments"][0]["ephemeral"]
         del message_payload["activity"]["party_id"]
         del message_payload["message_reference"]["message_id"]
         del message_payload["message_reference"]["guild_id"]
@@ -4455,13 +4493,6 @@ def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, me
         message = entity_factory_impl.deserialize_message(message_payload)
 
         assert message.mentions.channels == {}
-        # Attachment
-        assert len(message.attachments) == 1
-        attachment = message.attachments[0]
-        assert attachment.width is None
-        assert attachment.height is None
-        assert attachment.is_ephemeral is False
-        assert isinstance(attachment, message_models.Attachment)
 
         # Activity
         assert message.activity.party_id is None
@@ -4478,18 +4509,9 @@ def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, me
         assert isinstance(message.message_reference, message_models.MessageReference)
 
     def test_deserialize_message_with_null_sub_fields(self, entity_factory_impl, message_payload):
-        message_payload["attachments"][0]["height"] = None
-        message_payload["attachments"][0]["width"] = None
         message_payload["application"]["icon"] = None
         message = entity_factory_impl.deserialize_message(message_payload)
 
-        # Attachment
-        assert len(message.attachments) == 1
-        attachment = message.attachments[0]
-        assert attachment.width is None
-        assert attachment.height is None
-        assert isinstance(attachment, message_models.Attachment)
-
         # MessageApplication
         assert message.application.icon_hash is None
         assert isinstance(message.application, message_models.MessageApplication)