Repository: hikari-py/hikari
Pull request #809 (2021-10-01 17:14:52)

Implement GuildJoinEvent
### Summary
Separate the guild join event from the guild available event since apparently, new guilds won't have the `unavailable` field present.

### Checklist
- [x] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.


norinorin: Will the fragment go under feature or breaking?
Jonxslays: > Will the fragment go under feature or breaking?

likely breaking. 

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 088fea8a..52feadd1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -28,6 +28,12 @@ match that of the versioning scheme. There are utilities under `hikari.internal.
 
 To aid with the generation of `CHANGELOG.md` as well as the releases changelog we use `towncrier`.
 
+You will need to install `towncrier` and `hikari` from source before making changelog additions.
+```bash
+pip install towncrier
+pip install -e .
+```
+
 For every pull request made to this project, there should be a short explanation of the change under `changes/`
 with the following format: `{pull_request_number}.{type}.md`,
 
@@ -44,7 +50,7 @@ description so that a maintainer can skip the job that checks for newly added fr
 Best way to create the fragments is to run `towncrier create {pull_request_number}.{type}.md` after creating the
 pull request, edit the created file and committing the changes.
 
-Multiple fragments types can be created per pull request if it covers multiple areas.
+Multiple fragment types can be created per pull request if it covers multiple areas.
 
 # Branches
 
@@ -65,6 +71,11 @@ with a small description of the branch.
 
 We have nox to help out with running pipelines locally and provides some helpful functionality.
 
+You will need to install `nox` locally before running any pipelines.
+```bash
+pip install nox
+```
+
 Nox is similar to tox, but uses a pure Python configuration instead of an INI based configuration. Nox and tox are
 both tools for generating virtual environments and running commands in those environments. Examples of usage include
 installing, configuring, and running flake8, running pytest, etc.
diff --git a/changes/804.doc.md b/changes/804.doc.md
new file mode 100644
index 00000000..7e1e6309
--- /dev/null
+++ b/changes/804.doc.md
@@ -0,0 +1,1 @@
+Add docstrings to the remaining undocumented `GatewayBot` methods
diff --git a/changes/804.feature.md b/changes/804.feature.md
new file mode 100644
index 00000000..3c7eedd5
--- /dev/null
+++ b/changes/804.feature.md
@@ -0,0 +1,1 @@
+Add the `add_component` method to `hikari.api.special_endpoints.ActionRowBuilder`
diff --git a/changes/806.feature.md b/changes/806.feature.md
new file mode 100644
index 00000000..4f2405cb
--- /dev/null
+++ b/changes/806.feature.md
@@ -0,0 +1,2 @@
+Add `old_guild` attribute to `GuildLeaveEvent`.
+
diff --git a/changes/809.breaking.md b/changes/809.breaking.md
new file mode 100644
index 00000000..91d02175
--- /dev/null
+++ b/changes/809.breaking.md
@@ -0,0 +1,2 @@
+`GuildAvailableEvent` will no longer fire when the bot joins new guilds
+- Some `guild_create`-ish methods were renamed to `guild_available`
diff --git a/changes/809.feature.md b/changes/809.feature.md
new file mode 100644
index 00000000..b0afa582
--- /dev/null
+++ b/changes/809.feature.md
@@ -0,0 +1,1 @@
+Add `GuildJoinEvent` that will fire when the bot joins new guilds
diff --git a/changes/816.breaking.md b/changes/816.breaking.md
new file mode 100644
index 00000000..6a5dd694
--- /dev/null
+++ b/changes/816.breaking.md
@@ -0,0 +1,2 @@
+Remove `hikari.errors.RESTErrorCode` enum
+- The message that is sent with the error code is the info that the enum contained
diff --git a/changes/820.breaking.md b/changes/820.breaking.md
new file mode 100644
index 00000000..48c04e87
--- /dev/null
+++ b/changes/820.breaking.md
@@ -0,0 +1,1 @@
+PermissionOverwrite doesn't inherit from Unique anymore and isn't hashable. Equality checks now consider all its fields.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 3f747915..98dba780 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -8,11 +8,11 @@ mock==4.0.3
 # Py.test stuff.
 pytest==6.2.5
 pytest-asyncio==0.15.1
-pytest-cov==2.12.1
+pytest-cov==3.0.0
 pytest-randomly==3.10.1
 
 # Coverage testing.
-coverage==6.0b1
+coverage==6.0
 
 # Other stuff
 async-timeout==3.0.1  # Used for timeouts in some test cases.
diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py
index e01efd91..d5ccc338 100644
--- a/hikari/api/event_factory.py
+++ b/hikari/api/event_factory.py
@@ -236,10 +236,10 @@ def deserialize_typing_start_event(
     ################
 
     @abc.abstractmethod
-    def deserialize_guild_create_event(
+    def deserialize_guild_available_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
     ) -> guild_events.GuildAvailableEvent:
-        """Parse a raw payload from Discord into a guild create event object.
+        """Parse a raw payload from Discord into a guild available event object.
 
         Parameters
         ----------
@@ -254,13 +254,32 @@ def deserialize_guild_create_event(
             The parsed guild create event object.
         """
 
+    @abc.abstractmethod
+    def deserialize_guild_join_event(
+        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
+    ) -> guild_events.GuildJoinEvent:
+        """Parse a raw payload from Discord into a guild join event object.
+
+        Parameters
+        ----------
+        shard : hikari.api.shard.GatewayShard
+            The shard that emitted this event.
+        payload : hikari.internal.data_binding.JSONObject
+            The dict payload to parse.
+
+        Returns
+        -------
+        hikari.events.guild_events.GuildJoinEvent
+            The parsed guild join event object.
+        """
+
     @abc.abstractmethod
     def deserialize_guild_update_event(
         self,
         shard: gateway_shard.GatewayShard,
         payload: data_binding.JSONObject,
         *,
-        old_guild: typing.Optional[guild_models.Guild],
+        old_guild: typing.Optional[guild_models.GatewayGuild],
     ) -> guild_events.GuildUpdateEvent:
         """Parse a raw payload from Discord into a guild update event object.
 
@@ -270,7 +289,7 @@ def deserialize_guild_update_event(
             The shard that emitted this event.
         payload : hikari.internal.data_binding.JSONObject
             The dict payload to parse.
-        old_guild : typing.Optional[hikari.guilds.Guild]
+        old_guild : typing.Optional[hikari.guilds.GatewayGuild]
             The guild object or `builtins.None`.
 
         Returns
@@ -281,7 +300,11 @@ def deserialize_guild_update_event(
 
     @abc.abstractmethod
     def deserialize_guild_leave_event(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
+        self,
+        shard: gateway_shard.GatewayShard,
+        payload: data_binding.JSONObject,
+        *,
+        old_guild: typing.Optional[guild_models.GatewayGuild],
     ) -> guild_events.GuildLeaveEvent:
         """Parse a raw payload from Discord into a guild leave event object.
 
@@ -291,6 +314,8 @@ def deserialize_guild_leave_event(
             The shard that emitted this event.
         payload : hikari.internal.data_binding.JSONObject
             The dict payload to parse.
+        old_guild : typing.Optional[hikari.guilds.GatewayGuild]
+            The guild object or `builtins.None`.
 
         Returns
         -------
diff --git a/hikari/api/event_manager.py b/hikari/api/event_manager.py
index f0dc6810..90ec286c 100644
--- a/hikari/api/event_manager.py
+++ b/hikari/api/event_manager.py
@@ -231,9 +231,11 @@ async def on_everyone_mentioned(event):
 
         See Also
         --------
-        Subscribe: `hikari.api.event_manager.EventManager.subscribe`
+        Listen: `hikari.api.event_manager.EventManager.listen`
         Stream: `hikari.api.event_manager.EventManager.stream`
-        Wait for: `hikari.api.event_manager.EventManager.wait_for`
+        Subscribe: `hikari.api.event_manager.EventManager.subscribe`
+        Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe`
+        Wait_for: `hikari.api.event_manager.EventManager.wait_for`
         """
 
     # Yes, this is not generic. The reason for this is MyPy complains about
@@ -271,9 +273,11 @@ async def on_message(event):
 
         See Also
         --------
+        Dispatch: `hikari.api.event_manager.EventManager.dispatch`
         Listen: `hikari.api.event_manager.EventManager.listen`
         Stream: `hikari.api.event_manager.EventManager.stream`
-        Wait for: `hikari.api.event_manager.EventManager.wait_for`
+        Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe`
+        Wait_for: `hikari.api.event_manager.EventManager.wait_for`
         """
 
     # Yes, this is not generic. The reason for this is MyPy complains about
@@ -306,6 +310,14 @@ async def on_message(event):
 
         bot.unsubscribe(MessageCreateEvent, on_message)
         ```
+
+        See Also
+        --------
+        Dispatch: `hikari.api.event_manager.EventManager.dispatch`
+        Listen: `hikari.api.event_manager.EventManager.listen`
+        Stream: `hikari.api.event_manager.EventManager.stream`
+        Subscribe: `hikari.api.event_manager.EventManager.subscribe`
+        Wait_for: `hikari.api.event_manager.EventManager.wait_for`
         """
 
     @abc.abstractmethod
@@ -373,7 +385,7 @@ def listen(
         Stream: `hikari.api.event_manager.EventManager.stream`
         Subscribe: `hikari.api.event_manager.EventManager.subscribe`
         Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe`
-        Wait for: `hikari.api.event_manager.EventManager.wait_for`
+        Wait_for: `hikari.api.event_manager.EventManager.wait_for`
         """
 
     @abc.abstractmethod
@@ -439,7 +451,7 @@ def stream(
         Listen: `hikari.api.event_manager.EventManager.listen`
         Subscribe: `hikari.api.event_manager.EventManager.subscribe`
         Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe`
-        Wait for: `hikari.api.event_manager.EventManager.wait_for`
+        Wait_for: `hikari.api.event_manager.EventManager.wait_for`
         """
 
     @abc.abstractmethod
@@ -486,8 +498,9 @@ async def wait_for(
 
         See Also
         --------
+        Dispatch: `hikari.api.event_manager.EventManager.dispatch`
         Listen: `hikari.api.event_manager.EventManager.listen`
         Stream: `hikari.api.event_manager.EventManager.stream`
         Subscribe: `hikari.api.event_manager.EventManager.subscribe`
-        Dispatch: `hikari.api.event_manager.EventManager.dispatch`
+        Unsubscribe: `hikari.api.event_manager.EventManager.unsubscribe`
         """
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index 2f8a9003..6d73ca5f 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -1491,6 +1491,30 @@ def components(self) -> typing.Sequence[ComponentBuilder]:
             Sequence of the component builders registered within this action row.
         """
 
+    @abc.abstractmethod
+    def add_component(
+        self: _T,
+        component: ComponentBuilder,
+        /,
+    ) -> _T:
+        """Add a component to this action row builder.
+
+        !!! warning
+            It is generally better to use `ActionRowBuilder.add_button`
+            and `ActionRowBuilder.add_select_menu` to add your
+            component to the builder. Those methods utilize this one.
+
+        Parameters
+        ----------
+        component : ComponentBuilder
+            The component builder to add to the action row.
+
+        Returns
+        -------
+        ActionRowBuilder
+            The builder object to enable chained calls.
+        """
+
     @typing.overload
     @abc.abstractmethod
     def add_button(
diff --git a/hikari/channels.py b/hikari/channels.py
index a942b7e2..2fe57550 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -259,8 +259,8 @@ class PermissionOverwriteType(int, enums.Enum):
 
 
 @attr_extensions.with_copy
-@attr.define(hash=True, kw_only=True, weakref_slot=False)
-class PermissionOverwrite(snowflakes.Unique):
+@attr.define(kw_only=True, weakref_slot=False)
+class PermissionOverwrite:
     """Represents permission overwrites for a channel or role in a channel.
 
     You may sometimes need to make instances of this object to add/edit
@@ -286,29 +286,21 @@ class PermissionOverwrite(snowflakes.Unique):
     ```
     """
 
-    id: snowflakes.Snowflake = attr.field(
-        converter=snowflakes.Snowflake,
-        hash=True,
-        repr=True,
-    )
+    id: snowflakes.Snowflake = attr.field(converter=snowflakes.Snowflake, repr=True)
     """The ID of this entity."""
 
-    type: typing.Union[PermissionOverwriteType, int] = attr.field(
-        converter=PermissionOverwriteType, hash=True, repr=True
-    )
+    type: typing.Union[PermissionOverwriteType, int] = attr.field(converter=PermissionOverwriteType, repr=True)
     """The type of entity this overwrite targets."""
 
     allow: permissions.Permissions = attr.field(
         converter=permissions.Permissions,
         default=permissions.Permissions.NONE,
-        eq=False,
-        hash=False,
         repr=False,
     )
     """The permissions this overwrite allows."""
 
     deny: permissions.Permissions = attr.field(
-        converter=permissions.Permissions, default=permissions.Permissions.NONE, eq=False, hash=False, repr=False
+        converter=permissions.Permissions, default=permissions.Permissions.NONE, repr=False
     )
     """The permissions this overwrite denies."""
 
diff --git a/hikari/errors.py b/hikari/errors.py
index e5f68ae5..aaa6da05 100644
--- a/hikari/errors.py
+++ b/hikari/errors.py
@@ -36,7 +36,6 @@
     "UnauthorizedError",
     "ForbiddenError",
     "BadRequestError",
-    "RESTErrorCode",
     "HTTPError",
     "HTTPResponseError",
     "ClientHTTPResponseError",
@@ -223,221 +222,6 @@ class HTTPError(HikariError):
     """The error message."""
 
 
-@typing.final
-class RESTErrorCode(int, enums.Enum):
-    """Non-exhaustive enum of error codes provided as further info on errors returned by the REST API."""
-
-    GENERAL_ERROR = 0
-    """A general error, no further info provided."""
-
-    UNKNOWN_APPLICATION = 10_002
-    """Unknown application provided."""
-
-    UNKNOWN_CHANNEL = 10_003
-    """Unknown channel provided."""
-
-    UNKNOWN_GUILD = 10_004
-    """Unknown guild provided."""
-
-    UNKNOWN_INTEGRATION = 10_005
-    """Unknown integration provided."""
-
-    UNKNOWN_INVITE = 10_006
-    """Unknown invite provided."""
-
-    UNKNOWN_MEMBER = 10_007
-    """Unknown member provided."""
-
-    UNKNOWN_MESSAGE = 10_008
-    """Unknown message provided."""
-
-    UNKNOWN_PERMISSION_OVERWRITE = 10_009
-    """Unknown permission overwrite provided."""
-
-    UNKNOWN_ROLE = 10_011
-    """Unknown role provided."""
-
-    UNKNOWN_USER = 10_013
-    """Unknown user provided."""
-
-    UNKNOWN_EMOJI = 10_014
-    """Unknown emoji provided."""
-
-    UNKNOWN_WEBHOOK = 10_015
-    """Unknown webhook provided."""
-
-    UNKNOWN_BAN = 10_026
-    """Unknown ban provided."""
-
-    UNKNOWN_GUILD_TEMPLATE = 10_057
-    """Unknown guild template provided."""
-
-    UNKNOWN_STICKER = 10_060
-    """Unknown sticker provided."""
-
-    UNKNOWN_INTERACTION = 10_062
-    """Unknown interaction provided."""
-
-    UNKNOWN_APPLICATION_COMMAND = 10_063
-    """Unknown application command provided."""
-
-    UNKNOWN_APPLICATION_COMMAND_PERMISSIONS = 10_066
-    """Unknown application command permissions provided."""
-
-    UNKNOWN_STAGE_INSTANCE = 10_067
-    """Unknown stage instance provided."""
-
-    UNKNOWN_GUILD_WELCOME_SCREEN = 10_069
-    """Unknown guild welcome screen provided."""
-
-    EXPLICIT_CONTENT_BLOCKED = 20_009
-    """Explicit content cannot be sent to the desired recipient(s)."""
-
-    ANNOUNCEMENT_LIMIT_HIT = 20_022
-    """Message can not be edited due to announcement rate limits."""
-
-    WRITE_LIMIT_HIT = 20_028
-    """The global write limit on a channel has been hit."""
-
-    DISALLOWED_WORDS_FOR_PUBLIC_STAGES = 20_031
-    """The guild contains disallowed words for public stages.
-
-    This may include guild name, guild description or channel names.
-    """
-
-    GUILD_PREMIUM_LEVEL_TOO_LOW = 20_035
-    """The guilds premium level is too low."""
-
-    MAXIMUM_GUILDS = 30_001
-    """Maximum number of guilds reached (100)."""
-
-    MAXIMUM_PINS = 30_003
-    """Maximum number of pins reached for the channel (50)."""
-
-    MAXIMUM_RECIPIENTS = 30_004
-    """Maximum number of recipients reached (10)."""
-
-    MAXIMUM_ROLES = 30_005
-    """Maximum number of guild roles reached (250)."""
-
-    MAXIMUM_WEBHOOKS = 30_007
-    """Maximum number of webhooks in a channel reached (10)."""
-
-    MAXIMUM_EMOJIS = 30_008
-    """Maximum number of emojis reached."""
-
-    MAXIMUM_REACTIONS = 30_010
-    """Maximum number of reactions on a message reached (20)."""
-
-    MAXIMUM_CHANNELS = 30_013
-    """Maximum number of guild channels reached (500)."""
-
-    MAXIMUM_INVITES = 30_016
-    """Maximum number of invites reached (1000)."""
-
-    MAXIMUM_ANIMATED_EMOJIS = 30_018
-    """Maximum number of animated emojis reached."""
-
-    MAXIMUM_NUMBER_OF_GUILD_MEMBERS_REACHED = 30_019
-    """Maximum number of guild members reached."""
-
-    GUILD_ALREADY_HAS_TEMPLATE = 30_031
-    """Guild already has a template."""
-
-    MAXIMUM_NUMBER_OF_THREAD_PARTICIPANTS_REACHED = 30_033
-    """Maximum number of thread participants reached."""
-
-    MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS = 30_035
-    """Maximum number of bans for non-guild members reached."""
-
-    MAXIMUM_NUMBER_OF_STICKERS_REACHED = 30_037
-    """Maximum number of stickers reached."""
-
-    MAXIMUM_PRUNE_REQUESTS_REACHED = 30_040
-    """Maximum number of prune requests has been reached. Try again later."""
-
-    REQUEST_TOO_LARGE = 40_005
-    """Request too large. Try sending something smaller in size."""
-
-    TEMPORARILY_DISABLED = 40_006
-    """This feature has been temporarily disabled server-side."""
-
-    USER_BANNED = 40_003
-    """The user is banned from this guild."""
-
-    ALREADY_CROSSPOSTED = 40_033
-    """This message has already been crossposted."""
-
-    APPLICATION_COMMAND_ALREADY_EXISTS = 40_041
-    """An application command with that name already exists."""
-
-    INVALID_ACCOUNT_TYPE = 50_002
-    """Invalid account type."""
-
-    PROHIBITED_ON_DM = 50_003
-    """Cannot execute action on a DM channel."""
-
-    GUILD_WIDGET_DISABLED = 50_004
-    """Guild widget disabled."""
-
-    NOT_MESSAGE_AUTHOR = 50_005
-    """Cannot edit a message created by another user."""
-
-    EMPTY_MESSAGE = 50_006
-    """Cannot send an empty message."""
-
-    USER_DM_CLOSED = 50_007
-    """Cannot send messages to this user."""
-
-    MESSAGE_IN_VC = 50_008
-    """Cannot send messages in a voice channel."""
-
-    PINS_ONLY_ON_ORIGIN_CHANNEL = 50_019
-    """A message can only be pinned to the channel it was sent in."""
-
-    INVALID_INVITE_CODE = 50_020
-    """Invite code was either invalid or taken."""
-
-    PROHIBITED_ON_SYSTEM_MESSAGE = 50_021
-    """Cannot execute action on a system message."""
-
-    PROHIBITED_ON_CHANNEL_TYPE = 50_024
-    """Cannot execute action on this channel type."""
-
-    INVALID_OAUTH2_TOKEN = 50_025
-    """Invalid OAuth2 access token provided."""
-
-    MISSING_REQUIRED_OAUTH2_SCOPE = 50_026
-    """Missing required OAuth2 scope."""
-
-    INVALID_ROLE = 50_028
-    """Invalid role."""
-
-    INVALID_RECIPIENTS = 50_033
-    """Invalid recipients."""
-
-    MESSAGE_TOO_OLD = 50_034
-    """A message provided was too old to bulk delete."""
-
-    REQUIRED_CHANNEL = 50_074
-    """Cannot delete a channel required for community guilds."""
-
-    NO_USERS_WITH_TAG = 80_004
-    """No users with this tag exist."""
-
-    REACTION_BLOCKED = 90_001
-    """The reaction was blocked."""
-
-    TWO_FACTOR_AUTHENTICATION_REQUIRED = 60_003
-    """2FA is required to use this endpoint."""
-
-    SYSTEM_OVERLOADED = 130_000
-    """API resource is currently overloaded. Try again a little later."""
-
-    STAGE_ALREADY_OPEN = 150_006
-    """The stage channel is already open."""
-
-
 @attr.define(auto_exc=True, repr=False, weakref_slot=False)
 class HTTPResponseError(HTTPError):
     """Base exception for an erroneous HTTP response."""
@@ -457,15 +241,15 @@ class HTTPResponseError(HTTPError):
     message: str = attr.field(default="")
     """The error message."""
 
-    code: typing.Union[RESTErrorCode, int] = attr.field(default=RESTErrorCode.GENERAL_ERROR)
+    code: int = attr.field(default=0)
     """The error code."""
 
     def __str__(self) -> str:
         name = self.status.name.replace("_", " ").title()
         name_value = f"{name} {self.status.value}"
 
-        if isinstance(self.code, RESTErrorCode) and self.code != RESTErrorCode.GENERAL_ERROR:
-            code_str = f" ({self.code.name})"
+        if self.code:
+            code_str = f" ({self.code})"
         else:
             code_str = ""
 
diff --git a/hikari/events/guild_events.py b/hikari/events/guild_events.py
index 9d194395..e0d48c22 100644
--- a/hikari/events/guild_events.py
+++ b/hikari/events/guild_events.py
@@ -28,6 +28,7 @@
     "GuildEvent",
     "GuildVisibilityEvent",
     "GuildAvailableEvent",
+    "GuildJoinEvent",
     "GuildUnavailableEvent",
     "GuildLeaveEvent",
     "GuildUpdateEvent",
@@ -138,7 +139,7 @@ class GuildVisibilityEvent(GuildEvent, abc.ABC):
 class GuildAvailableEvent(GuildVisibilityEvent):
     """Event fired when a guild becomes available.
 
-    This will occur on startup, after outages, and if the bot joins a new guild.
+    This will occur on startup or after outages.
 
     !!! note
         Some fields like `members` and `presences` are included here but not on
@@ -237,6 +238,62 @@ def guild_id(self) -> snowflakes.Snowflake:
         return self.guild.id
 
 
+@attr_extensions.with_copy
+@attr.define(kw_only=True, weakref_slot=False)
+@base_events.requires_intents(intents.Intents.GUILDS)
+class GuildJoinEvent(GuildVisibilityEvent):
+    """Event fired when the bot joins a new guild.
+
+    !!! note
+        Some fields like `members` and `presences` are included here but not on
+        the other `GuildUpdateEvent` and `GuildUnavailableEvent` guild visibility
+        event models.
+    """
+
+    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
+    # <<inherited docstring from ShardEvent>>.
+
+    guild: guilds.GatewayGuild = attr.field()
+    """The guild the bot just joined."""
+
+    emojis: typing.Mapping[snowflakes.Snowflake, emojis_.KnownCustomEmoji] = attr.field(repr=False)
+    """Mapping of emoji IDs to the emojis in the guild."""
+
+    roles: typing.Mapping[snowflakes.Snowflake, guilds.Role] = attr.field(repr=False)
+    """Mapping of role IDs to the roles in the guild."""
+
+    channels: typing.Mapping[snowflakes.Snowflake, channels_.GuildChannel] = attr.field(repr=False)
+    """Mapping of channel IDs to the channels in the guild."""
+
+    members: typing.Mapping[snowflakes.Snowflake, guilds.Member] = attr.field(repr=False)
+    """Mapping of user IDs to the members in the guild."""
+
+    presences: typing.Mapping[snowflakes.Snowflake, presences_.MemberPresence] = attr.field(repr=False)
+    """Mapping of user IDs to the presences for the guild."""
+
+    voice_states: typing.Mapping[snowflakes.Snowflake, voices.VoiceState] = attr.field(repr=False)
+    """Mapping of user IDs to the voice states active in this guild."""
+
+    chunk_nonce: typing.Optional[str] = attr.field(repr=False, default=None)
+    """Nonce used to request the member chunks for this guild.
+
+    This will be `builtins.None` if no chunks were requested.
+
+    !!! note
+        This is a synthetic field.
+    """
+
+    @property
+    def app(self) -> traits.RESTAware:
+        # <<inherited docstring from Event>>.
+        return self.guild.app
+
+    @property
+    def guild_id(self) -> snowflakes.Snowflake:
+        # <<inherited docstring from GuildEvent>>.
+        return self.guild.id
+
+
 @attr_extensions.with_copy
 @attr.define(kw_only=True, weakref_slot=False)
 @base_events.requires_intents(intents.Intents.GUILDS)
@@ -255,6 +312,12 @@ class GuildLeaveEvent(GuildVisibilityEvent):
     guild_id: snowflakes.Snowflake = attr.field()
     # <<inherited docstring from GuildEvent>>.
 
+    old_guild: typing.Optional[guilds.GatewayGuild] = attr.field()
+    """The old guild object.
+
+    This will be `builtins.None` if the guild missing from the cache.
+    """
+
     if typing.TYPE_CHECKING:
         # This should always fail.
         async def fetch_guild(self) -> typing.NoReturn:
@@ -286,7 +349,7 @@ class GuildUpdateEvent(GuildEvent):
     shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
     # <<inherited docstring from ShardEvent>>.
 
-    old_guild: typing.Optional[guilds.Guild] = attr.field()
+    old_guild: typing.Optional[guilds.GatewayGuild] = attr.field()
     """The old guild object.
 
     This will be `builtins.None` if the guild missing from the cache.
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 3cba854f..6c2202da 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -2931,7 +2931,7 @@ def get_voice_state(
     def get_emoji(
         self, emoji: snowflakes.SnowflakeishOr[emojis_.CustomEmoji]
     ) -> typing.Optional[emojis_.KnownCustomEmoji]:
-        """Get a cached role that belongs to the guild by it's ID or object.
+        """Get a cached emoji that belongs to the guild by it's ID or object.
 
         Parameters
         ----------
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index 96519772..50892b61 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -418,11 +418,112 @@ async def handle(name: str, awaitable: typing.Awaitable[typing.Any]) -> None:
         self._closed_event = None
 
     def dispatch(self, event: event_manager_.EventT_inv) -> asyncio.Future[typing.Any]:
+        """Dispatch an event.
+
+        Parameters
+        ----------
+        event : hikari.events.base_events.Event
+            The event to dispatch.
+
+        Example
+        -------
+        We can dispatch custom events by first defining a class that
+        derives from `hikari.events.base_events.Event`.
+
+        ```py
+        import attr
+
+        from hikari.traits import RESTAware
+        from hikari.events.base_events import Event
+        from hikari.users import User
+        from hikari.snowflakes import Snowflake
+
+        @attr.define()
+        class EveryoneMentionedEvent(Event):
+            app: RESTAware = attr.field()
+
+            author: User = attr.field()
+            '''The user who mentioned everyone.'''
+
+            content: str = attr.field()
+            '''The message that was sent.'''
+
+            message_id: Snowflake = attr.field()
+            '''The message ID.'''
+
+            channel_id: Snowflake = attr.field()
+            '''The channel ID.'''
+        ```
+
+        We can then dispatch our event as we see fit.
+
+        ```py
+        from hikari.events.messages import MessageCreateEvent
+
+        @bot.listen(MessageCreateEvent)
+        async def on_message(event):
+            if "@everyone" in event.content or "@here" in event.content:
+                event = EveryoneMentionedEvent(
+                    author=event.author,
+                    content=event.content,
+                    message_id=event.id,
+                    channel_id=event.channel_id,
+                )
+
+                bot.dispatch(event)
+        ```
+
+        This event can be listened to elsewhere by subscribing to it with
+        `EventManager.subscribe`.
+
+        ```py
+        @bot.listen(EveryoneMentionedEvent)
+        async def on_everyone_mentioned(event):
+            print(event.user, "just pinged everyone in", event.channel_id)
+        ```
+
+        Returns
+        -------
+        asyncio.Future[typing.Any]
+            A future that can be optionally awaited. If awaited, the future
+            will complete once all corresponding event listeners have been
+            invoked. If not awaited, this will schedule the dispatch of the
+            events in the background for later.
+
+        See Also
+        --------
+        Listen: `hikari.impl.bot.GatewayBot.listen`
+        Stream: `hikari.impl.bot.GatewayBot.stream`
+        Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
+        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
+        """
         return self._event_manager.dispatch(event)
 
     def get_listeners(
         self, event_type: typing.Type[event_manager_.EventT_co], /, *, polymorphic: bool = True
     ) -> typing.Collection[event_manager_.CallbackT[event_manager_.EventT_co]]:
+        """Get the listeners for a given event type, if there are any.
+
+        Parameters
+        ----------
+        event_type : typing.Type[T]
+            The event type to look for.
+            `T` must be a subclass of `hikari.events.base_events.Event`.
+        polymorphic : builtins.bool
+            If `builtins.True`, this will also return the listeners of the
+            subclasses of the given event type. If `builtins.False`, then
+            only listeners for this class specifically are returned. The
+            default is `builtins.True`.
+
+        Returns
+        -------
+        typing.Collection[typing.Callable[[T], typing.Coroutine[typing.Any, typing.Any, builtins.None]]
+            A copy of the collection of listeners for the event. Will return
+            an empty collection if nothing is registered.
+
+            `T` must be a subclass of `hikari.events.base_events.Event`.
+        """
         return self._event_manager.get_listeners(event_type, polymorphic=polymorphic)
 
     async def join(self, until_close: bool = True) -> None:
@@ -440,6 +541,34 @@ def listen(
         [event_manager_.CallbackT[event_manager_.EventT_co]],
         event_manager_.CallbackT[event_manager_.EventT_co],
     ]:
+        """Generate a decorator to subscribe a callback to an event type.
+
+        This is a second-order decorator.
+
+        Parameters
+        ----------
+        event_type : typing.Optional[typing.Type[T]]
+            The event type to subscribe to. The implementation may allow this
+            to be undefined. If this is the case, the event type will be inferred
+            instead from the type hints on the function signature.
+
+            `T` must be a subclass of `hikari.events.base_events.Event`.
+
+        Returns
+        -------
+        typing.Callable[[T], T]
+            A decorator for a coroutine function that passes it to
+            `EventManager.subscribe` before returning the function
+            reference.
+
+        See Also
+        --------
+        Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
+        Stream: `hikari.impl.bot.GatewayBot.stream`
+        Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
+        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
+        """
         return self._event_manager.listen(event_type)
 
     @staticmethod
@@ -872,13 +1001,138 @@ def stream(
         timeout: typing.Union[float, int, None],
         limit: typing.Optional[int] = None,
     ) -> event_manager_.EventStream[event_manager_.EventT_co]:
+        """Return a stream iterator for the given event and sub-events.
+
+        Parameters
+        ----------
+        event_type : typing.Type[hikari.events.base_events.Event]
+            The event type to listen for. This will listen for subclasses of
+            this type additionally.
+        timeout : typing.Optional[builtins.int, builtins.float]
+            How long this streamer should wait for the next event before
+            ending the iteration. If `builtins.None` then this will continue
+            until explicitly broken from.
+        limit : typing.Optional[builtins.int]
+            The limit for how many events this should queue at one time before
+            dropping extra incoming events, leave this as `builtins.None` for
+            the cache size to be unlimited.
+
+        Returns
+        -------
+        EventStream[hikari.events.base_events.Event]
+            The async iterator to handle streamed events. This must be started
+            with `async with stream:` or `await stream.open()` before
+            asynchronously iterating over it.
+
+        !!! warning
+            If you use `await stream.open()` to start the stream then you must
+            also close it with `await stream.close()` otherwise it may queue
+            events in memory indefinitely.
+
+        Examples
+        --------
+
+        ```py
+        async with bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id)) as stream:
+            async for user_id in stream.map("user_id").limit(50):
+                ...
+        ```
+
+        or using await `open()` and await `close()`
+
+        ```py
+        stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id))
+        await stream.open()
+
+        async for user_id in stream.map("user_id").limit(50)
+            ...
+
+        await stream.close()
+        ```
+
+        See Also
+        --------
+        Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
+        Listen: `hikari.impl.bot.GatewayBot.listen`
+        Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
+        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
+        """
         self._check_if_alive()
         return self._event_manager.stream(event_type, timeout=timeout, limit=limit)
 
     def subscribe(self, event_type: typing.Type[typing.Any], callback: event_manager_.CallbackT[typing.Any]) -> None:
+        """Subscribe a given callback to a given event type.
+
+        Parameters
+        ----------
+        event_type : typing.Type[T]
+            The event type to listen for. This will also listen for any
+            subclasses of the given type.
+            `T` must be a subclass of `hikari.events.base_events.Event`.
+        callback
+            Must be a coroutine function to invoke. This should
+            consume an instance of the given event, or an instance of a valid
+            subclass if one exists. Any result is discarded.
+
+        Example
+        -------
+        The following demonstrates subscribing a callback to message creation
+        events.
+
+        ```py
+        from hikari.events.messages import MessageCreateEvent
+
+        async def on_message(event):
+            ...
+
+        bot.subscribe(MessageCreateEvent, on_message)
+        ```
+
+        See Also
+        --------
+        Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
+        Listen: `hikari.impl.bot.GatewayBot.listen`
+        Stream: `hikari.impl.bot.GatewayBot.stream`
+        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
+        """
         self._event_manager.subscribe(event_type, callback)
 
     def unsubscribe(self, event_type: typing.Type[typing.Any], callback: event_manager_.CallbackT[typing.Any]) -> None:
+        """Unsubscribe a given callback from a given event type, if present.
+
+        Parameters
+        ----------
+        event_type : typing.Type[T]
+            The event type to unsubscribe from. This must be the same exact
+            type as was originally subscribed with to be removed correctly.
+            `T` must derive from `hikari.events.base_events.Event`.
+        callback
+            The callback to unsubscribe.
+
+        Example
+        -------
+        The following demonstrates unsubscribing a callback from a message
+        creation event.
+
+        ```py
+        from hikari.events.messages import MessageCreateEvent
+
+        async def on_message(event):
+            ...
+
+        bot.unsubscribe(MessageCreateEvent, on_message)
+        ```
+
+        See Also
+        --------
+        Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
+        Listen: `hikari.impl.bot.GatewayBot.listen`
+        Stream: `hikari.impl.bot.GatewayBot.stream`
+        Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
+        Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
+        """
         self._event_manager.unsubscribe(event_type, callback)
 
     async def wait_for(
@@ -888,6 +1142,48 @@ async def wait_for(
         timeout: typing.Union[float, int, None],
         predicate: typing.Optional[event_manager_.PredicateT[event_manager_.EventT_co]] = None,
     ) -> event_manager_.EventT_co:
+        """Wait for a given event to occur once, then return the event.
+
+        Parameters
+        ----------
+        event_type : typing.Type[hikari.events.base_events.Event]
+            The event type to listen for. This will listen for subclasses of
+            this type additionally.
+        predicate
+            A function taking the event as the single parameter.
+            This should return `builtins.True` if the event is one you want to
+            return, or `builtins.False` if the event should not be returned.
+            If left as `None` (the default), then the first matching event type
+            that the bot receives (or any subtype) will be the one returned.
+
+            !!! warning
+                Async predicates are not supported.
+        timeout : typing.Union[builtins.float, builtins.int, builtins.None]
+            The amount of time to wait before raising an `asyncio.TimeoutError`
+            and giving up instead. This is measured in seconds. If
+            `builtins.None`, then no timeout will be waited for (no timeout can
+            result in "leaking" of coroutines that never complete if called in
+            an uncontrolled way, so is not recommended).
+
+        Returns
+        -------
+        hikari.events.base_events.Event
+            The event that was provided.
+
+        Raises
+        ------
+        asyncio.TimeoutError
+            If the timeout is not `builtins.None` and is reached before an
+            event is received that the predicate returns `builtins.True` for.
+
+        See Also
+        --------
+        Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
+        Listen: `hikari.impl.bot.GatewayBot.listen`
+        Stream: `hikari.impl.bot.GatewayBot.stream`
+        Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
+        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        """
         self._check_if_alive()
         return await self._event_manager.wait_for(event_type, timeout=timeout, predicate=predicate)
 
diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py
index 80a23b24..4ed55e9a 100644
--- a/hikari/impl/event_factory.py
+++ b/hikari/impl/event_factory.py
@@ -198,7 +198,7 @@ def deserialize_typing_start_event(
     # GUILD EVENTS #
     ################
 
-    def deserialize_guild_create_event(
+    def deserialize_guild_available_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
     ) -> guild_events.GuildAvailableEvent:
         guild_information = self._app.entity_factory.deserialize_gateway_guild(payload)
@@ -217,12 +217,31 @@ def deserialize_guild_create_event(
             voice_states=guild_information.voice_states,
         )
 
+    def deserialize_guild_join_event(
+        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
+    ) -> guild_events.GuildJoinEvent:
+        guild_information = self._app.entity_factory.deserialize_gateway_guild(payload)
+        assert guild_information.channels is not None
+        assert guild_information.members is not None
+        assert guild_information.presences is not None
+        assert guild_information.voice_states is not None
+        return guild_events.GuildJoinEvent(
+            shard=shard,
+            guild=guild_information.guild,
+            emojis=guild_information.emojis,
+            roles=guild_information.roles,
+            channels=guild_information.channels,
+            members=guild_information.members,
+            presences=guild_information.presences,
+            voice_states=guild_information.voice_states,
+        )
+
     def deserialize_guild_update_event(
         self,
         shard: gateway_shard.GatewayShard,
         payload: data_binding.JSONObject,
         *,
-        old_guild: typing.Optional[guild_models.Guild],
+        old_guild: typing.Optional[guild_models.GatewayGuild],
     ) -> guild_events.GuildUpdateEvent:
         guild_information = self._app.entity_factory.deserialize_gateway_guild(payload)
         return guild_events.GuildUpdateEvent(
@@ -234,9 +253,15 @@ def deserialize_guild_update_event(
         )
 
     def deserialize_guild_leave_event(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
+        self,
+        shard: gateway_shard.GatewayShard,
+        payload: data_binding.JSONObject,
+        *,
+        old_guild: typing.Optional[guild_models.GatewayGuild],
     ) -> guild_events.GuildLeaveEvent:
-        return guild_events.GuildLeaveEvent(app=self._app, shard=shard, guild_id=snowflakes.Snowflake(payload["id"]))
+        return guild_events.GuildLeaveEvent(
+            app=self._app, shard=shard, guild_id=snowflakes.Snowflake(payload["id"]), old_guild=old_guild
+        )
 
     def deserialize_guild_unavailable_event(
         self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py
index 8c36fb5b..cc6db2a5 100644
--- a/hikari/impl/event_manager.py
+++ b/hikari/impl/event_manager.py
@@ -143,7 +143,12 @@ async def on_channel_pins_update(self, shard: gateway_shard.GatewayShard, payloa
 
     async def on_guild_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None:
         """See https://discord.com/developers/docs/topics/gateway#guild-create for more info."""
-        event = self._event_factory.deserialize_guild_create_event(shard, payload)
+        event: typing.Union[guild_events.GuildAvailableEvent, guild_events.GuildJoinEvent]
+
+        if "unavailable" in payload:
+            event = self._event_factory.deserialize_guild_available_event(shard, payload)
+        else:
+            event = self._event_factory.deserialize_guild_join_event(shard, payload)
 
         if self._cache:
             self._cache.update_guild(event.guild)
@@ -218,18 +223,20 @@ async def on_guild_delete(self, shard: gateway_shard.GatewayShard, payload: data
                 self._cache.set_guild_availability(event.guild_id, False)
 
         else:
-            event = self._event_factory.deserialize_guild_leave_event(shard, payload)
-
+            old: typing.Optional[guilds.GatewayGuild] = None
             if self._cache:
+                guild_id = snowflakes.Snowflake(payload["id"])
                 #  TODO: this doesn't work in all intent scenarios
-                self._cache.delete_guild(event.guild_id)
-                self._cache.clear_voice_states_for_guild(event.guild_id)
-                self._cache.clear_invites_for_guild(event.guild_id)
-                self._cache.clear_members_for_guild(event.guild_id)
-                self._cache.clear_presences_for_guild(event.guild_id)
-                self._cache.clear_guild_channels_for_guild(event.guild_id)
-                self._cache.clear_emojis_for_guild(event.guild_id)
-                self._cache.clear_roles_for_guild(event.guild_id)
+                old = self._cache.delete_guild(guild_id)
+                self._cache.clear_voice_states_for_guild(guild_id)
+                self._cache.clear_invites_for_guild(guild_id)
+                self._cache.clear_members_for_guild(guild_id)
+                self._cache.clear_presences_for_guild(guild_id)
+                self._cache.clear_guild_channels_for_guild(guild_id)
+                self._cache.clear_emojis_for_guild(guild_id)
+                self._cache.clear_roles_for_guild(guild_id)
+
+            event = self._event_factory.deserialize_guild_leave_event(shard, payload, old_guild=old)
 
         await self.dispatch(event)
 
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 3e4f89e5..098ab63e 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -1067,6 +1067,7 @@ async def edit_permission_overwrites(
                     "Cannot determine the type of the target to update. Try specifying 'target_type' manually."
                 )
 
+        target = target.id if isinstance(target, channels_.PermissionOverwrite) else target
         route = routes.PUT_CHANNEL_PERMISSIONS.compile(channel=channel, overwrite=target)
         body = data_binding.JSONObjectBuilder()
         body.put("type", target_type)
diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py
index 8fd4c416..1d6d0576 100644
--- a/hikari/interactions/base_interactions.py
+++ b/hikari/interactions/base_interactions.py
@@ -127,12 +127,7 @@ class ResponseType(int, enums.Enum):
 * `ResponseType.MESSAGE_UPDATE`
 """
 
-MessageResponseTypesT = typing.Union[
-    typing.Literal[ResponseType.MESSAGE_CREATE],
-    typing.Literal[4],
-    typing.Literal[ResponseType.MESSAGE_UPDATE],
-    typing.Literal[7],
-]
+MessageResponseTypesT = typing.Literal[ResponseType.MESSAGE_CREATE, 4, ResponseType.MESSAGE_UPDATE, 7]
 """Type-hint of the response types which are valid for message responses.
 
 The following are valid for this:
@@ -152,12 +147,10 @@ class ResponseType(int, enums.Enum):
 * `ResponseType.DEFERRED_MESSAGE_UPDATE`
 """
 
-DeferredResponseTypesT = typing.Union[
-    typing.Literal[ResponseType.DEFERRED_MESSAGE_CREATE],
-    typing.Literal[5],
-    typing.Literal[ResponseType.DEFERRED_MESSAGE_UPDATE],
-    typing.Literal[6],
+DeferredResponseTypesT = typing.Literal[
+    ResponseType.DEFERRED_MESSAGE_CREATE, 5, ResponseType.DEFERRED_MESSAGE_UPDATE, 6
 ]
+
 """Type-hint of the response types which are valid for deferred messages responses.
 
 The following are valid for this:
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index ca5c6beb..1ec913b6 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -62,11 +62,8 @@
 * `hikari.interactions.base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE`
 """
 
-CommandResponseTypesT = typing.Union[
-    typing.Literal[base_interactions.ResponseType.MESSAGE_CREATE],
-    typing.Literal[4],
-    typing.Literal[base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE],
-    typing.Literal[5],
+CommandResponseTypesT = typing.Literal[
+    base_interactions.ResponseType.MESSAGE_CREATE, 4, base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE, 5
 ]
 """Type-hint of the response types which are valid for a command interaction.
 
diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py
index 9dd10160..8fb78c4a 100644
--- a/hikari/interactions/component_interactions.py
+++ b/hikari/interactions/component_interactions.py
@@ -44,20 +44,14 @@
 _DEFERRED_TYPES: typing.AbstractSet[_DeferredTypesT] = frozenset(
     [base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE, base_interactions.ResponseType.DEFERRED_MESSAGE_UPDATE]
 )
-_DeferredTypesT = typing.Union[
-    typing.Literal[base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE],
-    typing.Literal[5],
-    typing.Literal[base_interactions.ResponseType.DEFERRED_MESSAGE_UPDATE],
-    typing.Literal[6],
+_DeferredTypesT = typing.Literal[
+    base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE, 5, base_interactions.ResponseType.DEFERRED_MESSAGE_UPDATE, 6
 ]
 _IMMEDIATE_TYPES: typing.AbstractSet[_ImmediateTypesT] = frozenset(
     [base_interactions.ResponseType.MESSAGE_CREATE, base_interactions.ResponseType.MESSAGE_UPDATE]
 )
-_ImmediateTypesT = typing.Union[
-    typing.Literal[base_interactions.ResponseType.MESSAGE_CREATE],
-    typing.Literal[4],
-    typing.Literal[base_interactions.ResponseType.MESSAGE_UPDATE],
-    typing.Literal[7],
+_ImmediateTypesT = typing.Literal[
+    base_interactions.ResponseType.MESSAGE_CREATE, 4, base_interactions.ResponseType.MESSAGE_UPDATE, 7
 ]
 
 
diff --git a/hikari/internal/net.py b/hikari/internal/net.py
index 1dfc2283..d60aac3e 100644
--- a/hikari/internal/net.py
+++ b/hikari/internal/net.py
@@ -48,7 +48,7 @@ async def generate_error_response(response: aiohttp.ClientResponse) -> errors.HT
     try:
         json_body = await response.json()
         args.append(json_body.get("message", ""))
-        args.append(errors.RESTErrorCode(json_body.get("code", 0)))
+        args.append(json_body.get("code", 0))
         raw_error_array: typing.Optional[data_binding.JSONObject] = json_body.get("errors")
     except aiohttp.ContentTypeError:
         raw_error_array = None
diff --git a/hikari/messages.py b/hikari/messages.py
index 4bc194c3..3c679c8f 100644
--- a/hikari/messages.py
+++ b/hikari/messages.py
@@ -1419,16 +1419,20 @@ async def remove_reaction(
             # reaction.
             await message.remove_reaction("\N{OK HAND SIGN}")
 
+            # Using a custom emoji's name and ID to remove a specific user's
+            # reaction from this reaction.
+            await message.remove_reaction("a:Distraction", 745991233939439616, user=some_user)
+
             # Using a unicode emoji and removing a specific user from this
             # reaction.
-            await message.remove_reaction("\N{OK HAND SIGN}", some_user)
+            await message.remove_reaction("\N{OK HAND SIGN}", user=some_user)
 
             # Using the name and id.
             await message.add_reaction("rooAYAYA", 705837374319493284)
 
             # Using an Emoji object and removing a specific user from this
             # reaction.
-            await message.remove_reaction(some_emoji_object, some_user)
+            await message.remove_reaction(some_emoji_object, user=some_user)
 
         Raises
         ------
diff --git a/requirements.txt b/requirements.txt
index ef3960f9..75b00d57 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 aiohttp==3.7.4.post0
 attrs==21.2.0
 colorlog==6.4.1
-multidict==5.1.0
+multidict==5.2.0
 pure25519==0.0.1


Test patch:
diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py
index a6727b53..a7bcc115 100644
--- a/tests/hikari/impl/test_event_factory.py
+++ b/tests/hikari/impl/test_event_factory.py
@@ -236,10 +236,10 @@ def test_deserialize_typing_start_event_for_dm(self, event_factory, mock_app, mo
     # GUILD EVENTS #
     ################
 
-    def test_deserialize_guild_create_event(self, event_factory, mock_app, mock_shard):
+    def test_deserialize_guild_available_event(self, event_factory, mock_app, mock_shard):
         mock_payload = mock.Mock(app=mock_app)
 
-        event = event_factory.deserialize_guild_create_event(mock_shard, mock_payload)
+        event = event_factory.deserialize_guild_available_event(mock_shard, mock_payload)
 
         mock_app.entity_factory.deserialize_gateway_guild.assert_called_once_with(mock_payload)
         assert isinstance(event, guild_events.GuildAvailableEvent)
@@ -252,6 +252,22 @@ def test_deserialize_guild_create_event(self, event_factory, mock_app, mock_shar
         assert event.presences is mock_app.entity_factory.deserialize_gateway_guild.return_value.presences
         assert event.voice_states is mock_app.entity_factory.deserialize_gateway_guild.return_value.voice_states
 
+    def test_deserialize_guild_join_event(self, event_factory, mock_app, mock_shard):
+        mock_payload = mock.Mock(app=mock_app)
+
+        event = event_factory.deserialize_guild_join_event(mock_shard, mock_payload)
+
+        mock_app.entity_factory.deserialize_gateway_guild.assert_called_once_with(mock_payload)
+        assert isinstance(event, guild_events.GuildJoinEvent)
+        assert event.shard is mock_shard
+        assert event.guild is mock_app.entity_factory.deserialize_gateway_guild.return_value.guild
+        assert event.emojis is mock_app.entity_factory.deserialize_gateway_guild.return_value.emojis
+        assert event.roles is mock_app.entity_factory.deserialize_gateway_guild.return_value.roles
+        assert event.channels is mock_app.entity_factory.deserialize_gateway_guild.return_value.channels
+        assert event.members is mock_app.entity_factory.deserialize_gateway_guild.return_value.members
+        assert event.presences is mock_app.entity_factory.deserialize_gateway_guild.return_value.presences
+        assert event.voice_states is mock_app.entity_factory.deserialize_gateway_guild.return_value.voice_states
+
     def test_deserialize_guild_update_event(self, event_factory, mock_app, mock_shard):
         mock_payload = mock.Mock(app=mock_app)
         mock_old_guild = object()
@@ -268,13 +284,15 @@ def test_deserialize_guild_update_event(self, event_factory, mock_app, mock_shar
 
     def test_deserialize_guild_leave_event(self, event_factory, mock_app, mock_shard):
         mock_payload = {"id": "43123123"}
+        mock_old_guild = object()
 
-        event = event_factory.deserialize_guild_leave_event(mock_shard, mock_payload)
+        event = event_factory.deserialize_guild_leave_event(mock_shard, mock_payload, old_guild=mock_old_guild)
 
         assert isinstance(event, guild_events.GuildLeaveEvent)
         assert event.app is mock_app
         assert event.shard is mock_shard
         assert event.guild_id == 43123123
+        assert event.old_guild is mock_old_guild
 
     def test_deserialize_guild_unavailable_event(self, event_factory, mock_app, mock_shard):
         mock_payload = {"id": "6541233"}
diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py
index 8ec18f8d..dc66657e 100644
--- a/tests/hikari/impl/test_event_manager.py
+++ b/tests/hikari/impl/test_event_manager.py
@@ -226,7 +226,52 @@ async def test_on_channel_pins_update(self, stateless_event_manager, shard, even
         stateless_event_manager.dispatch.assert_awaited_once_with(event)
 
     @pytest.mark.asyncio()
-    async def test_on_guild_create_stateful(self, event_manager, shard, event_factory):
+    async def test_on_guild_create_stateful_with_unavailable_field(self, event_manager, shard, event_factory):
+        payload = {"unavailable": False}
+        event = mock.Mock(
+            guild=mock.Mock(id=123, is_large=False),
+            channels={"TestChannel": 456},
+            emojis={"TestEmoji": 789},
+            roles={"TestRole": 1234},
+            members={"TestMember": 5678},
+            presences={"TestPresence": 9012},
+            voice_states={"TestState": 345},
+            chunk_nonce=None,
+        )
+
+        event_factory.deserialize_guild_available_event.return_value = event
+        shard.request_guild_members = mock.AsyncMock()
+
+        await event_manager.on_guild_create(shard, payload)
+
+        assert event.chunk_nonce is None
+        shard.request_guild_members.assert_not_called()
+
+        event_manager._cache.update_guild.assert_called_once_with(event.guild)
+
+        event_manager._cache.clear_guild_channels_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_guild_channel.assert_called_once_with(456)
+
+        event_manager._cache.clear_emojis_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_emoji.assert_called_once_with(789)
+
+        event_manager._cache.clear_roles_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_role.assert_called_once_with(1234)
+
+        event_manager._cache.clear_members_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_member.assert_called_once_with(5678)
+
+        event_manager._cache.clear_presences_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_presence.assert_called_once_with(9012)
+
+        event_manager._cache.clear_voice_states_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_voice_state.assert_called_once_with(345)
+
+        event_factory.deserialize_guild_available_event.assert_called_once_with(shard, payload)
+        event_manager.dispatch.assert_awaited_once_with(event)
+
+    @pytest.mark.asyncio()
+    async def test_on_guild_create_stateful_without_unavailable_field(self, event_manager, shard, event_factory):
         payload = {}
         event = mock.Mock(
             guild=mock.Mock(id=123, is_large=False),
@@ -239,7 +284,7 @@ async def test_on_guild_create_stateful(self, event_manager, shard, event_factor
             chunk_nonce=None,
         )
 
-        event_factory.deserialize_guild_create_event.return_value = event
+        event_factory.deserialize_guild_join_event.return_value = event
         shard.request_guild_members = mock.AsyncMock()
 
         await event_manager.on_guild_create(shard, payload)
@@ -267,11 +312,73 @@ async def test_on_guild_create_stateful(self, event_manager, shard, event_factor
         event_manager._cache.clear_voice_states_for_guild.assert_called_once_with(123)
         event_manager._cache.set_voice_state.assert_called_once_with(345)
 
-        event_factory.deserialize_guild_create_event.assert_called_once_with(shard, payload)
+        event_factory.deserialize_guild_join_event.assert_called_once_with(shard, payload)
         event_manager.dispatch.assert_awaited_once_with(event)
 
     @pytest.mark.asyncio()
-    async def test_on_guild_create_when_request_chunks(self, event_manager, shard, event_factory):
+    async def test_on_guild_create_when_request_chunks_with_unavailable_field(
+        self, event_manager, shard, event_factory
+    ):
+        payload = {"unavailable": False}
+        event = mock.Mock(
+            guild=mock.Mock(id=123, is_large=True),
+            channels={"TestChannel": 456},
+            emojis={"TestEmoji": 789},
+            roles={"TestRole": 1234},
+            members={"TestMember": 5678},
+            presences={"TestPresence": 9012},
+            voice_states={"TestState": 345},
+            chunk_nonce=None,
+        )
+
+        event_factory.deserialize_guild_available_event.return_value = event
+        shard.request_guild_members = mock.Mock()
+
+        stack = contextlib.ExitStack()
+        create_task = stack.enter_context(mock.patch.object(asyncio, "create_task"))
+        uuid = stack.enter_context(mock.patch("hikari.impl.event_manager._fixed_size_nonce", return_value="uuid"))
+        _request_guild_members = stack.enter_context(
+            mock.patch("hikari.impl.event_manager._request_guild_members", new_callable=mock.Mock)
+        )
+
+        with stack:
+            await event_manager.on_guild_create(shard, payload)
+
+        uuid.assert_called_once_with()
+        nonce = "987.uuid"
+        assert event.chunk_nonce == nonce
+        _request_guild_members.assert_called_once_with(shard, event.guild, include_presences=True, nonce=nonce)
+        create_task.assert_called_once_with(
+            _request_guild_members.return_value, name="987:123 guild create members request"
+        )
+
+        event_manager._cache.update_guild.assert_called_once_with(event.guild)
+
+        event_manager._cache.clear_guild_channels_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_guild_channel.assert_called_once_with(456)
+
+        event_manager._cache.clear_emojis_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_emoji.assert_called_once_with(789)
+
+        event_manager._cache.clear_roles_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_role.assert_called_once_with(1234)
+
+        event_manager._cache.clear_members_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_member.assert_called_once_with(5678)
+
+        event_manager._cache.clear_presences_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_presence.assert_called_once_with(9012)
+
+        event_manager._cache.clear_voice_states_for_guild.assert_called_once_with(123)
+        event_manager._cache.set_voice_state.assert_called_once_with(345)
+
+        event_factory.deserialize_guild_available_event.assert_called_once_with(shard, payload)
+        event_manager.dispatch.assert_awaited_once_with(event)
+
+    @pytest.mark.asyncio()
+    async def test_on_guild_create_when_request_chunks_without_unavailable_field(
+        self, event_manager, shard, event_factory
+    ):
         payload = {}
         event = mock.Mock(
             guild=mock.Mock(id=123, is_large=True),
@@ -284,7 +391,7 @@ async def test_on_guild_create_when_request_chunks(self, event_manager, shard, e
             chunk_nonce=None,
         )
 
-        event_factory.deserialize_guild_create_event.return_value = event
+        event_factory.deserialize_guild_join_event.return_value = event
         shard.request_guild_members = mock.Mock()
 
         stack = contextlib.ExitStack()
@@ -325,20 +432,37 @@ async def test_on_guild_create_when_request_chunks(self, event_manager, shard, e
         event_manager._cache.clear_voice_states_for_guild.assert_called_once_with(123)
         event_manager._cache.set_voice_state.assert_called_once_with(345)
 
-        event_factory.deserialize_guild_create_event.assert_called_once_with(shard, payload)
+        event_factory.deserialize_guild_join_event.assert_called_once_with(shard, payload)
         event_manager.dispatch.assert_awaited_once_with(event)
 
     @pytest.mark.asyncio()
-    async def test_on_guild_create_stateless(self, stateless_event_manager, shard, event_factory):
+    async def test_on_guild_create_stateless_with_unavailable_field(
+        self, stateless_event_manager, shard, event_factory
+    ):
+        payload = {"unavailable": False}
+
+        shard.request_guild_members = mock.AsyncMock()
+
+        await stateless_event_manager.on_guild_create(shard, payload)
+
+        event_factory.deserialize_guild_available_event.assert_called_once_with(shard, payload)
+        stateless_event_manager.dispatch.assert_awaited_once_with(
+            event_factory.deserialize_guild_available_event.return_value
+        )
+
+    @pytest.mark.asyncio()
+    async def test_on_guild_create_stateless_without_unavailable_field(
+        self, stateless_event_manager, shard, event_factory
+    ):
         payload = {}
 
         shard.request_guild_members = mock.AsyncMock()
 
         await stateless_event_manager.on_guild_create(shard, payload)
 
-        event_factory.deserialize_guild_create_event.assert_called_once_with(shard, payload)
+        event_factory.deserialize_guild_join_event.assert_called_once_with(shard, payload)
         stateless_event_manager.dispatch.assert_awaited_once_with(
-            event_factory.deserialize_guild_create_event.return_value
+            event_factory.deserialize_guild_join_event.return_value
         )
 
     @pytest.mark.asyncio()
@@ -376,7 +500,7 @@ async def test_on_guild_update_stateless(self, stateless_event_manager, shard, e
 
     @pytest.mark.asyncio()
     async def test_on_guild_delete_stateful_when_available(self, event_manager, shard, event_factory):
-        payload = {"unavailable": False}
+        payload = {"unavailable": False, "id": "123"}
         event = mock.Mock(guild_id=123)
 
         event_factory.deserialize_guild_leave_event.return_value = event
@@ -391,12 +515,14 @@ async def test_on_guild_delete_stateful_when_available(self, event_manager, shar
         event_manager._cache.clear_guild_channels_for_guild.assert_called_once_with(123)
         event_manager._cache.clear_emojis_for_guild.assert_called_once_with(123)
         event_manager._cache.clear_roles_for_guild.assert_called_once_with(123)
-        event_factory.deserialize_guild_leave_event.assert_called_once_with(shard, payload)
+        event_factory.deserialize_guild_leave_event.assert_called_once_with(
+            shard, payload, old_guild=event_manager._cache.delete_guild.return_value
+        )
         event_manager.dispatch.assert_awaited_once_with(event)
 
     @pytest.mark.asyncio()
     async def test_on_guild_delete_stateful_when_unavailable(self, event_manager, shard, event_factory):
-        payload = {"unavailable": True}
+        payload = {"unavailable": True, "id": "123"}
         event = mock.Mock(guild_id=123)
 
         event_factory.deserialize_guild_unavailable_event.return_value = event
@@ -409,11 +535,11 @@ async def test_on_guild_delete_stateful_when_unavailable(self, event_manager, sh
 
     @pytest.mark.asyncio()
     async def test_on_guild_delete_stateless_when_available(self, stateless_event_manager, shard, event_factory):
-        payload = {"unavailable": False}
+        payload = {"unavailable": False, "id": "123"}
 
         await stateless_event_manager.on_guild_delete(shard, payload)
 
-        event_factory.deserialize_guild_leave_event.assert_called_once_with(shard, payload)
+        event_factory.deserialize_guild_leave_event.assert_called_once_with(shard, payload, old_guild=None)
         stateless_event_manager.dispatch.assert_awaited_once_with(
             event_factory.deserialize_guild_leave_event.return_value
         )
diff --git a/tests/hikari/test_errors.py b/tests/hikari/test_errors.py
index 875dfcee..99bf71c2 100644
--- a/tests/hikari/test_errors.py
+++ b/tests/hikari/test_errors.py
@@ -81,23 +81,23 @@ def error(self):
             {},
             "raw body",
             "message",
-            errors.RESTErrorCode.UNKNOWN_GUILD,
+            12345,
         )
 
     def test_str(self, error):
-        assert str(error) == "Bad Request 400: (UNKNOWN_GUILD) 'message' for https://some.url"
+        assert str(error) == "Bad Request 400: (12345) 'message' for https://some.url"
 
     def test_str_when_message_is_None(self, error):
         error.message = None
-        assert str(error) == "Bad Request 400: (UNKNOWN_GUILD) 'raw body' for https://some.url"
+        assert str(error) == "Bad Request 400: (12345) 'raw body' for https://some.url"
 
-    def test_str_when_code_is_GENERAL_ERROR(self, error):
-        error.code = errors.RESTErrorCode.GENERAL_ERROR
+    def test_str_when_code_is_zero(self, error):
+        error.code = 0
         assert str(error) == "Bad Request 400: 'message' for https://some.url"
 
-    def test_str_when_code_is_int(self, error):
+    def test_str_when_code_is_not_zero(self, error):
         error.code = 100
-        assert str(error) == "Bad Request 400: 'message' for https://some.url"
+        assert str(error) == "Bad Request 400: (100) 'message' for https://some.url"
 
 
 class TestBadRequestError: