Repository: hikari-py/hikari
Pull request #1024 (2022-02-17 17:15:56)

Add a slash command example
### Summary
Added a slash command example since Hikari was lacking one.

### Checklist
- [x] I have run `nox` and all the pipelines have passed.




Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6faf5e6..48b82b3e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,7 +7,7 @@ on:
 jobs:
   test:
     strategy:
-      # Allows for matrix sub-jobs to fail without canceling the rest
+      # Allows for matrix sub-jobs to fail without cancelling the rest
       fail-fast: false
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
@@ -20,7 +20,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -60,7 +60,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -90,7 +90,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -107,7 +107,7 @@ jobs:
       - name: Linting
         if: always()
         run: |
-          nox -s safety mypy verify-types flake8 codespell check-trailing-whitespaces
+          nox -s safety mypy verify-types flake8 slotscheck codespell check-trailing-whitespaces
 
   twemoji:
     runs-on: ubuntu-latest
@@ -117,7 +117,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -134,12 +134,12 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
       - name: Setup node
-        uses: actions/setup-node@v2.5.1
+        uses: actions/setup-node@v3
 
       - name: Build pages
         run: |
diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml
index a23eb0f8..3c7cec9b 100644
--- a/.github/workflows/fragments-check.yml
+++ b/.github/workflows/fragments-check.yml
@@ -20,7 +20,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 792cc43f..cdd80e24 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -8,7 +8,7 @@ on:
         required: true
 
 jobs:
-  prepare-relese:
+  prepare-release:
     runs-on: ubuntu-latest
 
     steps:
@@ -16,7 +16,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 21456af2..62237822 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,7 +15,7 @@ jobs:
           token: ${{ secrets.PAT_TOKEN }}
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39bc2380..b62581b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,7 +77,7 @@ Bugfixes
 - Fix `PartialSticker.image_url` not passing the hash as a string ([#930](https://github.com/hikari-py/hikari/issues/930))
 - Fixed the url being generated for role icons to not erroneously insert ".png" before the file extension ([#931](https://github.com/hikari-py/hikari/issues/931))
 - Fix some bugs in message deserialization
-    - Remove case for setting `member` and `referece_message` to `undefined.Undefined` in full message deserialization
+    - Remove case for setting `member` and `reference_message` to `undefined.Undefined` in full message deserialization
     - Don't set `message.member` to `undefined.UNDEFINED` on partial message deserialization if message was sent by a webhook ([#933](https://github.com/hikari-py/hikari/issues/933))
 
 
diff --git a/README.md b/README.md
index 48576317..88ada165 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,18 @@ development.
 
 Python 3.8, 3.9 and 3.10 are currently supported.
 
+## Installation
+
+Install Hikari from PyPI with the following command:
+
+```bash
+python -m pip install -U hikari
+# Windows users may need to run this instead...
+py -3 -m pip install -U hikari
+```
+
+----
+
 ## Bots
 
 ```py
@@ -116,18 +128,6 @@ async def print_my_user(token):
 asyncio.run(print_my_user("user token here"))
 ```
 
-----
-
-## Installation
-
-Install Hikari from PyPI with the following command:
-
-```bash
-python -m pip install -U hikari
-# Windows users may need to run this instead...
-py -3 -m pip install -U hikari
-```
-
 ---
 
 ## Optional Features
diff --git a/changes/1022.feature.md b/changes/1022.feature.md
new file mode 100644
index 00000000..4c98a0de
--- /dev/null
+++ b/changes/1022.feature.md
@@ -0,0 +1,1 @@
+Custom substitutions can now be used in `hikari.internal.ux.print_banner`.
diff --git a/changes/1025.feature.md b/changes/1025.feature.md
new file mode 100644
index 00000000..e3d34eca
--- /dev/null
+++ b/changes/1025.feature.md
@@ -0,0 +1,1 @@
+`get_guild()` is now available on `hikari.Member`.
diff --git a/changes/1041.feature.md b/changes/1041.feature.md
new file mode 100644
index 00000000..40856818
--- /dev/null
+++ b/changes/1041.feature.md
@@ -0,0 +1,1 @@
+The noturious "failed to communicate with server" log message is now a warning rather than an error.
diff --git a/changes/1042.feature.md b/changes/1042.feature.md
new file mode 100644
index 00000000..3a71eb71
--- /dev/null
+++ b/changes/1042.feature.md
@@ -0,0 +1,1 @@
+`hikari.applications`, `hikari.files`, `hikari.snowflakes` and `hikari.undefined` are now all explicitly exported by `hikari.__init__`, allowing pyright to see them without a direct import.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 832bded0..1e9e4d32 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -12,7 +12,7 @@ pytest-cov==3.0.0
 pytest-randomly==3.11.0
 
 # Coverage testing.
-coverage[toml]==6.3.1
+coverage[toml]==6.3.2
 
 # Other stuff
 async-timeout==4.0.2  # Used for timeouts in some test cases.
@@ -29,7 +29,7 @@ sphobjinv==2.2.1
 #################
 
 mypy==0.931
-pyright==0.0.13
+pyright==1.1.225
 
 #######################
 # DEPENDENCY CHECKING #
@@ -37,13 +37,19 @@ pyright==0.0.13
 
 safety~=1.10.3
 
-#############
+##############
 # FORMATTING #
-#############
+##############
 
 black==22.1.0
 isort==5.10.1
 
+###########
+# Linting #
+###########
+
+slotscheck==0.14.0
+
 ##################
 # SPELL CHECKING #
 ##################
diff --git a/examples/slash.py b/examples/slash.py
new file mode 100644
index 00000000..af6da446
--- /dev/null
+++ b/examples/slash.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Hikari Examples - A collection of examples for Hikari.
+#
+# To the extent possible under law, the author(s) have dedicated all copyright
+# and related and neighboring rights to this software to the public domain worldwide.
+# This software is distributed without any warranty.
+#
+# You should have received a copy of the CC0 Public Domain Dedication along with this software.
+# If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
+"""A simple bot with some simple commands."""
+
+import os
+
+import hikari
+
+bot = hikari.GatewayBot(token=os.environ["BOT_TOKEN"])
+
+# Set this to the guild to create the slash commands in.
+# To create global commands, set to `hikari.UNDEFINED`.
+#
+# It is important to note that global commands can take up to an hour to show in the client
+# and should only be used when "publishing" commands after they are built. For testing
+# use guild based commands as they are instant.
+COMMAND_GUILD_ID = 0
+
+
+@bot.listen()
+async def register_commands(event: hikari.StartingEvent) -> None:
+    """Register ping and info commands."""
+    application = await bot.rest.fetch_application()
+
+    commands = [
+        bot.rest.slash_command_builder("ping", "Get the bot's latency."),
+        bot.rest.slash_command_builder("info", "Learn something about the bot."),
+        bot.rest.slash_command_builder("ephemeral", "Send a very secret message."),
+    ]
+
+    await bot.rest.set_application_commands(
+        application=application.id,
+        commands=commands,
+        guild=COMMAND_GUILD_ID,
+    )
+
+
+@bot.listen()
+async def handle_interactions(event: hikari.InteractionCreateEvent) -> None:
+    """Listen for slash commands being executed."""
+    if not isinstance(event.interaction, hikari.CommandInteraction):
+        # only listen to command interactions, no others!
+        return
+
+    if event.interaction.command_name == "ping":
+        await event.interaction.create_initial_response(
+            hikari.ResponseType.MESSAGE_CREATE,
+            f"Pong! {bot.heartbeat_latency * 1_000:.0f}ms",
+        )
+
+    elif event.interaction.command_name == "info":
+        await event.interaction.create_initial_response(
+            hikari.ResponseType.MESSAGE_CREATE,
+            "Hello, this is an example bot written in hikari!",
+        )
+
+    elif event.interaction.command_name == "ephemeral":
+        await event.interaction.create_initial_response(
+            hikari.ResponseType.MESSAGE_CREATE,
+            "Only you can see this, keep it a secret :)",
+            flags=hikari.MessageFlag.EPHEMERAL,
+        )
+
+
+bot.run()
diff --git a/flake8-requirements.txt b/flake8-requirements.txt
index dc5f44e6..140ce957 100644
--- a/flake8-requirements.txt
+++ b/flake8-requirements.txt
@@ -3,8 +3,9 @@ flake8==4.0.1
 # Plugins
 # Ref: https://github.com/DmytroLitvinov/awesome-flake8-extensions
 
+bandit==1.7.2                           # temporariry freeze to bandit due to incompatibility with flake8-bandit
 flake8-bandit~=2.1.2                    # runs bandit
-flake8-black==0.2.4                     # runs black
+flake8-black==0.3.2                     # runs black
 flake8-broken-line==0.4.0               # forbey "\" linebreaks
 flake8-builtins==1.5.3                  # builtin shadowing checks
 flake8-coding==1.3.2                    # coding magic-comment detection
@@ -13,7 +14,7 @@ flake8-deprecated==1.3                  # deprecated call checks
 flake8-docstrings==1.6.0                # pydocstyle support
 flake8-executable==2.1.1                # shebangs
 flake8-fixme==1.1.1                     # "fix me" counter
-flake8-functions==0.0.6                 # function linting
+flake8-functions==0.0.7                 # function linting
 flake8-html==0.4.1                      # html output
 flake8-if-statements==0.1.0             # condition linting
 flake8-isort==4.1.1                     # runs isort
@@ -23,4 +24,4 @@ flake8-print==4.0.0                     # complain about print statements in cod
 flake8-printf-formatting==1.1.2         # forbey printf-style python2 string formatting
 flake8-pytest-style==1.6.0              # pytest checks
 flake8-raise==0.0.5                     # exception raising linting
-flake8-use-fstring==1.3               # format string checking
+flake8-use-fstring==1.3                 # format string checking
diff --git a/hikari/__init__.py b/hikari/__init__.py
index f611993b..f678b383 100644
--- a/hikari/__init__.py
+++ b/hikari/__init__.py
@@ -31,7 +31,11 @@
 import os as _os
 
 from hikari import api
+from hikari import applications
+from hikari import files
 from hikari import impl
+from hikari import snowflakes
+from hikari import undefined
 from hikari._about import __author__
 from hikari._about import __ci__
 from hikari._about import __copyright__
diff --git a/hikari/__init__.pyi b/hikari/__init__.pyi
index 6d5b36df..27ec425b 100644
--- a/hikari/__init__.pyi
+++ b/hikari/__init__.pyi
@@ -4,7 +4,11 @@
 from typing import Any
 
 from hikari import api as api
+from hikari import applications as applications
+from hikari import files as files
 from hikari import impl as impl
+from hikari import snowflakes as snowflakes
+from hikari import undefined as undefined
 from hikari._about import __author__ as __author__
 from hikari._about import __ci__ as __ci__
 from hikari._about import __copyright__ as __copyright__
diff --git a/hikari/api/interaction_server.py b/hikari/api/interaction_server.py
index 2898b9c4..4d59902b 100644
--- a/hikari/api/interaction_server.py
+++ b/hikari/api/interaction_server.py
@@ -46,7 +46,7 @@
 """Type hint of a Interaction server's listener callback.
 
 This should be an async callback which takes in one positional argument which
-subclases `hikari.interactions.base_interactions.PartialInteraction` and may return an
+subclasses `hikari.interactions.base_interactions.PartialInteraction` and may return an
 instance of the relevant `hikari.api.special_endpoints.InteractionResponseBuilder`
 subclass for the provided interaction type which will instruct the server on how
 to respond.
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index f750d049..0f76717c 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -1988,7 +1988,7 @@ async def fetch_webhook(
         Other Parameters
         ----------------
         token : hikari.undefined.UndefinedOr[builtins.str]
-            If provided, the webhoook token that will be used to fetch
+            If provided, the webhook token that will be used to fetch
             the webhook instead of the token the client was initialized with.
 
         Returns
@@ -2125,7 +2125,7 @@ async def edit_webhook(
         Other Parameters
         ----------------
         token : hikari.undefined.UndefinedOr[builtins.str]
-            If provided, the webhoook token that will be used to edit
+            If provided, the webhook token that will be used to edit
             the webhook instead of the token the client was initialized with.
         name : hikari.undefined.UndefinedOr[builtins.str]
             If provided, the new webhook name.
@@ -2185,7 +2185,7 @@ async def delete_webhook(
         Other Parameters
         ----------------
         token : hikari.undefined.UndefinedOr[builtins.str]
-            If provided, the webhoook token that will be used to delete
+            If provided, the webhook token that will be used to delete
             the webhook instead of the token the client was initialized with.
 
         Raises
@@ -2196,7 +2196,7 @@ async def delete_webhook(
         hikari.errors.UnauthorizedError
             If you are unauthorized to make the request (invalid/missing token).
         hikari.errors.NotFoundError
-            If the webhoook is not found.
+            If the webhook 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.
@@ -2885,7 +2885,7 @@ def fetch_my_guilds(
         Other Parameters
         ----------------
         newest_first : builtins.bool
-            Whether to fetch the newest first or the olders first.
+            Whether to fetch the newest first or the oldest first.
             Defaults to `builtins.False`.
         start_at : hikari.undefined.UndefinedOr[hikari.snowflakes.SearchableSnowflakeishOr[hikari.guilds.PartialGuild]]
             If provided, will start at this snowflake. If you provide
@@ -3413,7 +3413,7 @@ def fetch_audit_log(
         user: undefined.UndefinedOr[snowflakes.SnowflakeishOr[users.PartialUser]] = undefined.UNDEFINED,
         event_type: undefined.UndefinedOr[typing.Union[audit_logs.AuditLogEventType, int]] = undefined.UNDEFINED,
     ) -> iterators.LazyIterator[audit_logs.AuditLog]:
-        """Fetch the guild's audit log.
+        """Fetch pages of the guild's audit log.
 
         Parameters
         ----------
@@ -7234,7 +7234,7 @@ async def fetch_application_command_permissions(
         guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
             Object or ID of the guild to fetch the command permissions for.
         command: hikari.snowflakes.SnowflakeishOr[hikari.commands.PartialCommand]
-            Objecr or ID of the command to fetch the command permissions for.
+            Object or ID of the command to fetch the command permissions for.
 
         Returns
         -------
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index cd3230d8..3777e52c 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -552,6 +552,8 @@ def add_stage_channel(
 class InteractionResponseBuilder(abc.ABC):
     """Base class for all interaction response builders used in the interaction server."""
 
+    __slots__: typing.Sequence[str] = ()
+
     @property
     @abc.abstractmethod
     def type(self) -> typing.Union[int, base_interactions.ResponseType]:
@@ -1018,6 +1020,8 @@ def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_bindin
 class SlashCommandBuilder(CommandBuilder):
     """SlashCommandBuilder."""
 
+    __slots__: typing.Sequence[str] = ()
+
     @property
     @abc.abstractmethod
     def description(self) -> str:
@@ -1065,6 +1069,8 @@ def add_option(self: _T, option: commands.CommandOption) -> _T:
 class ContextMenuCommandBuilder(CommandBuilder):
     """ContextMenuCommandBuilder."""
 
+    __slots__: typing.Sequence[str] = ()
+
 
 class ComponentBuilder(abc.ABC):
     """Base class for all component builder classes."""
@@ -1205,6 +1211,8 @@ def add_to_container(self) -> _ContainerT:
 class LinkButtonBuilder(ButtonBuilder[_ContainerT], abc.ABC):
     """Builder interface for link buttons."""
 
+    __slots__: typing.Sequence[str] = ()
+
     @property
     @abc.abstractmethod
     def url(self) -> str:
@@ -1220,6 +1228,8 @@ def url(self) -> str:
 class InteractiveButtonBuilder(ButtonBuilder[_ContainerT], abc.ABC):
     """Builder interface for interactive buttons."""
 
+    __slots__: typing.Sequence[str] = ()
+
     @property
     @abc.abstractmethod
     def custom_id(self) -> str:
diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py
index 45f5a211..7cb0723f 100644
--- a/hikari/audit_logs.py
+++ b/hikari/audit_logs.py
@@ -487,7 +487,7 @@ async def fetch_user(self) -> typing.Optional[users_.User]:
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, repr=False, weakref_slot=False)
 class AuditLog(typing.Sequence[AuditLogEntry]):
-    """Represents a guilds audit log."""
+    """Represents a guilds audit log's page."""
 
     entries: typing.Mapping[snowflakes.Snowflake, AuditLogEntry] = attr.field(repr=False)
     """A mapping of snowflake IDs to the audit log's entries."""
diff --git a/hikari/errors.py b/hikari/errors.py
index 7e3eed10..642ffd80 100644
--- a/hikari/errors.py
+++ b/hikari/errors.py
@@ -66,8 +66,9 @@
     from hikari.internal import routes
 
 
+# The standard exceptions are all unsloted so slotting here would be a waste of time.
 @attr_extensions.with_copy
-@attr.define(auto_exc=True, repr=False, init=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, init=False, slots=False)
 class HikariError(RuntimeError):
     """Base for an error raised by this API.
 
@@ -78,8 +79,9 @@ class HikariError(RuntimeError):
     """
 
 
+# The standard warnings are all unsloted so slotting here would be a waste of time.
 @attr_extensions.with_copy
-@attr.define(auto_exc=True, repr=False, init=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, init=False, slots=False)
 class HikariWarning(RuntimeWarning):
     """Base for a warning raised by this API.
 
@@ -90,7 +92,7 @@ class HikariWarning(RuntimeWarning):
     """
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class HikariInterrupt(KeyboardInterrupt, HikariError):
     """Exception raised when a kill signal is handled internally."""
 
@@ -101,7 +103,7 @@ class HikariInterrupt(KeyboardInterrupt, HikariError):
     """The signal name that was raised."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class ComponentStateConflictError(HikariError):
     """Exception thrown when an action cannot be executed in the component's current state.
 
@@ -116,7 +118,7 @@ def __str__(self) -> str:
         return self.reason
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class UnrecognisedEntityError(HikariError):
     """An exception thrown when an unrecognised entity is found."""
 
@@ -127,7 +129,7 @@ def __str__(self) -> str:
         return self.reason
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class GatewayError(HikariError):
     """A base exception type for anything that can be thrown by the Gateway."""
 
@@ -171,7 +173,7 @@ def is_standard(self) -> bool:
         return (self.value // 1000) == 1
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class GatewayConnectionError(GatewayError):
     """An exception thrown if a connection issue occurs."""
 
@@ -179,7 +181,7 @@ def __str__(self) -> str:
         return f"Failed to connect to server: {self.reason!r}"
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class GatewayServerClosedConnectionError(GatewayError):
     """An exception raised when the server closes the connection."""
 
@@ -214,7 +216,7 @@ def __str__(self) -> str:
         return f"Server closed connection with code {self.code} ({self.reason})"
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class HTTPError(HikariError):
     """Base exception raised if an HTTP error occurs while making a request."""
 
@@ -222,7 +224,7 @@ class HTTPError(HikariError):
     """The error message."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class HTTPResponseError(HTTPError):
     """Base exception for an erroneous HTTP response."""
 
@@ -266,7 +268,7 @@ def __str__(self) -> str:
         return f"{name_value}:{code_str} '{body[:200]}{'...' if chomped else ''}' for {self.url}"
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class ClientHTTPResponseError(HTTPResponseError):
     """Base exception for an erroneous HTTP response that is a client error.
 
@@ -275,7 +277,7 @@ class ClientHTTPResponseError(HTTPResponseError):
     """
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class BadRequestError(ClientHTTPResponseError):
     """Raised when you send an invalid request somehow."""
 
@@ -304,7 +306,7 @@ def __str__(self) -> str:
         return value
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class UnauthorizedError(ClientHTTPResponseError):
     """Raised when you are not authorized to access a specific resource."""
 
@@ -312,7 +314,7 @@ class UnauthorizedError(ClientHTTPResponseError):
     """The HTTP status code for the response."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class ForbiddenError(ClientHTTPResponseError):
     """Raised when you are not allowed to access a specific resource.
 
@@ -325,7 +327,7 @@ class ForbiddenError(ClientHTTPResponseError):
     """The HTTP status code for the response."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class NotFoundError(ClientHTTPResponseError):
     """Raised when something is not found."""
 
@@ -333,7 +335,7 @@ class NotFoundError(ClientHTTPResponseError):
     """The HTTP status code for the response."""
 
 
-@attr.define(auto_exc=True, kw_only=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, kw_only=True, repr=False, slots=False)
 class RateLimitedError(ClientHTTPResponseError):
     """Raised when a non-global rate limit that cannot be handled occurs.
 
@@ -367,7 +369,7 @@ def _(self) -> str:
         return f"You are being rate-limited for {self.retry_after:,} seconds on route {self.route}. Please slow down!"
 
 
-@attr.define(auto_exc=True, kw_only=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, kw_only=True, repr=False, slots=False)
 class RateLimitTooLongError(HTTPError):
     """Internal error raised if the wait for a rate limit is too long.
 
@@ -428,7 +430,7 @@ def __str__(self) -> str:
         return self.message
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class InternalServerError(HTTPResponseError):
     """Base exception for an erroneous HTTP response that is a server error.
 
@@ -437,7 +439,7 @@ class InternalServerError(HTTPResponseError):
     """
 
 
-@attr.define(auto_exc=True, repr=False, init=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, init=False, slots=False)
 class MissingIntentWarning(HikariWarning):
     """Warning raised when subscribing to an event that cannot be fired.
 
@@ -445,7 +447,7 @@ class MissingIntentWarning(HikariWarning):
     """
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class BulkDeleteError(HikariError):
     """Exception raised when a bulk delete fails midway through a call.
 
@@ -478,12 +480,12 @@ def __str__(self) -> str:
         return f"Error encountered when bulk deleting messages ({deleted}/{total} messages deleted)"
 
 
-@attr.define(auto_exc=True, repr=False, init=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, init=False, slots=False)
 class VoiceError(HikariError):
     """Error raised when a problem occurs with the voice subsystem."""
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+@attr.define(auto_exc=True, repr=False, slots=False)
 class MissingIntentError(HikariError, ValueError):
     """Error raised when you try to perform an action without an intent.
 
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 47a27f2c..b4728428 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -508,7 +508,7 @@ def mention(self) -> str:
     def communication_disabled_until(self) -> typing.Optional[datetime.datetime]:
         """Return when the timeout for this member ends.
 
-        Unlike `raw_communictation_disabled_until`, this will always be
+        Unlike `raw_communication_disabled_until`, this will always be
         `builtins.None` if the member is not currently timed out.
 
         !!! note
@@ -522,6 +522,19 @@ def communication_disabled_until(self) -> typing.Optional[datetime.datetime]:
             return self.raw_communication_disabled_until
         return None
 
+    def get_guild(self) -> typing.Optional[Guild]:
+        """Return the guild associated with this member.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.Guild]
+            `builtins.None` if the the guild is not cached.
+        """
+        if not isinstance(self.user.app, traits.CacheAware):
+            return None
+
+        return self.user.app.cache.get_guild(self.guild_id)
+
     def get_presence(self) -> typing.Optional[presences_.MemberPresence]:
         """Get the cached presence for this member, if known.
 
@@ -3334,7 +3347,7 @@ async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildT
         Returns
         -------
         typing.Optional[hikari.channels.GuildTextChannel]
-            The channel where discord sents relevant updates to moderators and admins.
+            The channel where discord sends relevant updates to moderators and admins.
 
         Raises
         ------
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index 67b1b3f7..34bb0536 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -572,7 +572,7 @@ async def on_everyone_mentioned(event):
         Listen: `hikari.impl.bot.GatewayBot.listen`
         Stream: `hikari.impl.bot.GatewayBot.stream`
         Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
-        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Unsubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
         Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
         """
         return self._event_manager.dispatch(event)
@@ -643,13 +643,18 @@ def listen(
         Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
         Stream: `hikari.impl.bot.GatewayBot.stream`
         Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
-        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Unsubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
         Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
         """
         return self._event_manager.listen(event_type)
 
     @staticmethod
-    def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: bool) -> None:
+    def print_banner(
+        banner: typing.Optional[str],
+        allow_color: bool,
+        force_color: bool,
+        extra_args: typing.Optional[typing.Dict[str, str]] = None,
+    ) -> None:
         """Print the banner.
 
         This allows library vendors to override this behaviour, or choose to
@@ -674,8 +679,16 @@ def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: b
 
         !!! note
             `force_color` will always take precedence over `allow_color`.
+        extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]]
+            If provided, extra $-substitutions to use when printing the banner.
+            Default substitutions can not be overwritten.
+
+        Raises
+        ------
+        builtins.ValueError
+            If `extra_args` contains a default $-substitution.
         """
-        ux.print_banner(banner, allow_color, force_color)
+        ux.print_banner(banner, allow_color, force_color, extra_args=extra_args)
 
     def run(
         self,
@@ -722,7 +735,7 @@ def run(
         close_loop : builtins.bool
             Defaults to `builtins.True`. If `builtins.True`, then once the bot
             enters a state where all components have shut down permanently
-            during application shutdown, then all asyngens and background tasks
+            during application shutdown, then all asyncgens and background tasks
             will be destroyed, and the event loop will be shut down.
 
             This will wait until all `hikari`-owned `aiohttp` connectors have
@@ -1105,33 +1118,33 @@ def stream(
         -------
         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
+            with `with stream:` or `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
+            If you use `stream.open()` to start the stream then you must
+            also close it with `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:
+        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()`
+        or using `open()` and `close()`
 
         ```py
         stream = bot.stream(events.ReactionAddEvent, timeout=30).filter(("message_id", message.id))
-        await stream.open()
+        stream.open()
 
         async for user_id in stream.map("user_id").limit(50)
             ...
 
-        await stream.close()
+        stream.close()
         ```
 
         See Also
@@ -1139,7 +1152,7 @@ def stream(
         Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
         Listen: `hikari.impl.bot.GatewayBot.listen`
         Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
-        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Unsubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
         Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
         """
         self._check_if_alive()
@@ -1178,7 +1191,7 @@ async def on_message(event):
         Dispatch: `hikari.impl.bot.GatewayBot.dispatch`
         Listen: `hikari.impl.bot.GatewayBot.listen`
         Stream: `hikari.impl.bot.GatewayBot.stream`
-        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Unsubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
         Wait_for: `hikari.impl.bot.GatewayBot.wait_for`
         """
         self._event_manager.subscribe(event_type, callback)
@@ -1266,7 +1279,7 @@ async def wait_for(
         Listen: `hikari.impl.bot.GatewayBot.listen`
         Stream: `hikari.impl.bot.GatewayBot.stream`
         Subscribe: `hikari.impl.bot.GatewayBot.subscribe`
-        Unubscribe: `hikari.impl.bot.GatewayBot.unsubscribe`
+        Unsubscribe: `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_manager_base.py b/hikari/impl/event_manager_base.py
index ed12ebe3..c70d04a7 100644
--- a/hikari/impl/event_manager_base.py
+++ b/hikari/impl/event_manager_base.py
@@ -135,7 +135,7 @@ async def __aenter__(self: _EventStreamT) -> _EventStreamT:
             # This is sync only.
             warnings.warn(
                 "Using EventStream as an async context manager has been deprecated since 2.0.0.dev104. "
-                "Please use it as a sycnrhonous context manager (e.g. with bot.stream(...)) instead.",
+                "Please use it as a synchronous context manager (e.g. with bot.stream(...)) instead.",
                 category=DeprecationWarning,
                 stacklevel=2,
             )
diff --git a/hikari/impl/interaction_server.py b/hikari/impl/interaction_server.py
index e3156a39..77d1f7dd 100644
--- a/hikari/impl/interaction_server.py
+++ b/hikari/impl/interaction_server.py
@@ -235,7 +235,7 @@ async def aiohttp_hook(self, request: aiohttp.web.Request) -> aiohttp.web.Respon
 
         Returns
         -------
-        aiohtttp.web.Response
+        aiohttp.web.Response
             The aiohttp response.
         """
         if request.content_type.lower() != _JSON_CONTENT_TYPE:
diff --git a/hikari/impl/rate_limits.py b/hikari/impl/rate_limits.py
index 54f41847..97551e6c 100644
--- a/hikari/impl/rate_limits.py
+++ b/hikari/impl/rate_limits.py
@@ -250,7 +250,7 @@ class WindowedBurstRateLimiter(BurstRateLimiter):
     Rate limiter for rate limits that last fixed periods of time with a
     fixed number of times it can be used in that time frame.
 
-    To use this, you should call WindowedBurstRateLimiter.aquire` and await the
+    To use this, you should call WindowedBurstRateLimiter.acquire` and await the
     result immediately before performing your rate-limited task.
 
     If the rate limit has been hit, acquiring time will return an incomplete
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index e24fe86b..9f3c92e8 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -490,7 +490,8 @@ def still_alive(self) -> _LiveAttributes:
         return self
 
 
-@attr.define(auto_exc=True, repr=False, weakref_slot=False)
+# The standard exceptions are all unsloted so slotting here would be a waste of time.
+@attr.define(auto_exc=True, repr=False, slots=False)
 class _RetryRequest(RuntimeError):
     ...
 
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index a531b195..4f83bff3 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -316,7 +316,12 @@ def executor(self) -> typing.Optional[concurrent.futures.Executor]:
         return self._executor
 
     @staticmethod
-    def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: bool) -> None:
+    def print_banner(
+        banner: typing.Optional[str],
+        allow_color: bool,
+        force_color: bool,
+        extra_args: typing.Optional[typing.Dict[str, str]] = None,
+    ) -> None:
         """Print the banner.
 
         This allows library vendors to override this behaviour, or choose to
@@ -341,8 +346,16 @@ def print_banner(banner: typing.Optional[str], allow_color: bool, force_color: b
 
         !!! note
             `force_color` will always take precedence over `allow_color`.
+        extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]]
+            If provided, extra $-substitutions to use when printing the banner.
+            Default substitutions can not be overwritten.
+
+        Raises
+        ------
+        builtins.ValueError
+            If `extra_args` contains a default $-substitution.
         """
-        ux.print_banner(banner, allow_color, force_color)
+        ux.print_banner(banner, allow_color, force_color, extra_args=extra_args)
 
     async def close(self) -> None:
         if not self._close_event:
@@ -402,7 +415,7 @@ def run(
         close_loop : builtins.bool
             Defaults to `builtins.True`. If `builtins.True`, then once the bot
             enters a state where all components have shut down permanently
-            during application shutdown, then all asyngens and background tasks
+            during application shutdown, then all asyncgens and background tasks
             will be destroyed, and the event loop will be shut down.
 
             This will wait until all `hikari`-owned `aiohttp` connectors have
diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py
index 036ca75e..2fc4a64c 100644
--- a/hikari/impl/shard.py
+++ b/hikari/impl/shard.py
@@ -112,6 +112,7 @@ def filterer(entry: str) -> str:
     _ZlibDecompressor = zlib._Decompress
 
 
+# aiohttp.ClientWebSocketResponse isn't slotted
 @typing.final
 class _GatewayTransport(aiohttp.ClientWebSocketResponse):
     """Internal component to handle lower-level communication logic.
@@ -123,8 +124,6 @@ class _GatewayTransport(aiohttp.ClientWebSocketResponse):
     Payload logging is also performed here.
     """
 
-    __slots__: typing.Sequence[str] = ("zlib", "logger", "log_filterer", "sent_close")
-
     def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
         super().__init__(*args, **kwargs)
         self.zlib: _ZlibDecompressor = zlib.decompressobj()
@@ -823,7 +822,7 @@ async def _run(self) -> None:
                         return
 
                 except errors.GatewayConnectionError as ex:
-                    self._logger.error(
+                    self._logger.warning(
                         "failed to communicate with server, reason was: %r. Will retry shortly", ex.reason
                     )
 
diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py
index bb63e863..9d6fdc4f 100644
--- a/hikari/impl/special_endpoints.py
+++ b/hikari/impl/special_endpoints.py
@@ -80,7 +80,6 @@
     _T = typing.TypeVar("_T")
     _CommandBuilderT = typing.TypeVar("_CommandBuilderT", bound="CommandBuilder")
     _SlashCommandBuilderT = typing.TypeVar("_SlashCommandBuilderT", bound="SlashCommandBuilder")
-    _ContextMenuCommandBuilderT = typing.TypeVar("_ContextMenuCommandBuilderT", bound="ContextMenuCommandBuilder")
     _InteractionMessageBuilderT = typing.TypeVar("_InteractionMessageBuilderT", bound="InteractionMessageBuilder")
     _InteractionDeferredBuilderT = typing.TypeVar("_InteractionDeferredBuilderT", bound="InteractionDeferredBuilder")
     _InteractionAutocompleteBuilderT = typing.TypeVar(
diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py
index 249d6270..e6550e13 100644
--- a/hikari/internal/data_binding.py
+++ b/hikari/internal/data_binding.py
@@ -49,7 +49,7 @@
     import concurrent
     import contextlib
 
-    T = typing.TypeVar("T", covariant=True)
+    T_co = typing.TypeVar("T_co", covariant=True)
 
 Headers = typing.Mapping[str, str]
 """Type hint for HTTP headers."""
@@ -170,10 +170,10 @@ def put(
     def put(
         self,
         key: str,
-        value: undefined.UndefinedOr[T],
+        value: undefined.UndefinedOr[T_co],
         /,
         *,
-        conversion: typing.Callable[[T], Stringish],
+        conversion: typing.Callable[[T_co], Stringish],
     ) -> None:
         ...
 
@@ -255,10 +255,10 @@ def put(self, key: str, value: undefined.UndefinedNoneOr[JSONish], /) -> None:
     def put(
         self,
         key: str,
-        value: undefined.UndefinedNoneOr[T],
+        value: undefined.UndefinedNoneOr[T_co],
         /,
         *,
-        conversion: typing.Callable[[T], JSONish],
+        conversion: typing.Callable[[T_co], JSONish],
     ) -> None:
         ...
 
@@ -309,10 +309,10 @@ def put_array(
     def put_array(
         self,
         key: str,
-        values: undefined.UndefinedOr[typing.Iterable[T]],
+        values: undefined.UndefinedOr[typing.Iterable[T_co]],
         /,
         *,
-        conversion: typing.Callable[[T], JSONish],
+        conversion: typing.Callable[[T_co], JSONish],
     ) -> None:
         ...
 
@@ -334,7 +334,7 @@ def put_array(
         ----------
         key : builtins.str
             The key to give the element.
-        values : hikari.undefined.UndefinedOr[typing.Iterable[T]]
+        values : hikari.undefined.UndefinedOr[typing.Iterable[T_co]]
             The JSON types to put. This may be an iterable of non-JSON types if
             a conversion is also specified. This may alternatively be undefined.
             In the latter case, nothing is performed.
diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py
index 3fefaae9..25746880 100644
--- a/hikari/internal/enums.py
+++ b/hikari/internal/enums.py
@@ -696,7 +696,7 @@ def split(self: _T) -> typing.Sequence[_T]:
 
         Any unrecognised bits will be omitted for brevity.
 
-        The result will be a name-sorted `typing.Sequence` of each membe
+        The result will be a name-sorted `typing.Sequence` of each member
         """
         return sorted(
             (member for member in self.__class__._powers_of_2_to_member_map_.values() if member.value & self),
diff --git a/hikari/internal/time.py b/hikari/internal/time.py
index 26740f9b..813e479b 100644
--- a/hikari/internal/time.py
+++ b/hikari/internal/time.py
@@ -201,12 +201,12 @@ def utc_datetime() -> datetime.datetime:
 
 # time.monotonic_ns is no slower than time.monotonic, but is more accurate.
 # Also, fun fact that monotonic_ns appears to be 1µs faster on average than
-# monotonic on AARM64 architectures, but on x86, monotonic is around 1ns faster
+# monotonic on ARM64 architectures, but on x86, monotonic is around 1ns faster
 # than monotonic_ns. Just thought that was kind of interesting to note down.
 # (RPi 3B versus i7 6700)
 
-# time.perf_counter and time.perf_counter_ns dont have proper typehints, causing
-# pdoc to not be able to recognice them. This is just a little hack around that.
+# time.perf_counter and time.perf_counter_ns don't have proper typehints, causing
+# pdoc to not be able to recognise them. This is just a little hack around that.
 def monotonic() -> float:
     """Performance counter for benchmarking."""  # noqa: D401 - Imperative mood
     return time.perf_counter()
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index e3a30690..b3a9f695 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -148,7 +148,12 @@ def init_logging(
 """Set of env variables which always indicate that ANSI flags should be included."""
 
 
-def print_banner(package: typing.Optional[str], allow_color: bool, force_color: bool) -> None:
+def print_banner(
+    package: typing.Optional[str],
+    allow_color: bool,
+    force_color: bool,
+    extra_args: typing.Optional[typing.Dict[str, str]] = None,
+) -> None:
     """Print a banner of choice to `sys.stdout`.
 
     Inspired by Spring Boot, we display an ASCII logo on startup. This is styled
@@ -172,6 +177,14 @@ def print_banner(package: typing.Optional[str], allow_color: bool, force_color:
         If `builtins.True`, return `builtins.True` always, otherwise only
         return `builtins.True` if the device supports colour output and the
         `allow_color` flag is not `builtins.False`.
+    extra_args : typing.Optional[typing.Dict[builtins.str, builtins.str]]
+        If provided, extra $-substitutions to use when printing the banner.
+        Default substitutions can not be overwritten.
+
+    Raises
+    ------
+    builtins.ValueError
+        If `extra_args` contains a default $-substitution.
     """
     if package is None:
         return
@@ -198,6 +211,12 @@ def print_banner(package: typing.Optional[str], allow_color: bool, force_color:
         "system_description": " ".join(filtered_system_bits),
     }
 
+    if extra_args:
+        for key in extra_args:
+            if key in args:
+                raise ValueError(f"Cannot overwrite $-substitution `{key}`. Please use a different key.")
+        args.update(extra_args)
+
     if supports_color(allow_color, force_color):
         args.update(colorlog.escape_codes.escape_codes)
     else:
diff --git a/hikari/presences.py b/hikari/presences.py
index 8c03e74c..7a6248f4 100644
--- a/hikari/presences.py
+++ b/hikari/presences.py
@@ -170,7 +170,7 @@ def large_image_url(self) -> typing.Optional[files.URL]:
 
         !!! note
             This will be `builtins.None` if no large image asset exists or if the
-            asset's dymamic URL (indicated by a `{name}:` prefix) is not known.
+            asset's dynamic URL (indicated by a `{name}:` prefix) is not known.
         """
         try:
             return self.make_large_image_url()
@@ -215,7 +215,7 @@ def small_image_url(self) -> typing.Optional[files.URL]:
 
         !!! note
             This will be `builtins.None` if no large image asset exists or if the
-            asset's dymamic URL (indicated by a `{name}:` prefix) is not known.
+            asset's dynamic URL (indicated by a `{name}:` prefix) is not known.
         """
         try:
             return self.make_small_image_url()
@@ -295,7 +295,7 @@ class ActivityFlag(enums.Flag):
     """Party privacy: friends only."""
 
     PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7
-    """Party prvacy: voice channel only."""
+    """Party privacy: voice channel only."""
 
     EMBEDDED = 1 << 8
     """An activity that's embedded into a voice channel."""
diff --git a/hikari/snowflakes.py b/hikari/snowflakes.py
index 501d63a0..6e97be91 100644
--- a/hikari/snowflakes.py
+++ b/hikari/snowflakes.py
@@ -218,7 +218,7 @@ def calculate_shard_id(
 
 The valid types for this type hint are:
 
-- `buitlins.int`
+- `builtins.int`
 - `Snowflake`
 """
 
@@ -235,7 +235,7 @@ def calculate_shard_id(
 
 The valid types for this type hint are:
 
-- `buitlins.int`
+- `builtins.int`
 - `Snowflake`
 - `datetime.datetime`
 """
diff --git a/pipelines/nox.py b/pipelines/nox.py
index 9cbef36a..f55ddc8e 100644
--- a/pipelines/nox.py
+++ b/pipelines/nox.py
@@ -32,7 +32,7 @@
 from pipelines import config
 
 # Default sessions should be defined here
-_options.sessions = ["reformat-code", "pytest", "flake8", "mypy", "verify-types"]
+_options.sessions = ["reformat-code", "pytest", "flake8", "slotscheck", "mypy", "verify-types"]
 
 _NoxCallbackSig = typing.Callable[[Session], None]
 
diff --git a/pipelines/pyright.nox.py b/pipelines/pyright.nox.py
index e91abed1..ad27730a 100644
--- a/pipelines/pyright.nox.py
+++ b/pipelines/pyright.nox.py
@@ -33,4 +33,4 @@ def verify_types(session: nox.Session) -> None:
     # session.env["PYRIGHT_PYTHON_GLOBAL_NODE"] = "off"
     session.env["PYRIGHT_PYTHON_FORCE_VERSION"] = config.PYRIGHT_VERSION
     session.run("python", "-m", "pyright", "--version")
-    session.run("python", "-m", "pyright", "--verifytypes", "hikari", "--ignoreexternal")
+    session.run("python", "-m", "pyright", "--verifytypes", config.MAIN_PACKAGE, "--ignoreexternal")
diff --git a/pipelines/slotscheck.nox.py b/pipelines/slotscheck.nox.py
new file mode 100644
index 00000000..67531fcd
--- /dev/null
+++ b/pipelines/slotscheck.nox.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Nekokatt
+# Copyright (c) 2021-present davfsa
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Check for common slotting mistakes."""
+
+from pipelines import config
+from pipelines import nox
+
+
+@nox.session()
+def slotscheck(session: nox.Session) -> None:
+    """Check for common slotting mistakes."""
+    session.install(".", "-r", "dev-requirements.txt")
+    session.run("slotscheck", "-m", config.MAIN_PACKAGE)
diff --git a/pyproject.toml b/pyproject.toml
index c421d77e..19a3e06d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -132,3 +132,14 @@ warn_unused_ignores = true
 [[tool.mypy.overrides]]
 module = 'rillrate.*'
 ignore_missing_imports = true
+
+[tool.slotscheck]
+strict-imports = true
+require-superclass = true
+require-subclass = true
+exclude-classes = """
+(
+    ^hikari\\.internal\\.enums:(Enum|Flag|_EnumMeta|_FlagMeta)$
+    |^hikari\\.files:MultiprocessingFileReader$
+)
+"""


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index 8d1cc85e..5e05c0c7 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -528,9 +528,9 @@ def test_listen(self, bot, event_manager):
 
     def test_print_banner(self, bot):
         with mock.patch.object(ux, "print_banner") as print_banner:
-            bot.print_banner("testing", False, True)
+            bot.print_banner("testing", False, True, extra_args={"test_key": "test_value"})
 
-        print_banner.assert_called_once_with("testing", False, True)
+        print_banner.assert_called_once_with("testing", False, True, extra_args={"test_key": "test_value"})
 
     def test_run_when_already_running(self, bot):
         bot._is_alive = True
diff --git a/tests/hikari/impl/test_buckets.py b/tests/hikari/impl/test_buckets.py
index a6a65965..391945ff 100644
--- a/tests/hikari/impl/test_buckets.py
+++ b/tests/hikari/impl/test_buckets.py
@@ -272,7 +272,7 @@ async def test_acquire_route_when_not_in_routes_to_real_hashes_makes_new_bucket_
     async def test_acquire_route_when_not_in_routes_to_real_hashes_doesnt_cache_route(self):
         with buckets.RESTBucketManager(max_rate_limit=float("inf")) as mgr:
             route = mock.Mock()
-            route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+            route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
 
             mgr.acquire(route)
 
@@ -296,7 +296,7 @@ async def test_acquire_route_returns_context_manager(self):
 
             bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
             with mock.patch.object(buckets, "RESTBucket", return_value=bucket):
-                route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+                route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
 
                 assert mgr.acquire(route) is bucket
 
@@ -315,7 +315,7 @@ async def test_acquire_unknown_route_returns_context_manager_for_new_bucket(self
     async def test_update_rate_limits_if_wrong_bucket_hash_reroutes_route(self):
         with buckets.RESTBucketManager(max_rate_limit=float("inf")) as mgr:
             route = mock.Mock()
-            route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+            route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
             mgr.routes_to_hashes[route.route] = "123"
 
             with mock.patch.object(hikari_date, "monotonic", return_value=27):
@@ -330,7 +330,7 @@ async def test_update_rate_limits_if_wrong_bucket_hash_reroutes_route(self):
     async def test_update_rate_limits_if_unknown_bucket_hash_reroutes_route(self):
         with buckets.RESTBucketManager(max_rate_limit=float("inf")) as mgr:
             route = mock.Mock()
-            route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+            route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
             mgr.routes_to_hashes[route.route] = "123"
             bucket = mock.Mock()
             mgr.real_hashes_to_buckets["UNKNOWN;bobs"] = bucket
@@ -349,7 +349,7 @@ async def test_update_rate_limits_if_unknown_bucket_hash_reroutes_route(self):
     async def test_update_rate_limits_if_right_bucket_hash_does_nothing_to_hash(self):
         with buckets.RESTBucketManager(max_rate_limit=float("inf")) as mgr:
             route = mock.Mock()
-            route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+            route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
             mgr.routes_to_hashes[route.route] = "123"
             bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
             mgr.real_hashes_to_buckets["123;bobs"] = bucket
@@ -365,7 +365,7 @@ async def test_update_rate_limits_if_right_bucket_hash_does_nothing_to_hash(self
     async def test_update_rate_limits_updates_params(self):
         with buckets.RESTBucketManager(max_rate_limit=float("inf")) as mgr:
             route = mock.Mock()
-            route.create_real_bucket_hash = mock.Mock(wraps=lambda intial_hash: intial_hash + ";bobs")
+            route.create_real_bucket_hash = mock.Mock(wraps=lambda initial_hash: initial_hash + ";bobs")
             mgr.routes_to_hashes[route.route] = "123"
             bucket = mock.Mock(reset_at=time.perf_counter() + 999999999999999999999999999)
             mgr.real_hashes_to_buckets["123;bobs"] = bucket
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index b825389f..da8e0fb5 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -232,9 +232,9 @@ def test_is_alive_property(self, mock_rest_bot, close_event, expected):
 
     def test_print_banner(self, mock_rest_bot):
         with mock.patch.object(ux, "print_banner") as print_banner:
-            mock_rest_bot.print_banner("okokok", True, False)
+            mock_rest_bot.print_banner("okokok", True, False, {"test_key": "test_value"})
 
-            print_banner.assert_called_once_with("okokok", True, False)
+            print_banner.assert_called_once_with("okokok", True, False, extra_args={"test_key": "test_value"})
 
     @pytest.mark.asyncio()
     async def test_close(self, mock_rest_bot, mock_interaction_server, mock_rest_client):
diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py
index f3ff235a..401bebeb 100644
--- a/tests/hikari/internal/test_ux.py
+++ b/tests/hikari/internal/test_ux.py
@@ -250,6 +250,61 @@ def test_when_doesnt_supports_color(self, mock_args):
         abspath.assert_called_once_with(dirname())
         supports_color.assert_called_once_with(True, False)
 
+    def test_use_extra_args(self, mock_args):
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(colorlog.escape_codes, "escape_codes", new={}))
+        read_text = stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        template = stack.enter_context(mock.patch.object(string, "Template"))
+        write = stack.enter_context(mock.patch.object(sys.stdout, "write"))
+        stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
+
+        extra_args = {
+            "extra_argument_1": "one",
+            "extra_argument_2": "two",
+        }
+
+        with stack:
+            ux.print_banner("hikari", True, False, extra_args=extra_args)
+
+        args = {
+            # Hikari stuff.
+            "hikari_version": "2.2.2",
+            "hikari_git_sha1": "12345678",
+            "hikari_copyright": "© 2020 Nekokatt",
+            "hikari_license": "MIT",
+            "hikari_install_location": "some path",
+            "hikari_documentation_url": "https://nekokatt.github.io/hikari/docs",
+            "hikari_discord_invite": "https://discord.gg/Jx4cNGG",
+            "hikari_source_url": "https://nekokatt.github.io/hikari",
+            "python_implementation": "CPython",
+            "python_version": "4.0.0",
+            "system_description": "Machine Potato 1.0.0",
+        }
+
+        args.update(extra_args)
+
+        template.assert_called_once_with(read_text())
+        template().safe_substitute.assert_called_once_with(args)
+        write.assert_called_once_with(template().safe_substitute())
+
+    def test_overwrite_args_raises_error(self, mock_args):
+        stack = contextlib.ExitStack()
+        stack.enter_context(mock.patch.object(colorlog.escape_codes, "escape_codes", new={}))
+        stack.enter_context(mock.patch.object(importlib.resources, "read_text"))
+        stack.enter_context(mock.patch.object(string, "Template"))
+        stack.enter_context(mock.patch.object(sys.stdout, "write"))
+        stack.enter_context(mock.patch.object(os.path, "abspath", return_value="some path"))
+
+        extra_args = {
+            "hikari_version": "overwrite",
+        }
+
+        with stack:
+            with pytest.raises(
+                ValueError, match=r"Cannot overwrite \$-substitution `hikari_version`. Please use a different key."
+            ):
+                ux.print_banner("hikari", True, False, extra_args=extra_args)
+
 
 class TestSupportsColor:
     def test_when_not_allow_color(self):
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index ec90efdd..3caf43cc 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -296,7 +296,7 @@ def test_communication_disabled_until_when_raw_communication_disabled_until_is_N
         with mock.patch.object(time, "utc_datetime", return_value=datetime.datetime(2021, 10, 18)):
             assert model.communication_disabled_until() is None
 
-    def test_comminucation_disabled_until_when_raw_communication_disabled_until_is_in_the_past(self, model):
+    def test_communication_disabled_until_when_raw_communication_disabled_until_is_in_the_past(self, model):
         model.raw_communication_disabled_until = datetime.datetime(2021, 10, 18)
 
         with mock.patch.object(time, "utc_datetime", return_value=datetime.datetime(2021, 11, 22)):
@@ -469,6 +469,26 @@ def test_mention_property_when_no_nickname(self, model, mock_user):
         model.nickname = None
         assert model.mention == mock_user.mention
 
+    def test_get_guild(self, model):
+        guild = mock.Mock(id=456)
+        model.user.app.cache.get_guild.side_effect = [guild]
+
+        assert model.get_guild() == guild
+
+        model.user.app.cache.get_guild.assert_has_calls([mock.call(456)])
+
+    def test_get_guild_when_guild_not_in_cache(self, model):
+        model.user.app.cache.get_guild.side_effect = [None]
+
+        assert model.get_guild() is None
+
+        model.user.app.cache.get_guild.assert_has_calls([mock.call(456)])
+
+    def test_get_guild_when_no_cache_trait(self, model):
+        model.user.app = object()
+
+        assert model.get_guild() is None
+
     def test_get_roles(self, model):
         role1 = mock.Mock(id=321, position=2)
         role2 = mock.Mock(id=654, position=1)