Repository: hikari-py/hikari
Pull request #631 (2021-05-31 21:01:20)

Implement `target_application` and rename `target_user_type` to `target_type`
### 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.




Patch:
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index fb532f15..ab396e69 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -678,8 +678,11 @@ async def create_invite(
         max_uses: undefined.UndefinedOr[int] = undefined.UNDEFINED,
         temporary: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
         unique: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        target_type: undefined.UndefinedOr[invites.TargetType] = undefined.UNDEFINED,
         target_user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users.PartialUser]] = undefined.UNDEFINED,
-        target_user_type: undefined.UndefinedOr[invites.TargetUserType] = undefined.UNDEFINED,
+        target_application: undefined.UndefinedOr[
+            snowflakes.SnowflakeishOr[guilds.PartialApplication]
+        ] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> invites.InviteWithMetadata:
         """Create an invite to the given guild channel.
@@ -700,11 +703,22 @@ async def create_invite(
             If provided, whether the invite only grants temporary membership.
         unique : hikari.undefined.UndefinedOr[builtins.bool]
             If provided, whether the invite should be unique.
+        target_type : hikari.undefined.UndefinedOr[hikari.invites.TargetType]
+            If provided, the target type of this invite.
         target_user : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]]
             If provided, the target user id for this invite. This may be the
             object or the ID of an existing user.
-        target_user_type : hikari.undefined.UndefinedOr[hikari.invites.TargetUserType]
-            If provided, the type of target user for this invite.
+
+            !!! note
+                This is required if `target_type` is `STREAM` and the targeted
+                user must be streaming into the channel.
+        target_application : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]]
+            If provided, the target application id for this invite. This may be
+            the object or the ID of an existing application.
+
+            !!! note
+                This is required if `target_type` is `EMBEDDED_APPLICATION` and
+                the targeted application must have the `EMBEDDED` flag.
         reason : hikari.undefined.UndefinedOr[builtins.str]
             If provided, the reason that will be recorded in the audit logs.
             Maximum of 512 characters.
diff --git a/hikari/applications.py b/hikari/applications.py
index 77e539d1..9e8d6827 100644
--- a/hikari/applications.py
+++ b/hikari/applications.py
@@ -25,6 +25,7 @@
 from __future__ import annotations
 
 __all__: typing.List[str] = [
+    "InviteApplication",
     "Application",
     "ApplicationFlags",
     "AuthorizationApplication",
@@ -453,6 +454,68 @@ def make_icon_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optiona
         )
 
 
+@attr_extensions.with_copy
+@attr.define(hash=True, kw_only=True, weakref_slot=False)
+class InviteApplication(guilds.PartialApplication):
+    """Represents the information of an Invite Application."""
+
+    app: traits.RESTAware = attr.field(
+        repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True}
+    )
+    """The client application that models may use for procedures."""
+
+    cover_image_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
+    """The CDN's hash of this application's cover image, used in the store or for embedded games."""
+
+    public_key: bytes = attr.field(eq=False, hash=False, repr=False)
+    """The key used for verifying interaction and GameSDK payload signatures."""
+
+    @property
+    def cover_image_url(self) -> typing.Optional[files.URL]:
+        """Cover image URL used in the store or for embedded games.
+
+        Returns
+        -------
+        typing.Optional[hikari.files.URL]
+            The URL, or `builtins.None` if no cover image exists.
+        """
+        return self.make_cover_image_url()
+
+    def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
+        """Generate the cover image URL used in the store or for embedded games, if set.
+
+        Parameters
+        ----------
+        ext : builtins.str
+            The extension to use for this URL, defaults to `png`.
+            Supports `png`, `jpeg`, `jpg` and `webp`.
+        size : builtins.int
+            The size to set for the URL, defaults to `4096`.
+            Can be any power of two between 16 and 4096.
+
+        Returns
+        -------
+        typing.Optional[hikari.files.URL]
+            The URL, or `builtins.None` if no cover image exists.
+
+        Raises
+        ------
+        builtins.ValueError
+            If the size is not an integer power of 2 between 16 and 4096
+            (inclusive).
+        """
+        if self.cover_image_hash is None:
+            return None
+
+        return routes.CDN_APPLICATION_COVER.compile_to_file(
+            urls.CDN_URL,
+            application_id=self.id,
+            hash=self.cover_image_hash,
+            size=size,
+            file_format=ext,
+        )
+
+
 @attr_extensions.with_copy
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
 class Application(guilds.PartialApplication):
@@ -497,7 +560,7 @@ class Application(guilds.PartialApplication):
     """
 
     cover_image_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
-    """The CDN's hash of this application's cover image, used on the store."""
+    """The CDN's hash of this application's cover image, used in the store or for embedded games."""
 
     terms_of_service_url: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
     """The URL of this application's terms of service."""
@@ -507,7 +570,7 @@ class Application(guilds.PartialApplication):
 
     @property
     def cover_image_url(self) -> typing.Optional[files.URL]:
-        """Cover image URL used on the store.
+        """Cover image URL used in the store or for embedded games.
 
         Returns
         -------
@@ -517,7 +580,7 @@ def cover_image_url(self) -> typing.Optional[files.URL]:
         return self.make_cover_image_url()
 
     def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.Optional[files.URL]:
-        """Generate the cover image URL used in the store, if set.
+        """Generate the cover image URL used in the store or for embedded games, if set.
 
         Parameters
         ----------
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index cb7bd65e..b2305bf0 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -151,7 +151,8 @@ class _InviteFields:
     channel_id: snowflakes.Snowflake = attr.field()
     inviter: typing.Optional[user_models.User] = attr.field()
     target_user: typing.Optional[user_models.User] = attr.field()
-    target_user_type: typing.Union[invite_models.TargetUserType, int, None] = attr.field()
+    target_application: typing.Optional[application_models.InviteApplication] = attr.field()
+    target_type: typing.Union[invite_models.TargetType, int, None] = attr.field()
     approximate_active_member_count: typing.Optional[int] = attr.field()
     approximate_member_count: typing.Optional[int] = attr.field()
 
@@ -1558,9 +1559,19 @@ def _set_invite_attributes(self, payload: data_binding.JSONObject) -> _InviteFie
         else:
             channel_id = snowflakes.Snowflake(payload["channel_id"])
 
-        target_user_type = (
-            invite_models.TargetUserType(payload["target_user_type"]) if "target_user_type" in payload else None
-        )
+        target_application: typing.Optional[application_models.InviteApplication] = None
+        if (invite_payload := payload.get("target_application")) is not None:
+            target_application = application_models.InviteApplication(
+                app=self._app,
+                id=snowflakes.Snowflake(invite_payload["id"]),
+                name=invite_payload["name"],
+                description=invite_payload["description"] or None,
+                summary=invite_payload["summary"] or None,
+                public_key=bytes.fromhex(invite_payload["verify_key"]),
+                icon_hash=invite_payload.get("icon"),
+                cover_image_hash=invite_payload.get("cover_image"),
+            )
+
         approximate_active_member_count = (
             int(payload["approximate_presence_count"]) if "approximate_presence_count" in payload else None
         )
@@ -1574,8 +1585,9 @@ def _set_invite_attributes(self, payload: data_binding.JSONObject) -> _InviteFie
             channel=channel,
             channel_id=channel_id,
             inviter=self.deserialize_user(payload["inviter"]) if "inviter" in payload else None,
+            target_type=invite_models.TargetType(payload["target_type"]) if "target_type" in payload else None,
             target_user=self.deserialize_user(payload["target_user"]) if "target_user" in payload else None,
-            target_user_type=target_user_type,
+            target_application=target_application,
             approximate_active_member_count=approximate_active_member_count,
             approximate_member_count=approximate_member_count,
         )
@@ -1595,8 +1607,9 @@ def deserialize_invite(self, payload: data_binding.JSONObject) -> invite_models.
             channel=invite_fields.channel,
             channel_id=invite_fields.channel_id,
             inviter=invite_fields.inviter,
+            target_type=invite_fields.target_type,
             target_user=invite_fields.target_user,
-            target_user_type=invite_fields.target_user_type,
+            target_application=invite_fields.target_application,
             approximate_member_count=invite_fields.approximate_member_count,
             approximate_active_member_count=invite_fields.approximate_active_member_count,
             expires_at=expires_at,
@@ -1621,8 +1634,9 @@ def deserialize_invite_with_metadata(self, payload: data_binding.JSONObject) ->
             channel=invite_fields.channel,
             channel_id=invite_fields.channel_id,
             inviter=invite_fields.inviter,
+            target_type=invite_fields.target_type,
             target_user=invite_fields.target_user,
-            target_user_type=invite_fields.target_user_type,
+            target_application=invite_fields.target_application,
             approximate_member_count=invite_fields.approximate_member_count,
             approximate_active_member_count=invite_fields.approximate_active_member_count,
             uses=int(payload["uses"]),
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index f791f621..5352bbb2 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -1025,8 +1025,11 @@ async def create_invite(
         max_uses: undefined.UndefinedOr[int] = undefined.UNDEFINED,
         temporary: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
         unique: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        target_type: undefined.UndefinedOr[invites.TargetType] = undefined.UNDEFINED,
         target_user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users.PartialUser]] = undefined.UNDEFINED,
-        target_user_type: undefined.UndefinedOr[invites.TargetUserType] = undefined.UNDEFINED,
+        target_application: undefined.UndefinedOr[
+            snowflakes.SnowflakeishOr[guilds.PartialApplication]
+        ] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> invites.InviteWithMetadata:
         route = routes.POST_CHANNEL_INVITES.compile(channel=channel)
@@ -1035,8 +1038,9 @@ async def create_invite(
         body.put("max_uses", max_uses)
         body.put("temporary", temporary)
         body.put("unique", unique)
+        body.put("target_type", target_type)
         body.put_snowflake("target_user_id", target_user)
-        body.put("target_user_type", target_user_type)
+        body.put_snowflake("target_application_id", target_application)
         response = await self._request(route, json=body, reason=reason)
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_invite_with_metadata(response)
diff --git a/hikari/internal/cache.py b/hikari/internal/cache.py
index ca0d65e3..da981568 100644
--- a/hikari/internal/cache.py
+++ b/hikari/internal/cache.py
@@ -68,6 +68,7 @@
 from hikari.internal import collections
 
 if typing.TYPE_CHECKING:
+    from hikari import applications
     from hikari import channels as channels_
     from hikari import traits
     from hikari import users as users_
@@ -336,8 +337,9 @@ class InviteData(BaseData[invites.InviteWithMetadata]):
     guild_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     channel_id: snowflakes.Snowflake = attr.field()
     inviter: typing.Optional[RefCell[users_.User]] = attr.field()
+    target_type: typing.Union[invites.TargetType, int, None] = attr.field()
     target_user: typing.Optional[RefCell[users_.User]] = attr.field()
-    target_user_type: typing.Union[invites.TargetUserType, int, None] = attr.field()
+    target_application: typing.Optional[applications.InviteApplication] = attr.ib()
     uses: int = attr.field()
     max_uses: typing.Optional[int] = attr.field()
     max_age: typing.Optional[datetime.timedelta] = attr.field()
@@ -349,7 +351,8 @@ def build_entity(self, app: traits.RESTAware, /) -> invites.InviteWithMetadata:
             code=self.code,
             guild_id=self.guild_id,
             channel_id=self.channel_id,
-            target_user_type=self.target_user_type,
+            target_type=self.target_type,
+            target_application=self.target_application,
             uses=self.uses,
             max_uses=self.max_uses,
             max_age=self.max_age,
@@ -384,7 +387,8 @@ def build_from_entity(
             code=invite.code,
             guild_id=invite.guild_id,
             channel_id=invite.channel_id,
-            target_user_type=invite.target_user_type,
+            target_type=invite.target_type,
+            target_application=invite.target_application,
             uses=invite.uses,
             max_uses=invite.max_uses,
             max_age=invite.max_age,
diff --git a/hikari/invites.py b/hikari/invites.py
index f9123d38..24e3f4dd 100644
--- a/hikari/invites.py
+++ b/hikari/invites.py
@@ -25,7 +25,7 @@
 from __future__ import annotations
 
 __all__: typing.List[str] = [
-    "TargetUserType",
+    "TargetType",
     "VanityURL",
     "InviteGuild",
     "Invite",
@@ -47,6 +47,7 @@
 if typing.TYPE_CHECKING:
     import datetime
 
+    from hikari import applications
     from hikari import channels
     from hikari import files
     from hikari import snowflakes
@@ -55,12 +56,15 @@
 
 
 @typing.final
-class TargetUserType(int, enums.Enum):
-    """The reason a invite targets a user."""
+class TargetType(int, enums.Enum):
+    """The target of the invite."""
 
     STREAM = 1
     """This invite is targeting a "Go Live" stream."""
 
+    EMBEDDED_APPLICATION = 2
+    """This invite is targeting an embedded application."""
+
 
 class InviteCode(abc.ABC):
     """A representation of a guild/channel invite."""
@@ -255,11 +259,14 @@ class Invite(InviteCode):
     inviter: typing.Optional[users.User] = attr.field(eq=False, hash=False, repr=False)
     """The object of the user who created this invite."""
 
+    target_type: typing.Union[TargetType, int, None] = attr.ib(eq=False, hash=False, repr=False)
+    """The type of the target of this invite, if applicable."""
+
     target_user: typing.Optional[users.User] = attr.field(eq=False, hash=False, repr=False)
     """The object of the user who this invite targets, if set."""
 
-    target_user_type: typing.Union[TargetUserType, int, None] = attr.field(eq=False, hash=False, repr=False)
-    """The type of user target this invite is, if applicable."""
+    target_application: typing.Optional[applications.InviteApplication] = attr.ib(eq=False, hash=False, repr=False)
+    """The embedded application this invite targets, if applicable."""
 
     approximate_active_member_count: typing.Optional[int] = attr.field(eq=False, hash=False, repr=False)
     """The approximate amount of presences in this invite's guild.


Test patch:
diff --git a/tests/hikari/impl/test_cache.py b/tests/hikari/impl/test_cache.py
index c1db92ec..8a252338 100644
--- a/tests/hikari/impl/test_cache.py
+++ b/tests/hikari/impl/test_cache.py
@@ -925,13 +925,15 @@ def test_update_guild_channel(self, cache_impl):
     def test__build_invite(self, cache_impl):
         mock_inviter = mock.MagicMock(users.User)
         mock_target_user = mock.MagicMock(users.User)
+        mock_application = object()
         invite_data = cache_utilities.InviteData(
             code="okokok",
             guild_id=snowflakes.Snowflake(965234),
             channel_id=snowflakes.Snowflake(87345234),
             inviter=cache_utilities.RefCell(mock_inviter),
+            target_type=invites.TargetType.STREAM,
             target_user=cache_utilities.RefCell(mock_target_user),
-            target_user_type=invites.TargetUserType.STREAM,
+            target_application=mock_application,
             uses=42,
             max_uses=999,
             max_age=datetime.timedelta(days=7),
@@ -951,7 +953,8 @@ def test__build_invite(self, cache_impl):
         assert invite.target_user == mock_target_user
         assert invite.inviter is not mock_inviter
         assert invite.target_user is not mock_target_user
-        assert invite.target_user_type is invites.TargetUserType.STREAM
+        assert invite.target_type is invites.TargetType.STREAM
+        assert invite.target_application is mock_application
         assert invite.approximate_active_member_count is None
         assert invite.approximate_member_count is None
         assert invite.uses == 42
@@ -966,8 +969,9 @@ def test__build_invite_without_users(self, cache_impl):
             guild_id=snowflakes.Snowflake(965234),
             channel_id=snowflakes.Snowflake(87345234),
             inviter=None,
+            target_type=invites.TargetType.STREAM,
             target_user=None,
-            target_user_type=invites.TargetUserType.STREAM,
+            target_application=None,
             uses=42,
             max_uses=999,
             max_age=datetime.timedelta(days=7),
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index cca260b9..0227b2e1 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -187,7 +187,7 @@ def owner_payload(self, user_payload):
         return {**user_payload, "flags": 1 << 10}
 
     @pytest.fixture()
-    def application_information_payload(self, owner_payload, user_payload):
+    def application_payload(self, owner_payload, user_payload):
         return {
             "id": "209333111222",
             "name": "Dream Sweet in Sea Major",
@@ -217,9 +217,10 @@ def application_information_payload(self, owner_payload, user_payload):
         }
 
     def test_deserialize_application(
-        self, entity_factory_impl, mock_app, application_information_payload, owner_payload, user_payload
+        self, entity_factory_impl, mock_app, application_payload, owner_payload, user_payload
     ):
-        application = entity_factory_impl.deserialize_application(application_information_payload)
+        application = entity_factory_impl.deserialize_application(application_payload)
+
         assert application.app is mock_app
         assert application.id == 209333111222
         assert application.name == "Dream Sweet in Sea Major"
@@ -262,7 +263,6 @@ def test_deserialize_application_with_unset_fields(self, entity_factory_impl, mo
             {
                 "id": "209333111222",
                 "name": "Dream Sweet in Sea Major",
-                "icon": "3123123",
                 "description": "I am an application",
                 "summary": "not a blank string",
                 "bot_public": True,
@@ -271,11 +271,13 @@ def test_deserialize_application_with_unset_fields(self, entity_factory_impl, mo
                 "owner": owner_payload,
             }
         )
+
         assert application.rpc_origins is None
         assert application.team is None
         assert application.guild_id is None
         assert application.primary_sku_id is None
         assert application.slug is None
+        assert application.icon_hash is None
         assert application.cover_image_hash is None
         assert application.privacy_policy_url is None
         assert application.terms_of_service_url is None
@@ -286,8 +288,8 @@ def test_deserialize_application_with_null_fields(self, entity_factory_impl, moc
                 "id": "209333111222",
                 "name": "Dream Sweet in Sea Major",
                 "icon": None,
-                "description": "I am an application",
-                "summary": "not a blank string",
+                "description": "",
+                "summary": "",
                 "team": None,
                 "owner": owner_payload,
                 "bot_public": True,
@@ -295,9 +297,25 @@ def test_deserialize_application_with_null_fields(self, entity_factory_impl, moc
                 "verify_key": "1232313223",
             }
         )
+
+        assert application.description is None
+        assert application.summary is None
         assert application.icon_hash is None
+        assert application.cover_image_hash is None
         assert application.team is None
 
+    @pytest.fixture()
+    def invite_application_payload(self):
+        return {
+            "id": "773336526917861400",
+            "name": "Betrayal.io",
+            "icon": "0227b2e89ea08d666c43003fbadbc72a",
+            "description": "Play inside Discord with your friends!",
+            "summary": "Play inside Discord with your friends! (but as a summary)",
+            "cover_image": "0227b2e89ea08d666c43003fbadbc72a (but as cover)",
+            "verify_key": "1bf78fdbfcbabe2e1256f9b133818976591203a22febabba5ff89f86f24760ff",
+        }
+
     @pytest.fixture()
     def authorization_information_payload(self, user_payload):
         return {
@@ -3029,7 +3047,12 @@ def alternative_user_payload(self):
 
     @pytest.fixture()
     def invite_payload(
-        self, partial_channel_payload, user_payload, alternative_user_payload, guild_welcome_screen_payload
+        self,
+        partial_channel_payload,
+        user_payload,
+        alternative_user_payload,
+        guild_welcome_screen_payload,
+        invite_application_payload,
     ):
         return {
             "code": "aCode",
@@ -3048,8 +3071,9 @@ def invite_payload(
             },
             "channel": partial_channel_payload,
             "inviter": user_payload,
+            "target_type": 1,
             "target_user": alternative_user_payload,
-            "target_user_type": 1,
+            "target_application": invite_application_payload,
             "approximate_presence_count": 42,
             "approximate_member_count": 84,
             "expires_at": "2021-05-08T00:15:24.534000+00:00",
@@ -3064,6 +3088,7 @@ def test_deserialize_invite(
         user_payload,
         guild_welcome_screen_payload,
         alternative_user_payload,
+        application_payload,
     ):
         invite = entity_factory_impl.deserialize_invite(invite_payload)
         assert invite.app is mock_app
@@ -3087,21 +3112,31 @@ def test_deserialize_invite(
         assert invite.channel == entity_factory_impl.deserialize_partial_channel(partial_channel_payload)
         assert invite.channel_id == 561884984214814750
         assert invite.inviter == entity_factory_impl.deserialize_user(user_payload)
+        assert invite.target_type == invite_models.TargetType.STREAM
         assert invite.target_user == entity_factory_impl.deserialize_user(alternative_user_payload)
-        assert invite.target_user_type == invite_models.TargetUserType.STREAM
         assert invite.approximate_member_count == 84
         assert invite.approximate_active_member_count == 42
         assert invite.expires_at == datetime.datetime(2021, 5, 8, 0, 15, 24, 534000, tzinfo=datetime.timezone.utc)
         assert isinstance(invite, invite_models.Invite)
 
-    def test_deserialize_invite_with_unset_guild_fields(self, entity_factory_impl, invite_payload):
-        del invite_payload["guild"]["welcome_screen"]
-
-        invite = entity_factory_impl.deserialize_invite(invite_payload)
-
-        assert invite.guild.welcome_screen is None
+        # InviteApplication
+        application = invite.target_application
+        assert application.app is mock_app
+        assert application.id == 773336526917861400
+        assert application.name == "Betrayal.io"
+        assert application.description == "Play inside Discord with your friends!"
+        assert application.summary == "Play inside Discord with your friends! (but as a summary)"
+        assert (
+            application.public_key
+            == b"\x1b\xf7\x8f\xdb\xfc\xba\xbe.\x12V\xf9\xb13\x81\x89vY\x12\x03\xa2/\xeb\xab\xba_\xf8\x9f\x86\xf2G`\xff"
+        )
+        assert application.icon_hash == "0227b2e89ea08d666c43003fbadbc72a"
+        assert application.cover_image_hash == "0227b2e89ea08d666c43003fbadbc72a (but as cover)"
+        assert isinstance(application, application_models.InviteApplication)
 
-    def test_deserialize_invite_with_null_fields(self, entity_factory_impl, partial_channel_payload):
+    def test_deserialize_invite_with_null_fields(
+        self, entity_factory_impl, partial_channel_payload, invite_application_payload
+    ):
         invite = entity_factory_impl.deserialize_invite(
             {
                 "code": "aCode",
@@ -3109,9 +3144,18 @@ def test_deserialize_invite_with_null_fields(self, entity_factory_impl, partial_
                 "approximate_member_count": 231,
                 "approximate_presence_count": 9,
                 "expires_at": None,
+                "target_application": {
+                    "id": "773336526917861400",
+                    "name": "Betrayal.io",
+                    "description": "",
+                    "summary": "",
+                    "verify_key": "1bf78fdbfcbabe2e1256f9b133818976591203a22febabba5ff89f86f24760ff",
+                },
             }
         )
         assert invite.expires_at is None
+        assert invite.target_application.description is None
+        assert invite.target_application.summary is None
 
     def test_deserialize_invite_with_unset_fields(self, entity_factory_impl, partial_channel_payload):
         invite = entity_factory_impl.deserialize_invite(
@@ -3126,10 +3170,27 @@ def test_deserialize_invite_with_unset_fields(self, entity_factory_impl, partial
         assert invite.channel_id == 43123123
         assert invite.guild is None
         assert invite.inviter is None
+        assert invite.target_type is None
         assert invite.target_user is None
-        assert invite.target_user_type is None
+        assert invite.target_application is None
         assert invite.expires_at is None
 
+    def test_deserialize_invite_with_unset_sub_fields(self, entity_factory_impl, invite_payload):
+        del invite_payload["guild"]["welcome_screen"]
+        invite_payload["target_application"] = {
+            "id": "773336526917861400",
+            "name": "Betrayal.io",
+            "description": "Play inside Discord with your friends!",
+            "summary": "Play inside Discord with your friends! (but as a summary)",
+            "verify_key": "1bf78fdbfcbabe2e1256f9b133818976591203a22febabba5ff89f86f24760ff",
+        }
+
+        invite = entity_factory_impl.deserialize_invite(invite_payload)
+
+        assert invite.guild.welcome_screen is None
+        assert invite.target_application.icon_hash is None
+        assert invite.target_application.cover_image_hash is None
+
     def test_deserialize_invite_with_guild_and_channel_ids_without_objects(self, entity_factory_impl):
         invite = entity_factory_impl.deserialize_invite({"code": "aCode", "guild_id": "42", "channel_id": "202020"})
         assert invite.channel is None
@@ -3139,7 +3200,12 @@ def test_deserialize_invite_with_guild_and_channel_ids_without_objects(self, ent
 
     @pytest.fixture()
     def invite_with_metadata_payload(
-        self, partial_channel_payload, user_payload, alternative_user_payload, guild_welcome_screen_payload
+        self,
+        partial_channel_payload,
+        user_payload,
+        alternative_user_payload,
+        guild_welcome_screen_payload,
+        invite_application_payload,
     ):
         return {
             "code": "aCode",
@@ -3158,8 +3224,9 @@ def invite_with_metadata_payload(
             },
             "channel": partial_channel_payload,
             "inviter": user_payload,
+            "target_type": 1,
             "target_user": alternative_user_payload,
-            "target_user_type": 1,
+            "target_application": invite_application_payload,
             "approximate_presence_count": 42,
             "approximate_member_count": 84,
             "uses": 3,
@@ -3178,6 +3245,7 @@ def test_deserialize_invite_with_metadata(
         user_payload,
         alternative_user_payload,
         guild_welcome_screen_payload,
+        invite_application_payload,
     ):
         invite_with_metadata = entity_factory_impl.deserialize_invite_with_metadata(invite_with_metadata_payload)
         assert invite_with_metadata.app is mock_app
@@ -3199,8 +3267,8 @@ def test_deserialize_invite_with_metadata(
 
         assert invite_with_metadata.channel == entity_factory_impl.deserialize_partial_channel(partial_channel_payload)
         assert invite_with_metadata.inviter == entity_factory_impl.deserialize_user(user_payload)
+        assert invite_with_metadata.target_type == invite_models.TargetType.STREAM
         assert invite_with_metadata.target_user == entity_factory_impl.deserialize_user(alternative_user_payload)
-        assert invite_with_metadata.target_user_type == invite_models.TargetUserType.STREAM
         assert invite_with_metadata.approximate_member_count == 84
         assert invite_with_metadata.approximate_active_member_count == 42
         assert invite_with_metadata.uses == 3
@@ -3215,6 +3283,21 @@ def test_deserialize_invite_with_metadata(
         )
         assert isinstance(invite_with_metadata, invite_models.InviteWithMetadata)
 
+        # InviteApplication
+        application = invite_with_metadata.target_application
+        assert application.app is mock_app
+        assert application.id == 773336526917861400
+        assert application.name == "Betrayal.io"
+        assert application.description == "Play inside Discord with your friends!"
+        assert application.summary == "Play inside Discord with your friends! (but as a summary)"
+        assert (
+            application.public_key
+            == b"\x1b\xf7\x8f\xdb\xfc\xba\xbe.\x12V\xf9\xb13\x81\x89vY\x12\x03\xa2/\xeb\xab\xba_\xf8\x9f\x86\xf2G`\xff"
+        )
+        assert application.icon_hash == "0227b2e89ea08d666c43003fbadbc72a"
+        assert application.cover_image_hash == "0227b2e89ea08d666c43003fbadbc72a (but as cover)"
+        assert isinstance(application, application_models.InviteApplication)
+
     def test_deserialize_invite_with_metadata_with_unset_and_0_fields(
         self, entity_factory_impl, partial_channel_payload
     ):
@@ -3233,8 +3316,9 @@ def test_deserialize_invite_with_metadata_with_unset_and_0_fields(
         )
         assert invite_with_metadata.guild is None
         assert invite_with_metadata.inviter is None
+        assert invite_with_metadata.target_type is None
         assert invite_with_metadata.target_user is None
-        assert invite_with_metadata.target_user_type is None
+        assert invite_with_metadata.target_application is None
         assert invite_with_metadata.max_age is None
         assert invite_with_metadata.max_uses is None
         assert invite_with_metadata.expires_at is None
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 875a18e8..7b3b46b8 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -1601,8 +1601,9 @@ async def test_create_invite(self, rest_client):
             "max_uses": 4,
             "temporary": True,
             "unique": True,
+            "target_type": invites.TargetType.STREAM,
             "target_user_id": "456",
-            "target_user_type": invites.TargetUserType.STREAM,
+            "target_application_id": "789",
         }
 
         result = await rest_client.create_invite(
@@ -1611,8 +1612,9 @@ async def test_create_invite(self, rest_client):
             max_uses=4,
             temporary=True,
             unique=True,
+            target_type=invites.TargetType.STREAM,
             target_user=StubModel(456),
-            target_user_type=invites.TargetUserType.STREAM,
+            target_application=StubModel(789),
             reason="cause why not :)",
         )