Repository: hikari-py/hikari
Pull request #591 (2021-05-09 06:52:07)

Add guild methods
### Summary
This PR adds the methods listed in #37 by using the bot's REST client.

### 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.

### Related issues
#37 


ashwinvin: Rebased !
ashwinvin: I think I have implemented every thing specified in the issue.
ashwinvin: Seems like mypy is complaining after changing the return types for the fetch_* methods 
davfsa: > 
> 
> Seems like mypy is complaining after changing the return types for the fetch_* methods

You will need to write assertions after the fetch call. You can check other `fetch_*` calls that do the same
davfsa: @ashwinvin You got merge conflicts + what Faster pointed out

Patch:
diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py
index 9c6f7cbd..5baf9647 100644
--- a/hikari/api/entity_factory.py
+++ b/hikari/api/entity_factory.py
@@ -882,7 +882,7 @@ def deserialize_integration(
         """
 
     @abc.abstractmethod
-    def deserialize_guild_member_ban(self, payload: data_binding.JSONObject) -> guild_models.GuildMemberBan:
+    def deserialize_guild_member_ban(self, payload: data_binding.JSONObject) -> guild_models.GuildBan:
         """Parse a raw payload from Discord into a guild member ban object.
 
         Parameters
@@ -892,7 +892,7 @@ def deserialize_guild_member_ban(self, payload: data_binding.JSONObject) -> guil
 
         Returns
         -------
-        hikari.guilds.GuildMemberBan
+        hikari.guilds.GuildBan
             The deserialized guild member ban object.
         """
 
@@ -1309,7 +1309,54 @@ def deserialize_voice_region(self, payload: data_binding.JSONObject) -> voice_mo
     ##################
 
     @abc.abstractmethod
-    def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_models.Webhook:
+    def deserialize_incoming_webhook(self, payload: data_binding.JSONObject) -> webhook_models.IncomingWebhook:
+        """Parse a raw payload from Discord into a incoming webhook object.
+
+        Parameters
+        ----------
+        payload : hikari.internal.data_binding.JSONObject
+            The JSON payload to deserialize.
+
+        Returns
+        -------
+        hikari.webhooks.IncomingWebhook
+            The parsed incoming webhook object.
+        """
+
+    @abc.abstractmethod
+    def deserialize_channel_follower_webhook(
+        self, payload: data_binding.JSONObject
+    ) -> webhook_models.ChannelFollowerWebhook:
+        """Parse a raw payload from Discord into a channel follower webhook object.
+
+        Parameters
+        ----------
+        payload : hikari.internal.data_binding.JSONObject
+            The JSON payload to deserialize.
+
+        Returns
+        -------
+        hikari.webhooks.ChannelFollowerWebhook
+            The parsed channel follower webhook object.
+        """
+
+    @abc.abstractmethod
+    def deserialize_application_webhook(self, payload: data_binding.JSONObject) -> webhook_models.ApplicationWebhook:
+        """Parse a raw payload from Discord into an application webhook object.
+
+        Parameters
+        ----------
+        payload : hikari.internal.data_binding.JSONObject
+            The JSON payload to deserialize.
+
+        Returns
+        -------
+        hikari.webhooks.ApplicationWebhook
+            The parsed application webhook object.
+        """
+
+    @abc.abstractmethod
+    def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_models.PartialWebhook:
         """Parse a raw payload from Discord into a webhook object.
 
         Parameters
@@ -1319,6 +1366,6 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        hikari.webhooks.PartialWebhook
             The deserialized webhook object.
         """
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 552736ff..7ca01c8e 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.
@@ -1830,7 +1844,7 @@ async def create_webhook(
         *,
         avatar: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.IncomingWebhook:
         """Create webhook in a channel.
 
         Parameters
@@ -1851,7 +1865,7 @@ async def create_webhook(
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        hikari.webhooks.IncomingWebhook
             The created webhook.
 
         Raises
@@ -1882,15 +1896,15 @@ async def create_webhook(
     @abc.abstractmethod
     async def fetch_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.PartialWebhook:
         """Fetch an existing webhook.
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.Webhook]
+        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.PartialWebhook]
             The webhook to fetch. This may be the object or the ID
             of an existing webhook.
 
@@ -1902,7 +1916,7 @@ async def fetch_webhook(
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        hikari.webhooks.PartialWebhook
             The requested webhook.
 
         Raises
@@ -1933,7 +1947,7 @@ async def fetch_webhook(
     async def fetch_channel_webhooks(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextChannel],
-    ) -> typing.Sequence[webhooks.Webhook]:
+    ) -> typing.Sequence[webhooks.PartialWebhook]:
         """Fetch all channel webhooks.
 
         Parameters
@@ -1945,7 +1959,7 @@ async def fetch_channel_webhooks(
 
         Returns
         -------
-        typing.Sequence[hikari.webhooks.Webhook]
+        typing.Sequence[hikari.webhooks.PartialWebhook]
             The fetched webhooks.
 
         Raises
@@ -1975,7 +1989,7 @@ async def fetch_channel_webhooks(
     async def fetch_guild_webhooks(
         self,
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
-    ) -> typing.Sequence[webhooks.Webhook]:
+    ) -> typing.Sequence[webhooks.PartialWebhook]:
         """Fetch all guild webhooks.
 
         Parameters
@@ -1986,7 +2000,7 @@ async def fetch_guild_webhooks(
 
         Returns
         -------
-        typing.Sequence[hikari.webhooks.Webhook]
+        typing.Sequence[hikari.webhooks.PartialWebhook]
             The fetched webhooks.
 
         Raises
@@ -2015,19 +2029,19 @@ async def fetch_guild_webhooks(
     @abc.abstractmethod
     async def edit_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
         name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
         avatar: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
         channel: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.TextChannel]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.PartialWebhook:
         """Edit a webhook.
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.Webhook]
+        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.PartialWebhook]
             The webhook to edit. This may be the object or the
             ID of an existing webhook.
 
@@ -2049,7 +2063,7 @@ async def edit_webhook(
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        hikari.webhooks.PartialWebhook
             The edited webhook.
 
         Raises
@@ -2079,7 +2093,7 @@ async def edit_webhook(
     @abc.abstractmethod
     async def delete_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> None:
@@ -2087,7 +2101,7 @@ async def delete_webhook(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.Webhook]
+        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.PartialWebhook]
             The webhook to delete. This may be the object or the
             ID of an existing webhook.
 
@@ -4894,7 +4908,7 @@ async def fetch_ban(
         self,
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
         user: snowflakes.SnowflakeishOr[users.PartialUser],
-    ) -> guilds.GuildMemberBan:
+    ) -> guilds.GuildBan:
         """Fetch the guild's ban info for a user.
 
         Parameters
@@ -4908,7 +4922,7 @@ async def fetch_ban(
 
         Returns
         -------
-        hikari.guilds.GuildMemberBan
+        hikari.guilds.GuildBan
             The requested ban info.
 
         Raises
@@ -4939,7 +4953,7 @@ async def fetch_ban(
     async def fetch_bans(
         self,
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
-    ) -> typing.Sequence[guilds.GuildMemberBan]:
+    ) -> typing.Sequence[guilds.GuildBan]:
         """Fetch the bans of a guild.
 
         Parameters
@@ -4950,7 +4964,7 @@ async def fetch_bans(
 
         Returns
         -------
-        typing.Sequence[hikari.guilds.GuildMemberBan]
+        typing.Sequence[hikari.guilds.GuildBan]
             The requested bans.
 
         Raises
diff --git a/hikari/applications.py b/hikari/applications.py
index 3596842c..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
         ----------
@@ -629,7 +692,7 @@ class OAuth2AuthorizationToken(PartialOAuth2Token):
     refresh_token: int = attr.field(eq=False, hash=False, repr=False)
     """Refresh token used to obtain new access tokens with the same grant."""
 
-    webhook: typing.Optional[webhooks.Webhook] = attr.field(eq=False, hash=False, repr=True)
+    webhook: typing.Optional[webhooks.IncomingWebhook] = attr.field(eq=False, hash=False, repr=True)
     """Object of the webhook that was created.
 
     This will only be present if this token was authorized with the
diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py
index 28cf5133..509f1c48 100644
--- a/hikari/audit_logs.py
+++ b/hikari/audit_logs.py
@@ -484,7 +484,7 @@ class AuditLog(typing.Sequence[AuditLogEntry]):
     users: typing.Mapping[snowflakes.Snowflake, users_.User] = attr.field(repr=False)
     """A mapping of the objects of users found in this audit log."""
 
-    webhooks: typing.Mapping[snowflakes.Snowflake, webhooks_.Webhook] = attr.field(repr=False)
+    webhooks: typing.Mapping[snowflakes.Snowflake, webhooks_.PartialWebhook] = attr.field(repr=False)
     """A mapping of the objects of webhooks found in this audit log."""
 
     @typing.overload
diff --git a/hikari/channels.py b/hikari/channels.py
index 4371a22f..ea201397 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -54,6 +54,7 @@
 from hikari import traits
 from hikari import undefined
 from hikari import urls
+from hikari import webhooks
 from hikari.internal import attr_extensions
 from hikari.internal import enums
 from hikari.internal import routes
@@ -68,7 +69,6 @@
     from hikari import messages
     from hikari import users
     from hikari import voices
-    from hikari import webhooks
     from hikari.api import special_endpoints
     from hikari.internal import time
 
@@ -187,12 +187,12 @@ async def fetch_channel(self) -> typing.Union[GuildNewsChannel, GuildTextChannel
         assert isinstance(channel, (GuildTextChannel, GuildNewsChannel))
         return channel
 
-    async def fetch_webhook(self) -> webhooks.Webhook:
+    async def fetch_webhook(self) -> webhooks.ChannelFollowerWebhook:
         """Fetch the webhook attached to this follow.
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        hikari.webhooks.ChannelFollowerWebhook
             The webhook attached to this follow.
 
         Raises
@@ -218,7 +218,9 @@ async def fetch_webhook(self) -> webhooks.Webhook:
         hikari.errors.InternalServerError
             If an internal error occurs on Discord while handling the request.
         """
-        return await self.app.rest.fetch_webhook(self.webhook_id)
+        webhook = await self.app.rest.fetch_webhook(self.webhook_id)
+        assert isinstance(webhook, webhooks.ChannelFollowerWebhook)
+        return webhook
 
     @property
     def channel(self) -> typing.Union[GuildNewsChannel, GuildTextChannel, None]:
diff --git a/hikari/events/channel_events.py b/hikari/events/channel_events.py
index 082c1e18..e5e2ea23 100644
--- a/hikari/events/channel_events.py
+++ b/hikari/events/channel_events.py
@@ -779,12 +779,12 @@ class WebhookUpdateEvent(GuildChannelEvent):
 
     # <<inherited docstring from GuildChannelEvent>>.
 
-    async def fetch_channel_webhooks(self) -> typing.Sequence[webhooks.Webhook]:
+    async def fetch_channel_webhooks(self) -> typing.Sequence[webhooks.PartialWebhook]:
         """Perform an API call to fetch the webhooks for this channel.
 
         Returns
         -------
-        typing.Sequence[hikari.webhooks.Webhook]
+        typing.Sequence[hikari.webhooks.PartialWebhook]
             The webhooks in this channel.
 
         Raises
@@ -811,12 +811,12 @@ async def fetch_channel_webhooks(self) -> typing.Sequence[webhooks.Webhook]:
         """
         return await self.app.rest.fetch_channel_webhooks(self.channel_id)
 
-    async def fetch_guild_webhooks(self) -> typing.Sequence[webhooks.Webhook]:
+    async def fetch_guild_webhooks(self) -> typing.Sequence[webhooks.PartialWebhook]:
         """Perform an API call to fetch the webhooks for this guild.
 
         Returns
         -------
-        typing.Sequence[hikari.webhooks.Webhook]
+        typing.Sequence[hikari.webhooks.PartialWebhook]
             The webhooks in this guild.
 
         Raises
diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py
index 85161dbd..ce56dce7 100644
--- a/hikari/events/guild_events.py
+++ b/hikari/events/guild_events.py
@@ -384,7 +384,7 @@ def user_id(self) -> snowflakes.Snowflake:
         """
         return self.user.id
 
-    async def fetch_ban(self) -> guilds.GuildMemberBan:
+    async def fetch_ban(self) -> guilds.GuildBan:
         """Perform an API call to fetch the details about this ban.
 
         This will include the optionally defined audit log reason for the
@@ -392,7 +392,7 @@ async def fetch_ban(self) -> guilds.GuildMemberBan:
 
         Returns
         -------
-        hikari.guilds.GuildMemberBan
+        hikari.guilds.GuildBan
             The ban details.
         """
         return await self.app.rest.fetch_ban(self.guild_id, self.user)
diff --git a/hikari/guilds.py b/hikari/guilds.py
index cc46b0aa..c3fda2ef 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -39,7 +39,7 @@
     "GuildVerificationLevel",
     "GuildPremiumTier",
     "GuildPreview",
-    "GuildMemberBan",
+    "GuildBan",
     "GuildNSFWLevel",
     "Member",
     "Integration",
@@ -60,6 +60,7 @@
 
 import attr
 
+from hikari import channels as channels_
 from hikari import snowflakes
 from hikari import traits
 from hikari import undefined
@@ -72,7 +73,6 @@
 if typing.TYPE_CHECKING:
     import datetime
 
-    from hikari import channels as channels_
     from hikari import colors
     from hikari import colours
     from hikari import emojis as emojis_
@@ -80,6 +80,7 @@
     from hikari import permissions as permissions_
     from hikari import presences as presences_
     from hikari import voices as voices_
+    from hikari.internal import time
 
 
 @typing.final
@@ -289,6 +290,52 @@ class GuildWidget:
     is_enabled: bool = attr.field(repr=True)
     """Whether this embed is enabled."""
 
+    async def fetch_channel(self) -> typing.Optional[channels_.GuildChannel]:
+        """Fetch the widget channel.
+
+        This will be `builtins.None` if not set.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildChannel]
+            The requested channel.
+
+            You can check the type of the channel by
+            using `builtins.isinstance`.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.channel_id:
+            return None
+
+        widget_channel = await self.app.rest.fetch_channel(self.channel_id)
+        assert isinstance(widget_channel, channels_.GuildChannel)
+
+        return widget_channel
+
 
 @attr_extensions.with_copy
 @attr.define(eq=False, hash=False, kw_only=True, weakref_slot=False)
@@ -515,6 +562,40 @@ async def fetch_self(self) -> Member:
         """
         return await self.user.app.rest.fetch_member(self.guild_id, self.user.id)
 
+    async def fetch_dm_channel(self) -> channels_.DMChannel:
+        return await self.user.fetch_dm_channel()
+
+    async def fetch_roles(self) -> typing.Sequence[Role]:
+        """Fetch an up-to-date view of this member's roles from the API.
+
+        Returns
+        -------
+        typing.Sequence[hikari.guilds.Role]
+            An up-to-date view of this member's roles.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the member is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        fetched_roles = await self.app.rest.fetch_roles(self.guild_id)
+        return [role for role in fetched_roles if role.id in self.role_ids]
+
     async def ban(
         self,
         *,
@@ -560,6 +641,255 @@ async def ban(
             self.guild_id, self.user.id, delete_message_days=delete_message_days, reason=reason
         )
 
+    async def unban(
+        self,
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Unban this member from the guild.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `BAN_MEMBERS` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.user.app.rest.unban_user(self.guild_id, self.user.id, reason=reason)
+
+    async def kick(
+        self,
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Kick this member from this guild.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `KICK_MEMBERS` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.user.app.rest.kick_user(self.guild_id, self.user.id, reason=reason)
+
+    async def add_role(
+        self, role: snowflakes.SnowflakeishOr[PartialRole], *, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED
+    ) -> None:
+        """Add a role to the member.
+
+        Parameters
+        ----------
+        role : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialRole]
+            The role to add. This may be the object or the
+            ID of an existing role.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_ROLES` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild, user or role are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.user.app.rest.add_role_to_member(self.guild_id, self.user.id, role, reason=reason)
+
+    async def remove_role(
+        self, role: snowflakes.SnowflakeishOr[PartialRole], *, reason: undefined.UndefinedOr[str] = undefined.UNDEFINED
+    ) -> None:
+        """Remove a role from the member.
+
+        Parameters
+        ----------
+        role : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialRole]
+            The role to remove. This may be the object or the
+            ID of an existing role.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_ROLES` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild, user or role are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.user.app.rest.remove_role_from_member(self.guild_id, self.user.id, role, reason=reason)
+
+    async def edit(
+        self,
+        *,
+        nick: undefined.UndefinedNoneOr[str] = undefined.UNDEFINED,
+        roles: undefined.UndefinedOr[snowflakes.SnowflakeishSequence[PartialRole]] = undefined.UNDEFINED,
+        mute: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        deaf: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        voice_channel: undefined.UndefinedNoneOr[
+            snowflakes.SnowflakeishOr[channels_.GuildVoiceChannel]
+        ] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> Member:
+        """Edit the member.
+
+        Other Parameters
+        ----------------
+        nick : hikari.undefined.UndefinedNoneOr[builtins.str]
+            If provided, the new nick for the member. If `builtins.None`,
+            will remove the members nick.
+
+            Requires the `MANAGE_NICKNAMES` permission.
+        roles : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishSequence[hikari.guilds.PartialRole]]
+            If provided, the new roles for the member.
+
+            Requires the `MANAGE_ROLES` permission.
+        mute : hikari.undefined.UndefinedOr[builtins.bool]
+            If provided, the new server mute state for the member.
+
+            Requires the `MUTE_MEMBERS` permission.
+        deaf : hikari.undefined.UndefinedOr[builtins.bool]
+            If provided, the new server deaf state for the member.
+
+            Requires the `DEAFEN_MEMBERS` permission.
+        voice_channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]]]
+            If provided, `builtins.None` or the object or the ID of
+            an existing voice channel to move the member to.
+            If `builtins.None`, will disconnect the member from voice.
+
+            Requires the `MOVE_MEMBERS` permission and the `CONNECT`
+            permission in the original voice channel and the target
+            voice channel.
+
+            !!! note
+                If the member is not in a voice channel, this will
+                take no effect.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.guilds.Member
+            Object of the member that was updated.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing a permission to do an action.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or the user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.user.app.rest.edit_member(
+            self.guild_id,
+            self.user.id,
+            nick=nick,
+            roles=roles,
+            mute=mute,
+            deaf=deaf,
+            voice_channel=voice_channel,
+            reason=reason,
+        )
+
     def __str__(self) -> str:
         return str(self.user)
 
@@ -883,7 +1213,7 @@ class WelcomeScreen:
 
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
-class GuildMemberBan:
+class GuildBan:
     """Used to represent guild bans."""
 
     reason: typing.Optional[str] = attr.field(repr=True)
@@ -977,15 +1307,863 @@ def make_icon_url(self, *, ext: typing.Optional[str] = None, size: int = 4096) -
             file_format=ext,
         )
 
+    async def ban(
+        self,
+        user: snowflakes.SnowflakeishOr[users.PartialUser],
+        *,
+        delete_message_days: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Ban the given user from this guild.
 
-@attr.define(hash=True, kw_only=True, weakref_slot=False)
-class GuildPreview(PartialGuild):
-    """A preview of a guild with the `GuildFeature.DISCOVERABLE` feature."""
+        Parameters
+        ----------
+        user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser]
+            The user to ban from the guild
 
-    features: typing.Sequence[GuildFeatureish] = attr.field(eq=False, hash=False, repr=False)
-    """A list of the features in this guild."""
+        Other Parameters
+        ----------------
+        delete_message_days : hikari.undefined.UndefinedNoneOr[builtins.int]
+            If provided, the number of days to delete messages for.
+            This must be between 0 and 7.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
 
-    splash_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `BAN_MEMBERS` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.app.rest.ban_user(self.id, user, delete_message_days=delete_message_days, reason=reason)
+
+    async def unban(
+        self,
+        user: snowflakes.SnowflakeishOr[users.PartialUser],
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Unban the given user from this guild.
+
+        Parameters
+        ----------
+        user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser]
+            The user to unban from the guild
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `BAN_MEMBERS` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.app.rest.unban_user(self.id, user, reason=reason)
+
+    async def kick(
+        self,
+        user: snowflakes.SnowflakeishOr[users.PartialUser],
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Kicks the given user from this guild.
+
+        Parameters
+        ----------
+        user: hikari.snowflakes.Snowflakeish[hikari.users.PartialUser]
+            The user to kick from the guild
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `KICK_MEMBERS` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        await self.app.rest.kick_user(self.id, user, reason=reason)
+
+    async def edit(
+        self,
+        *,
+        name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        verification_level: undefined.UndefinedOr[GuildVerificationLevel] = undefined.UNDEFINED,
+        default_message_notifications: undefined.UndefinedOr[GuildMessageNotificationsLevel] = undefined.UNDEFINED,
+        explicit_content_filter_level: undefined.UndefinedOr[GuildExplicitContentFilterLevel] = undefined.UNDEFINED,
+        afk_channel: undefined.UndefinedOr[
+            snowflakes.SnowflakeishOr[channels_.GuildVoiceChannel]
+        ] = undefined.UNDEFINED,
+        afk_timeout: undefined.UndefinedOr[time.Intervalish] = undefined.UNDEFINED,
+        icon: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
+        owner: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users.PartialUser]] = undefined.UNDEFINED,
+        splash: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
+        banner: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
+        system_channel: undefined.UndefinedNoneOr[
+            snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
+        ] = undefined.UNDEFINED,
+        rules_channel: undefined.UndefinedNoneOr[
+            snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
+        ] = undefined.UNDEFINED,
+        public_updates_channel: undefined.UndefinedNoneOr[
+            snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
+        ] = undefined.UNDEFINED,
+        preferred_locale: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> RESTGuild:
+        """Edits the guild.
+
+        Parameters
+        ----------
+        name : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new name for the guild.
+        verification_level : hikari.undefined.UndefinedOr[hikari.guilds.GuildVerificationLevel]
+            If provided, the new verification level.
+        default_message_notifications : hikari.undefined.UndefinedOr[hikari.guilds.GuildMessageNotificationsLevel]
+            If provided, the new default message notifications level.
+        explicit_content_filter_level : hikari.undefined.UndefinedOr[hikari.guilds.GuildExplicitContentFilterLevel]
+            If provided, the new explicit content filter level.
+        afk_channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildVoiceChannel]]
+            If provided, the new afk channel. Requires `afk_timeout` to
+            be set to work.
+        afk_timeout : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
+            If provided, the new afk timeout.
+        icon : hikari.undefined.UndefinedOr[hikari.files.Resourceish]
+            If provided, the new guild icon. Must be a 1024x1024 image or can be
+            an animated gif when the guild has the `ANIMATED_ICON` feature.
+        owner : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.users.PartialUser]]]
+            If provided, the new guild owner.
+
+            !!! warning
+                You need to be the owner of the server to use this.
+        splash : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish]
+            If provided, the new guild splash. Must be a 16:9 image and the
+            guild must have the `INVITE_SPLASH` feature.
+        banner : hikari.undefined.UndefinedNoneOr[hikari.files.Resourceish]
+            If provided, the new guild banner. Must be a 16:9 image and the
+            guild must have the `BANNER` feature.
+        system_channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildTextChannel]]
+            If provided, the new system channel.
+        rules_channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildTextChannel]]
+            If provided, the new rules channel.
+        public_updates_channel : hikari.undefined.UndefinedNoneOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildTextChannel]]
+            If provided, the new public updates channel.
+        preferred_locale : hikari.undefined.UndefinedNoneOr[builtins.str]
+            If provided, the new preferred locale.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.guilds.RESTGuild
+            The edited guild.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value. Or
+            you are missing the
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_GUILD` permission or if you tried to
+            pass ownership without being the server owner.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """  # noqa: E501 - Line too long
+        return await self.app.rest.edit_guild(
+            self.id,
+            name=name,
+            verification_level=verification_level,
+            default_message_notifications=default_message_notifications,
+            explicit_content_filter_level=explicit_content_filter_level,
+            afk_channel=afk_channel,
+            afk_timeout=afk_timeout,
+            icon=icon,
+            owner=owner,
+            splash=splash,
+            banner=banner,
+            system_channel=system_channel,
+            rules_channel=rules_channel,
+            public_updates_channel=public_updates_channel,
+            preferred_locale=preferred_locale,
+            reason=reason,
+        )
+
+    async def fetch_emojis(self) -> typing.Sequence[emojis_.KnownCustomEmoji]:
+        """Fetch the emojis of the guild.
+
+        Returns
+        -------
+        typing.Sequence[hikari.emojis.KnownCustomEmoji]
+            The requested emojis.
+
+        Raises
+        ------
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild_emojis(self.id)
+
+    async def fetch_emoji(self, emoji: snowflakes.SnowflakeishOr[emojis_.CustomEmoji]) -> emojis_.KnownCustomEmoji:
+        """Fetch an emoji from the guild.
+
+        Parameters
+        ----------
+        emoji : hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]
+            The emoji to fetch. This can be a `hikari.emojis.CustomEmoji`
+            or the ID of an existing emoji.
+
+        Returns
+        -------
+        hikari.emojis.KnownCustomEmoji
+            The requested emoji.
+
+        Raises
+        ------
+        hikari.errors.NotFoundError
+            If the guild or the emoji are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_emoji(self.id, emoji)
+
+    async def create_category(
+        self,
+        name: str,
+        *,
+        position: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        permission_overwrites: undefined.UndefinedOr[
+            typing.Sequence[channels_.PermissionOverwrite]
+        ] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> channels_.GuildCategory:
+        """Create a category in the guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The channels name. Must be between 2 and 1000 characters.
+
+        Other Parameters
+        ----------------
+        position : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the position of the category.
+        permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
+            If provided, the permission overwrites for the category.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.channels.GuildCategory
+            The created category.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_guild_category(
+            self.id, name, position=position, permission_overwrites=permission_overwrites, reason=reason
+        )
+
+    async def create_text_channel(
+        self,
+        name: str,
+        *,
+        position: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        topic: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        nsfw: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        rate_limit_per_user: undefined.UndefinedOr[time.Intervalish] = undefined.UNDEFINED,
+        permission_overwrites: undefined.UndefinedOr[
+            typing.Sequence[channels_.PermissionOverwrite]
+        ] = undefined.UNDEFINED,
+        category: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.GuildCategory]] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> channels_.GuildTextChannel:
+        """Create a text channel in the guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The channels name. Must be between 2 and 1000 characters.
+
+        Other Parameters
+        ----------------
+        position : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the position of the channel (relative to the
+            category, if any).
+        topic : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the channels topic. Maximum 1024 characters.
+        nsfw : hikari.undefined.UndefinedOr[builtins.bool]
+            If provided, whether to mark the channel as NSFW.
+        rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
+            If provided, the ammount of seconds a user has to wait
+            before being able to send another message in the channel.
+            Maximum 21600 seconds.
+        permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
+            If provided, the permission overwrites for the channel.
+        category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]]
+            The category to create the channel under. This may be the
+            object or the ID of an existing category.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.channels.GuildTextChannel
+            The created channel.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_guild_text_channel(
+            self.id,
+            name,
+            position=position,
+            topic=topic,
+            nsfw=nsfw,
+            rate_limit_per_user=rate_limit_per_user,
+            permission_overwrites=permission_overwrites,
+            category=category,
+            reason=reason,
+        )
+
+    async def create_news_channel(
+        self,
+        name: str,
+        *,
+        position: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        topic: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        nsfw: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
+        rate_limit_per_user: undefined.UndefinedOr[time.Intervalish] = undefined.UNDEFINED,
+        permission_overwrites: undefined.UndefinedOr[
+            typing.Sequence[channels_.PermissionOverwrite]
+        ] = undefined.UNDEFINED,
+        category: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.GuildCategory]] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> channels_.GuildNewsChannel:
+        """Create a news channel in the guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The channels name. Must be between 2 and 1000 characters.
+
+        Other Parameters
+        ----------------
+        position : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the position of the channel (relative to the
+            category, if any).
+        topic : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the channels topic. Maximum 1024 characters.
+        nsfw : hikari.undefined.UndefinedOr[builtins.bool]
+            If provided, whether to mark the channel as NSFW.
+        rate_limit_per_user : hikari.undefined.UndefinedOr[hikari.internal.time.Intervalish]
+            If provided, the ammount of seconds a user has to wait
+            before being able to send another message in the channel.
+            Maximum 21600 seconds.
+        permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
+            If provided, the permission overwrites for the channel.
+        category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]]
+            The category to create the channel under. This may be the
+            object or the ID of an existing category.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.channels.GuildNewsChannel
+            The created channel.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_guild_news_channel(
+            self.id,
+            name,
+            position=position,
+            topic=topic,
+            nsfw=nsfw,
+            rate_limit_per_user=rate_limit_per_user,
+            permission_overwrites=permission_overwrites,
+            category=category,
+            reason=reason,
+        )
+
+    async def create_voice_channel(
+        self,
+        name: str,
+        *,
+        position: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        user_limit: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        bitrate: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        video_quality_mode: undefined.UndefinedOr[typing.Union[channels_.VideoQualityMode, int]] = undefined.UNDEFINED,
+        permission_overwrites: undefined.UndefinedOr[
+            typing.Sequence[channels_.PermissionOverwrite]
+        ] = undefined.UNDEFINED,
+        region: undefined.UndefinedOr[voices_.VoiceRegionish] = undefined.UNDEFINED,
+        category: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.GuildCategory]] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> channels_.GuildVoiceChannel:
+        """Create a voice channel in a guild.
+
+        Parameters
+        ----------
+        guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
+            The guild to create the channel in. This may be the
+            object or the ID of an existing guild.
+        name : builtins.str
+            The channels name. Must be between 2 and 1000 characters.
+
+        Other Parameters
+        ----------------
+        position : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the position of the channel (relative to the
+            category, if any).
+        user_limit : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the maximum users in the channel at once.
+            Must be between 0 and 99 with 0 meaning no limit.
+        bitrate : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the bitrate for the channel. Must be
+            between 8000 and 96000 or 8000 and 128000 for VIP
+            servers.
+        video_quality_mode: hikari.undefined.UndefinedOr[typing.Union[hikari.channels.VideoQualityMode, builtins.int]]
+            If provided, the new video quality mode for the channel.
+        permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
+            If provided, the permission overwrites for the channel.
+        region : hikari.undefined.UndefinedOr[hikari.voices.VoiceRegionish]
+            If provided, the voice region to for this channel. Passing
+            `builtins.None` here will set it to "auto" mode where the used
+            region will be decided based on the first person who connects to it
+            when it's empty.
+        category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]]
+            The category to create the channel under. This may be the
+            object or the ID of an existing category.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.channels.GuildVoiceChannel
+            The created channel.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the gui  ld is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_guild_voice_channel(
+            self.id,
+            name,
+            position=position,
+            user_limit=user_limit,
+            bitrate=bitrate,
+            video_quality_mode=video_quality_mode,
+            permission_overwrites=permission_overwrites,
+            region=region,
+            category=category,
+            reason=reason,
+        )
+
+    async def create_stage_channel(
+        self,
+        name: str,
+        *,
+        position: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        user_limit: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        bitrate: undefined.UndefinedOr[int] = undefined.UNDEFINED,
+        permission_overwrites: undefined.UndefinedOr[
+            typing.Sequence[channels_.PermissionOverwrite]
+        ] = undefined.UNDEFINED,
+        region: undefined.UndefinedOr[voices_.VoiceRegionish] = undefined.UNDEFINED,
+        category: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.GuildCategory]] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> channels_.GuildStageChannel:
+        """Create a stage channel in the guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The channel's name. Must be between 2 and 1000 characters.
+
+        Other Parameters
+        ----------------
+        position : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the position of the channel (relative to the
+            category, if any).
+        user_limit : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the maximum users in the channel at once.
+            Must be between 0 and 99 with 0 meaning no limit.
+        bitrate : hikari.undefined.UndefinedOr[builtins.int]
+            If provided, the bitrate for the channel. Must be
+            between 8000 and 96000 or 8000 and 128000 for VIP
+            servers.
+        permission_overwrites : hikari.undefined.UndefinedOr[typing.Sequence[hikari.channels.PermissionOverwrite]]
+            If provided, the permission overwrites for the channel.
+        region : hikari.undefined.UndefinedOr[hikari.voices.VoiceRegionish]
+            If provided, the voice region to for this channel. Passing
+            `builtins.None` here will set it to "auto" mode where the used
+            region will be decided based on the first person who connects to it
+            when it's empty.
+        category : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildCategory]]
+            The category to create the channel under. This may be the
+            object or the ID of an existing category.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.channels.GuildStageChannel
+            The created channel.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_guild_stage_channel(
+            self.id,
+            name,
+            position=position,
+            user_limit=user_limit,
+            bitrate=bitrate,
+            permission_overwrites=permission_overwrites,
+            region=region,
+            category=category,
+            reason=reason,
+        )
+
+    async def delete_channel(
+        self, channel: snowflakes.SnowflakeishOr[channels_.GuildChannel]
+    ) -> channels_.GuildChannel:
+        """Delete a channel in the guild.
+
+        !!! note
+            This method can also be used for deleting guild categories as well.
+
+        Parameters
+        ----------
+        channel : hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildChannel]
+            The channel or category to delete. This may be the object or the ID of an
+            existing channel.
+
+        Returns
+        -------
+        hikari.channels.GuildChannel
+            Object of the channel or category that was deleted.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError, or close a DM.
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `MANAGE_CHANNEL` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+
+        !!! note
+            For Public servers, the set 'Rules' or 'Guidelines' channels and the
+            'Public Server Updates' channel cannot be deleted.
+        """
+        deleted_channel = await self.app.rest.delete_channel(channel)
+        assert isinstance(deleted_channel, channels_.GuildChannel)
+
+        return deleted_channel
+
+    async def fetch_self(self) -> RESTGuild:
+        """Fetch the guild.
+
+        Returns
+        -------
+        hikari.guilds.RESTGuild
+            The requested guild.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the guild.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild(self.id)
+
+    async def fetch_roles(self) -> typing.Sequence[Role]:
+        """Fetch the roles of the guild.
+
+        Returns
+        -------
+        typing.Sequence[hikari.guilds.Role]
+            The requested roles.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+            hikari.errors.NotFoundError
+                If the guild is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_roles(self.id)
+
+
+@attr.define(hash=True, kw_only=True, weakref_slot=False)
+class GuildPreview(PartialGuild):
+    """A preview of a guild with the `GuildFeature.DISCOVERABLE` feature."""
+
+    features: typing.Sequence[GuildFeatureish] = attr.field(eq=False, hash=False, repr=False)
+    """A list of the features in this guild."""
+
+    splash_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
     """The hash of the splash for the guild, if there is one."""
 
     discovery_splash_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
@@ -1521,6 +2699,244 @@ def get_emoji(
     def get_role(self, role: snowflakes.SnowflakeishOr[PartialRole]) -> typing.Optional[Role]:
         """Get a role from the cache by it's ID."""
 
+    async def fetch_owner(self) -> Member:
+        """Fetch the owner of the guild.
+
+        Returns
+        -------
+        hikari.guilds.Member
+            The guild owner.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the guild or the user are not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_member(self.id, self.owner_id)
+
+    async def fetch_widget_channel(self) -> typing.Optional[channels_.GuildChannel]:
+        """Fetch the widget channel.
+
+        This will be `builtins.None` if not set.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildChannel]
+            The channel the widget is linked to or else `builtins.None`.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.widget_channel_id:
+            return None
+
+        widget_channel = await self.app.rest.fetch_channel(self.widget_channel_id)
+        assert isinstance(widget_channel, channels_.GuildChannel)
+        return widget_channel
+
+    async def fetch_afk_channel(self) -> typing.Optional[channels_.GuildVoiceChannel]:
+        """Fetch the channel that AFK voice users get sent to.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildVoiceChannel]
+            The AFK channel or `builtins.None` if not enabled.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.afk_channel_id:
+            return None
+
+        afk_channel = await self.app.rest.fetch_channel(self.afk_channel_id)
+        assert isinstance(afk_channel, channels_.GuildVoiceChannel)
+        return afk_channel
+
+    async def fetch_system_channel(self) -> typing.Optional[channels_.GuildTextChannel]:
+        """Fetch the system channel.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildTextChannel]
+            The system channel for this guild or `builtins.None` if not
+            enabled.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.system_channel_id:
+            return None
+
+        system_channel = await self.app.rest.fetch_channel(self.system_channel_id)
+        assert isinstance(system_channel, channels_.GuildTextChannel)
+        return system_channel
+
+    async def fetch_rules_channel(self) -> typing.Optional[channels_.GuildTextChannel]:
+        """Fetch the channel where guilds display rules and guidelines.
+
+        If the `GuildFeature.COMMUNITY` feature is not defined, then this is `builtins.None`.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildTextChannel]
+            The channel where the rules of the guild are specified or else `builtins.None`.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.rules_channel_id:
+            return None
+
+        rules_channel = await self.app.rest.fetch_channel(self.rules_channel_id)
+        assert isinstance(rules_channel, channels_.GuildTextChannel)
+        return rules_channel
+
+    async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildTextChannel]:
+        """Fetch channel ID of the channel where admins and moderators receive notices from Discord.
+
+        This is only present if `GuildFeature.COMMUNITY` is in `Guild.features` for
+        this guild. For all other purposes, it should be considered to be `builtins.None`.
+
+        Returns
+        -------
+        typing.Optional[hikari.channels.GuildTextChannel]
+            The channel where discord sents relevent updates to moderators and admins.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.ForbiddenError
+            If you are missing the `READ_MESSAGES` permission in the channel.
+        hikari.errors.NotFoundError
+            If the channel is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.public_updates_channel_id:
+            return None
+
+        updates_channel = await self.app.rest.fetch_channel(self.public_updates_channel_id)
+        assert isinstance(updates_channel, channels_.GuildTextChannel)
+        return updates_channel
+
 
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
 class RESTGuild(Guild):
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index c6458ecc..0b4c1da8 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()
 
@@ -180,6 +181,7 @@ class EntityFactoryImpl(entity_factory.EntityFactory):
         "_dm_channel_type_mapping",
         "_guild_channel_type_mapping",
         "_interaction_type_mapping",
+        "_webhook_type_mapping",
     )
 
     def __init__(self, app: traits.RESTAware) -> None:
@@ -250,6 +252,11 @@ def __init__(self, app: traits.RESTAware) -> None:
         ] = {
             interaction_models.InteractionType.APPLICATION_COMMAND: self.deserialize_command_interaction,
         }
+        self._webhook_type_mapping = {
+            webhook_models.WebhookType.INCOMING: self.deserialize_incoming_webhook,
+            webhook_models.WebhookType.CHANNEL_FOLLOWER: self.deserialize_channel_follower_webhook,
+            webhook_models.WebhookType.APPLICATION: self.deserialize_application_webhook,
+        }
 
     ######################
     # APPLICATION MODELS #
@@ -369,7 +376,7 @@ def deserialize_authorization_token(
             expires_in=datetime.timedelta(seconds=int(payload["expires_in"])),
             scopes=[application_models.OAuth2Scope(scope) for scope in payload["scope"].split(" ")],
             refresh_token=payload["refresh_token"],
-            webhook=self.deserialize_webhook(payload["webhook"]) if "webhook" in payload else None,
+            webhook=self.deserialize_incoming_webhook(payload["webhook"]) if "webhook" in payload else None,
             guild=self.deserialize_rest_guild(payload["guild"]) if "guild" in payload else None,
         )
 
@@ -519,9 +526,17 @@ def deserialize_audit_log(self, payload: data_binding.JSONObject) -> audit_log_m
             for integration in payload["integrations"]
         }
         users = {snowflakes.Snowflake(user["id"]): self.deserialize_user(user) for user in payload["users"]}
-        webhooks = {
-            snowflakes.Snowflake(webhook["id"]): self.deserialize_webhook(webhook) for webhook in payload["webhooks"]
-        }
+
+        webhooks: typing.Dict[snowflakes.Snowflake, webhook_models.PartialWebhook] = {}
+        for webhook_payload in payload["webhooks"]:
+            try:
+                webhook = self.deserialize_webhook(webhook_payload)
+
+            except errors.UnrecognisedEntityError:
+                continue
+
+            webhooks[webhook.id] = webhook
+
         return audit_log_models.AuditLog(entries=entries, integrations=integrations, users=users, webhooks=webhooks)
 
     ##################
@@ -790,7 +805,7 @@ def deserialize_channel(
         *,
         guild_id: undefined.UndefinedOr[snowflakes.Snowflake] = undefined.UNDEFINED,
     ) -> channel_models.PartialChannel:
-        channel_type = payload["type"]
+        channel_type = channel_models.ChannelType(payload["type"])
         if guild_channel_model := self._guild_channel_type_mapping.get(channel_type):
             return guild_channel_model(payload, guild_id=guild_id)
 
@@ -1275,8 +1290,8 @@ def deserialize_integration(
             application=application,
         )
 
-    def deserialize_guild_member_ban(self, payload: data_binding.JSONObject) -> guild_models.GuildMemberBan:
-        return guild_models.GuildMemberBan(reason=payload["reason"], user=self.deserialize_user(payload["user"]))
+    def deserialize_guild_member_ban(self, payload: data_binding.JSONObject) -> guild_models.GuildBan:
+        return guild_models.GuildBan(reason=payload["reason"], user=self.deserialize_user(payload["user"]))
 
     def deserialize_guild_preview(self, payload: data_binding.JSONObject) -> guild_models.GuildPreview:
         guild_id = snowflakes.Snowflake(payload["id"])
@@ -1544,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
         )
@@ -1560,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,
         )
@@ -1581,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,
@@ -1607,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"]),
@@ -2441,40 +2469,73 @@ def deserialize_voice_region(self, payload: data_binding.JSONObject) -> voice_mo
     # WEBHOOK MODELS #
     ##################
 
-    def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_models.Webhook:
+    def deserialize_incoming_webhook(self, payload: data_binding.JSONObject) -> webhook_models.IncomingWebhook:
         application_id: typing.Optional[snowflakes.Snowflake] = None
         if (raw_application_id := payload.get("application_id")) is not None:
             application_id = snowflakes.Snowflake(raw_application_id)
 
-        source_channel: typing.Optional[channel_models.PartialChannel] = None
-        if "source_channel" in payload:
-            raw_source_channel = payload["source_channel"]
-            # In this case the channel type isn't provided as we can safely
-            # assume it's a news channel.
-            raw_source_channel["type"] = channel_models.ChannelType.GUILD_NEWS
-            source_channel = self.deserialize_partial_channel(raw_source_channel)
-
-        source_guild: typing.Optional[guild_models.PartialGuild] = None
-        if "source_guild" in payload:
-            source_guild_payload = payload["source_guild"]
-            source_guild = guild_models.PartialGuild(
-                app=self._app,
-                id=snowflakes.Snowflake(source_guild_payload["id"]),
-                name=source_guild_payload["name"],
-                icon_hash=source_guild_payload["icon"],
-            )
-
-        return webhook_models.Webhook(
+        return webhook_models.IncomingWebhook(
             app=self._app,
             id=snowflakes.Snowflake(payload["id"]),
             type=webhook_models.WebhookType(payload["type"]),
-            guild_id=snowflakes.Snowflake(payload["guild_id"]) if "guild_id" in payload else None,
+            guild_id=snowflakes.Snowflake(payload["guild_id"]),
             channel_id=snowflakes.Snowflake(payload["channel_id"]),
             author=self.deserialize_user(payload["user"]) if "user" in payload else None,
             name=payload["name"],
             avatar_hash=payload["avatar"],
             token=payload.get("token"),
             application_id=application_id,
+        )
+
+    def deserialize_channel_follower_webhook(
+        self, payload: data_binding.JSONObject
+    ) -> webhook_models.ChannelFollowerWebhook:
+        application_id: typing.Optional[snowflakes.Snowflake] = None
+        if (raw_application_id := payload.get("application_id")) is not None:
+            application_id = snowflakes.Snowflake(raw_application_id)
+
+        raw_source_channel = payload["source_channel"]
+        # In this case the channel type isn't provided as we can safely
+        # assume it's a news channel.
+        raw_source_channel["type"] = channel_models.ChannelType.GUILD_NEWS
+        source_channel = self.deserialize_partial_channel(raw_source_channel)
+        source_guild_payload = payload["source_guild"]
+        source_guild = guild_models.PartialGuild(
+            app=self._app,
+            id=snowflakes.Snowflake(source_guild_payload["id"]),
+            name=source_guild_payload["name"],
+            icon_hash=source_guild_payload.get("icon"),
+        )
+
+        return webhook_models.ChannelFollowerWebhook(
+            app=self._app,
+            id=snowflakes.Snowflake(payload["id"]),
+            type=webhook_models.WebhookType(payload["type"]),
+            guild_id=snowflakes.Snowflake(payload["guild_id"]),
+            channel_id=snowflakes.Snowflake(payload["channel_id"]),
+            author=self.deserialize_user(payload["user"]) if "user" in payload else None,
+            name=payload["name"],
+            avatar_hash=payload["avatar"],
+            application_id=application_id,
             source_channel=source_channel,
             source_guild=source_guild,
         )
+
+    def deserialize_application_webhook(self, payload: data_binding.JSONObject) -> webhook_models.ApplicationWebhook:
+        return webhook_models.ApplicationWebhook(
+            app=self._app,
+            id=snowflakes.Snowflake(payload["id"]),
+            type=webhook_models.WebhookType(payload["type"]),
+            name=payload["name"],
+            avatar_hash=payload["avatar"],
+            application_id=snowflakes.Snowflake(payload["application_id"]),
+        )
+
+    def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_models.PartialWebhook:
+        webhook_type = webhook_models.WebhookType(payload["type"])
+
+        if converter := self._webhook_type_mapping.get(webhook_type):
+            return converter(payload)
+
+        _LOGGER.debug(f"Unrecognised webhook type {webhook_type}")
+        raise errors.UnrecognisedEntityError(f"Unrecognised webhook type {webhook_type}")
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 60363c17..d495633e 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)
@@ -1585,7 +1589,7 @@ async def create_webhook(
         *,
         avatar: undefined.UndefinedOr[files.Resourceish] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.IncomingWebhook:
         route = routes.POST_CHANNEL_WEBHOOKS.compile(channel=channel)
         body = data_binding.JSONObjectBuilder()
         body.put("name", name)
@@ -1597,14 +1601,14 @@ async def create_webhook(
 
         response = await self._request(route, json=body, reason=reason)
         assert isinstance(response, dict)
-        return self._entity_factory.deserialize_webhook(response)
+        return self._entity_factory.deserialize_incoming_webhook(response)
 
     async def fetch_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.PartialWebhook:
         if token is undefined.UNDEFINED:
             route = routes.GET_WEBHOOK.compile(webhook=webhook)
             no_auth = False
@@ -1619,7 +1623,7 @@ async def fetch_webhook(
     async def fetch_channel_webhooks(
         self,
         channel: snowflakes.SnowflakeishOr[channels_.TextChannel],
-    ) -> typing.Sequence[webhooks.Webhook]:
+    ) -> typing.Sequence[webhooks.PartialWebhook]:
         route = routes.GET_CHANNEL_WEBHOOKS.compile(channel=channel)
         response = await self._request(route)
         assert isinstance(response, list)
@@ -1628,7 +1632,7 @@ async def fetch_channel_webhooks(
     async def fetch_guild_webhooks(
         self,
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
-    ) -> typing.Sequence[webhooks.Webhook]:
+    ) -> typing.Sequence[webhooks.PartialWebhook]:
         route = routes.GET_GUILD_WEBHOOKS.compile(guild=guild)
         response = await self._request(route)
         assert isinstance(response, list)
@@ -1636,14 +1640,14 @@ async def fetch_guild_webhooks(
 
     async def edit_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
         name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
         avatar: undefined.UndefinedNoneOr[files.Resourceish] = undefined.UNDEFINED,
         channel: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.TextChannel]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
-    ) -> webhooks.Webhook:
+    ) -> webhooks.PartialWebhook:
         if token is undefined.UNDEFINED:
             route = routes.PATCH_WEBHOOK.compile(webhook=webhook)
             no_auth = False
@@ -1668,7 +1672,7 @@ async def edit_webhook(
 
     async def delete_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.Webhook],
+        webhook: snowflakes.SnowflakeishOr[webhooks.PartialWebhook],
         *,
         token: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> None:
@@ -2553,7 +2557,7 @@ async def fetch_ban(
         self,
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
         user: snowflakes.SnowflakeishOr[users.PartialUser],
-    ) -> guilds.GuildMemberBan:
+    ) -> guilds.GuildBan:
         route = routes.GET_GUILD_BAN.compile(guild=guild, user=user)
         response = await self._request(route)
         assert isinstance(response, dict)
@@ -2561,7 +2565,7 @@ async def fetch_ban(
 
     async def fetch_bans(
         self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]
-    ) -> typing.Sequence[guilds.GuildMemberBan]:
+    ) -> typing.Sequence[guilds.GuildBan]:
         route = routes.GET_GUILD_BANS.compile(guild=guild)
         response = await self._request(route)
         assert isinstance(response, list)
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.
diff --git a/hikari/presences.py b/hikari/presences.py
index 8e438f04..73192e78 100644
--- a/hikari/presences.py
+++ b/hikari/presences.py
@@ -50,7 +50,9 @@
     import datetime
 
     from hikari import emojis as emojis_
+    from hikari import guilds
     from hikari import traits
+    from hikari import users
 
 
 @typing.final
@@ -297,3 +299,63 @@ class MemberPresence:
 
     client_status: ClientStatus = attr.field(eq=False, hash=False, repr=False)
     """Platform-specific user-statuses."""
+
+    async def fetch_user(self) -> users.User:
+        """Fetch the user this presence is for.
+
+        Returns
+        -------
+        hikari.users.User
+            The requested user.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the user is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_user(self.user_id)
+
+    async def fetch_member(self) -> guilds.Member:
+        """Fetch the member this presence is for.
+
+        Returns
+        -------
+        hikari.guilds.Member
+            The requested member.
+
+        Raises
+        ------
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.NotFoundError
+            If the user is not found.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_member(self.guild_id, self.user_id)
diff --git a/hikari/webhooks.py b/hikari/webhooks.py
index 974057eb..b6702937 100644
--- a/hikari/webhooks.py
+++ b/hikari/webhooks.py
@@ -24,7 +24,14 @@
 
 from __future__ import annotations
 
-__all__: typing.List[str] = ["ExecutableWebhook", "WebhookType", "Webhook"]
+__all__: typing.List[str] = [
+    "ApplicationWebhook",
+    "ChannelFollowerWebhook",
+    "ExecutableWebhook",
+    "PartialWebhook",
+    "WebhookType",
+    "IncomingWebhook",
+]
 
 import abc
 import typing
@@ -59,6 +66,9 @@ class WebhookType(int, enums.Enum):
     CHANNEL_FOLLOWER = 2
     """Channel Follower webhook."""
 
+    APPLICATION = 3
+    """Application webhook (from the interactions flow)."""
+
 
 class ExecutableWebhook(abc.ABC):
     """An abstract class with logic for executing entities as webhooks."""
@@ -207,7 +217,7 @@ async def execute(
         hikari.errors.UnauthorizedError
             If you pass a token that's invalid for the target webhook.
         builtins.ValueError
-            If either `Webhook.token` is `builtins.None` or more than 100 unique
+            If either `ExecutableWebhook.token` is `builtins.None` or more than 100 unique
             objects/entities are passed for `role_mentions` or `user_mentions or
             if `token` is not available.
         builtins.TypeError
@@ -482,13 +492,8 @@ async def delete_message(self, message: snowflakes.SnowflakeishOr[messages_.Mess
 
 @attr_extensions.with_copy
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
-class Webhook(snowflakes.Unique, ExecutableWebhook):
-    """Represents a webhook object on Discord.
-
-    This is an endpoint that can have messages sent to it using standard
-    HTTP requests, which enables external services that are not bots to
-    send informational messages to specific channels.
-    """
+class PartialWebhook(snowflakes.Unique):
+    """Base class for all webhook implementations."""
 
     app: traits.RESTAware = attr.field(
         repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True}
@@ -501,57 +506,18 @@ class Webhook(snowflakes.Unique, ExecutableWebhook):
     type: typing.Union[WebhookType, int] = attr.field(eq=False, hash=False, repr=True)
     """The type of the webhook."""
 
-    guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=True)
-    """The guild ID of the webhook."""
-
-    channel_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
-    """The channel ID this webhook is for."""
-
-    author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True)
-    """The user that created the webhook
-
-    !!! info
-        This will be `builtins.None` when getting a webhook with bot authorization rather
-        than the webhook's token.
-    """
-
-    name: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
+    name: str = attr.field(eq=False, hash=False, repr=True)
     """The name of the webhook."""
 
     avatar_hash: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
     """The avatar hash of the webhook."""
 
-    token: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
-    """The token for the webhook.
-
-    !!! info
-        This is only available for incoming webhooks that are created in the
-        channel settings.
-    """
-
     application_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False)
     """The ID of the application that created this webhook."""
 
-    source_channel: typing.Optional[channels_.PartialChannel] = attr.field(eq=False, hash=False, repr=True)
-    """The partial object of the channel a `CHANNEL_FOLLOWER` webhook is following.
-
-    Will be `builtins.None` for other webhook types.
-    """
-
-    source_guild: typing.Optional[guilds_.PartialGuild] = attr.field(eq=False, hash=False, repr=True)
-    """The partial object of the guild a `CHANNEL_FOLLOWER` webhook is following.
-
-    Will be `builtins.None` for other webhook types.
-    """
-
     def __str__(self) -> str:
         return self.name if self.name is not None else f"Unnamed webhook ID {self.id}"
 
-    @property
-    def webhook_id(self) -> snowflakes.Snowflake:
-        # <<inherited docstring from ExecutableWebhook>>.
-        return self.id
-
     @property
     def mention(self) -> str:
         """Return a raw mention string for the given webhook's user.
@@ -576,6 +542,103 @@ def mention(self) -> str:
         """
         return f"<@{self.id}>"
 
+    @property
+    def avatar(self) -> files_.URL:
+        """URL for this webhook's custom avatar or default avatar.
+
+        If the webhook has a custom avatar, a URL to this is returned. Otherwise
+        a URL to the default avatar is provided instead.
+        """
+        return self.make_avatar_url() or self.default_avatar
+
+    @property
+    def default_avatar(self) -> files_.URL:
+        """URL for this webhook's default avatar.
+
+        This is used if no avatar is set.
+        """
+        return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(
+            urls.CDN_URL,
+            discriminator=0,
+            file_format="png",
+        )
+
+    def make_avatar_url(self, ext: str = "png", size: int = 4096) -> typing.Optional[files_.URL]:
+        """Generate the avatar URL for this webhook's custom avatar if set.
+
+        If no avatar is specified, return `None`. In this case, you should
+        use `default_avatar` instead.
+
+        Parameters
+        ----------
+        ext : builtins.str
+            The extension to use for this URL, defaults to `png`.
+            Supports `png`, `jpeg`, `jpg`, `webp`. This will be ignored for
+            default avatars which can only be `png`.
+        size : builtins.int
+            The size to set for the URL, defaults to `4096`.
+            Can be any power of two between 16 and 4096.
+            Will be ignored for default avatars.
+
+        Returns
+        -------
+        typing.Optional[hikari.files.URL]
+            The URL of the resource. `builtins.None` if no avatar is set (in
+            this case, use the `default_avatar` instead).
+
+        Raises
+        ------
+        builtins.ValueError
+            If `size` is not a power of two between 16 and 4096 (inclusive).
+        """
+        if self.avatar_hash is None:
+            return None
+
+        return routes.CDN_USER_AVATAR.compile_to_file(
+            urls.CDN_URL,
+            user_id=self.id,
+            hash=self.avatar_hash,
+            size=size,
+            file_format=ext,
+        )
+
+
+@attr.define(hash=True, kw_only=True, weakref_slot=False)
+class IncomingWebhook(PartialWebhook, ExecutableWebhook):
+    """Represents an incoming webhook object on Discord.
+
+    This is an endpoint that can have messages sent to it using standard
+    HTTP requests, which enables external services that are not bots to
+    send informational messages to specific channels.
+    """
+
+    channel_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
+    """The channel ID this webhook is for."""
+
+    guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
+    """The guild ID of the webhook."""
+
+    author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True)
+    """The user that created the webhook
+
+    !!! info
+        This will be `builtins.None` when fetched with the webhook's token
+        rather than bot authorization or when received within audit logs.
+    """
+
+    token: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
+    """The token for the webhook.
+
+    !!! info
+        This is only available for incoming webhooks that are created in the
+        channel settings.
+    """
+
+    @property
+    def webhook_id(self) -> snowflakes.Snowflake:
+        # <<inherited docstring from ExecutableWebhook>>.
+        return self.id
+
     async def delete(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UNDEFINED) -> None:
         """Delete this webhook.
 
@@ -595,15 +658,17 @@ async def delete(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UND
             If you either lack the `MANAGE_WEBHOOKS` permission or
             are not a member of the guild this webhook belongs to.
         builtins.ValueError
-            If `use_token` is passed as `builtins.True` when `Webhook.token` is
+            If `use_token` is passed as `builtins.True` when `IncomingWebhook.token` is
             `builtins.None`.
         """
+        token: undefined.UndefinedOr[str] = undefined.UNDEFINED
         if use_token:
             if self.token is None:
                 raise ValueError("This webhook's token is unknown, so cannot be used")
-            token: undefined.UndefinedOr[str] = self.token
-        else:
-            token = undefined.UNDEFINED
+            token = self.token
+
+        elif use_token is undefined.UNDEFINED and self.token:
+            token = self.token
 
         await self.app.rest.delete_webhook(self.id, token=token)
 
@@ -615,7 +680,7 @@ async def edit(
         channel: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.TextChannel]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
         use_token: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
-    ) -> Webhook:
+    ) -> IncomingWebhook:
         """Edit this webhook.
 
         Other Parameters
@@ -640,13 +705,13 @@ async def edit(
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        IncomingWebhook
             The updated webhook object.
 
         Raises
         ------
         builtins.ValueError
-            If `use_token` is passed as `builtins.True` when `Webhook.token` is `builtins.None`.
+            If `use_token` is passed as `builtins.True` when `IncomingWebhook.token` is `builtins.None`.
         hikari.errors.BadRequestError
             If any invalid snowflake IDs are passed; a snowflake may be invalid
             due to it being outside of the range of a 64 bit integer.
@@ -671,14 +736,16 @@ async def edit(
         hikari.errors.InternalServerError
             If an internal error occurs on Discord while handling the request.
         """  # noqa: E501 - Line too long
+        token: undefined.UndefinedOr[str] = undefined.UNDEFINED
         if use_token:
             if self.token is None:
                 raise ValueError("This webhook's token is unknown, so cannot be used")
-            token: undefined.UndefinedOr[str] = self.token
-        else:
-            token = undefined.UNDEFINED
+            token = self.token
 
-        return await self.app.rest.edit_webhook(
+        elif use_token is undefined.UNDEFINED and self.token:
+            token = self.token
+
+        webhook = await self.app.rest.edit_webhook(
             self.id,
             token=token,
             name=name,
@@ -686,40 +753,10 @@ async def edit(
             channel=channel,
             reason=reason,
         )
+        assert isinstance(webhook, IncomingWebhook)
+        return webhook
 
-    async def fetch_channel(self) -> channels_.PartialChannel:
-        """Fetch the channel this webhook is for.
-
-        Returns
-        -------
-        hikari.channels.PartialChannel
-            The object of the channel this webhook targets.
-
-        Raises
-        ------
-        hikari.errors.ForbiddenError
-            If you don't have access to the channel this webhook belongs to.
-        hikari.errors.NotFoundError
-            If the channel this message was created in does not exist.
-        hikari.errors.UnauthorizedError
-            If you are unauthorized to make the request (invalid/missing token).
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitedError
-            Usually, Hikari will handle and retry on hitting
-            rate-limits automatically. This includes most bucket-specific
-            rate-limits and global rate-limits. In some rare edge cases,
-            however, Discord implements other undocumented rules for
-            rate-limiting, such as limits per attribute. These cannot be
-            detected or handled normally by Hikari due to their undocumented
-            nature, and will trigger this exception if they occur.
-        hikari.errors.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        return await self.app.rest.fetch_channel(self.channel_id)
-
-    async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UNDEFINED) -> Webhook:
+    async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined.UNDEFINED) -> IncomingWebhook:
         """Fetch this webhook.
 
         Other Parameters
@@ -732,7 +769,7 @@ async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined
 
         Returns
         -------
-        hikari.webhooks.Webhook
+        IncomingWebhook
             The requested webhook object.
 
         Raises
@@ -761,71 +798,164 @@ async def fetch_self(self, *, use_token: undefined.UndefinedOr[bool] = undefined
         hikari.errors.InternalServerError
             If an internal error occurs on Discord while handling the request.
         """
+        token: undefined.UndefinedOr[str] = undefined.UNDEFINED
         if use_token:
             if self.token is None:
                 raise ValueError("This webhook's token is unknown, so cannot be used")
-            token: undefined.UndefinedOr[str] = self.token
-        else:
-            token = undefined.UNDEFINED
+            token = self.token
 
-        return await self.app.rest.fetch_webhook(self.id, token=token)
+        elif use_token is undefined.UNDEFINED and self.token:
+            token = self.token
 
-    @property
-    def avatar(self) -> files_.URL:
-        """URL for this webhook's custom avatar or default avatar.
+        webhook = await self.app.rest.fetch_webhook(self.id, token=token)
+        assert isinstance(webhook, IncomingWebhook)
+        return webhook
 
-        If the webhook has a custom avatar, a URL to this is returned. Otherwise
-        a URL to the default avatar is provided instead.
-        """
-        return self.make_avatar_url() or self.default_avatar
 
-    @property
-    def default_avatar(self) -> files_.URL:
-        """URL for this webhook's default avatar.
+@attr.define(hash=True, kw_only=True, weakref_slot=False)
+class ChannelFollowerWebhook(PartialWebhook):
+    """Represents a channel follower webhook object on Discord."""
 
-        This is used if no avatar is set.
+    channel_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
+    """The channel ID this webhook is for."""
+
+    guild_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
+    """The guild ID of the webhook."""
+
+    author: typing.Optional[users_.User] = attr.field(eq=False, hash=False, repr=True)
+    """The user that created the webhook
+
+    !!! info
+        This will be `builtins.None` when received within an audit log.
+    """
+
+    source_channel: channels_.PartialChannel = attr.field(eq=False, hash=False, repr=True)
+    """The partial object of the channel this webhook is following."""
+
+    source_guild: guilds_.PartialGuild = attr.field(eq=False, hash=False, repr=True)
+    """The partial object of the guild this webhook is following."""
+
+    async def delete(self) -> None:
+        """Delete this webhook.
+
+        Raises
+        ------
+        hikari.errors.NotFoundError
+            If this webhook is not found.
+        hikari.errors.ForbiddenError
+            If you either lack the `MANAGE_WEBHOOKS` permission or
+            are not a member of the guild this webhook belongs to.
         """
-        return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(
-            urls.CDN_URL,
-            discriminator=0,
-            file_format="png",
-        )
+        await self.app.rest.delete_webhook(self.id)
 
-    def make_avatar_url(self, ext: str = "png", size: int = 4096) -> typing.Optional[files_.URL]:
-        """Generate the avatar URL for this webhook's custom avatar if set.
+    async def edit(
+        self,
+        *,
+        name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        avatar: undefined.UndefinedNoneOr[files_.Resource[files_.AsyncReader]] = undefined.UNDEFINED,
+        channel: undefined.UndefinedOr[snowflakes.SnowflakeishOr[channels_.TextChannel]] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> ChannelFollowerWebhook:
+        """Edit this webhook.
 
-        If no avatar is specified, return `None`. In this case, you should
-        use `default_avatar` instead.
+        Other Parameters
+        ----------------
+        name : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new name string.
+        avatar : hikari.undefined.UndefinedOr[hikari.files.Resourceish]
+            If provided, the new avatar image. If `builtins.None`, then
+            it is removed. If not specified, nothing is changed.
+        channel : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.channels.TextChannel]]
+            If provided, the object or ID of the new channel the given
+            webhook should be moved to.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the audit log reason explaining why the operation
+            was performed. This field will be used when using the webhook's
+            token rather than bot authorization.
 
-        Parameters
-        ----------
-        ext : builtins.str
-            The extension to use for this URL, defaults to `png`.
-            Supports `png`, `jpeg`, `jpg`, `webp`. This will be ignored for
-            default avatars which can only be `png`.
-        size : builtins.int
-            The size to set for the URL, defaults to `4096`.
-            Can be any power of two between 16 and 4096.
-            Will be ignored for default avatars.
+        Returns
+        -------
+        hikari.webhooks.ChannelFollowerWebhook
+            The updated webhook object.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any invalid snowflake IDs are passed; a snowflake may be invalid
+            due to it being outside of the range of a 64 bit integer.
+        hikari.errors.NotFoundError
+            If either the webhook or the channel are not found.
+        hikari.errors.ForbiddenError
+            If you either lack the `MANAGE_WEBHOOKS` permission or
+            are not a member of the guild this webhook belongs to.
+        hikari.errors.UnauthorizedError
+            If you pass a token that's invalid for the target webhook.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """  # noqa: E501 - Line too long
+        webhook = await self.app.rest.edit_webhook(
+            self.id,
+            name=name,
+            avatar=avatar,
+            channel=channel,
+            reason=reason,
+        )
+        assert isinstance(webhook, ChannelFollowerWebhook)
+        return webhook
+
+    async def fetch_self(self) -> ChannelFollowerWebhook:
+        """Fetch this webhook.
 
         Returns
         -------
-        typing.Optional[hikari.files.URL]
-            The URL of the resource. `builtins.None` if no avatar is set (in
-            this case, use the `default_avatar` instead).
+        hikari.webhooks.ChannelFollowerWebhook
+            The requested webhook object.
 
         Raises
         ------
-        builtins.ValueError
-            If `size` is not a power of two between 16 and 4096 (inclusive).
+        hikari.errors.ForbiddenError
+            If you're not in the guild that owns this webhook or
+            lack the `MANAGE_WEBHOOKS` permission.
+        hikari.errors.NotFoundError
+            If the webhook is not found.
+        hikari.errors.UnauthorizedError
+            If you pass a token that's invalid for the target webhook.
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
         """
-        if self.avatar_hash is None:
-            return None
+        webhook = await self.app.rest.fetch_webhook(self.id)
+        assert isinstance(webhook, ChannelFollowerWebhook)
+        return webhook
 
-        return routes.CDN_USER_AVATAR.compile_to_file(
-            urls.CDN_URL,
-            user_id=self.id,
-            hash=self.avatar_hash,
-            size=size,
-            file_format=ext,
-        )
+
+@attr.define(hash=True, kw_only=True, weakref_slot=False)
+class ApplicationWebhook(PartialWebhook):
+    """Represents an application webhook object on Discord.
+
+    This is from the interactions flow.
+    """
+
+    application_id: snowflakes.Snowflake = attr.ib()
+    # <<inherited docstring from PartialWebhook>>.


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 a500e0e6..cf0f3ef9 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 {
@@ -387,7 +405,7 @@ def test_deserialize_partial_token(self, entity_factory_impl, client_credentials
         assert isinstance(partial_token, application_models.PartialOAuth2Token)
 
     @pytest.fixture()
-    def access_token_payload(self, deserialize_rest_guild_payload, webhook_payload):
+    def access_token_payload(self, deserialize_rest_guild_payload, incoming_webhook_payload):
         return {
             "token_type": "Bearer",
             "guild": deserialize_rest_guild_payload,
@@ -395,11 +413,11 @@ def access_token_payload(self, deserialize_rest_guild_payload, webhook_payload):
             "scope": "bot webhook.incoming",
             "expires_in": 2419200,
             "refresh_token": "mgp8qnvBwJcmadwgCYKyYD5CAzGAX4",
-            "webhook": webhook_payload,
+            "webhook": incoming_webhook_payload,
         }
 
     def test_deserialize_authorization_token(
-        self, entity_factory_impl, access_token_payload, deserialize_rest_guild_payload, webhook_payload
+        self, entity_factory_impl, access_token_payload, deserialize_rest_guild_payload, incoming_webhook_payload
     ):
         access_token = entity_factory_impl.deserialize_authorization_token(access_token_payload)
 
@@ -412,7 +430,7 @@ def test_deserialize_authorization_token(
         ]
         assert access_token.expires_in == datetime.timedelta(weeks=4)
         assert access_token.refresh_token == "mgp8qnvBwJcmadwgCYKyYD5CAzGAX4"
-        assert access_token.webhook == entity_factory_impl.deserialize_webhook(webhook_payload)
+        assert access_token.webhook == entity_factory_impl.deserialize_incoming_webhook(incoming_webhook_payload)
 
     def test_deserialize_authorization_token_without_optional_fields(self, entity_factory_impl, access_token_payload):
         del access_token_payload["guild"]
@@ -588,28 +606,20 @@ def partial_integration_payload(self):
         }
 
     @pytest.fixture()
-    def webhook_payload(self, user_payload):
-        return {
-            "id": "1234",
-            "type": 1,
-            "guild_id": "123",
-            "channel_id": "456",
-            "user": user_payload,
-            "name": "hikari webhook",
-            "avatar": "bb71f469c158984e265093a81b3397fb",
-            "token": "ueoqrialsdfaKJLKfajslkdf",
-            "application_id": "1234567890",
-            "source_channel": {"id": "381871767846780666", "name": "announcements"},
-            "source_guild": {"id": "574921006817476608", "name": "Hikari API", "icon": "a363a8443211231232fdfb2e50e6"},
-        }
-
-    @pytest.fixture()
-    def audit_log_payload(self, audit_log_entry_payload, user_payload, webhook_payload, partial_integration_payload):
+    def audit_log_payload(
+        self,
+        audit_log_entry_payload,
+        user_payload,
+        incoming_webhook_payload,
+        application_webhook_payload,
+        follower_webhook_payload,
+        partial_integration_payload,
+    ):
         return {
             "audit_log_entries": [audit_log_entry_payload],
             "integrations": [partial_integration_payload],
             "users": [user_payload],
-            "webhooks": [webhook_payload],
+            "webhooks": [incoming_webhook_payload, application_webhook_payload, follower_webhook_payload],
         }
 
     def test_deserialize_audit_log(
@@ -618,7 +628,9 @@ def test_deserialize_audit_log(
         mock_app,
         audit_log_payload,
         user_payload,
-        webhook_payload,
+        incoming_webhook_payload,
+        application_webhook_payload,
+        follower_webhook_payload,
         partial_integration_payload,
     ):
         audit_log = entity_factory_impl.deserialize_audit_log(audit_log_payload)
@@ -655,7 +667,11 @@ def test_deserialize_audit_log(
             4949494949: entity_factory_impl.deserialize_partial_integration(partial_integration_payload)
         }
         assert audit_log.users == {115590097100865541: entity_factory_impl.deserialize_user(user_payload)}
-        assert audit_log.webhooks == {1234: entity_factory_impl.deserialize_webhook(webhook_payload)}
+        assert audit_log.webhooks == {
+            223704706495545344: entity_factory_impl.deserialize_incoming_webhook(incoming_webhook_payload),
+            658822586720976555: entity_factory_impl.deserialize_application_webhook(application_webhook_payload),
+            752831914402115456: entity_factory_impl.deserialize_channel_follower_webhook(follower_webhook_payload),
+        }
 
     def test_deserialize_audit_log_with_unset_or_unknown_fields(self, entity_factory_impl, audit_log_payload):
         # Unset fields
@@ -714,6 +730,26 @@ def test_deserialize_audit_log_with_action_type_unknown_gets_ignored(self, entit
 
         assert len(audit_log.entries) == 0
 
+    def test_deserialize_audit_log_skips_unknown_webhook_type(
+        self,
+        entity_factory_impl,
+        incoming_webhook_payload,
+        application_webhook_payload,
+    ):
+        audit_log = entity_factory_impl.deserialize_audit_log(
+            {
+                "webhooks": [incoming_webhook_payload, {"type": -99999}, application_webhook_payload],
+                "users": [],
+                "audit_log_entries": [],
+                "integrations": [],
+            }
+        )
+
+        assert audit_log.webhooks == {
+            223704706495545344: entity_factory_impl.deserialize_incoming_webhook(incoming_webhook_payload),
+            658822586720976555: entity_factory_impl.deserialize_application_webhook(application_webhook_payload),
+        }
+
     ##################
     # CHANNEL MODELS #
     ##################
@@ -2105,7 +2141,7 @@ def test_deserialize_guild_member_ban(self, entity_factory_impl, guild_member_ba
         member_ban = entity_factory_impl.deserialize_guild_member_ban(guild_member_ban_payload)
         assert member_ban.reason == "Get nyaa'ed"
         assert member_ban.user == entity_factory_impl.deserialize_user(user_payload)
-        assert isinstance(member_ban, guild_models.GuildMemberBan)
+        assert isinstance(member_ban, guild_models.GuildBan)
 
     def test_deserialize_guild_member_ban_with_null_fields(self, entity_factory_impl, user_payload):
         assert entity_factory_impl.deserialize_guild_member_ban({"reason": None, "user": user_payload}).reason is None
@@ -3011,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",
@@ -3030,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",
@@ -3046,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
@@ -3069,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",
@@ -3091,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(
@@ -3108,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
@@ -3121,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",
@@ -3140,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,
@@ -3160,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
@@ -3181,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
@@ -3197,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
     ):
@@ -3215,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
@@ -4234,38 +4336,158 @@ def test_deserialize_voice_region(self, entity_factory_impl, voice_region_payloa
     # WEBHOOK MODELS #
     ##################
 
-    def test_deserialize_webhook(self, entity_factory_impl, webhook_payload, user_payload):
-        webhook = entity_factory_impl.deserialize_webhook(webhook_payload)
-        assert webhook.id == 1234
-        assert webhook.type == webhook_models.WebhookType.INCOMING
-        assert webhook.guild_id == 123
-        assert webhook.channel_id == 456
+    @pytest.fixture()
+    def incoming_webhook_payload(self, user_payload):
+        return {
+            "name": "test webhook",
+            "type": 1,
+            "channel_id": "199737254929760256",
+            "token": "3d89bb7572e0fb30d8128367b3b1b44fecd1726de135cbe28a41f8b2f777c372ba2939e72279b94526ff5d1bd4358d65cf11",
+            "avatar": "dppdpdpdpdpd",
+            "guild_id": "199737254929760256",
+            "id": "223704706495545344",
+            "application_id": "32123123123",
+            "user": user_payload,
+        }
+
+    @pytest.fixture()
+    def follower_webhook_payload(self, user_payload, partial_channel_payload):
+        return {
+            "type": 2,
+            "id": "752831914402115456",
+            "name": "Guildy name",
+            "avatar": "bb71f469c158984e265093a81b3397fb",
+            "channel_id": "561885260615255432",
+            "guild_id": "56188498421443265",
+            "application_id": "312123123",
+            "source_guild": {
+                "id": "56188498421476534",
+                "name": "Guildy name",
+                "icon": "bb71f469c158984e265093a81b3397fb",
+            },
+            "source_channel": {"id": "5618852344134324", "name": "announcements"},
+            "user": user_payload,
+        }
+
+    @pytest.fixture()
+    def application_webhook_payload(self):
+        return {
+            "type": 3,
+            "id": "658822586720976555",
+            "name": "Clyde",
+            "avatar": "689161dc90ac261d00f1608694ac6bfd",
+            "channel_id": None,  # This field is always null
+            "guild_id": None,  # This field is always null
+            "application_id": "658822586720976555",
+        }
+
+    def test_deserialize_incoming_webhook(self, entity_factory_impl, mock_app, incoming_webhook_payload, user_payload):
+        webhook = entity_factory_impl.deserialize_incoming_webhook(incoming_webhook_payload)
+
+        assert webhook.app is mock_app
+        assert webhook.name == "test webhook"
+        assert webhook.type is webhook_models.WebhookType.INCOMING
+        assert webhook.channel_id == 199737254929760256
+        assert (
+            webhook.token
+            == "3d89bb7572e0fb30d8128367b3b1b44fecd1726de135cbe28a41f8b2f777c372ba2939e72279b94526ff5d1bd4358d65cf11"
+        )
+        assert webhook.avatar_hash == "dppdpdpdpdpd"
+        assert webhook.guild_id == 199737254929760256
+        assert webhook.id == 223704706495545344
+        assert webhook.application_id == 32123123123
         assert webhook.author == entity_factory_impl.deserialize_user(user_payload)
-        assert webhook.name == "hikari webhook"
+        assert isinstance(webhook, webhook_models.IncomingWebhook)
+
+    def test_deserialize_incoming_webhook_with_null_fields(
+        self, entity_factory_impl, incoming_webhook_payload, user_payload
+    ):
+        del incoming_webhook_payload["user"]
+        del incoming_webhook_payload["token"]
+        del incoming_webhook_payload["application_id"]
+        incoming_webhook_payload["avatar"] = None
+
+        webhook = entity_factory_impl.deserialize_incoming_webhook(incoming_webhook_payload)
+
+        assert webhook.name == "test webhook"
+        assert webhook.type is webhook_models.WebhookType.INCOMING
+        assert webhook.channel_id == 199737254929760256
+        assert webhook.token is None
+        assert webhook.avatar_hash is None
+        assert webhook.application_id is None
+        assert webhook.author is None
+        assert isinstance(webhook, webhook_models.IncomingWebhook)
+
+    def test_deserialize_channel_follower_webhook(
+        self, entity_factory_impl, mock_app, follower_webhook_payload, user_payload
+    ):
+        webhook = entity_factory_impl.deserialize_channel_follower_webhook(follower_webhook_payload)
+
+        assert webhook.app is mock_app
+        assert webhook.type is webhook_models.WebhookType.CHANNEL_FOLLOWER
+        assert webhook.id == 752831914402115456
+        assert webhook.name == "Guildy name"
         assert webhook.avatar_hash == "bb71f469c158984e265093a81b3397fb"
-        assert webhook.token == "ueoqrialsdfaKJLKfajslkdf"
-        assert webhook.application_id == 1234567890
+        assert webhook.channel_id == 561885260615255432
+        assert webhook.guild_id == 56188498421443265
+        assert webhook.application_id == 312123123
+
+        assert webhook.source_guild.app is mock_app
+        assert webhook.source_guild.id == 56188498421476534
+        assert webhook.source_guild.name == "Guildy name"
+        assert webhook.source_guild.icon_hash == "bb71f469c158984e265093a81b3397fb"
+        assert isinstance(webhook.source_guild, guild_models.PartialGuild)
 
         assert webhook.source_channel == entity_factory_impl.deserialize_partial_channel(
-            {"id": "381871767846780666", "name": "announcements", "type": 5}
+            {"id": "5618852344134324", "name": "announcements", "type": channel_models.ChannelType.GUILD_NEWS}
         )
+        assert webhook.author == entity_factory_impl.deserialize_user(user_payload)
+        assert isinstance(webhook, webhook_models.ChannelFollowerWebhook)
 
-        assert webhook.source_guild.id == 574921006817476608
-        assert webhook.source_guild.name == "Hikari API"
-        assert webhook.source_guild.icon_hash == "a363a8443211231232fdfb2e50e6"
-        assert isinstance(webhook.source_guild, guild_models.PartialGuild)
+    def test_deserialize_channel_follower_webhook_without_optional_fields(
+        self, entity_factory_impl, mock_app, follower_webhook_payload, user_payload
+    ):
+        follower_webhook_payload["avatar"] = None
+        del follower_webhook_payload["user"]
+        del follower_webhook_payload["application_id"]
 
-        assert isinstance(webhook, webhook_models.Webhook)
+        webhook = entity_factory_impl.deserialize_channel_follower_webhook(follower_webhook_payload)
 
-    def test_deserialize_webhook_with_null_and_unset_fields(self, entity_factory_impl):
-        webhook = entity_factory_impl.deserialize_webhook(
-            {"id": "1234", "type": 1, "channel_id": "456", "name": None, "avatar": None, "application_id": None}
-        )
-        assert webhook.guild_id is None
-        assert webhook.author is None
-        assert webhook.name is None
         assert webhook.avatar_hash is None
-        assert webhook.token is None
         assert webhook.application_id is None
-        assert webhook.source_channel is None
-        assert webhook.source_guild is None
+        assert webhook.author is None
+
+    def test_deserialize_application_webhook(self, entity_factory_impl, mock_app, application_webhook_payload):
+        webhook = entity_factory_impl.deserialize_application_webhook(application_webhook_payload)
+
+        assert webhook.app is mock_app
+        assert webhook.type is webhook_models.WebhookType.APPLICATION
+        assert webhook.id == 658822586720976555
+        assert webhook.name == "Clyde"
+        assert webhook.avatar_hash == "689161dc90ac261d00f1608694ac6bfd"
+        assert webhook.application_id == 658822586720976555
+        assert isinstance(webhook, webhook_models.ApplicationWebhook)
+
+    def test_deserialize_application_webhook_without_optional_fields(
+        self, entity_factory_impl, mock_app, application_webhook_payload
+    ):
+        application_webhook_payload["avatar"] = None
+
+        webhook = entity_factory_impl.deserialize_application_webhook(application_webhook_payload)
+
+        assert webhook.avatar_hash is None
+
+    def test_deserialize_webhook(
+        self, entity_factory_impl, incoming_webhook_payload, follower_webhook_payload, application_webhook_payload
+    ):
+        for expected_type, payload in [
+            (webhook_models.IncomingWebhook, incoming_webhook_payload),
+            (webhook_models.ChannelFollowerWebhook, follower_webhook_payload),
+            (webhook_models.ApplicationWebhook, application_webhook_payload),
+        ]:
+            result = entity_factory_impl.deserialize_webhook(payload)
+            assert isinstance(result, expected_type)
+
+    def test_deserialize_webhook_for_unexpected_webhook_type(self, entity_factory_impl):
+        with pytest.raises(errors.UnrecognisedEntityError):
+            entity_factory_impl.deserialize_webhook({"type": -7999})
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index d633c435..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 :)",
         )
 
@@ -1811,7 +1813,7 @@ async def test_create_webhook(self, rest_client, file_resource_patch):
         expected_route = routes.POST_CHANNEL_WEBHOOKS.compile(channel=123)
         rest_client._request = mock.AsyncMock(return_value={"id": "456"})
         expected_json = {"name": "test webhook", "avatar": "some data"}
-        rest_client._entity_factory.deserialize_webhook = mock.Mock(return_value=webhook)
+        rest_client._entity_factory.deserialize_incoming_webhook = mock.Mock(return_value=webhook)
 
         returned = await rest_client.create_webhook(
             StubModel(123), "test webhook", avatar="someavatar.png", reason="why not"
@@ -1819,18 +1821,18 @@ async def test_create_webhook(self, rest_client, file_resource_patch):
         assert returned is webhook
 
         rest_client._request.assert_awaited_once_with(expected_route, json=expected_json, reason="why not")
-        rest_client._entity_factory.deserialize_webhook.assert_called_once_with({"id": "456"})
+        rest_client._entity_factory.deserialize_incoming_webhook.assert_called_once_with({"id": "456"})
 
     async def test_create_webhook_without_optionals(self, rest_client):
         webhook = StubModel(456)
         expected_route = routes.POST_CHANNEL_WEBHOOKS.compile(channel=123)
         expected_json = {"name": "test webhook"}
         rest_client._request = mock.AsyncMock(return_value={"id": "456"})
-        rest_client._entity_factory.deserialize_webhook = mock.Mock(return_value=webhook)
+        rest_client._entity_factory.deserialize_incoming_webhook = mock.Mock(return_value=webhook)
 
         assert await rest_client.create_webhook(StubModel(123), "test webhook") is webhook
         rest_client._request.assert_awaited_once_with(expected_route, json=expected_json, reason=undefined.UNDEFINED)
-        rest_client._entity_factory.deserialize_webhook.assert_called_once_with({"id": "456"})
+        rest_client._entity_factory.deserialize_incoming_webhook.assert_called_once_with({"id": "456"})
 
     async def test_fetch_webhook(self, rest_client):
         webhook = StubModel(123)
diff --git a/tests/hikari/test_channels.py b/tests/hikari/test_channels.py
index c21de3eb..bc3ec8dc 100644
--- a/tests/hikari/test_channels.py
+++ b/tests/hikari/test_channels.py
@@ -30,6 +30,7 @@
 from hikari import snowflakes
 from hikari import undefined
 from hikari import users
+from hikari import webhooks
 from tests.hikari import hikari_test_helpers
 
 
@@ -54,15 +55,14 @@ async def test_fetch_channel(self, mock_app):
 
     @pytest.mark.asyncio()
     async def test_fetch_webhook(self, mock_app):
-        mock_webhook = object()
-        mock_app.rest.fetch_webhook = mock.AsyncMock(return_value=mock_webhook)
+        mock_app.rest.fetch_webhook = mock.AsyncMock(return_value=mock.Mock(webhooks.ChannelFollowerWebhook))
         follow = channels.ChannelFollow(
             webhook_id=snowflakes.Snowflake(54123123), app=mock_app, channel_id=snowflakes.Snowflake(94949494)
         )
 
         result = await follow.fetch_webhook()
 
-        assert result is mock_webhook
+        assert result is mock_app.rest.fetch_webhook.return_value
         mock_app.rest.fetch_webhook.assert_awaited_once_with(54123123)
 
     def test_channel(self, mock_app):
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index 2768208f..ca78a70e 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -24,12 +24,13 @@
 import mock
 import pytest
 
+from hikari import channels as channels_
 from hikari import colors
 from hikari import guilds
 from hikari import permissions
 from hikari import snowflakes
+from hikari import undefined
 from hikari import urls
-from hikari import users
 from hikari.impl import bot
 from hikari.internal import routes
 from tests.hikari import hikari_test_helpers
@@ -147,10 +148,40 @@ def test_colour_property(self, model):
         assert model.colour == colors.Color(0x1A2B3C)
 
 
+class TestGuildWidget:
+    @pytest.fixture()
+    def model(self, mock_app):
+        return guilds.GuildWidget(app=mock_app, channel_id=snowflakes.Snowflake(420), is_enabled=True)
+
+    def test_app_property(self, model, mock_app):
+        assert model.app is mock_app
+
+    def test_channel_property(self, model):
+        assert model.channel_id == snowflakes.Snowflake(420)
+
+    def test_is_enabled_property(self, model):
+        assert model.is_enabled is True
+
+    @pytest.mark.asyncio()
+    async def test_fetch_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(420)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_channel_when_None(self, model):
+        model.app.rest.fetch_channel = mock.AsyncMock()
+        model.channel_id = None
+
+        assert await model.fetch_channel() is None
+
+
 class TestMember:
     @pytest.fixture()
     def mock_user(self):
-        return mock.Mock(spec_set=users.User, id=snowflakes.Snowflake(123))
+        return mock.Mock(id=snowflakes.Snowflake(123))
 
     @pytest.fixture()
     def model(self, mock_user):
@@ -212,6 +243,12 @@ async def test_fetch_self(self, model):
 
         model.user.app.rest.fetch_member.assert_awaited_once_with(456, 123)
 
+    @pytest.mark.asyncio()
+    async def test_fetch_roles(self, model):
+        model.user.app.rest.fetch_roles = mock.AsyncMock()
+        await model.fetch_roles()
+        model.user.app.rest.fetch_roles.assert_awaited_once_with(456)
+
     @pytest.mark.asyncio()
     async def test_ban(self, model):
         model.app.rest.ban_user = mock.AsyncMock()
@@ -220,6 +257,58 @@ async def test_ban(self, model):
 
         model.app.rest.ban_user.assert_awaited_once_with(456, 123, delete_message_days=10, reason="bored")
 
+    @pytest.mark.asyncio()
+    async def test_unban(self, model):
+        model.app.rest.unban_user = mock.AsyncMock()
+
+        await model.unban(reason="Unbored")
+
+        model.app.rest.unban_user.assert_awaited_once_with(456, 123, reason="Unbored")
+
+    @pytest.mark.asyncio()
+    async def test_kick(self, model):
+        model.app.rest.kick_user = mock.AsyncMock()
+
+        await model.kick(reason="bored")
+
+        model.app.rest.kick_user.assert_awaited_once_with(456, 123, reason="bored")
+
+    @pytest.mark.asyncio()
+    async def test_add_role(self, model):
+        model.app.rest.add_role_to_member = mock.AsyncMock()
+
+        await model.add_role(563412, reason="Promoted")
+
+        model.app.rest.add_role_to_member.assert_awaited_once_with(456, 123, 563412, reason="Promoted")
+
+    @pytest.mark.asyncio()
+    async def test_remove_role(self, model):
+        model.app.rest.remove_role_from_member = mock.AsyncMock()
+
+        await model.remove_role(563412, reason="Demoted")
+
+        model.app.rest.remove_role_from_member.assert_awaited_once_with(456, 123, 563412, reason="Demoted")
+
+    @pytest.mark.asyncio()
+    async def test_edit(self, model):
+        model.app.rest.edit_member = mock.AsyncMock()
+        edit = await model.edit(
+            nick="Imposter", roles=[123, 432, 345], mute=False, deaf=True, voice_channel=4321245, reason="I'm God"
+        )
+
+        model.app.rest.edit_member.assert_awaited_once_with(
+            456,
+            123,
+            nick="Imposter",
+            roles=[123, 432, 345],
+            mute=False,
+            deaf=True,
+            voice_channel=4321245,
+            reason="I'm God",
+        )
+
+        assert edit == model.app.rest.edit_member.return_value
+
     def test_default_avatar_url_property(self, model, mock_user):
         assert model.default_avatar_url is mock_user.default_avatar_url
 
@@ -368,6 +457,209 @@ def test_make_icon_url_with_all_args(self, model):
             file_format="url",
         )
 
+    @pytest.mark.asyncio()
+    async def test_kick(self, model):
+        model.app.rest.kick_user = mock.AsyncMock()
+        await model.kick(4321, reason="Go away!")
+
+        model.app.rest.kick_user.assert_awaited_once_with(90210, 4321, reason="Go away!")
+
+    @pytest.mark.asyncio()
+    async def test_ban(self, model):
+        model.app.rest.ban_user = mock.AsyncMock()
+        await model.ban(4321, delete_message_days=10, reason="Go away!")
+
+        model.app.rest.ban_user.assert_awaited_once_with(90210, 4321, delete_message_days=10, reason="Go away!")
+
+    @pytest.mark.asyncio()
+    async def test_unban(self, model):
+        model.app.rest.unban_user = mock.AsyncMock()
+        await model.unban(4321, reason="Comeback!!")
+
+        model.app.rest.unban_user.assert_awaited_once_with(90210, 4321, reason="Comeback!!")
+
+    @pytest.mark.asyncio()
+    async def test_edit(self, model):
+        model.app.rest.edit_guild = mock.AsyncMock()
+        edited_guild = await model.edit(
+            name="chad server",
+            verification_level=guilds.GuildVerificationLevel.LOW,
+            default_message_notifications=guilds.GuildMessageNotificationsLevel.ALL_MESSAGES,
+            explicit_content_filter_level=guilds.GuildExplicitContentFilterLevel.DISABLED,
+            owner=6996,
+            afk_timeout=400,
+            preferred_locale="us-en",
+            reason="beep boop",
+        )
+
+        model.app.rest.edit_guild.assert_awaited_once_with(
+            90210,
+            name="chad server",
+            verification_level=guilds.GuildVerificationLevel.LOW,
+            default_message_notifications=guilds.GuildMessageNotificationsLevel.ALL_MESSAGES,
+            explicit_content_filter_level=guilds.GuildExplicitContentFilterLevel.DISABLED,
+            afk_channel=undefined.UNDEFINED,
+            afk_timeout=400,
+            icon=undefined.UNDEFINED,
+            owner=6996,
+            splash=undefined.UNDEFINED,
+            banner=undefined.UNDEFINED,
+            system_channel=undefined.UNDEFINED,
+            rules_channel=undefined.UNDEFINED,
+            public_updates_channel=undefined.UNDEFINED,
+            preferred_locale="us-en",
+            reason="beep boop",
+        )
+
+        assert edited_guild is model.app.rest.edit_guild.return_value
+
+    @pytest.mark.asyncio()
+    async def test_fetch_emojis(self, model):
+        model.app.rest.fetch_guild_emojis = mock.AsyncMock()
+
+        emojis = await model.fetch_emojis()
+
+        model.app.rest.fetch_guild_emojis.assert_awaited_once_with(model.id)
+        assert emojis is model.app.rest.fetch_guild_emojis.return_value
+
+    @pytest.mark.asyncio()
+    async def test_fetch_emoji(self, model):
+        model.app.rest.fetch_emoji = mock.AsyncMock()
+
+        emoji = await model.fetch_emoji(349)
+
+        model.app.rest.fetch_emoji.assert_awaited_once_with(model.id, 349)
+        assert emoji is model.app.rest.fetch_emoji.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_category(self, model):
+        model.app.rest.create_guild_category = mock.AsyncMock()
+
+        category = await model.create_category("very cool category", position=2)
+
+        model.app.rest.create_guild_category.assert_awaited_once_with(
+            90210,
+            "very cool category",
+            position=2,
+            permission_overwrites=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert category is model.app.rest.create_guild_category.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_text_channel(self, model):
+        model.app.rest.create_guild_text_channel = mock.AsyncMock()
+
+        text_channel = await model.create_text_channel(
+            "cool text channel", position=3, nsfw=False, rate_limit_per_user=30
+        )
+
+        model.app.rest.create_guild_text_channel.assert_awaited_once_with(
+            90210,
+            "cool text channel",
+            position=3,
+            topic=undefined.UNDEFINED,
+            nsfw=False,
+            rate_limit_per_user=30,
+            permission_overwrites=undefined.UNDEFINED,
+            category=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert text_channel is model.app.rest.create_guild_text_channel.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_news_channel(self, model):
+        model.app.rest.create_guild_news_channel = mock.AsyncMock()
+
+        news_channel = await model.create_news_channel(
+            "cool news channel", position=1, nsfw=False, rate_limit_per_user=420
+        )
+
+        model.app.rest.create_guild_news_channel.assert_awaited_once_with(
+            90210,
+            "cool news channel",
+            position=1,
+            topic=undefined.UNDEFINED,
+            nsfw=False,
+            rate_limit_per_user=420,
+            permission_overwrites=undefined.UNDEFINED,
+            category=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert news_channel is model.app.rest.create_guild_news_channel.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_voice_channel(self, model):
+        model.app.rest.create_guild_voice_channel = mock.AsyncMock()
+
+        voice_channel = await model.create_voice_channel(
+            "cool voice channel", position=1, bitrate=3200, video_quality_mode=2
+        )
+
+        model.app.rest.create_guild_voice_channel.assert_awaited_once_with(
+            90210,
+            "cool voice channel",
+            position=1,
+            user_limit=undefined.UNDEFINED,
+            bitrate=3200,
+            video_quality_mode=2,
+            permission_overwrites=undefined.UNDEFINED,
+            region=undefined.UNDEFINED,
+            category=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert voice_channel is model.app.rest.create_guild_voice_channel.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_stage_channel(self, model):
+        model.app.rest.create_guild_stage_channel = mock.AsyncMock()
+
+        stage_channel = await model.create_stage_channel("cool stage channel", position=1, bitrate=3200, user_limit=100)
+
+        model.app.rest.create_guild_stage_channel.assert_awaited_once_with(
+            90210,
+            "cool stage channel",
+            position=1,
+            user_limit=100,
+            bitrate=3200,
+            permission_overwrites=undefined.UNDEFINED,
+            region=undefined.UNDEFINED,
+            category=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert stage_channel is model.app.rest.create_guild_stage_channel.return_value
+
+    @pytest.mark.asyncio()
+    async def test_delete_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildChannel)
+        model.app.rest.delete_channel = mock.AsyncMock(return_value=mock_channel)
+
+        deleted_channel = await model.delete_channel(1288820)
+
+        model.app.rest.delete_channel.assert_awaited_once_with(1288820)
+        assert deleted_channel is model.app.rest.delete_channel.return_value
+
+    @pytest.mark.asyncio()
+    async def test_fetch_guild(self, model):
+        model.app.rest.fetch_guild = mock.AsyncMock(return_value=model)
+
+        assert await model.fetch_self() is model.app.rest.fetch_guild.return_value
+        model.app.rest.fetch_guild.assert_awaited_once_with(model.id)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_roles(self, model):
+        model.app.rest.fetch_roles = mock.AsyncMock()
+
+        roles = await model.fetch_roles()
+
+        model.app.rest.fetch_roles.assert_awaited_once_with(90210)
+        assert roles is model.app.rest.fetch_roles.return_value
+
 
 class TestGuildPreview:
     @pytest.fixture()
@@ -465,12 +757,12 @@ def model(self, mock_app):
             preferred_locale="en-GB",
             premium_subscription_count=12,
             premium_tier=guilds.GuildPremiumTier.TIER_3,
-            public_updates_channel_id=None,
-            rules_channel_id=None,
-            system_channel_id=None,
+            public_updates_channel_id=snowflakes.Snowflake(99699),
+            rules_channel_id=snowflakes.Snowflake(123445),
+            system_channel_id=snowflakes.Snowflake(123888),
             vanity_url_code="yeet",
             verification_level=guilds.GuildVerificationLevel.VERY_HIGH,
-            widget_channel_id=None,
+            widget_channel_id=snowflakes.Snowflake(192729),
             system_channel_flags=guilds.GuildSystemChannelFlag.SUPPRESS_PREMIUM_SUBSCRIPTION,
         )
 
@@ -582,6 +874,83 @@ def test_make_banner_url_when_no_hash(self, model):
         model.banner_hash = None
         assert model.make_banner_url(ext="png", size=2048) is None
 
+    @pytest.mark.asyncio()
+    async def test_fetch_owner(self, model):
+        model.app.rest.fetch_member = mock.AsyncMock()
+
+        assert await model.fetch_owner() is model.app.rest.fetch_member.return_value
+        model.app.rest.fetch_member.assert_awaited_once_with(123, 1111)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_widget_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_widget_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(192729)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_widget_channel_when_None(self, model):
+        model.widget_channel_id = None
+
+        assert await model.fetch_widget_channel() is None
+
+    @pytest.mark.asyncio()
+    async def test_fetch_rules_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildTextChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_rules_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(123445)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_rules_channel_when_None(self, model):
+        model.rules_channel_id = None
+
+        assert await model.fetch_rules_channel() is None
+
+    @pytest.mark.asyncio()
+    async def test_fetch_system_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildTextChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_system_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(123888)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_system_channel_when_None(self, model):
+        model.system_channel_id = None
+
+        assert await model.fetch_system_channel() is None
+
+    @pytest.mark.asyncio()
+    async def test_fetch_public_updates_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildTextChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_public_updates_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(99699)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_public_updates_channel_when_None(self, model):
+        model.public_updates_channel_id = None
+
+        assert await model.fetch_public_updates_channel() is None
+
+    @pytest.mark.asyncio()
+    async def test_fetch_afk_channel(self, model):
+        mock_channel = mock.Mock(channels_.GuildVoiceChannel)
+        model.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel)
+
+        assert await model.fetch_afk_channel() is model.app.rest.fetch_channel.return_value
+        model.app.rest.fetch_channel.assert_awaited_once_with(1234)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_afk_channel_when_None(self, model):
+        model.afk_channel_id = None
+
+        assert await model.fetch_afk_channel() is None
+
     def test_get_channel(self, model):
         assert model.get_channel(456) is model.app.cache.get_guild_channel.return_value
         model.app.cache.get_guild_channel.assert_called_once_with(456)
@@ -723,6 +1092,24 @@ def model(self, mock_app):
             nsfw_level=guilds.GuildNSFWLevel.AGE_RESTRICTED,
         )
 
+    @pytest.fixture()
+    def channels(self):
+        return {
+            4321: mock.Mock(channels_.GuildTextChannel),
+            3321: mock.Mock(channels_.GuildNewsChannel),
+            2321: mock.Mock(channels_.GuildStoreChannel),
+            5321: mock.Mock(channels_.GuildVoiceChannel),
+            6321: mock.Mock(channels_.GuildStageChannel),
+        }
+
+    def test_channels(self, model):
+        assert model.channels is model.app.cache.get_guild_channels_view_for_guild.return_value
+        model.app.cache.get_guild_channels_view_for_guild.assert_called_once_with(123)
+
+    def test_channels_when_no_cache_trait(self, model):
+        model.app = object()
+        assert model.channels == {}
+
     def test_emojis(self, model):
         assert model.emojis is model.app.cache.get_emojis_view_for_guild.return_value
         model.app.cache.get_emojis_view_for_guild.assert_called_once_with(123)
diff --git a/tests/hikari/test_presences.py b/tests/hikari/test_presences.py
index b45918d4..3b107bbf 100644
--- a/tests/hikari/test_presences.py
+++ b/tests/hikari/test_presences.py
@@ -20,9 +20,46 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
+import mock
+import pytest
+
 from hikari import presences
+from hikari import snowflakes
+from hikari.impl import bot
+
+
+@pytest.fixture()
+def mock_app():
+    return mock.Mock(spec_set=bot.BotApp)
 
 
 def test_Activity_str_operator():
     activity = presences.Activity(name="something", type=presences.ActivityType(1))
     assert str(activity) == "something"
+
+
+class TestMemberPresence:
+    @pytest.fixture()
+    def model(self, mock_app):
+        return presences.MemberPresence(
+            app=mock_app,
+            user_id=snowflakes.Snowflake(432),
+            guild_id=snowflakes.Snowflake(234),
+            visible_status=presences.Status.ONLINE,
+            activities=mock.Mock(presences.RichActivity),
+            client_status=mock.Mock(presences.ClientStatus),
+        )
+
+    @pytest.mark.asyncio()
+    async def test_fetch_user(self, model):
+        model.app.rest.fetch_user = mock.AsyncMock()
+
+        assert await model.fetch_user() is model.app.rest.fetch_user.return_value
+        model.app.rest.fetch_user.assert_awaited_once_with(432)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_member(self, model):
+        model.app.rest.fetch_member = mock.AsyncMock()
+
+        assert await model.fetch_member() is model.app.rest.fetch_member.return_value
+        model.app.rest.fetch_member.assert_awaited_once_with(234, 432)
diff --git a/tests/hikari/test_webhooks.py b/tests/hikari/test_webhooks.py
index 94ba20d0..a0e62f73 100644
--- a/tests/hikari/test_webhooks.py
+++ b/tests/hikari/test_webhooks.py
@@ -102,7 +102,7 @@ async def test_fetch_message(self, executable_webhook):
 
         assert returned is returned_message
 
-        executable_webhook.app.rest.fetch_webhook_message.assert_called_once_with(
+        executable_webhook.app.rest.fetch_webhook_message.assert_awaited_once_with(
             executable_webhook.webhook_id, token=executable_webhook.token, message=message
         )
 
@@ -171,22 +171,16 @@ async def test_delete_message_when_no_token(self, executable_webhook):
             assert await executable_webhook.delete_message(987)
 
 
-class TestWebhook:
+class TestPartialWebhook:
     @pytest.fixture()
     def webhook(self):
-        return webhooks.Webhook(
-            app=mock.Mock(),
+        return webhooks.PartialWebhook(
+            app=mock.Mock(rest=mock.AsyncMock()),
             id=987654321,
             type=webhooks.WebhookType.CHANNEL_FOLLOWER,
-            guild_id=123,
-            channel_id=456,
-            author=None,
             name="not a webhook",
-            avatar_hash=None,
-            token="abc123bca",
+            avatar_hash="hoook",
             application_id=None,
-            source_channel=object(),
-            source_guild=object(),
         )
 
     def test_str(self, webhook):
@@ -196,5 +190,242 @@ def test_str_when_name_is_None(self, webhook):
         webhook.name = None
         assert str(webhook) == "Unnamed webhook ID 987654321"
 
+    def test_mention_property(self, webhook):
+        assert webhook.mention == "<@987654321>"
+
+    def test_avatar_property(self, webhook):
+        assert webhook.avatar == webhook.make_avatar_url()
+
+    def test_avatar_property_when_default(self, webhook):
+        webhook.avatar_hash = None
+
+        assert webhook.avatar == webhook.default_avatar
+
+    def test_default_avatar(self, webhook):
+        assert webhook.default_avatar.url == "https://cdn.discordapp.com/embed/avatars/0.png"
+
+    def test_make_avatar_url(self, webhook):
+        result = webhook.make_avatar_url(ext="jpeg", size=2048)
+
+        assert result.url == "https://cdn.discordapp.com/avatars/987654321/hoook.jpeg?size=2048"
+
+    def test_make_avatar_url_when_no_avatar(self, webhook):
+        webhook.avatar_hash = None
+
+        assert webhook.make_avatar_url() is None
+
+
+class TestIncomingWebhook:
+    @pytest.fixture()
+    def webhook(self):
+        return webhooks.IncomingWebhook(
+            app=mock.Mock(rest=mock.AsyncMock()),
+            id=987654321,
+            type=webhooks.WebhookType.CHANNEL_FOLLOWER,
+            guild_id=123,
+            channel_id=456,
+            author=None,
+            name="not a webhook",
+            avatar_hash=None,
+            token="abc123bca",
+            application_id=None,
+        )
+
     def test_webhook_id_property(self, webhook):
         assert webhook.webhook_id is webhook.id
+
+    @pytest.mark.asyncio()
+    async def test_delete(self, webhook):
+        webhook.token = None
+
+        await webhook.delete()
+
+        webhook.app.rest.delete_webhook.assert_awaited_once_with(987654321, token=undefined.UNDEFINED)
+
+    @pytest.mark.asyncio()
+    async def test_delete_uses_token_property(self, webhook):
+        webhook.token = "123321"
+
+        await webhook.delete()
+
+        webhook.app.rest.delete_webhook.assert_awaited_once_with(987654321, token="123321")
+
+    @pytest.mark.asyncio()
+    async def test_delete_use_token_is_true(self, webhook):
+        webhook.token = "322312"
+
+        await webhook.delete(use_token=True)
+
+        webhook.app.rest.delete_webhook.assert_awaited_once_with(987654321, token="322312")
+
+    @pytest.mark.asyncio()
+    async def test_delete_use_token_is_true_without_token(self, webhook):
+        webhook.token = None
+
+        with pytest.raises(ValueError, match="This webhook's token is unknown, so cannot be used"):
+            await webhook.delete(use_token=True)
+
+        webhook.app.rest.delete_webhook.assert_not_called()
+
+    @pytest.mark.asyncio()
+    async def test_delete_use_token_is_false(self, webhook):
+        webhook.token = "322312"
+
+        await webhook.delete(use_token=False)
+
+        webhook.app.rest.delete_webhook.assert_awaited_once_with(987654321, token=undefined.UNDEFINED)
+
+    @pytest.mark.asyncio()
+    async def test_edit(self, webhook):
+        webhook.token = None
+        webhook.app.rest.edit_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+        mock_avatar = object()
+
+        result = await webhook.edit(name="OK", avatar=mock_avatar, channel=33333, reason="byebye")
+
+        assert result is webhook.app.rest.edit_webhook.return_value
+        webhook.app.rest.edit_webhook.assert_awaited_once_with(
+            987654321, token=undefined.UNDEFINED, name="OK", avatar=mock_avatar, channel=33333, reason="byebye"
+        )
+
+    @pytest.mark.asyncio()
+    async def test_edit_uses_token_property(self, webhook):
+        webhook.token = "aye"
+        webhook.app.rest.edit_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+        mock_avatar = object()
+
+        result = await webhook.edit(name="bye", avatar=mock_avatar, channel=33333, reason="byebye")
+
+        assert result is webhook.app.rest.edit_webhook.return_value
+        webhook.app.rest.edit_webhook.assert_awaited_once_with(
+            987654321, token="aye", name="bye", avatar=mock_avatar, channel=33333, reason="byebye"
+        )
+
+    @pytest.mark.asyncio()
+    async def test_edit_when_use_token_is_true(self, webhook):
+        webhook.token = "owoowow"
+        webhook.app.rest.edit_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+        mock_avatar = object()
+
+        result = await webhook.edit(use_token=True, name="hiu", avatar=mock_avatar, channel=231, reason="sus")
+
+        assert result is webhook.app.rest.edit_webhook.return_value
+        webhook.app.rest.edit_webhook.assert_awaited_once_with(
+            987654321, token="owoowow", name="hiu", avatar=mock_avatar, channel=231, reason="sus"
+        )
+
+    @pytest.mark.asyncio()
+    async def test_edit_when_use_token_is_true_and_no_token(self, webhook):
+        webhook.token = None
+
+        with pytest.raises(ValueError, match="This webhook's token is unknown, so cannot be used"):
+            await webhook.edit(use_token=True)
+
+        webhook.app.rest.edit_webhook.assert_not_called()
+
+    @pytest.mark.asyncio()
+    async def test_edit_when_use_token_is_false(self, webhook):
+        webhook.token = "owoowow"
+        webhook.app.rest.edit_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+        mock_avatar = object()
+
+        result = await webhook.edit(use_token=False, name="eee", avatar=mock_avatar, channel=231, reason="rrr")
+
+        assert result is webhook.app.rest.edit_webhook.return_value
+        webhook.app.rest.edit_webhook.assert_awaited_once_with(
+            987654321, token=undefined.UNDEFINED, name="eee", avatar=mock_avatar, channel=231, reason="rrr"
+        )
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self(self, webhook):
+        webhook.token = None
+        webhook.app.rest.fetch_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+
+        result = await webhook.fetch_self()
+
+        assert result is webhook.app.rest.fetch_webhook.return_value
+        webhook.app.rest.fetch_webhook.assert_awaited_once_with(987654321, token=undefined.UNDEFINED)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self_uses_token_property(self, webhook):
+        webhook.token = "no gnomo"
+        webhook.app.rest.fetch_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+
+        result = await webhook.fetch_self()
+
+        assert result is webhook.app.rest.fetch_webhook.return_value
+        webhook.app.rest.fetch_webhook.assert_awaited_once_with(987654321, token="no gnomo")
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self_when_use_token_is_true(self, webhook):
+        webhook.token = "no momo"
+        webhook.app.rest.fetch_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+
+        result = await webhook.fetch_self(use_token=True)
+
+        assert result is webhook.app.rest.fetch_webhook.return_value
+        webhook.app.rest.fetch_webhook.assert_awaited_once_with(987654321, token="no momo")
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self_when_use_token_is_true_without_token_property(self, webhook):
+        webhook.token = None
+
+        with pytest.raises(ValueError, match="This webhook's token is unknown, so cannot be used"):
+            await webhook.fetch_self(use_token=True)
+
+        webhook.app.rest.fetch_webhook.assert_not_called()
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self_when_use_token_is_false(self, webhook):
+        webhook.token = "no momo"
+        webhook.app.rest.fetch_webhook.return_value = mock.Mock(webhooks.IncomingWebhook)
+
+        result = await webhook.fetch_self(use_token=False)
+
+        assert result is webhook.app.rest.fetch_webhook.return_value
+        webhook.app.rest.fetch_webhook.assert_awaited_once_with(987654321, token=undefined.UNDEFINED)
+
+
+class TestChannelFollowerWebhook:
+    @pytest.fixture()
+    def webhook(self):
+        return webhooks.ChannelFollowerWebhook(
+            app=mock.Mock(rest=mock.AsyncMock()),
+            id=987654321,
+            type=webhooks.WebhookType.CHANNEL_FOLLOWER,
+            guild_id=123,
+            channel_id=456,
+            author=None,
+            name="not a webhook",
+            avatar_hash=None,
+            application_id=None,
+            source_channel=object(),
+            source_guild=object(),
+        )
+
+    @pytest.mark.asyncio()
+    async def test_delete(self, webhook):
+        await webhook.delete()
+
+        webhook.app.rest.delete_webhook.assert_awaited_once_with(987654321)
+
+    @pytest.mark.asyncio()
+    async def test_edit(self, webhook):
+        mock_avatar = object()
+        webhook.app.rest.edit_webhook.return_value = mock.Mock(webhooks.ChannelFollowerWebhook)
+
+        result = await webhook.edit(name="hi", avatar=mock_avatar, channel=43123, reason="ok")
+
+        assert result is webhook.app.rest.edit_webhook.return_value
+        webhook.app.rest.edit_webhook.assert_awaited_once_with(
+            987654321, name="hi", avatar=mock_avatar, channel=43123, reason="ok"
+        )
+
+    @pytest.mark.asyncio()
+    async def test_fetch_self(self, webhook):
+        webhook.app.rest.fetch_webhook.return_value = mock.Mock(webhooks.ChannelFollowerWebhook)
+
+        result = await webhook.fetch_self()
+
+        assert result is webhook.app.rest.fetch_webhook.return_value
+        webhook.app.rest.fetch_webhook.assert_awaited_once_with(987654321)