Repository: hikari-py/hikari
Pull request #967 (2022-01-13 21:56:14)

feat: add `locale` and `guild_locale` properties to interactions
### Summary
<!-- Small summary of the merge request -->

Discord announced its release a few days ago.

1. `locale` will always be present (except PING, which is responded with pong).
2. `guild_locale` will only be present in guild context interactions, and defaults to `en-US` if the guild doesn't have `COMMUNITY` feature.

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] 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)
-->


FasterSpeeding: Other than that, LGTM

Patch:
diff --git a/changes/962.feature.md b/changes/962.feature.md
new file mode 100644
index 00000000..36f94575
--- /dev/null
+++ b/changes/962.feature.md
@@ -0,0 +1,1 @@
+Add locale and guild_locale properties to interactions.
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 902d8d9d..8c655f55 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1900,6 +1900,8 @@ def deserialize_command_interaction(
             command_name=data_payload["name"],
             options=options,
             resolved=resolved,
+            locale=payload["locale"],
+            guild_locale=payload.get("guild_locale"),
         )
 
     def deserialize_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction:
@@ -1970,6 +1972,8 @@ def deserialize_component_interaction(
             custom_id=data_payload["custom_id"],
             component_type=message_models.ComponentType(data_payload["component_type"]),
             message=self.deserialize_message(payload["message"]),
+            locale=payload["locale"],
+            guild_locale=payload.get("guild_locale"),
         )
 
     ##################
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index 8f9cdf93..d274e4e0 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -145,6 +145,16 @@ class CommandInteraction(base_interactions.MessageResponseMixin[CommandResponseT
     This will be `builtins.None` for command interactions triggered in DMs.
     """
 
+    guild_locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
+    """The preferred language of the guild this command interaction was triggered in.
+
+    This will be `builtins.None` for command interactions triggered in DMs.
+
+    !!! note
+        This value can usually only be changed if `COMMUNITY` is in `hikari.guilds.Guild.features`
+        for the guild and will otherwise default to `en-US`.
+    """
+
     member: typing.Optional[base_interactions.InteractionMember] = attr.field(eq=False, hash=False, repr=True)
     """The member who triggered this command interaction.
 
@@ -158,6 +168,9 @@ class CommandInteraction(base_interactions.MessageResponseMixin[CommandResponseT
     user: users_.User = attr.field(eq=False, hash=False, repr=True)
     """The user who triggered this command interaction."""
 
+    locale: str = attr.field(eq=False, hash=False, repr=True)
+    """The selected language of the user who triggered this command interaction."""
+
     command_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
     """ID of the command being invoked."""
 
diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py
index 8fb78c4a..b53c8560 100644
--- a/hikari/interactions/component_interactions.py
+++ b/hikari/interactions/component_interactions.py
@@ -105,8 +105,19 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo
     guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False)
     """ID of the guild this interaction was triggered in.
 
-    This will be `builtins.None` for command interactions triggered in DMs.
+    This will be `builtins.None` for component interactions triggered in DMs.
     """
+
+    guild_locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
+    """The preferred language of the guild this component interaction was triggered in.
+
+    This will be `builtins.None` for component interactions triggered in DMs.
+
+    !!! note
+        This value can usually only be changed if `COMMUNITY` is in `hikari.guilds.Guild.features`
+        for the guild and will otherwise default to `en-US`.
+    """
+
     message: messages.Message = attr.field(eq=False, repr=False)
     """Object of the message the components for this interaction are attached to."""
 
@@ -123,6 +134,9 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo
     user: users.User = attr.field(eq=False, hash=False, repr=True)
     """The user who triggered this interaction."""
 
+    locale: str = attr.field(eq=False, hash=False, repr=True)
+    """The selected language of the user who triggered this component interaction."""
+
     def build_response(self, type_: _ImmediateTypesT, /) -> special_endpoints.InteractionMessageBuilder:
         """Get a message response builder for use in the REST server flow.
 
diff --git a/hikari/internal/enums.pyi b/hikari/internal/enums.pyi
index 5633c147..5fbf00c9 100644
--- a/hikari/internal/enums.pyi
+++ b/hikari/internal/enums.pyi
@@ -63,19 +63,18 @@ class Flag(__enum.IntFlag):
     def __len__(self) -> int: ...
     @staticmethod
     def __new__(cls: __Type[__FlagT], value: int = 0) -> __FlagT: ...
-
     # Aliases
-    def isdisjoint(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_disjoint
-    def issuperset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ... # is_superset
-    def symmetricdifference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference
+    def isdisjoint(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ...  # is_disjoint
+    def issuperset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ...  # is_superset
+    def symmetricdifference(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # symmetric_difference
     def issubset(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ...  # is_subset
     def __contains__(self: __FlagT, other: __Union[int, __FlagT]) -> bool: ...  # is_subset
-    def __rand__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection
-    def __and__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # intersection
-    def __ror__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union
-    def __or__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # union
-    def __rsub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference
-    def __sub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # difference
-    def __rxor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference
-    def __xor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ... # symmetric_difference
-    def __invert__(self: __FlagT) -> __FlagT: ... # invert
+    def __rand__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # intersection
+    def __and__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # intersection
+    def __ror__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # union
+    def __or__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # union
+    def __rsub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # difference
+    def __sub__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # difference
+    def __rxor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # symmetric_difference
+    def __xor__(self: __FlagT, other: __Union[int, __FlagT]) -> __FlagT: ...  # symmetric_difference
+    def __invert__(self: __FlagT) -> __FlagT: ...  # invert


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 1c623e6e..80d5e978 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -3004,6 +3004,8 @@ def command_interaction_payload(self, member_payload, user_payload, guild_role_p
             "channel_id": "49949494",
             "member": interaction_member_payload,
             "token": "moe cat girls",
+            "locale": "es-ES",
+            "guild_locale": "en-US",
             "version": 69420,
             "application_id": "76234234",
         }
@@ -3031,6 +3033,8 @@ def test_deserialize_command_interaction(
         assert interaction.version == 69420
         assert interaction.channel_id == 49949494
         assert interaction.guild_id == 43123123
+        assert interaction.locale == "es-ES"
+        assert interaction.guild_locale == "en-US"
 
         # InteractionMember
         assert interaction.member == entity_factory_impl._deserialize_interaction_member(
@@ -3243,6 +3247,8 @@ def component_interaction_payload(self, interaction_member_payload, message_payl
             "data": {"custom_id": "click_one", "component_type": 2, "values": ["1", "2", "67"]},
             "channel_id": "345626669114982999",
             "application_id": "290926444748734465",
+            "locale": "es-ES",
+            "guild_locale": "en-US",
         }
 
     def test_deserialize_component_interaction(
@@ -3266,6 +3272,8 @@ def test_deserialize_component_interaction(
         )
         assert interaction.user is interaction.member.user
         assert interaction.values == ["1", "2", "67"]
+        assert interaction.locale == "es-ES"
+        assert interaction.guild_locale == "en-US"
         assert isinstance(interaction, component_interactions.ComponentInteraction)
 
     def test_deserialize_component_interaction_with_undefined_fields(
@@ -3282,6 +3290,7 @@ def test_deserialize_component_interaction_with_undefined_fields(
                 "data": {"custom_id": "click_one", "component_type": 2},
                 "channel_id": "345626669114982999",
                 "application_id": "290926444748734465",
+                "locale": "es-ES",
             }
         )
 
@@ -3289,6 +3298,7 @@ def test_deserialize_component_interaction_with_undefined_fields(
         assert interaction.member is None
         assert interaction.user == entity_factory_impl.deserialize_user(user_payload)
         assert interaction.values == ()
+        assert interaction.guild_locale is None
         assert isinstance(interaction, component_interactions.ComponentInteraction)
 
     ##################
diff --git a/tests/hikari/interactions/test_command_interactions.py b/tests/hikari/interactions/test_command_interactions.py
index c8792475..055b1641 100644
--- a/tests/hikari/interactions/test_command_interactions.py
+++ b/tests/hikari/interactions/test_command_interactions.py
@@ -52,6 +52,8 @@ def mock_command_interaction(self, mock_app):
             command_name="OKOKOK",
             options=[],
             resolved=None,
+            locale="es-ES",
+            guild_locale="en-US",
         )
 
     def test_build_response(self, mock_command_interaction, mock_app):
diff --git a/tests/hikari/interactions/test_component_interactions.py b/tests/hikari/interactions/test_component_interactions.py
index 7b50e018..1eb7f8b5 100644
--- a/tests/hikari/interactions/test_component_interactions.py
+++ b/tests/hikari/interactions/test_component_interactions.py
@@ -53,6 +53,8 @@ def mock_component_interaction(self, mock_app):
             values=(),
             custom_id="OKOKOK",
             message=object(),
+            locale="es-ES",
+            guild_locale="en-US",
         )
 
     def test_build_response(self, mock_component_interaction, mock_app):