Repository: hikari-py/hikari
Pull request #841 (2021-10-10 20:47:47)

Fix bucket lock not being released on errors while being acquired
### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.


FasterSpeeding: Has this been tested in the real world?
davfsa: > Has this been tested in the real world?

I did back when I opened it. A lot has changed since then so will probably have to revisit it

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6faf5e6..130ddf50 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]
@@ -17,10 +17,10 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -57,10 +57,10 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -87,10 +87,10 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -107,17 +107,17 @@ 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
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.2
+        uses: actions/setup-python@v3
         with:
           python-version: 3.8
 
@@ -131,15 +131,15 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - 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/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index f20fad6e..5aece327 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -15,7 +15,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2.4.0
+      uses: actions/checkout@v3
 
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v1
diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml
index a23eb0f8..5f40c08c 100644
--- a/.github/workflows/fragments-check.yml
+++ b/.github/workflows/fragments-check.yml
@@ -13,14 +13,14 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
         with:
           # `towncrier check` runs `git diff --name-only origin/main...`, which
           # needs a non-shallow clone.
           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..91b1dfb0 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -8,15 +8,15 @@ on:
         required: true
 
 jobs:
-  prepare-relese:
+  prepare-release:
     runs-on: ubuntu-latest
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
 
       - 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..73f30e0a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,12 +10,12 @@ jobs:
 
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v2.4.0
+        uses: actions/checkout@v3
         with:
           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/1014.bugfix.md b/changes/1014.bugfix.md
new file mode 100644
index 00000000..e516080c
--- /dev/null
+++ b/changes/1014.bugfix.md
@@ -0,0 +1,1 @@
+Attempt to reconnect on a gateway `TimeoutError`.
diff --git a/changes/1015.feature.md b/changes/1015.feature.md
new file mode 100644
index 00000000..86e6fb83
--- /dev/null
+++ b/changes/1015.feature.md
@@ -0,0 +1,1 @@
+Added attachment command option type support.
diff --git a/changes/1021.feature.md b/changes/1021.feature.md
new file mode 100644
index 00000000..21ca311c
--- /dev/null
+++ b/changes/1021.feature.md
@@ -0,0 +1,1 @@
+Add MESSAGE_CONTENT intent.
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/changes/841.bugfix.md b/changes/841.bugfix.md
new file mode 100644
index 00000000..3bc02402
--- /dev/null
+++ b/changes/841.bugfix.md
@@ -0,0 +1,1 @@
+Fix bucket lock not being released on errors while being acquired, which locked the bucket infinitely
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 69ac9fbf..b7781f3d 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -6,13 +6,13 @@
 mock==4.0.3
 
 # Py.test stuff.
-pytest==7.0.0
-pytest-asyncio==0.18.0
+pytest==7.0.1
+pytest-asyncio==0.18.2
 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.226
 
 #######################
 # 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/LICENSE b/examples/LICENSE
new file mode 100644
index 00000000..670154e3
--- /dev/null
+++ b/examples/LICENSE
@@ -0,0 +1,116 @@
+CC0 1.0 Universal
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display, communicate,
+  and translate a Work;
+
+  ii. moral rights retained by the original author(s) and/or performer(s);
+
+  iii. publicity and privacy rights pertaining to a person's image or likeness
+  depicted in a Work;
+
+  iv. rights protecting against unfair competition in regards to a Work,
+  subject to the limitations in paragraph 4(a), below;
+
+  v. rights protecting the extraction, dissemination, use and reuse of data in
+  a Work;
+
+  vi. database rights (such as those arising under Directive 96/9/EC of the
+  European Parliament and of the Council of 11 March 1996 on the legal
+  protection of databases, and under any national implementation thereof,
+  including any amended or successor version of such directive); and
+
+  vii. other similar, equivalent or corresponding rights throughout the world
+  based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+  a. No trademark or patent rights held by Affirmer are waived, abandoned,
+  surrendered, licensed or otherwise affected by this document.
+
+  b. Affirmer offers the Work as-is and makes no representations or warranties
+  of any kind concerning the Work, express, implied, statutory or otherwise,
+  including without limitation warranties of title, merchantability, fitness
+  for a particular purpose, non infringement, or the absence of latent or
+  other defects, accuracy, or the present or absence of errors, whether or not
+  discoverable, all to the greatest extent permissible under applicable law.
+
+  c. Affirmer disclaims responsibility for clearing rights of other persons
+  that may apply to the Work or any use thereof, including without limitation
+  any person's Copyright and Related Rights in the Work. Further, Affirmer
+  disclaims responsibility for obtaining any necessary consents, permissions
+  or other rights required for any use of the Work.
+
+  d. Affirmer understands and acknowledges that Creative Commons is not a
+  party to this document and has no duty or obligation with respect to this
+  CC0 or use of the Work.
+
+For more information, please see
+<http://creativecommons.org/publicdomain/zero/1.0/>
diff --git a/examples/__init__.py b/examples/__init__.py
index df8d1a43..10e6d281 100644
--- a/examples/__init__.py
+++ b/examples/__init__.py
@@ -1,22 +1,10 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2020 Nekokatt
-# Copyright (c) 2021-present davfsa
+# Hikari Examples - A collection of examples for Hikari.
 #
-# 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:
+# 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.
 #
-# 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.
+# 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/>.
 """Allows mypy to run here."""
diff --git a/examples/hello_world.py b/examples/hello_world.py
new file mode 100644
index 00000000..3f3f5f19
--- /dev/null
+++ b/examples/hello_world.py
@@ -0,0 +1,30 @@
+# -*- 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 only a `!ping` command."""
+
+import os
+
+import hikari
+
+bot = hikari.GatewayBot(token=os.environ["BOT_TOKEN"])
+
+
+@bot.listen()
+async def on_message(event: hikari.MessageCreateEvent) -> None:
+    """Listen for messages being created."""
+    if not event.is_human:
+        # Do not respond to bots or webhooks!
+        return
+
+    if event.content == "!ping":
+        await event.message.respond(f"Pong! {bot.heartbeat_latency * 1_000:.0f}ms")
+
+
+bot.run()
diff --git a/examples/hello_world/hello_world.bat b/examples/hello_world/hello_world.bat
deleted file mode 100644
index bb7b1d20..00000000
--- a/examples/hello_world/hello_world.bat
+++ /dev/null
@@ -1,51 +0,0 @@
-:: 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.
-
-:: Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-@echo off
-
-if "%~1"=="" (
-    echo Please pass a bot token as the first parameter to this script!
-    :: This is used so that if the script is executed by double clicking it, it gives some feedback before exiting
-    pause
-    exit /B 1
-)
-
-setlocal
-:: This will only loop once
-for %%I in (%0) do (
-    set SCRIPT_NAME=%%~nI
-    set SCRIPT_PATH=%%~dpI
-)
-
-:: PYTHONPATH might not be set
-if defined PYTHONPATH (
-    set PYTHONPATH=%PYTHONPATH%;%SCRIPT_PATH%..\..
-) else (
-    set PYTHONPATH=%SCRIPT_PATH%..\..
-)
-
-set BOT_TOKEN=%1
-
-py "%SCRIPT_PATH%%SCRIPT_NAME%.py"
-
-endlocal
diff --git a/examples/hello_world/hello_world.py b/examples/hello_world/hello_world.py
deleted file mode 100644
index c6e93298..00000000
--- a/examples/hello_world/hello_world.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- 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.
-"""A simple bot with only a `!ping` command."""
-
-import os
-
-import hikari
-
-bot = hikari.GatewayBot(token=os.environ["BOT_TOKEN"])
-
-
-@bot.listen()
-async def on_message(event: hikari.MessageCreateEvent) -> None:
-    """Listen for messages being created."""
-    if not event.is_human:
-        # Do not respond to bots or webhooks!
-        return
-
-    if event.content == "!ping":
-        await event.message.respond(f"Pong! {bot.heartbeat_latency * 1_000:.0f}ms")
-
-
-bot.run()
diff --git a/examples/hello_world/hello_world.sh b/examples/hello_world/hello_world.sh
deleted file mode 100755
index 3b98362e..00000000
--- a/examples/hello_world/hello_world.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/sh -e
-# 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.
-
-# Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-if [ -z "${1}" ]; then
-    echo "Please pass a bot token as the first parameter to this script!"
-    exit 1
-fi
-
-SCRIPT=$(readlink -f "${0}")
-SCRIPT_PATH=$(dirname "${SCRIPT}")
-
-# PYTHONPATH might not be set
-if [ $PYTHONPATH ]; then
-    PYTHONPATH="${PYTHONPATH}:${SCRIPT_PATH}/../.."
-else
-    PYTHONPATH="${SCRIPT_PATH}/../.."
-fi
-
-export PYTHONPATH
-export BOT_TOKEN="${1}"
-
-python3 "${SCRIPT_PATH}/$(basename "${SCRIPT_PATH}").py"
diff --git a/examples/image_resources/image_resources.py b/examples/image_resources.py
similarity index 70%
rename from examples/image_resources/image_resources.py
rename to examples/image_resources.py
index 4cb333c3..95c1d0ef 100644
--- a/examples/image_resources/image_resources.py
+++ b/examples/image_resources.py
@@ -1,24 +1,12 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2020 Nekokatt
-# Copyright (c) 2021-present davfsa
+# Hikari Examples - A collection of examples for Hikari.
 #
-# 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:
+# 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.
 #
-# 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.
+# 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 a `!image` command that shows the image for various things.
 
 This demonstrates how many Hikari objects can act as attachments without any
diff --git a/examples/image_resources/__init__.py b/examples/image_resources/__init__.py
deleted file mode 100644
index 9c9fd302..00000000
--- a/examples/image_resources/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- 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.
-"""Allows mypy to detect the scripts here."""
diff --git a/examples/image_resources/image_resources.bat b/examples/image_resources/image_resources.bat
deleted file mode 100644
index bb7b1d20..00000000
--- a/examples/image_resources/image_resources.bat
+++ /dev/null
@@ -1,51 +0,0 @@
-:: 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.
-
-:: Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-@echo off
-
-if "%~1"=="" (
-    echo Please pass a bot token as the first parameter to this script!
-    :: This is used so that if the script is executed by double clicking it, it gives some feedback before exiting
-    pause
-    exit /B 1
-)
-
-setlocal
-:: This will only loop once
-for %%I in (%0) do (
-    set SCRIPT_NAME=%%~nI
-    set SCRIPT_PATH=%%~dpI
-)
-
-:: PYTHONPATH might not be set
-if defined PYTHONPATH (
-    set PYTHONPATH=%PYTHONPATH%;%SCRIPT_PATH%..\..
-) else (
-    set PYTHONPATH=%SCRIPT_PATH%..\..
-)
-
-set BOT_TOKEN=%1
-
-py "%SCRIPT_PATH%%SCRIPT_NAME%.py"
-
-endlocal
diff --git a/examples/image_resources/image_resources.sh b/examples/image_resources/image_resources.sh
deleted file mode 100755
index 3b98362e..00000000
--- a/examples/image_resources/image_resources.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/sh -e
-# 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.
-
-# Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-if [ -z "${1}" ]; then
-    echo "Please pass a bot token as the first parameter to this script!"
-    exit 1
-fi
-
-SCRIPT=$(readlink -f "${0}")
-SCRIPT_PATH=$(dirname "${SCRIPT}")
-
-# PYTHONPATH might not be set
-if [ $PYTHONPATH ]; then
-    PYTHONPATH="${PYTHONPATH}:${SCRIPT_PATH}/../.."
-else
-    PYTHONPATH="${SCRIPT_PATH}/../.."
-fi
-
-export PYTHONPATH
-export BOT_TOKEN="${1}"
-
-python3 "${SCRIPT_PATH}/$(basename "${SCRIPT_PATH}").py"
diff --git a/examples/simple_dashboard.py b/examples/simple_dashboard.py
new file mode 100644
index 00000000..88a6335e
--- /dev/null
+++ b/examples/simple_dashboard.py
@@ -0,0 +1,115 @@
+# -*- 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 to demonstrate how to use rillrate with hikari to make a web dashboard for the bot.
+
+Just visit `http://localhost:6361/ui/` to explore your dashboard!
+"""
+import logging
+import os
+
+import rillrate
+from rillrate import prime as rr_prime
+
+import hikari
+
+PREFIX = ","
+
+# Name used to group dashboards.
+# You could have multiple packages for different applications, such as a package for the bot
+# dashboards, and another package for a web server running alongside the bot.
+PACKAGE = "Rillrate Example"
+# Dashboards are a part inside of package, they can be used to group different types of
+# dashboards that you may want to use, like a dashboard for system status, another dashboard
+# for cache status, and another one to configure features or trigger actions on the bot.
+DASHBOARD = "Control Panel"
+# These are menus inside the dashboard, you can use them to group specific sets
+# of data inside the same dashboard.
+GROUP_CONFIG = "1 - Example"
+# All the 3 configurable namespaces are sorted alphabetically.
+
+
+# Class with all our dashboard logic
+class RillRateDashboard:
+    """Global data shared across the entire bot, used to store dashboard values."""
+
+    __slots__ = ("logger", "value", "selector", "slider")
+
+    def __init__(self) -> None:
+        self.logger = logging.getLogger("dashboard")
+        self.value = 0
+
+        # Install rillrate - Spins up the rillrate service in a separate thread, making it non-blocking :)
+        rillrate.install()
+
+        # Register the dashboard objects
+        dummy_values = [str(i) for i in range(0, 256 + 1, 32)]
+        self.selector = rr_prime.Selector(
+            f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Selector", label="Choose!", options=dummy_values
+        )
+        self.slider = rr_prime.Slider(
+            f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Slider", label="More fine grain control", min=0, max=256, step=2
+        )
+
+        # Add sync callbacks - This way we tell rillrate what functions to call when a sync event occurs
+        self.selector.sync_callback(self._selector_callback)
+        self.slider.sync_callback(self._slider_callback)
+
+    def _selector_callback(self, activity: rillrate.Activity, action: rillrate.Action) -> None:
+        self.logger.info("Selector activity: %s | action = %s", activity, action)
+
+        if action is not None:
+            value = int(action.value)
+            self.logger.info("Selected: %s", value)
+
+            # Update the slider too, so they show the same value.
+            self.slider.apply(value)
+
+            # Overwrite the current stored value on the global data with the new selected value.
+            self.value = value
+
+    def _slider_callback(self, activity: rillrate.Activity, action: rillrate.Action) -> None:
+        self.logger.info("Slider activity: %s | action = %s", activity, action)
+
+        if action is not None:
+            value = int(action.value)
+            self.logger.info("Slided to: %s", value)
+
+            # Update the selector too, so they show the same value.
+            # It is important to note that since not all values are present in the selector, it might be empty sometimes
+            self.selector.apply(str(value))
+
+            # Overwrite the current stored value on the global data with the new selected value.
+            self.value = value
+
+
+bot = hikari.GatewayBot(token=os.environ["BOT_TOKEN"])
+dashboard = RillRateDashboard()
+
+
+def is_command(cmd_name: str, content: str) -> bool:
+    """Check if the message sent is a valid command."""
+    return content == f"{PREFIX}{cmd_name}"
+
+
+@bot.listen()
+async def message(event: hikari.GuildMessageCreateEvent) -> None:
+    """Listen for messages being created."""
+    if not event.is_human or not event.content:
+        return
+
+    # Command Framework 101 :D
+    if event.content.startswith(PREFIX):
+        if is_command("ping", event.content):
+            await event.message.respond("Pong!")
+        elif is_command("value", event.content):
+            await event.message.respond(f"Current value: {dashboard.value}")
+
+
+bot.run()
diff --git a/examples/simple_dashboard/__init__.py b/examples/simple_dashboard/__init__.py
deleted file mode 100644
index 9c9fd302..00000000
--- a/examples/simple_dashboard/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- 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.
-"""Allows mypy to detect the scripts here."""
diff --git a/examples/simple_dashboard/requirements.txt b/examples/simple_dashboard/requirements.txt
deleted file mode 100644
index 6d82c68d..00000000
--- a/examples/simple_dashboard/requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-rillrate==0.38.0
diff --git a/examples/simple_dashboard/simple_dashboard.bat b/examples/simple_dashboard/simple_dashboard.bat
deleted file mode 100644
index 0e6be5d2..00000000
--- a/examples/simple_dashboard/simple_dashboard.bat
+++ /dev/null
@@ -1,52 +0,0 @@
-:: 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.
-
-:: Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-@echo off
-
-if "%~1"=="" (
-    echo Please pass a bot token as the first parameter to this script!
-    :: This is used so that if the script is executed by double clicking it, it gives some feedback before exiting
-    pause
-    exit /B 1
-)
-
-setlocal
-:: This will only loop once
-for %%I in (%0) do (
-    set SCRIPT_NAME=%%~nI
-    set SCRIPT_PATH=%%~dpI
-)
-
-:: PYTHONPATH might not be set
-if defined PYTHONPATH (
-    set PYTHONPATH=%PYTHONPATH%;%SCRIPT_PATH%..\..
-) else (
-    set PYTHONPATH=%SCRIPT_PATH%..\..
-)
-
-set BOT_TOKEN=%1
-
-py -m pip install -r requirements.txt
-py "%SCRIPT_PATH%%SCRIPT_NAME%.py"
-
-endlocal
diff --git a/examples/simple_dashboard/simple_dashboard.py b/examples/simple_dashboard/simple_dashboard.py
deleted file mode 100644
index 994c84f0..00000000
--- a/examples/simple_dashboard/simple_dashboard.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-# This code is licended under the WTFPL license.
-"""A simple bot to demonstrate how to use rillrate with hikari to make a web dashboard for the bot.
-
-Just connect to `http://localhost:6361/ui/` to explore your dashboard!
-"""
-import logging
-import os
-from typing import Any
-
-import rillrate
-from rillrate import prime as rr_prime
-
-import hikari
-
-PREFIX = ","
-
-# Name used to group dashboards.
-# You could have multiple packages for different applications, such as a package for the bot
-# dashboards, and another package for a web server running alongside the bot.
-PACKAGE = "Package"
-# Dashboards are a part inside of package, they can be used to group different types of
-# dashboards that you may want to use, like a dashboard for system status, another dashboard
-# for cache status, and another one to configure features or trigger actions on the bot.
-DASHBOARD = "Dashboard"
-# This are menus inside the dashboard, you can use them to group specific sets
-# of data inside the same dashboard.
-GROUP_CONFIG = "1 - Config"
-# All of the 3 configurable namescapes are sorted alphabetically.
-
-
-def is_command(cmd_name: str, content: str) -> bool:
-    """Check if the message sent is a valid command."""
-    return content.startswith(f"{PREFIX}{cmd_name}")
-
-
-# This is how I do global data on the library, as Bot is generally accessible everywhere, so
-# it's the best you can do to put the global data in there.
-# In this case, I'll use this to store the values modifiable by the dashboard.
-class Data:
-    """Global data shared across the entire bot, used to store dashboard values."""
-
-    def __init__(self) -> None:
-        self.value = 0
-
-
-class Bot(hikari.GatewayBot):
-    """Just implementing the data to the Bot."""
-
-    def __init__(self, **kwargs: Any) -> None:
-        super().__init__(**kwargs)
-        self.data = Data()
-
-
-# Startup the dashboard.
-# To see the dashboard, go to http://localhost:6361/ui/
-rillrate.install()
-bot = Bot(token=os.environ["BOT_TOKEN"])
-
-
-values = [str(i) for i in range(0, 256 + 1, 32)]
-
-selector = rr_prime.Selector(f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Selector", label="Choose!", options=values)
-slider = rr_prime.Slider(
-    f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Slider", label="More fine grain control", min=0, max=256, step=2
-)
-
-
-def _selector_callback(activity: rillrate.Activity, action: rillrate.Action) -> None:
-    logging.info("Selector activity: %s | action = %s", activity, action)
-
-    if action is not None:
-        logging.info("Selected: %s", action.value)
-
-        # Update both the selector and slider, so they show with the same value.
-        selector.apply(action.value)
-        slider.apply(float(action.value))
-
-        # Overwrite the current stored value on the global data with the new selected value.
-        bot.data.value = int(action.value)
-
-
-def _slider_callback(activity: rillrate.Activity, action: rillrate.Action) -> None:
-    logging.info("Slider activity: %s | action = %s", activity, action)
-
-    if action is not None:
-        logging.info("Slided: %f", action.value)
-
-        slider.apply(action.value)
-        selector.apply(str(int(action.value)))
-
-        # Overwrite the current stored value on the global data with the new selected value.
-        bot.data.value = int(action.value)
-
-
-selector.sync_callback(_selector_callback)
-slider.sync_callback(_slider_callback)
-
-
-@bot.listen()
-async def message(event: hikari.GuildMessageCreateEvent) -> None:
-    """Listen for messages being created."""
-    if event.is_bot or not event.content:
-        return
-
-    # Command Framework 101 :D
-    if event.content.startswith(PREFIX):
-        if is_command("ping", event.content):
-            await event.message.respond("Ping?")
-        elif is_command("value", event.content):
-            await event.message.respond(f"Current value: {bot.data.value}")
-
-
-@bot.listen()
-async def on_ready(event: hikari.ShardReadyEvent) -> None:
-    """Log when the bot is ready."""
-    logging.info("Bot is ready! %s", event)
-
-
-bot.run()
diff --git a/examples/simple_dashboard/simple_dashboard.sh b/examples/simple_dashboard/simple_dashboard.sh
deleted file mode 100755
index cf3e928c..00000000
--- a/examples/simple_dashboard/simple_dashboard.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh -e
-# 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.
-
-# Script to set the PYTHONPATH variable up to point to the root of this repository.
-
-if [ -z "${1}" ]; then
-    echo "Please pass a bot token as the first parameter to this script!"
-    exit 1
-fi
-
-SCRIPT=$(readlink -f "${0}")
-SCRIPT_PATH=$(dirname "${SCRIPT}")
-
-# PYTHONPATH might not be set
-if [ $PYTHONPATH ]; then
-    PYTHONPATH="${PYTHONPATH}:${SCRIPT_PATH}/../.."
-else
-    PYTHONPATH="${SCRIPT_PATH}/../.."
-fi
-
-export PYTHONPATH
-export BOT_TOKEN="${1}"
-
-python3 -m pip install -r requirements.txt
-python3 "${SCRIPT_PATH}/$(basename "${SCRIPT_PATH}").py"
diff --git a/examples/simple_dashboard_requirements.txt b/examples/simple_dashboard_requirements.txt
new file mode 100644
index 00000000..3bf0b99e
--- /dev/null
+++ b/examples/simple_dashboard_requirements.txt
@@ -0,0 +1,1 @@
+rillrate==0.40
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/channels.py b/hikari/channels.py
index fc992702..aa34ece2 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -1080,9 +1080,7 @@ async def edit_overwrite(
 
     async def remove_overwrite(
         self,
-        target: snowflakes.SnowflakeishOr[
-            typing.Union[PermissionOverwrite, guilds.PartialRole, users.PartialUser, snowflakes.Snowflakeish]
-        ],
+        target: typing.Union[PermissionOverwrite, guilds.PartialRole, users.PartialUser, snowflakes.Snowflakeish],
     ) -> None:
         """Delete a custom permission for an entity in a given guild channel.
 
diff --git a/hikari/commands.py b/hikari/commands.py
index 55a7ba31..6fa77cb3 100644
--- a/hikari/commands.py
+++ b/hikari/commands.py
@@ -104,6 +104,9 @@ class OptionType(int, enums.Enum):
     This is range limited between -2^53 and 2^53.
     """
 
+    ATTACHMENT = 11
+    """Denotes a command option where the value will be an attachment."""
+
 
 @attr_extensions.with_copy
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
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/buckets.py b/hikari/impl/buckets.py
index 35e30bd3..a0441ffc 100644
--- a/hikari/impl/buckets.py
+++ b/hikari/impl/buckets.py
@@ -244,19 +244,24 @@ async def __aexit__(
         exc: typing.Optional[BaseException],
         exc_tb: typing.Optional[types.TracebackType],
     ) -> None:
-        self._lock.release()
+        self.release()
 
     @property
     def is_unknown(self) -> bool:
         """Return `builtins.True` if the bucket represents an `UNKNOWN` bucket."""
         return self.name.startswith(UNKNOWN_HASH)
 
+    def release(self) -> None:
+        """Release the lock on the bucket."""
+        self._lock.release()
+
     async def acquire(self) -> None:
-        """Acquire time on this rate limiter.
+        """Acquire time and the lock on this bucket.
 
         !!! note
             You should afterwards invoke `RESTBucket.update_rate_limit` to
-            update any rate limit information you are made aware of.
+            update any rate limit information you are made aware of and
+            `RESTBucket.release` to release the lock.
 
         Raises
         ------
@@ -272,6 +277,8 @@ async def acquire(self) -> None:
         retry_after = self.reset_at - now
 
         if self.is_rate_limited(now) and retry_after > self._max_rate_limit:
+            # Release lock before we error
+            self._lock.release()
             raise errors.RateLimitTooLongError(
                 route=self._compiled_route,
                 retry_after=retry_after,
@@ -304,18 +311,6 @@ def update_rate_limit(self, remaining: int, limit: int, reset_at: float) -> None
         self.reset_at: float = reset_at
         self.period: float = max(0.0, self.reset_at - time.monotonic())
 
-    def drip(self) -> None:
-        """Decrement the remaining count for this bucket.
-
-        !!! note
-            If the bucket is marked as `RESTBucket.is_unknown`, then this will
-            not do anything. `Unknown` buckets have infinite rate limits.
-        """
-        # We don't drip unknown buckets: we cannot rate limit them as we don't know their real bucket hash or
-        # the current rate limit values Discord put on them...
-        if not self.is_unknown:
-            self.remaining -= 1
-
     def resolve(self, real_bucket_hash: str) -> None:
         """Resolve an unknown bucket.
 
@@ -533,7 +528,7 @@ def do_gc_pass(self, expire_after: float) -> None:
 
         _LOGGER.log(ux.TRACE, "purged %s stale buckets, %s remain in survival, %s active", dead, survival, active)
 
-    def acquire(self, compiled_route: routes.CompiledRoute) -> typing.AsyncContextManager[None]:
+    def acquire(self, compiled_route: routes.CompiledRoute) -> RESTBucket:
         """Acquire a bucket for the given route.
 
         Parameters
@@ -543,17 +538,16 @@ def acquire(self, compiled_route: routes.CompiledRoute) -> typing.AsyncContextMa
 
         Returns
         -------
-        typing.AsyncContextManager[builtins.None]
-            A context manager to enter while doing the request.
+        hikari.impl.RESTBucket
+            The bucket for this route.
 
         !!! note
-            You MUST keep the context manager acquired during the whole of the
-            request. From making the request until calling `update_rate_limits`.
+            You MUST keep the context manager of the bucket acquired during the
+            full duration of the request. From making the request until calling
+            `update_rate_limits`.
         """
-        template = compiled_route.route
-
         try:
-            bucket_hash = self.routes_to_hashes[template]
+            bucket_hash = self.routes_to_hashes[compiled_route.route]
             real_bucket_hash = compiled_route.create_real_bucket_hash(bucket_hash)
         except KeyError:
             real_bucket_hash = _create_unknown_hash(compiled_route)
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 1d36158a..72aecf19 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1931,12 +1931,19 @@ def _deserialize_resolved_option_data(
         else:
             messages = {}
 
+        if raw_attachments := payload.get("attachments"):
+            attachments = {a.id: a for a in map(self._deserialize_message_attachment, raw_attachments.values())}
+
+        else:
+            attachments = {}
+
         return command_interactions.ResolvedOptionData(
+            attachments=attachments,
             channels=channels,
             members=members,
-            users=users,
-            roles=roles,
             messages=messages,
+            roles=roles,
+            users=users,
         )
 
     def deserialize_command_interaction(
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..5818cce1 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
@@ -307,22 +307,21 @@ async def acquire(self) -> None:
         Calling this function will cause it to block until you are not longer
         being rate limited.
         """
-        loop = asyncio.get_running_loop()
-        future = loop.create_future()
-
         # If we are rate limited, delegate invoking this to the throttler and spin it up
         # if it hasn't started. Likewise, if the throttle task is still running, we should
         # delegate releasing the future to the throttler task so that we still process
         # first-come-first-serve
         if self.throttle_task is not None or self.is_rate_limited(time.monotonic()):
+            loop = asyncio.get_running_loop()
+            future = loop.create_future()
+
             self.queue.append(future)
             if self.throttle_task is None:
                 self.throttle_task = loop.create_task(self.throttle())
+
+            await future
         else:
             self.drip()
-            future.set_result(None)
-
-        await future
 
     def get_time_until_reset(self, now: float) -> float:
         """Determine how long until the current rate limit is reset.
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index e24fe86b..260164a9 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):
     ...
 
@@ -720,6 +721,7 @@ async def _request(
         retry_count = 0
 
         stack = contextlib.AsyncExitStack()
+        trace_logging_enabled = _LOGGER.isEnabledFor(ux.TRACE)
         while True:
             try:
                 uuid = time.uuid()
@@ -732,7 +734,7 @@ async def _request(
                     if not no_auth:
                         await live_attributes.still_alive().global_rate_limit.acquire()
 
-                    if _LOGGER.isEnabledFor(ux.TRACE):
+                    if trace_logging_enabled:
                         _LOGGER.log(
                             ux.TRACE,
                             "%s %s %s\n%s",
@@ -757,7 +759,7 @@ async def _request(
                         proxy_headers=self._proxy_settings.all_headers,
                     )
 
-                    if _LOGGER.isEnabledFor(ux.TRACE):
+                    if trace_logging_enabled:
                         time_taken = (time.monotonic() - start) * 1_000
                         _LOGGER.log(
                             ux.TRACE,
@@ -1330,11 +1332,11 @@ def _build_message_payload(  # noqa: C901- Function too complex
             )
 
         if final_attachments:
-            form = data_binding.URLEncodedFormBuilder(executor=self._executor)
+            form_builder = data_binding.URLEncodedFormBuilder(executor=self._executor)
 
             for i, attachment in enumerate(final_attachments):
-                form.add_resource(f"file{i}", attachment)
-            return body, form
+                form_builder.add_resource(f"file{i}", attachment)
+            return body, form_builder
 
         return body, None
 
@@ -1362,7 +1364,7 @@ async def create_message(
         ] = undefined.UNDEFINED,
     ) -> messages_.Message:
         route = routes.POST_CHANNEL_MESSAGES.compile(channel=channel)
-        body, form = self._build_message_payload(
+        body, form_builder = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1379,9 +1381,9 @@ async def create_message(
         body.put("nonce", nonce)
         body.put("message_reference", reply, conversion=lambda m: {"message_id": str(int(m))})
 
-        if form is not None:
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-            response = await self._request(route, form_builder=form)
+        if form_builder is not None:
+            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form_builder)
         else:
             response = await self._request(route, json=body)
 
@@ -1426,7 +1428,7 @@ async def edit_message(
         flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
     ) -> messages_.Message:
         route = routes.PATCH_CHANNEL_MESSAGE.compile(channel=channel, message=message)
-        body, form = self._build_message_payload(
+        body, form_builder = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1443,9 +1445,9 @@ async def edit_message(
             edit=True,
         )
 
-        if form is not None:
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-            response = await self._request(route, form_builder=form)
+        if form_builder is not None:
+            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form_builder)
         else:
             response = await self._request(route, json=body)
 
@@ -1755,7 +1757,7 @@ async def execute_webhook(
         query = data_binding.StringMapBuilder()
         query.put("wait", True)
 
-        body, form = self._build_message_payload(
+        body, form_builder = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1772,9 +1774,9 @@ async def execute_webhook(
         body.put("username", username)
         body.put("avatar_url", avatar_url, conversion=str)
 
-        if form is not None:
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-            response = await self._request(route, form_builder=form, query=query, no_auth=True)
+        if form_builder is not None:
+            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form_builder, query=query, no_auth=True)
         else:
             response = await self._request(route, json=body, query=query, no_auth=True)
 
@@ -1822,7 +1824,7 @@ async def edit_webhook_message(
         webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
         route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
 
-        body, form = self._build_message_payload(
+        body, form_builder = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -1837,9 +1839,9 @@ async def edit_webhook_message(
             edit=True,
         )
 
-        if form is not None:
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-            response = await self._request(route, form_builder=form, no_auth=True)
+        if form_builder is not None:
+            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form_builder, no_auth=True)
         else:
             response = await self._request(route, json=body, no_auth=True)
 
@@ -1981,11 +1983,13 @@ async def authorize_client_credentials_token(
         scopes: typing.Sequence[typing.Union[applications.OAuth2Scope, str]],
     ) -> applications.PartialOAuth2Token:
         route = routes.POST_TOKEN.compile()
-        form = data_binding.URLEncodedFormBuilder()
-        form.add_field("grant_type", "client_credentials")
-        form.add_field("scope", " ".join(scopes))
+        form_builder = data_binding.URLEncodedFormBuilder()
+        form_builder.add_field("grant_type", "client_credentials")
+        form_builder.add_field("scope", " ".join(scopes))
 
-        response = await self._request(route, form_builder=form, auth=self._gen_oauth2_token(client, client_secret))
+        response = await self._request(
+            route, form_builder=form_builder, auth=self._gen_oauth2_token(client, client_secret)
+        )
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_partial_token(response)
 
@@ -1997,12 +2001,14 @@ async def authorize_access_token(
         redirect_uri: str,
     ) -> applications.OAuth2AuthorizationToken:
         route = routes.POST_TOKEN.compile()
-        form = data_binding.URLEncodedFormBuilder()
-        form.add_field("grant_type", "authorization_code")
-        form.add_field("code", code)
-        form.add_field("redirect_uri", redirect_uri)
+        form_builder = data_binding.URLEncodedFormBuilder()
+        form_builder.add_field("grant_type", "authorization_code")
+        form_builder.add_field("code", code)
+        form_builder.add_field("redirect_uri", redirect_uri)
 
-        response = await self._request(route, form_builder=form, auth=self._gen_oauth2_token(client, client_secret))
+        response = await self._request(
+            route, form_builder=form_builder, auth=self._gen_oauth2_token(client, client_secret)
+        )
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_authorization_token(response)
 
@@ -2017,14 +2023,16 @@ async def refresh_access_token(
         ] = undefined.UNDEFINED,
     ) -> applications.OAuth2AuthorizationToken:
         route = routes.POST_TOKEN.compile()
-        form = data_binding.URLEncodedFormBuilder()
-        form.add_field("grant_type", "refresh_token")
-        form.add_field("refresh_token", refresh_token)
+        form_builder = data_binding.URLEncodedFormBuilder()
+        form_builder.add_field("grant_type", "refresh_token")
+        form_builder.add_field("refresh_token", refresh_token)
 
         if scopes is not undefined.UNDEFINED:
-            form.add_field("scope", " ".join(scopes))
+            form_builder.add_field("scope", " ".join(scopes))
 
-        response = await self._request(route, form_builder=form, auth=self._gen_oauth2_token(client, client_secret))
+        response = await self._request(
+            route, form_builder=form_builder, auth=self._gen_oauth2_token(client, client_secret)
+        )
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_authorization_token(response)
 
@@ -2035,9 +2043,9 @@ async def revoke_access_token(
         token: typing.Union[str, applications.PartialOAuth2Token],
     ) -> None:
         route = routes.POST_TOKEN_REVOKE.compile()
-        form = data_binding.URLEncodedFormBuilder()
-        form.add_field("token", str(token))
-        await self._request(route, form_builder=form, auth=self._gen_oauth2_token(client, client_secret))
+        form_builder = data_binding.URLEncodedFormBuilder()
+        form_builder.add_field("token", str(token))
+        await self._request(route, form_builder=form_builder, auth=self._gen_oauth2_token(client, client_secret))
 
     async def add_user_to_guild(
         self,
@@ -3473,7 +3481,7 @@ async def edit_interaction_response(
     ) -> messages_.Message:
         route = routes.PATCH_INTERACTION_RESPONSE.compile(webhook=application, token=token)
 
-        body, form = self._build_message_payload(
+        body, form_builder = self._build_message_payload(
             content=content,
             attachment=attachment,
             attachments=attachments,
@@ -3488,9 +3496,9 @@ async def edit_interaction_response(
             edit=True,
         )
 
-        if form is not None:
-            form.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
-            response = await self._request(route, form_builder=form, no_auth=True)
+        if form_builder is not None:
+            form_builder.add_field("payload_json", data_binding.dump_json(body), content_type=_APPLICATION_JSON)
+            response = await self._request(route, form_builder=form_builder, no_auth=True)
         else:
             response = await self._request(route, json=body, no_auth=True)
 
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 a4e73332..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()
@@ -135,21 +134,22 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
         self.logger: logging.Logger
         self.log_filterer: typing.Callable[[str], str]
 
-    async def send_close(self, *, code: int = 1000, message: bytes = b"") -> bool:
+    async def send_close(self, *, code: int = 1000, message: bytes = b"") -> None:
         # aiohttp may close the socket by invoking close() internally. By giving
         # a different name, we can ensure aiohttp won't invoke this method.
         # We can then guarantee any call to this method was made by us, as
         # opposed to, for example, Windows injecting a spurious EOF when
         # something disconnects, which makes aiohttp just shut down as if we
         # did it.
-        if not self.sent_close:
-            self.sent_close = True
-            self.logger.debug("sending close frame with code %s and message %s", int(code), message)
-            try:
-                return await asyncio.wait_for(super().close(code=code, message=message), timeout=5)
-            except asyncio.TimeoutError:
-                self.logger.debug("failed to send close frame in time, probably connection issues")
-        return False
+        if self.sent_close:
+            return
+
+        self.sent_close = True
+        self.logger.debug("sending close frame with code %s and message %s", int(code), message)
+        try:
+            await asyncio.wait_for(super().close(code=code, message=message), timeout=5)
+        except asyncio.TimeoutError:
+            self.logger.debug("failed to send close frame in time, probably connection issues")
 
     async def receive_json(
         self,
@@ -296,17 +296,23 @@ async def connect(
                         message=b"client is shutting down",
                     )
 
-        except (aiohttp.ClientOSError, aiohttp.ClientConnectionError, aiohttp.WSServerHandshakeError) as ex:
+        except (
+            aiohttp.ClientOSError,
+            aiohttp.ClientConnectionError,
+            aiohttp.WSServerHandshakeError,
+            asyncio.TimeoutError,
+        ) as ex:
             # Windows will sometimes raise an aiohttp.ClientOSError
             # If we cannot do DNS lookup, this will fail with a ClientConnectionError
-            # usually.
+            # usually, but it might also fail with asyncio.TimeoutError if its gets stuck in a weird way
             #
-            # aiohttp.WSServerHandshakeError has a really bad str, so we use the repr instead.
+            # aiohttp.WSServerHandshakeError has a really bad str, so we use the repr instead
             if isinstance(ex, aiohttp.WSServerHandshakeError):
                 reason = repr(ex)
+            elif isinstance(ex, asyncio.TimeoutError):
+                reason = "Timeout exceeded"
             else:
                 reason = str(ex)
-
             raise errors.GatewayConnectionError(reason) from None
 
         finally:
@@ -495,7 +501,7 @@ def intents(self) -> intents_.Intents:
 
     @property
     def is_alive(self) -> bool:
-        return self._run_task is not None and not self._run_task.done()
+        return self._ws is not None and not self._ws.sent_close
 
     @property
     def shard_count(self) -> int:
@@ -816,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/intents.py b/hikari/intents.py
index 52a04401..085bc709 100644
--- a/hikari/intents.py
+++ b/hikari/intents.py
@@ -288,11 +288,20 @@ class Intents(enums.Flag):
     """
 
     DM_MESSAGE_TYPING = 1 << 14
-    """Subscribes to the following events
+    """Subscribes to the following events:
 
     * `TYPING_START` (in private message channels (non-guild bound) only)
     """
 
+    MESSAGE_CONTENT = 1 << 15
+    """Receive message content for all messages.
+
+    DM's to the bot and messages that mention it are exempt from this.
+
+    !!! warning
+        This intent is privileged, and requires enabling/whitelisting to use.
+    """
+
     # Annoyingly, enums hide classmethods and staticmethods from __dir__ in
     # EnumMeta which means if I make methods to generate these, then stuff
     # will not be documented by pdoc. Alas, my dream of being smart with
@@ -346,7 +355,7 @@ class Intents(enums.Flag):
     ALL_UNPRIVILEGED = ALL_GUILDS_UNPRIVILEGED | ALL_DMS
     """All unprivileged intents."""
 
-    ALL_PRIVILEGED = ALL_GUILDS_PRIVILEGED
+    ALL_PRIVILEGED = ALL_GUILDS_PRIVILEGED | MESSAGE_CONTENT
     """All privileged intents.
 
     !!! warning
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index 29d8e9f5..e39f0b0c 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -92,20 +92,23 @@ class InteractionChannel(channels.PartialChannel):
 class ResolvedOptionData:
     """Represents the resolved objects of entities referenced in a command's options."""
 
-    users: typing.Mapping[snowflakes.Snowflake, users_.User] = attr.field(repr=False)
-    """Mapping of snowflake IDs to the resolved option user objects."""
+    attachments: typing.Mapping[snowflakes.Snowflake, messages_.Attachment] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the attachment objects."""
+
+    channels: typing.Mapping[snowflakes.Snowflake, InteractionChannel] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the resolved option partial channel objects."""
 
     members: typing.Mapping[snowflakes.Snowflake, base_interactions.InteractionMember] = attr.field(repr=False)
     """Mapping of snowflake IDs to the resolved option member objects."""
 
+    messages: typing.Mapping[snowflakes.Snowflake, messages_.Message]
+    """Mapping of snowflake IDs to the resolved option partial message objects."""
+
     roles: typing.Mapping[snowflakes.Snowflake, guilds.Role] = attr.field(repr=False)
     """Mapping of snowflake IDs to the resolved option role objects."""
 
-    channels: typing.Mapping[snowflakes.Snowflake, InteractionChannel] = attr.field(repr=False)
-    """Mapping of snowflake iDs to the resolved option partial channel objects."""
-
-    messages: typing.Mapping[snowflakes.Snowflake, messages_.Message]
-    """Mapping of snowflake iDs to the resolved option partial message objects."""
+    users: typing.Mapping[snowflakes.Snowflake, users_.User] = attr.field(repr=False)
+    """Mapping of snowflake IDs to the resolved option user objects."""
 
 
 @attr_extensions.with_copy
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/config.py b/pipelines/config.py
index 81b047dd..0e917e74 100644
--- a/pipelines/config.py
+++ b/pipelines/config.py
@@ -37,7 +37,6 @@
 FLAKE8_REPORT = "public/flake8"
 PYPROJECT_TOML = "pyproject.toml"
 COVERAGE_HTML_PATH = _os.path.join(ARTIFACT_DIRECTORY, "coverage", "html")
-PYRIGHT_VERSION = "1.1.190"  # 2021-11-25 (UTC+0)
 
 # Reformatting paths
 
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..6b61b2cc 100644
--- a/pipelines/pyright.nox.py
+++ b/pipelines/pyright.nox.py
@@ -31,6 +31,5 @@ def verify_types(session: nox.Session) -> None:
     session.install("-r", "dev-requirements.txt")
     session.install(".")
     # 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/examples/hello_world/__init__.py b/pipelines/slotscheck.nox.py
similarity index 78%
rename from examples/hello_world/__init__.py
rename to pipelines/slotscheck.nox.py
index 9c9fd302..67531fcd 100644
--- a/examples/hello_world/__init__.py
+++ b/pipelines/slotscheck.nox.py
@@ -19,4 +19,14 @@
 # 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.
-"""Allows mypy to detect the scripts here."""
+"""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$
+)
+"""
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index bdefd07b..fca1684c 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -61,7 +61,7 @@ pip install -r requirements.txt
 
 echo "===== DEPLOYING TO PYPI ====="
 echo "-- Setting __git_sha1__ (ref: ${REF}) --"
-sed "/^__git__sha1__.*/, \${s||__git__sha1__: typing.Final[str] = \"${REF}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__git__sha1__' not found in about!" && exit 1)
+sed "/^__git_sha1__.*/, \${s||__git_sha1__: typing.Final[str] = \"${REF}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__git_sha1__' not found in about!" && exit 1)
 echo "=========================================================================="
 cat hikari/_about.py
 echo "=========================================================================="
@@ -93,7 +93,7 @@ git fetch origin
 git checkout -f master
 
 echo "-- Bumping to development version (${NEW_VERSION}) --"
-sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${NEW_VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${NEW_VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Pushing to repository --"
 git commit -am "Bump to development version (${NEW_VERSION})"
diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh
index 858ee922..f4bb9029 100644
--- a/scripts/prepare-release.sh
+++ b/scripts/prepare-release.sh
@@ -38,7 +38,7 @@ echo "-- Checkout branch --"
 git checkout -b "task/prepare-release-${VERSION}"
 
 echo "-- Bumping repository version to ${VERSION} --"
-sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${VERSION}\"|g; b}; $q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
+sed "/^__version__.*/, \${s||__version__: typing.Final[str] = \"${VERSION}\"|g; b}; \$q1" -i hikari/_about.py || (echo "Variable '__version__' not found in about!" && exit 1)
 
 echo "-- Running towncrier --"
 towncrier --yes


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..4984250e 100644
--- a/tests/hikari/impl/test_buckets.py
+++ b/tests/hikari/impl/test_buckets.py
@@ -20,6 +20,7 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 import asyncio
+import contextlib
 import time
 
 import mock
@@ -44,20 +45,27 @@ def compiled_route(self, template):
 
     @pytest.mark.asyncio()
     async def test_async_context_manager(self, compiled_route):
-        with mock.patch.object(asyncio, "Lock") as lock:
-            lock.return_value.acquire = mock.AsyncMock()
-            with mock.patch.object(buckets.RESTBucket, "acquire", new=mock.AsyncMock()) as acquire:
+        with mock.patch.object(buckets.RESTBucket, "acquire", new=mock.AsyncMock()) as acquire:
+            with mock.patch.object(buckets.RESTBucket, "release") as release:
                 async with buckets.RESTBucket("spaghetti", compiled_route, float("inf")):
                     acquire.assert_awaited_once_with()
-                    lock.return_value.release.assert_not_called()
+                    release.assert_not_called()
 
-                lock.return_value.release.assert_called_once_with()
+            release.assert_called_once_with()
 
     @pytest.mark.parametrize("name", ["spaghetti", buckets.UNKNOWN_HASH])
     def test_is_unknown(self, name, compiled_route):
         with buckets.RESTBucket(name, compiled_route, float("inf")) as rl:
             assert rl.is_unknown is (name == buckets.UNKNOWN_HASH)
 
+    def test_release(self, compiled_route):
+        with buckets.RESTBucket(__name__, compiled_route, float("inf")) as rl:
+            rl._lock = mock.Mock()
+
+            rl.release()
+
+            rl._lock.release.assert_called_once_with()
+
     def test_update_rate_limit(self, compiled_route):
         with buckets.RESTBucket(__name__, compiled_route, float("inf")) as rl:
             rl.remaining = 1
@@ -73,13 +81,6 @@ def test_update_rate_limit(self, compiled_route):
             assert rl.reset_at == 27
             assert rl.period == 27 - 4.20
 
-    @pytest.mark.parametrize("name", ["spaghetti", buckets.UNKNOWN_HASH])
-    def test_drip(self, name, compiled_route):
-        with buckets.RESTBucket(name, compiled_route, float("inf")) as rl:
-            rl.remaining = 1
-            rl.drip()
-            assert rl.remaining == 0 if name != buckets.UNKNOWN_HASH else 1
-
     @pytest.mark.asyncio()
     async def test_acquire_when_unknown_bucket(self, compiled_route):
         with buckets.RESTBucket(buckets.UNKNOWN_HASH, compiled_route, float("inf")) as rl:
@@ -92,11 +93,18 @@ async def test_acquire_when_unknown_bucket(self, compiled_route):
 
     @pytest.mark.asyncio()
     async def test_acquire_when_too_long_ratelimit(self, compiled_route):
-        with buckets.RESTBucket("spaghetti", compiled_route, 60) as rl:
-            rl.reset_at = time.perf_counter() + 999999999999999999999999999
-            with mock.patch.object(buckets.RESTBucket, "is_rate_limited", return_value=True):
-                with pytest.raises(errors.RateLimitTooLongError):
-                    await rl.acquire()
+        stack = contextlib.ExitStack()
+        rl = stack.enter_context(buckets.RESTBucket("spaghetti", compiled_route, 60))
+        rl._lock = mock.Mock(acquire=mock.AsyncMock())
+        rl.reset_at = time.perf_counter() + 999999999999999999999999999
+        stack.enter_context(mock.patch.object(buckets.RESTBucket, "is_rate_limited", return_value=True))
+        stack.enter_context(pytest.raises(errors.RateLimitTooLongError))
+
+        with stack:
+            await rl.acquire()
+
+        rl._lock.acquire.assert_awaited_once_with()
+        rl._lock.release.assert_called_once_with()
 
     @pytest.mark.asyncio()
     async def test_acquire(self, compiled_route):
@@ -272,7 +280,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 +304,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 +323,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 +338,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 +357,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 +373,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_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index d6d31fd3..fab2d7a3 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2970,6 +2970,7 @@ def test__deserialize_resolved_option_data(
         self,
         entity_factory_impl,
         interaction_resolved_data_payload,
+        attachment_payload,
         user_payload,
         guild_role_payload,
         interaction_member_payload,
@@ -2979,7 +2980,6 @@ def test__deserialize_resolved_option_data(
             interaction_resolved_data_payload, guild_id=123321
         )
 
-        # ResolvedOptionData
         assert len(resolved.channels) == 1
         channel = resolved.channels[695382395666300958]
         assert channel.type is channel_models.ChannelType.GUILD_TEXT
@@ -2993,6 +2993,9 @@ def test__deserialize_resolved_option_data(
             interaction_member_payload, guild_id=123321, user=entity_factory_impl.deserialize_user(user_payload)
         )
 
+        assert resolved.attachments == {
+            690922406474154014: entity_factory_impl._deserialize_message_attachment(attachment_payload)
+        }
         assert resolved.roles == {
             41771983423143936: entity_factory_impl.deserialize_role(guild_role_payload, guild_id=123321)
         }
@@ -3004,6 +3007,7 @@ def test__deserialize_resolved_option_data(
     def test__deserialize_resolved_option_data_with_empty_resolved_resources(self, entity_factory_impl):
         resolved = entity_factory_impl._deserialize_resolved_option_data({})
 
+        assert resolved.attachments == {}
         assert resolved.channels == {}
         assert resolved.members == {}
         assert resolved.roles == {}
@@ -3012,9 +3016,10 @@ def test__deserialize_resolved_option_data_with_empty_resolved_resources(self, e
 
     @pytest.fixture()
     def interaction_resolved_data_payload(
-        self, interaction_member_payload, guild_role_payload, user_payload, message_payload
+        self, interaction_member_payload, attachment_payload, guild_role_payload, user_payload, message_payload
     ):
         return {
+            "attachments": {"690922406474154014": attachment_payload},
             "channels": {
                 "695382395666300958": {
                     "id": "695382395666300958",
@@ -4087,6 +4092,20 @@ def referenced_message(self, user_payload):
             "flags": "222",
         }
 
+    @pytest.fixture()
+    def attachment_payload(self):
+        return {
+            "id": "690922406474154014",
+            "filename": "IMG.jpg",
+            "content_type": "image/png",
+            "size": 660521,
+            "url": "https://somewhere.com/attachments/123/456/IMG.jpg",
+            "proxy_url": "https://media.somewhere.com/attachments/123/456/IMG.jpg",
+            "width": 1844,
+            "height": 2638,
+            "ephemeral": True,
+        }
+
     @pytest.fixture()
     def message_payload(
         self,
@@ -4098,6 +4117,7 @@ def message_payload(
         referenced_message,
         action_row_payload,
         partial_sticker_payload,
+        attachment_payload,
     ):
         member_payload = member_payload.copy()
         del member_payload["user"]
@@ -4117,19 +4137,7 @@ def message_payload(
             ],
             "mention_roles": ["987"],
             "mention_channels": [{"id": "456", "guild_id": "678", "type": 1, "name": "hikari-testing"}],
-            "attachments": [
-                {
-                    "id": "690922406474154014",
-                    "filename": "IMG.jpg",
-                    "content_type": "image/png",
-                    "size": 660521,
-                    "url": "https://somewhere.com/attachments/123/456/IMG.jpg",
-                    "proxy_url": "https://media.somewhere.com/attachments/123/456/IMG.jpg",
-                    "width": 1844,
-                    "height": 2638,
-                    "ephemeral": True,
-                }
-            ],
+            "attachments": [attachment_payload],
             "embeds": [embed_payload],
             "reactions": [{"emoji": custom_emoji_payload, "count": 100, "me": True}],
             "pinned": True,
@@ -4151,6 +4159,51 @@ def message_payload(
             "components": [action_row_payload, {"type": 1000000000}],
         }
 
+    def test__deserialize_message_attachment(self, entity_factory_impl, attachment_payload):
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.id == 690922406474154014
+        assert attachment.filename == "IMG.jpg"
+        assert attachment.size == 660521
+        assert attachment.media_type == "image/png"
+        assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
+        assert attachment.proxy_url == "https://media.somewhere.com/attachments/123/456/IMG.jpg"
+        assert attachment.width == 1844
+        assert attachment.height == 2638
+        assert attachment.is_ephemeral is True
+        assert isinstance(attachment, message_models.Attachment)
+
+    def test__deserialize_message_attachment_with_null_fields(
+        self,
+        entity_factory_impl,
+        attachment_payload,
+    ):
+        attachment_payload["height"] = None
+        attachment_payload["width"] = None
+
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.height is None
+        assert attachment.width is None
+        assert isinstance(attachment, message_models.Attachment)
+
+    def test__deserialize_message_attachment_with_unset_fields(
+        self,
+        entity_factory_impl,
+        attachment_payload,
+    ):
+        del attachment_payload["content_type"]
+        del attachment_payload["height"]
+        del attachment_payload["width"]
+        del attachment_payload["ephemeral"]
+
+        attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
+
+        assert attachment.media_type is None
+        assert attachment.height is None
+        assert attachment.width is None
+        assert attachment.is_ephemeral is False
+
     def test_deserialize_partial_message(
         self,
         entity_factory_impl,
@@ -4163,6 +4216,7 @@ def test_deserialize_partial_message(
         embed_payload,
         referenced_message,
         action_row_payload,
+        attachment_payload,
     ):
         partial_message = entity_factory_impl.deserialize_partial_message(message_payload)
 
@@ -4186,19 +4240,7 @@ def test_deserialize_partial_message(
         assert partial_message.mentions.user_ids == [5678]
         assert partial_message.mentions.role_ids == [987]
         assert partial_message.mentions.channels_ids == [456]
-        # Attachment
-        assert len(partial_message.attachments) == 1
-        attachment = partial_message.attachments[0]
-        assert attachment.id == 690922406474154014
-        assert attachment.filename == "IMG.jpg"
-        assert attachment.size == 660521
-        assert attachment.media_type == "image/png"
-        assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
-        assert attachment.proxy_url == "https://media.somewhere.com/attachments/123/456/IMG.jpg"
-        assert attachment.width == 1844
-        assert attachment.height == 2638
-        assert attachment.is_ephemeral is True
-        assert isinstance(attachment, message_models.Attachment)
+        assert partial_message.attachments == [entity_factory_impl._deserialize_message_attachment(attachment_payload)]
 
         expected_embed = entity_factory_impl.deserialize_embed(embed_payload)
         assert partial_message.embeds == [expected_embed]
@@ -4443,10 +4485,6 @@ def test_deserialize_message(
     def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, message_payload):
         del message_payload["application"]["cover_image"]
         del message_payload["application"]["primary_sku_id"]
-        del message_payload["attachments"][0]["content_type"]
-        del message_payload["attachments"][0]["height"]
-        del message_payload["attachments"][0]["width"]
-        del message_payload["attachments"][0]["ephemeral"]
         del message_payload["activity"]["party_id"]
         del message_payload["message_reference"]["message_id"]
         del message_payload["message_reference"]["guild_id"]
@@ -4455,13 +4493,6 @@ def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, me
         message = entity_factory_impl.deserialize_message(message_payload)
 
         assert message.mentions.channels == {}
-        # Attachment
-        assert len(message.attachments) == 1
-        attachment = message.attachments[0]
-        assert attachment.width is None
-        assert attachment.height is None
-        assert attachment.is_ephemeral is False
-        assert isinstance(attachment, message_models.Attachment)
 
         # Activity
         assert message.activity.party_id is None
@@ -4478,18 +4509,9 @@ def test_deserialize_message_with_unset_sub_fields(self, entity_factory_impl, me
         assert isinstance(message.message_reference, message_models.MessageReference)
 
     def test_deserialize_message_with_null_sub_fields(self, entity_factory_impl, message_payload):
-        message_payload["attachments"][0]["height"] = None
-        message_payload["attachments"][0]["width"] = None
         message_payload["application"]["icon"] = None
         message = entity_factory_impl.deserialize_message(message_payload)
 
-        # Attachment
-        assert len(message.attachments) == 1
-        attachment = message.attachments[0]
-        assert attachment.width is None
-        assert attachment.height is None
-        assert isinstance(attachment, message_models.Attachment)
-
         # MessageApplication
         assert message.application.icon_hash is None
         assert isinstance(message.application, message_models.MessageApplication)
diff --git a/tests/hikari/impl/test_rate_limits.py b/tests/hikari/impl/test_rate_limits.py
index 059997a4..20783737 100644
--- a/tests/hikari/impl/test_rate_limits.py
+++ b/tests/hikari/impl/test_rate_limits.py
@@ -210,13 +210,12 @@ async def test_drip_if_not_throttled_and_not_ratelimited(self, ratelimiter, even
         ratelimiter.drip = mock.Mock()
         ratelimiter.throttle_task = None
         ratelimiter.is_rate_limited = mock.Mock(return_value=False)
-        future = MockFuture()
-        event_loop.create_future = mock.Mock(return_value=future)
+        event_loop.create_future = mock.Mock()
 
         await ratelimiter.acquire()
 
         ratelimiter.drip.assert_called_once_with()
-        future.set_result.assert_called_once_with(None)
+        event_loop.create_future.assert_not_called()
 
     @pytest.mark.asyncio()
     async def test_no_drip_if_throttle_task_is_not_None(self, ratelimiter, event_loop):
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/impl/test_shard.py b/tests/hikari/impl/test_shard.py
index b465707c..ab5fa4b9 100644
--- a/tests/hikari/impl/test_shard.py
+++ b/tests/hikari/impl/test_shard.py
@@ -23,6 +23,7 @@
 import contextlib
 import datetime
 import platform
+import re
 
 import aiohttp
 import mock
@@ -35,7 +36,6 @@
 from hikari import presences
 from hikari import undefined
 from hikari.impl import shard
-from hikari.internal import aio
 from hikari.internal import time
 from tests.hikari import client_session_stub
 from tests.hikari import hikari_test_helpers
@@ -94,7 +94,7 @@ async def test_send_close_when_not_closed_nor_closing_logs(self, transport_impl)
 
         with mock.patch.object(aiohttp.ClientWebSocketResponse, "close", new=mock.Mock()) as close:
             with mock.patch.object(asyncio, "wait_for", return_value=mock.AsyncMock()) as wait_for:
-                assert await transport_impl.send_close(code=1234, message=b"some message") is wait_for.return_value
+                await transport_impl.send_close(code=1234, message=b"some message")
 
         wait_for.assert_awaited_once_with(close.return_value, timeout=5)
         close.assert_called_once_with(code=1234, message=b"some message")
@@ -104,10 +104,19 @@ async def test_send_close_when_TimeoutError(self, transport_impl):
         transport_impl.sent_close = False
 
         with mock.patch.object(aiohttp.ClientWebSocketResponse, "close", side_effect=asyncio.TimeoutError) as close:
-            assert await transport_impl.send_close(code=1234, message=b"some message") is False
+            await transport_impl.send_close(code=1234, message=b"some message")
 
         close.assert_called_once_with(code=1234, message=b"some message")
 
+    @pytest.mark.asyncio()
+    async def test_send_close_when_already_sent(self, transport_impl):
+        transport_impl.sent_close = True
+
+        with mock.patch.object(aiohttp.ClientWebSocketResponse, "close", side_effect=asyncio.TimeoutError) as close:
+            await transport_impl.send_close(code=1234, message=b"some message")
+
+        close.assert_not_called()
+
     @pytest.mark.asyncio()
     @pytest.mark.parametrize("trace", [True, False])
     async def test_receive_json(self, transport_impl, trace):
@@ -468,42 +477,21 @@ def __init__(self):
         mock_websocket.assert_used_once()
 
     @pytest.mark.asyncio()
-    async def test_connect_when_error_connecting(self, http_settings, proxy_settings):
-        mock_client_session = hikari_test_helpers.AsyncContextManagerMock()
-        mock_client_session.ws_connect = mock.MagicMock(side_effect=aiohttp.ClientConnectionError("some error"))
-
-        stack = contextlib.ExitStack()
-        sleep = stack.enter_context(mock.patch.object(asyncio, "sleep"))
-        stack.enter_context(mock.patch.object(aiohttp, "ClientSession", return_value=mock_client_session))
-        stack.enter_context(mock.patch.object(aiohttp, "TCPConnector"))
-        stack.enter_context(mock.patch.object(aiohttp, "ClientTimeout"))
-        stack.enter_context(
-            pytest.raises(errors.GatewayConnectionError, match=r"Failed to connect to server: 'some error'")
-        )
-        logger = mock.Mock()
-        log_filterer = mock.Mock()
-
-        with stack:
-            async with shard._GatewayTransport.connect(
-                http_settings=http_settings,
-                proxy_settings=proxy_settings,
-                logger=logger,
-                url="https://some.url",
-                log_filterer=log_filterer,
-            ):
-                pass
-
-        sleep.assert_awaited_once_with(0.25)
-        mock_client_session.assert_used_once()
-
-    @pytest.mark.asyncio()
-    async def test_connect_when_handshake_error_with_unknown_reason(self, http_settings, proxy_settings):
+    @pytest.mark.parametrize(
+        ("error", "reason"),
+        [
+            (
+                aiohttp.WSServerHandshakeError(status=123, message="some error", request_info=None, history=None),
+                "WSServerHandshakeError(None, None, status=123, message='some error')",
+            ),
+            (aiohttp.ClientOSError("some error"), "some error"),
+            (aiohttp.ClientConnectionError("some error"), "some error"),
+            (asyncio.TimeoutError("some error"), "Timeout exceeded"),
+        ],
+    )
+    async def test_connect_when_expected_error_connecting(self, http_settings, proxy_settings, error, reason):
         mock_client_session = hikari_test_helpers.AsyncContextManagerMock()
-        mock_client_session.ws_connect = mock.MagicMock(
-            side_effect=aiohttp.WSServerHandshakeError(
-                status=123, message="some error", request_info=None, history=None
-            )
-        )
+        mock_client_session.ws_connect = mock.MagicMock(side_effect=error)
 
         stack = contextlib.ExitStack()
         sleep = stack.enter_context(mock.patch.object(asyncio, "sleep"))
@@ -511,12 +499,7 @@ async def test_connect_when_handshake_error_with_unknown_reason(self, http_setti
         stack.enter_context(mock.patch.object(aiohttp, "TCPConnector"))
         stack.enter_context(mock.patch.object(aiohttp, "ClientTimeout"))
         stack.enter_context(
-            pytest.raises(
-                errors.GatewayConnectionError,
-                match=(
-                    r'Failed to connect to server: "WSServerHandshakeError\(None, None, status=123, message=\'some error\'\)"'
-                ),
-            )
+            pytest.raises(errors.GatewayConnectionError, match=re.escape(f"Failed to connect to server: {reason!r}"))
         )
         logger = mock.Mock()
         log_filterer = mock.Mock()
@@ -535,27 +518,16 @@ async def test_connect_when_handshake_error_with_unknown_reason(self, http_setti
         mock_client_session.assert_used_once()
 
     @pytest.mark.asyncio()
-    async def test_connect_when_handshake_error_with_known_reason(self, http_settings, proxy_settings):
+    async def test_connect_when_unexpected_error_connecting(self, http_settings, proxy_settings):
         mock_client_session = hikari_test_helpers.AsyncContextManagerMock()
-        mock_client_session.ws_connect = mock.MagicMock(
-            side_effect=aiohttp.WSServerHandshakeError(
-                status=500, message="some error", request_info=None, history=None
-            )
-        )
+        mock_client_session.ws_connect = mock.MagicMock(side_effect=RuntimeError("in tests"))
 
         stack = contextlib.ExitStack()
         sleep = stack.enter_context(mock.patch.object(asyncio, "sleep"))
         stack.enter_context(mock.patch.object(aiohttp, "ClientSession", return_value=mock_client_session))
         stack.enter_context(mock.patch.object(aiohttp, "TCPConnector"))
         stack.enter_context(mock.patch.object(aiohttp, "ClientTimeout"))
-        stack.enter_context(
-            pytest.raises(
-                errors.GatewayConnectionError,
-                match=(
-                    r'Failed to connect to server: "WSServerHandshakeError\(None, None, status=500, message=\'some error\'\)"'
-                ),
-            )
-        )
+        stack.enter_context(pytest.raises(RuntimeError, match="in tests"))
         logger = mock.Mock()
         log_filterer = mock.Mock()
 
@@ -643,19 +615,12 @@ def test_intents_property(self, client):
         client._intents = mock_intents
         assert client.intents is mock_intents
 
-    def test_is_alive_property(self, client):
-        client._run_task = None
-        assert client.is_alive is False
-
-    @pytest.mark.asyncio()
-    async def test_is_alive_property_with_active_future(self, client):
-        client._run_task = asyncio.get_running_loop().create_future()
-        assert client.is_alive is True
-
-    @pytest.mark.asyncio()
-    async def test_is_alive_property_with_finished_future(self, client):
-        client._run_task = aio.completed_future()
-        assert client.is_alive is False
+    @pytest.mark.parametrize(
+        ("ws", "expected"), [(None, False), (mock.Mock(sent_close=True), False), (mock.Mock(sent_close=False), True)]
+    )
+    def test_is_alive_property(self, client, ws, expected):
+        client._ws = ws
+        assert client.is_alive is expected
 
     def test_shard_count_property(self, client):
         client._shard_count = 69
@@ -1106,7 +1071,7 @@ async def test__identify(self, client):
                     "$device": "hikari v1.0.0",
                 },
                 "shard": [0, 1],
-                "intents": 32767,
+                "intents": 65535,
                 "presence": {"presence": "payload"},
             },
         }
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)