Repository: hikari-py/hikari
Pull request #752 (2021-08-28 11:37:28)

Add a web dashboard example
	new file:   simple_dashboard/Pipfile
	new file:   simple_dashboard/Pipfile.lock
	new file:   simple_dashboard/bot.py

### Summary
<!-- Small summary of the merge request -->
This PR just aims to add an example on how to integrate Rillrate with hikari, to make a real time, dynamic web dashboard for your bot.

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
Still a draft.
- [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.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/.gitignore b/.gitignore
index c46fd678..a2c4d32f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ venv*/
 # CI pages, coverage reports, etc
 public/
 node_modules/
+package.json
 package-lock.json
 
 # PyCharm/IntelliJ-generated files
@@ -62,3 +63,5 @@ prof/
 
 # Logs
 *.log
+
+ Pipfile*
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..b6593585
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,22 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+*This file was added at the beginning of the development of version 2.0.0.dev102, so nothing before that is documented.*
+
+## [Unreleased]
+### Added
+ - Sticker methods to PartialGuild.
+ - Helpful methods to Application object.
+ - Missing Audit Log change keys.
+ - Sticker Audit Log event types.
+ - Retries for HTTP requests which fail with `5xx` status codes.
+
+### Changed
+ - Fix errors in rest sticker method docstrings.
+ - Remove duplicate raise type in api and guilds docstrings.
+
+### Fixed
+ - Handling of interaction models passed to the webhook message endpoints as the "webhook" field.
diff --git a/README.md b/README.md
index 0959289d..ba4008f8 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,16 @@
-[![Hikari PyPI version badge](https://img.shields.io/pypi/v/hikari)](https://pypi.org/project/hikari)
-[![Github Actions Status badge](https://github.com/hikari-py/hikari/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/hikari-py/hikari/actions)
-[![Supported Python versions badge](https://img.shields.io/pypi/pyversions/hikari)](https://pypi.org/project/hikari)
-[![Maintainability badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/maintainability)](https://codeclimate.com/github/hikari-py/hikari/maintainability)
-[![Test Coverage badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/test_coverage)](https://codeclimate.com/github/hikari-py/hikari/test_coverage)
-[![Open Pull Requests badge](https://img.shields.io/github/issues-pr/hikari-py/hikari)](https://github.com/hikari-py/hikari/pulls)
-[![Issues badge](https://img.shields.io/github/issues-raw/hikari-py/hikari)](https://github.com/hikari-py/hikari/issues)
-[![This project uses Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://pypi.org/project/black/)
-[![This project uses MyPy](http://www.mypy-lang.org/static/mypy_badge.svg)](https://pypi.org/project/mypy/)
-[![Documentation badge](https://img.shields.io/badge/documentation-up-00FF00.svg)](https://hikari-py.github.io/hikari/hikari)
-
-[![Discord](https://discord.com/api/guilds/574921006817476608/widget.png?style=banner2)](https://discord.gg/Jx4cNGG)
-
-# hikari
+<h1 align="center">hikari</h1>
+<p align="center">
+<a href="https://pypi.org/project/hikari"><img height="20" alt="PyPI version" src="https://img.shields.io/pypi/v/hikari"></a>
+<a href="https://pypi.org/project/hikari"><img height="20" alt="Supported python versions" src="https://img.shields.io/pypi/pyversions/hikari"></a>
+<br>
+<a href="https://github.com/hikari-py/hikari/actions"><img height="20" alt="CI status" src="https://github.com/hikari-py/hikari/actions/workflows/ci.yml/badge.svg?branch=master&event=push"></a>
+<a href="https://pypi.org/project/mypy/"><img height="20" alt="Mypy badge" src="http://www.mypy-lang.org/static/mypy_badge.svg"></a>
+<a href="https://pypi.org/project/black"><img height="20" alt="Black badge" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
+<a href="https://codeclimate.com/github/hikari-py/hikari/test_coverage"><img height="20" alt="Test coverage" src="https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/test_coverage"></a>
+<br>
+<a href="https://discord.gg/Jx4cNGG"><img height="20" alt="Discord invite" src="https://discord.com/api/guilds/574921006817476608/widget.png"></a>
+<a href="https://hikari-py.github.io/hikari/hikari"><img height="20" alt="Documentation status" src="https://img.shields.io/badge/documentation-up-00FF00.svg"></a>
+</p>
 
 An opinionated, static typed Discord microframework for Python3 and asyncio that supports Discord's V8 REST API and
 Gateway.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 2951f36c..28589b60 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -6,7 +6,7 @@
 mock==4.0.3
 
 # Py.test stuff.
-pytest==6.2.4
+pytest==6.2.5
 pytest-asyncio==0.15.1
 pytest-cov==2.12.1
 pytest-randomly==3.10.1
@@ -40,7 +40,7 @@ safety~=1.10.3
 # FORMATTING #
 #############
 
-black==21.7b0
+black==21.8b0
 isort==5.9.3
 
 ##################
diff --git a/examples/simple_dashboard/__init__.py b/examples/simple_dashboard/__init__.py
new file mode 100644
index 00000000..881603a1
--- /dev/null
+++ b/examples/simple_dashboard/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020 Nekokatt
+# Copyright (c) 2021 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
new file mode 100644
index 00000000..6d82c68d
--- /dev/null
+++ b/examples/simple_dashboard/requirements.txt
@@ -0,0 +1,1 @@
+rillrate==0.38.0
diff --git a/examples/simple_dashboard/simple_dashboard.bat b/examples/simple_dashboard/simple_dashboard.bat
new file mode 100644
index 00000000..86dd4db9
--- /dev/null
+++ b/examples/simple_dashboard/simple_dashboard.bat
@@ -0,0 +1,52 @@
+:: Copyright (c) 2020 Nekokatt
+:: Copyright (c) 2021 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
new file mode 100644
index 00000000..b1a144fd
--- /dev/null
+++ b/examples/simple_dashboard/simple_dashboard.py
@@ -0,0 +1,120 @@
+# -*- 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 = list(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
new file mode 100755
index 00000000..d23335a4
--- /dev/null
+++ b/examples/simple_dashboard/simple_dashboard.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+# Copyright (c) 2020 Nekokatt
+# Copyright (c) 2021 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/hikari/_about.py b/hikari/_about.py
index 3588948a..53c06633 100644
--- a/hikari/_about.py
+++ b/hikari/_about.py
@@ -36,5 +36,5 @@
 __issue_tracker__ = "https://github.com/hikari-py/hikari/issues"
 __license__ = "MIT"
 __url__ = "https://github.com/hikari-py/hikari"
-__version__ = "2.0.0.dev101"
+__version__ = "2.0.0.dev102"
 __git_sha1__ = "HEAD"
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index e9d288f6..1e508a68 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -177,9 +177,6 @@ async def fetch_channel(
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -2182,7 +2179,8 @@ async def delete_webhook(
     @abc.abstractmethod
     async def execute_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
         *,
@@ -2206,7 +2204,7 @@ async def execute_webhook(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snoflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2352,7 +2350,8 @@ async def execute_webhook(
     @abc.abstractmethod
     async def fetch_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
     ) -> messages_.Message:
@@ -2360,7 +2359,7 @@ async def fetch_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2398,7 +2397,8 @@ async def fetch_webhook_message(
     @abc.abstractmethod
     async def edit_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
         content: undefined.UndefinedNoneOr[typing.Any] = undefined.UNDEFINED,
@@ -2420,7 +2420,7 @@ async def edit_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -2559,7 +2559,8 @@ async def edit_webhook_message(
     @abc.abstractmethod
     async def delete_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        # MyPy might not say this but SnowflakeishOr[ExecutableWebhook] isn't valid as ExecutableWebhook isn't Unique
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
     ) -> None:
@@ -2567,7 +2568,7 @@ async def delete_webhook_message(
 
         Parameters
         ----------
-        webhook : hikari.snowflakes.SnowflakeishOr[hikari.webhooks.ExecutableWebhook]
+        webhook : typing.Union[hikari.snowflakes.Snowflakeish, hikari.webhooks.ExecutableWebhook]
             The webhook to execute. This may be the object
             or the ID of an existing webhook.
         token: builtins.str
@@ -3706,7 +3707,8 @@ async def fetch_sticker(
         Parameters
         ----------
         sticker : snowflakes.SnowflakeishOr[stickers.PartialSticker]
-            The sticker to fetch.
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
 
         Returns
         -------
@@ -3743,7 +3745,8 @@ async def fetch_guild_stickers(
         Parameters
         ----------
         guild : snowflakes.SnowflakeishOr[stickers.PartialGuild]
-            The guild to request stickers for.
+            The guild to request stickers for. This can be a guild object or the
+            ID of an existing guild.
 
         Returns
         -------
@@ -3784,9 +3787,11 @@ async def fetch_guild_sticker(
         Parameters
         ----------
         guild : snowflakes.SnowflakeishOr[stickers.PartialGuild]
-            The guild the sticker is in.
+            The guild the sticker is in. This can be a guild object or the
+            ID of an existing guild.
         sticker : snowflakes.SnowflakeishOr[stickers.PartialSticker]
-            The sticker to fetch.
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
 
         Returns
         -------
@@ -3901,7 +3906,7 @@ async def edit_sticker(
         Parameters
         ----------
         guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
-            The guild to edit the emoji on. This can be a guild object or the
+            The guild to edit the sticker on. This can be a guild object or the
             ID of an existing guild.
         sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
             The sticker to edit. This can be a sticker object or the ID of an
@@ -3964,7 +3969,7 @@ async def delete_sticker(
         guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
             The guild to delete the sticker on. This can be a guild object or
             the ID of an existing guild.
-        sticker : hikari.snowflakes.SnowflakeishOr[hikari.emojis.CustomEmoji]
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
             The sticker to delete. This can be a sticker object or the ID
             of an existing sticker.
 
diff --git a/hikari/applications.py b/hikari/applications.py
index e49c3239..ec76a364 100644
--- a/hikari/applications.py
+++ b/hikari/applications.py
@@ -613,6 +613,76 @@ def make_cover_image_url(self, *, ext: str = "png", size: int = 4096) -> typing.
             file_format=ext,
         )
 
+    async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]:
+        """Fetch the guild this application is linked to if sold on Discord.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.RESTGuild]
+            The requested guild if the application is linked to a guild, else `builtins.None`.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the guild.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if self.guild_id is not None:
+            return await self.app.rest.fetch_guild(self.guild_id)
+        return None
+
+    async def fetch_guild_preview(self) -> typing.Optional[guilds.GuildPreview]:
+        """Fetch the preview of the guild this application is linked to if sold on Discord.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.GuildPreview]
+            The requested guild preview if the application is linked to a guild, else `builtins.None`.
+
+        !!! note
+            This will only work if you are a part of that guild or it is public.
+
+        Raises
+        ------
+        hikari.errors.NotFoundError
+            If the guild is not found or you are not part of the guild.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if self.guild_id is not None:
+            return await self.app.rest.fetch_guild_preview(self.guild_id)
+
+        return None
+
 
 @attr_extensions.with_copy
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py
index 28911b09..9936332e 100644
--- a/hikari/audit_logs.py
+++ b/hikari/audit_logs.py
@@ -71,13 +71,19 @@ class AuditLogChangeKey(str, enums.Enum):
     """
 
     NAME = "name"
+    DESCRIPTION = "description"
     ICON_HASH = "icon_hash"
     SPLASH_HASH = "splash_hash"
+    DISCOVERY_SPLASH_HASH = "discovery_splash_hash"
+    BANNER_HASH = "banner_hash"
     OWNER_ID = "owner_id"
     REGION = "region"  # TODO: remove when this disappears for the most part
+    PREFERRED_LOCALE = "preferred_locale"
     RTC_REGION = "rtc_region"
     AFK_CHANNEL_ID = "afk_channel_id"
     AFK_TIMEOUT = "afk_timeout"
+    RULES_CHANNEL_ID = "rules_channel_id"
+    PUBLIC_UPDATES_CHANNEL_ID = "public_updates_channel_id"
     MFA_LEVEL = "mfa_level"
     VERIFICATION_LEVEL = "verification_level"
     EXPLICIT_CONTENT_FILTER = "explicit_content_filter"
@@ -117,6 +123,11 @@ class AuditLogChangeKey(str, enums.Enum):
     EXPIRE_GRACE_PERIOD = "expire_grace_period"
     RATE_LIMIT_PER_USER = "rate_limit_per_user"
     SYSTEM_CHANNEL_ID = "system_channel_id"
+    TAGS = "tags"
+    FORMAT_TYPE = "format_type"
+    ASSETS = "asset"
+    AVAILABLE = "available"
+    GUILD_ID = "guild_id"
 
     # Who needs consistency?
     ADD_ROLE_TO_MEMBER = "$add"
@@ -180,6 +191,9 @@ class AuditLogEventType(int, enums.Enum):
     INTEGRATION_CREATE = 80
     INTEGRATION_UPDATE = 81
     INTEGRATION_DELETE = 82
+    STICKER_CREATE = 90
+    STICKER_UPDATE = 91
+    STICKER_DELETE = 92
 
 
 @attr.define(hash=False, kw_only=True, weakref_slot=False)
diff --git a/hikari/channels.py b/hikari/channels.py
index de508a3d..2dea3637 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -978,12 +978,6 @@ def shard_id(self) -> typing.Optional[int]:
     async def fetch_guild(self) -> guilds.PartialGuild:
         """Fetch the guild linked to this channel.
 
-        Parameters
-        ----------
-        guild : hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]
-            The guild to fetch. This can be the object
-            or the ID of an existing guild.
-
         Returns
         -------
         hikari.guilds.RESTGuild
diff --git a/hikari/colors.py b/hikari/colors.py
index cf3c1a71..b0fa8665 100644
--- a/hikari/colors.py
+++ b/hikari/colors.py
@@ -570,7 +570,7 @@ def to_bytes(self, length: int, byteorder: str, *, signed: bool = True) -> bytes
 2. `hikari.colours.Colour` (an alias for `hikari.colors.Color`).
 3. A value that can be cast to an `builtins.int` (RGB hex-code).
 4. a 3-`builtins.tuple` of `builtins.int` (RGB integers in range 0 through 255).
-5. a 3-`builtins.tuple` of `builtins.int` (RGB floats in range 0 through 1).
+5. a 3-`builtins.tuple` of `builtins.float` (RGB floats in range 0 through 1).
 6. a list of `builtins.int`.
 7. a list of `builtins.float`.
 8. a `builtins.str` hex colour code.
diff --git a/hikari/guilds.py b/hikari/guilds.py
index fd76c184..3cba854f 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -60,6 +60,7 @@
 
 from hikari import channels as channels_
 from hikari import snowflakes
+from hikari import stickers
 from hikari import traits
 from hikari import undefined
 from hikari import urls
@@ -303,9 +304,6 @@ async def fetch_channel(self) -> typing.Optional[channels_.GuildChannel]:
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -1632,6 +1630,247 @@ async def fetch_emoji(self, emoji: snowflakes.SnowflakeishOr[emojis_.CustomEmoji
         """
         return await self.app.rest.fetch_emoji(self.id, emoji)
 
+    async def fetch_stickers(self) -> typing.Sequence[stickers.GuildSticker]:
+        """Fetch the stickers of the guild.
+
+        Returns
+        -------
+        typing.Sequence[hikari.stickers.GuildSticker]
+            The requested stickers.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the server.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild_stickers(self.id)
+
+    async def fetch_sticker(self, sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker]) -> stickers.GuildSticker:
+        """Fetch a sticker from the guild.
+
+        Parameters
+        ----------
+        sticker : snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to fetch. This can be a sticker object or the
+            ID of an existing sticker.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The requested sticker.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_guild_sticker(self.id, sticker)
+
+    async def create_sticker(
+        self,
+        name: str,
+        tag: str,
+        image: files.Resourceish,
+        *,
+        description: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> stickers.GuildSticker:
+        """Create a sticker in a guild.
+
+        Parameters
+        ----------
+        name : builtins.str
+            The name for the sticker.
+        tag : builtins.str
+            The tag for the sticker.
+        image : hikari.files.Resourceish
+            The 320x320 image for the sticker. Maximum upload size is 500kb.
+            This can be a still or an animated PNG or a Lottie.
+
+            !!! note
+                Lottie support is only available for verified and partnered
+                servers.
+
+        Other Parameters
+        ----------------
+        description: hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the description of the sticker.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The created sticker.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value or
+            if there are no more spaces for the sticker in the guild.
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild is not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.create_sticker(self.id, name, tag, image, description=description, reason=reason)
+
+    async def edit_sticker(
+        self,
+        sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker],
+        *,
+        name: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        description: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        tag: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> stickers.GuildSticker:
+        """Edit a sticker in a guild.
+
+        Parameters
+        ----------
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to edit. This can be a sticker object or the ID of an
+            existing sticker.
+
+        Other Parameters
+        ----------------
+        name : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new name for the sticker.
+        description : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new description for the sticker.
+        tag : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the new sticker tag.
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Returns
+        -------
+        hikari.stickers.GuildSticker
+            The edited sticker.
+
+        Raises
+        ------
+        hikari.errors.BadRequestError
+            If any of the fields that are passed have an invalid value.
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.edit_sticker(
+            self.id, sticker, name=name, description=description, tag=tag, reason=reason
+        )
+
+    async def delete_sticker(
+        self,
+        sticker: snowflakes.SnowflakeishOr[stickers.PartialSticker],
+        *,
+        reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+    ) -> None:
+        """Delete a sticker in a guild.
+
+        Parameters
+        ----------
+        sticker : hikari.snowflakes.SnowflakeishOr[hikari.stickers.PartialSticker]
+            The sticker to delete. This can be a sticker object or the ID
+            of an existing sticker.
+
+        Other Parameters
+        ----------------
+        reason : hikari.undefined.UndefinedOr[builtins.str]
+            If provided, the reason that will be recorded in the audit logs.
+            Maximum of 512 characters.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are missing `MANAGE_EMOJIS_AND_STICKERS` in the server.
+        hikari.errors.NotFoundError
+            If the guild or the sticker are not found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.delete_sticker(self.id, sticker, reason=reason)
+
     async def create_category(
         self,
         name: str,
@@ -2779,9 +3018,6 @@ async def fetch_widget_channel(self) -> typing.Optional[channels_.GuildChannel]:
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -2819,9 +3055,6 @@ async def fetch_afk_channel(self) -> typing.Optional[channels_.GuildVoiceChannel
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -2860,9 +3093,6 @@ async def fetch_system_channel(self) -> typing.Optional[channels_.GuildTextChann
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -2902,9 +3132,6 @@ async def fetch_rules_channel(self) -> typing.Optional[channels_.GuildTextChanne
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -2945,9 +3172,6 @@ async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildT
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index ab31b18a..9c5040c4 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -167,6 +167,9 @@ class GatewayBot(traits.GatewayBotAware):
         Note that this only applies to the REST API component that communicates
         with Discord, and will not affect sharding or third party HTTP endpoints
         that may be in use.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[config.ProxySettings]
         Custom proxy settings to use with network-layer logic
         in your application to get through an HTTP-proxy.
@@ -242,6 +245,7 @@ def __init__(
         intents: intents_.Intents = intents_.Intents.ALL_UNPRIVILEGED,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -284,6 +288,7 @@ def __init__(
             max_rate_limit=max_rate_limit,
             proxy_settings=self._proxy_settings,
             rest_url=rest_url,
+            max_retries=max_retries,
             token=token,
             token_type=applications.TokenType.BOT,
         )
diff --git a/hikari/impl/cache.py b/hikari/impl/cache.py
index 0b694d1b..ae54b45e 100644
--- a/hikari/impl/cache.py
+++ b/hikari/impl/cache.py
@@ -132,7 +132,7 @@ def _is_cache_enabled_for(self, required_flag: config.CacheComponents) -> bool:
         return (self._settings.components & required_flag) == required_flag
 
     @staticmethod
-    def _increment_ref_count(obj: cache_utility.RefCell[_T], increment: int = 1) -> None:
+    def _increment_ref_count(obj: cache_utility.RefCell[typing.Any], increment: int = 1) -> None:
         obj.ref_count += increment
 
     def clear(self) -> None:
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 694ae8ce..bf10d5c1 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -191,6 +191,8 @@ def __init__(self, app: traits.RESTAware) -> None:
             audit_log_models.AuditLogChangeKey.OWNER_ID: snowflakes.Snowflake,
             audit_log_models.AuditLogChangeKey.AFK_CHANNEL_ID: snowflakes.Snowflake,
             audit_log_models.AuditLogChangeKey.AFK_TIMEOUT: _deserialize_seconds_timedelta,
+            audit_log_models.AuditLogChangeKey.RULES_CHANNEL_ID: snowflakes.Snowflake,
+            audit_log_models.AuditLogChangeKey.PUBLIC_UPDATES_CHANNEL_ID: snowflakes.Snowflake,
             audit_log_models.AuditLogChangeKey.MFA_LEVEL: guild_models.GuildMFALevel,
             audit_log_models.AuditLogChangeKey.VERIFICATION_LEVEL: guild_models.GuildVerificationLevel,
             audit_log_models.AuditLogChangeKey.EXPLICIT_CONTENT_FILTER: guild_models.GuildExplicitContentFilterLevel,
@@ -217,6 +219,8 @@ def __init__(self, app: traits.RESTAware) -> None:
             audit_log_models.AuditLogChangeKey.EXPIRE_GRACE_PERIOD: _deserialize_day_timedelta,
             audit_log_models.AuditLogChangeKey.RATE_LIMIT_PER_USER: _deserialize_seconds_timedelta,
             audit_log_models.AuditLogChangeKey.SYSTEM_CHANNEL_ID: snowflakes.Snowflake,
+            audit_log_models.AuditLogChangeKey.FORMAT_TYPE: sticker_models.StickerFormatType,
+            audit_log_models.AuditLogChangeKey.GUILD_ID: snowflakes.Snowflake,
             audit_log_models.AuditLogChangeKey.ADD_ROLE_TO_MEMBER: self._deserialize_audit_log_change_roles,
             audit_log_models.AuditLogChangeKey.REMOVE_ROLE_FROM_MEMBER: self._deserialize_audit_log_change_roles,
             audit_log_models.AuditLogChangeKey.PERMISSION_OVERWRITES: self._deserialize_audit_log_overwrites,
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 0d615dfe..9fa42e78 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -114,6 +114,8 @@
 _X_RATELIMIT_LIMIT_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Limit")
 _X_RATELIMIT_REMAINING_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Remaining")
 _X_RATELIMIT_RESET_AFTER_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Reset-After")
+_RETRY_ERROR_CODES: typing.Final[typing.Set[int]] = {500, 502, 503, 504}
+_MAX_BACKOFF_DURATION: typing.Final[int] = 16
 
 
 class ClientCredentialsStrategy(rest_api.TokenStrategy):
@@ -288,6 +290,9 @@ class RESTApp(traits.ExecutorAware):
         rate limits.
 
         Defaults to five minutes if unspecified.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[hikari.config.ProxySettings]
         Proxy settings to use. If `builtins.None` then no proxy configuration
         will be used.
@@ -304,6 +309,7 @@ class RESTApp(traits.ExecutorAware):
         "_executor",
         "_http_settings",
         "_max_rate_limit",
+        "_max_retries",
         "_proxy_settings",
         "_url",
     )
@@ -314,6 +320,7 @@ def __init__(
         executor: typing.Optional[concurrent.futures.Executor] = None,
         http_settings: typing.Optional[config.HTTPSettings] = None,
         max_rate_limit: float = 300,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         url: typing.Optional[str] = None,
     ) -> None:
@@ -321,6 +328,7 @@ def __init__(
         self._proxy_settings = config.ProxySettings() if proxy_settings is None else proxy_settings
         self._executor = executor
         self._max_rate_limit = max_rate_limit
+        self._max_retries = max_retries
         self._url = url
 
     @property
@@ -409,6 +417,7 @@ def acquire(
             executor=self._executor,
             http_settings=self._http_settings,
             max_rate_limit=self._max_rate_limit,
+            max_retries=self._max_retries,
             proxy_settings=self._proxy_settings,
             token=token,
             token_type=token_type,
@@ -503,6 +512,9 @@ class RESTClientImpl(rest_api.RESTClient):
 
         This is provided since some endpoints may respond with non-sensible
         rate limits.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     token : typing.Union[builtins.str, builtins.None, hikari.api.rest.TokenStrategy]
         The bot or bearer token. If no token is to be used,
         this can be undefined.
@@ -522,6 +534,7 @@ class RESTClientImpl(rest_api.RESTClient):
     builtins.ValueError
         * If `token_type` is provided when a token strategy is passed for `token`.
         * if `token_type` is left as `builtins.None` when a string is passed for `token`.
+        * If the a value more than 5 is provided for `max_retries`
     """
 
     __slots__: typing.Sequence[str] = (
@@ -531,6 +544,7 @@ class RESTClientImpl(rest_api.RESTClient):
         "_http_settings",
         "_live_attributes",
         "_max_rate_limit",
+        "_max_retries",
         "_proxy_settings",
         "_rest_url",
         "_token",
@@ -549,17 +563,22 @@ def __init__(
         executor: typing.Optional[concurrent.futures.Executor],
         http_settings: config.HTTPSettings,
         max_rate_limit: float,
+        max_retries: int = 3,
         proxy_settings: config.ProxySettings,
         token: typing.Union[str, None, rest_api.TokenStrategy],
         token_type: typing.Union[applications.TokenType, str, None],
         rest_url: typing.Optional[str],
     ) -> None:
+        if max_retries > 5:
+            raise ValueError("A value above 5 was provided for 'max_retries'")
+
         self._cache = cache
         self._entity_factory = entity_factory
         self._executor = executor
         self._http_settings = http_settings
         self._live_attributes: typing.Optional[_LiveAttributes] = None
         self._max_rate_limit = max_rate_limit
+        self._max_retries = max_retries
         self._proxy_settings = proxy_settings
 
         self._token: typing.Union[str, rest_api.TokenStrategy, None] = None
@@ -696,6 +715,11 @@ async def _request(
         headers.put(_X_AUDIT_LOG_REASON_HEADER, reason)
 
         url = compiled_route.create_url(self._rest_url)
+
+        # This is initiated the first time we hit a 5xx error to save memory when nothing goes wrong
+        backoff: typing.Optional[rate_limits.ExponentialBackOff] = None
+        retries_done = 0
+
         while True:
             try:
                 uuid = time.uuid()
@@ -759,6 +783,23 @@ async def _request(
                     real_url = str(response.real_url)
                     raise errors.HTTPError(f"Expected JSON [{response.content_type=}, {real_url=}]")
 
+                # Handling 5xx errors
+                if response.status in _RETRY_ERROR_CODES and retries_done < self._max_retries:
+                    if backoff is None:
+                        backoff = rate_limits.ExponentialBackOff(maximum=_MAX_BACKOFF_DURATION)
+
+                    sleep_time = next(backoff)
+                    _LOGGER.warning(
+                        "Received status %s on request, backing off for %.2fs and retrying. Retries remaining: %s",
+                        response.status,
+                        sleep_time,
+                        self._max_retries - retries_done,
+                    )
+                    retries_done += 1
+
+                    await asyncio.sleep(sleep_time)
+                    raise self._RetryRequest
+
                 can_re_auth = response.status == 401 and not (auth or no_auth or retried)
                 if can_re_auth and isinstance(self._token, rest_api.TokenStrategy):
                     assert token is not None
@@ -1725,7 +1766,7 @@ async def delete_webhook(
 
     async def execute_webhook(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         content: undefined.UndefinedOr[typing.Any] = undefined.UNDEFINED,
         *,
@@ -1745,7 +1786,9 @@ async def execute_webhook(
         ] = undefined.UNDEFINED,
         flags: typing.Union[undefined.UndefinedType, int, messages_.MessageFlag] = undefined.UNDEFINED,
     ) -> messages_.Message:
-        route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=webhook, token=token)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.POST_WEBHOOK_WITH_TOKEN.compile(webhook=webhook_id, token=token)
 
         body = data_binding.JSONObjectBuilder()
         body.put("username", username)
@@ -1772,18 +1815,20 @@ async def execute_webhook(
 
     async def fetch_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.PartialMessage],
     ) -> messages_.Message:
-        route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
         response = await self._request(route, no_auth=True)
         assert isinstance(response, dict)
         return self._entity_factory.deserialize_message(response)
 
     async def edit_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
         content: undefined.UndefinedNoneOr[typing.Any] = undefined.UNDEFINED,
@@ -1801,7 +1846,9 @@ async def edit_webhook_message(
             typing.Union[snowflakes.SnowflakeishSequence[guilds.PartialRole], bool]
         ] = undefined.UNDEFINED,
     ) -> messages_.Message:
-        route = routes.PATCH_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        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 = data_binding.JSONObjectBuilder()
         return await self._edit_message(
             route,
@@ -1821,11 +1868,13 @@ async def edit_webhook_message(
 
     async def delete_webhook_message(
         self,
-        webhook: snowflakes.SnowflakeishOr[webhooks.ExecutableWebhook],
+        webhook: typing.Union[webhooks.ExecutableWebhook, snowflakes.Snowflakeish],
         token: str,
         message: snowflakes.SnowflakeishOr[messages_.Message],
     ) -> None:
-        route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=webhook, token=token, message=message)
+        # int(ExecutableWebhook) isn't guaranteed to be valid nor the ID used to execute this entity as a webhook.
+        webhook_id = webhook if isinstance(webhook, int) else webhook.webhook_id
+        route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=webhook_id, token=token, message=message)
         await self._request(route, no_auth=True)
 
     async def fetch_gateway_url(self) -> str:
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index 24d82c45..99d793e1 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -149,6 +149,9 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer):
         Note that this only applies to the REST API component that communicates
         with Discord, and will not affect sharding or third party HTTP endpoints
         that may be in use.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[config.ProxySettings]
         Custom proxy settings to use with network-layer logic
         in your application to get through an HTTP-proxy.
@@ -197,6 +200,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -216,6 +220,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -234,6 +239,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -261,6 +267,7 @@ def __init__(
             executor=self._executor,
             http_settings=self._http_settings,
             max_rate_limit=max_rate_limit,
+            max_retries=max_retries,
             proxy_settings=self._proxy_settings,
             rest_url=rest_url,
             token=token,
diff --git a/hikari/internal/enums.py b/hikari/internal/enums.py
index b9a07efd..d4c565ec 100644
--- a/hikari/internal/enums.py
+++ b/hikari/internal/enums.py
@@ -82,7 +82,7 @@ def __setitem__(self, name: str, value: typing.Any) -> None:
                 raise TypeError(f"Cannot have unhashable values in this enum type ({name}: {value!r})") from None
 
         if name in self.names_to_values:
-            raise TypeError("Cannot define same name twice")
+            raise TypeError(f"Cannot define {name!r} name multiple times")
         if value in self.values_to_names:
             # We must have defined some alias, so just register the name
             self.names_to_values[name] = value
diff --git a/mypy.ini b/mypy.ini
index 31ac9bbc..391d8e05 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -27,3 +27,6 @@ warn_return_any = true
 warn_unreachable = true
 warn_unused_configs = true
 warn_unused_ignores = true
+
+[mypy-rillrate.*]
+ignore_missing_imports = true
diff --git a/scripts/deploy-manually.sh b/scripts/deploy-manually.sh
index 7c79b0e2..3b783ee5 100755
--- a/scripts/deploy-manually.sh
+++ b/scripts/deploy-manually.sh
@@ -41,4 +41,4 @@ echo "Detected GITHUB_SHA to be ${GITHUB_SHA}"
 
 set -x
 rm public -Rf || true
-. scripts/deploy.sh
+bash scripts/deploy.sh
diff --git a/scripts/deploy-pages.sh b/scripts/deploy-pages.sh
index c33d07ef..ebda3c9a 100755
--- a/scripts/deploy-pages.sh
+++ b/scripts/deploy-pages.sh
@@ -24,9 +24,9 @@ nox --sessions pages
 cd public || exit 1
 
 git init
-git config user.name "davfsa"
-git config user.email "29100934+davfsa@users.noreply.github.com"
-git remote add origin https://davfsa:${GITHUB_TOKEN}@github.com/${GITHUB_REPO_SLUG}
+git config user.name "github-actions"
+git config user.email "github-actions@github.com"
+git remote add origin "https://github-actions:${GITHUB_TOKEN}@github.com/${GITHUB_REPO_SLUG}"
 
 git checkout -B gh-pages
 git add -Av .
diff --git a/scripts/deploy_webhook.sh b/scripts/deploy-webhook.sh
similarity index 64%
rename from scripts/deploy_webhook.sh
rename to scripts/deploy-webhook.sh
index 62627af2..e335e519 100644
--- a/scripts/deploy_webhook.sh
+++ b/scripts/deploy-webhook.sh
@@ -19,22 +19,20 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-# We first format the payload and then replace ' with " to make it valid JSON
-PAYLOAD=$(echo "{
-        'username': 'Github Actions',
-        'embeds': [
-            {
-                'title': '${GITHUB_TAG} has been deployed to PyPI',
-                'color': 6697881,
-                'description': 'Install it now by executing: \`\`\`pip install hikari==${GITHUB_TAG}\`\`\`',
-                'footer': {
-                    'text': 'SHA: ${GITHUB_SHA}'
-                }
+curl \
+  -X POST \
+  -H "Content-Type: application/json" \
+  "${DEPLOY_WEBHOOK_URL}" \
+  -d '{
+        "username": "Github Actions",
+        "embeds": [
+          {
+            "title": "'"${VERSION} has been deployed to PyPI"'",
+            "color": 6697881,
+            "description": "'"Install it now by executing: \`\`\`pip install hikari==${VERSION}\`\`\`\\nDocumentation can be found at https://hikari-py.github.io/docs/${VERSION}"'",
+            "footer": {
+              "text": "'"SHA: ${REF}"'"
             }
+          }
         ]
-    }" | tr "'" '"')
-
-curl --request POST \
-  --url ${DEPLOY_WEBHOOK_URL} \
-  --header 'Content-Type: application/json' \
-  --data "${PAYLOAD}"
+    }'
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
index 9103e76e..9a7c5a97 100755
--- a/scripts/deploy.sh
+++ b/scripts/deploy.sh
@@ -37,8 +37,8 @@ if [ -z "${TWINE_USERNAME}" ]; then echo '$TWINE_USERNAME environment variable i
 if [ -z ${TWINE_PASSWORD+x} ]; then echo '$TWINE_PASSWORD environment variable is missing' && exit 1; fi
 if [ -z "${TWINE_PASSWORD}" ]; then echo '$TWINE_PASSWORD environment variable is empty' && exit 1; fi
 
-VERSION=${GITHUB_TAG}
-REF=${GITHUB_SHA}
+export VERSION=${GITHUB_TAG}
+export REF=${GITHUB_SHA}
 
 echo "===== INSTALLING DEPENDENCIES ====="
 # Note: We install each of these separately due to issues with the new PIP resolver
@@ -74,7 +74,7 @@ echo "-- Uploading to PyPI --"
 python -m twine upload --disable-progress-bar --skip-existing dist/* --non-interactive --repository-url https://upload.pypi.org/legacy/
 
 echo "===== DEPLOYING PAGES ====="
-source scripts/deploy-pages.sh
+bash scripts/deploy-pages.sh
 
 echo "===== UPDATING VERSION IN REPOSITORY ====="
 NEW_VERSION=$(python scripts/increase_version_number.py "${VERSION}")
@@ -82,8 +82,8 @@ NEW_VERSION=$(python scripts/increase_version_number.py "${VERSION}")
 echo "-- Setting up git --"
 git fetch origin
 git checkout -f master
-git config user.name "davfsa"
-git config user.email "29100934+davfsa@users.noreply.github.com"
+git config user.name "github-actions"
+git config user.email "github-actions@github.com"
 
 echo "-- Bumping master version to ${NEW_VERSION} --"
 sed "s|^__version__.*|__version__ = \"${NEW_VERSION}\"|g" -i hikari/_about.py
@@ -93,4 +93,4 @@ git commit -am "Bump version to ${NEW_VERSION}"
 git push
 
 echo "===== SENDING WEBHOOK ====="
-source scripts/deploy-webhook.sh
+bash scripts/deploy-webhook.sh


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index 9958cee1..4c7f7cad 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -113,7 +113,12 @@ def bot(
 
         with stack:
             return bot_impl.GatewayBot(
-                "token", executor=executor, http_settings=http_settings, proxy_settings=proxy_settings, intents=intents
+                "token",
+                executor=executor,
+                http_settings=http_settings,
+                proxy_settings=proxy_settings,
+                intents=intents,
+                max_retries=0,
             )
 
     def test_init(self):
@@ -144,6 +149,7 @@ def test_init(self):
                 intents=intents,
                 logs="DEBUG",
                 max_rate_limit=200,
+                max_retries=0,
                 proxy_settings=proxy_settings,
                 rest_url="somewhere.com",
             )
@@ -167,6 +173,7 @@ def test_init(self):
             executor=executor,
             http_settings=bot._http_settings,
             max_rate_limit=200,
+            max_retries=0,
             proxy_settings=bot._proxy_settings,
             rest_url="somewhere.com",
             token="token",
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index c71715f7..43f915db 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -42,6 +42,7 @@
 from hikari import undefined
 from hikari import urls
 from hikari import users
+from hikari import webhooks
 from hikari.api import rest as rest_api
 from hikari.impl import buckets
 from hikari.impl import entity_factory
@@ -330,6 +331,7 @@ def rest_app():
         executor=None,
         http_settings=mock.Mock(spec_set=config.HTTPSettings),
         max_rate_limit=float("inf"),
+        max_retries=0,
         proxy_settings=mock.Mock(spec_set=config.ProxySettings),
         url="https://some.url",
     )
@@ -369,6 +371,7 @@ def test_acquire(self, rest_app):
             executor=rest_app._executor,
             http_settings=rest_app._http_settings,
             max_rate_limit=float("inf"),
+            max_retries=0,
             proxy_settings=rest_app._proxy_settings,
             token="token",
             token_type="Type",
@@ -393,6 +396,7 @@ def test_acquire_defaults_to_bearer_for_a_string_token(self, rest_app):
             executor=rest_app._executor,
             http_settings=rest_app._http_settings,
             max_rate_limit=float("inf"),
+            max_retries=0,
             proxy_settings=rest_app._proxy_settings,
             token="token",
             token_type=applications.TokenType.BEARER,
@@ -435,6 +439,7 @@ def rest_client(rest_client_class, live_attributes, mock_cache):
         proxy_settings=mock.Mock(spec=config.ProxySettings),
         token="some_token",
         token_type="tYpe",
+        max_retries=0,
         rest_url="https://some.where/api/v3",
         executor=mock.Mock(),
         entity_factory=mock.Mock(),
@@ -1989,19 +1994,20 @@ async def test_execute_webhook(self, rest_client):
 
     async def test_execute_webhook_when_attachment_and_attachments_given(self, rest_client):
         with pytest.raises(ValueError, match="You may only specify one of 'attachment' or 'attachments', not both"):
-            await rest_client.execute_webhook(StubModel(123), "token", attachment=object(), attachments=object())
+            await rest_client.execute_webhook(123, "token", attachment=object(), attachments=object())
 
     async def test_execute_webhook_when_embed_and_embeds_given(self, rest_client):
         with pytest.raises(ValueError, match="You may only specify one of 'embed' or 'embeds', not both"):
-            await rest_client.execute_webhook(StubModel(123), "token", embed=object(), embeds=object())
+            await rest_client.execute_webhook(123, "token", embed=object(), embeds=object())
 
-    async def test_fetch_webhook_message(self, rest_client):
+    @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=432), 432])
+    async def test_fetch_webhook_message(self, rest_client, webhook):
         message_obj = mock.Mock()
-        expected_route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=123, token="hi, im a token", message=456)
+        expected_route = routes.GET_WEBHOOK_MESSAGE.compile(webhook=432, token="hi, im a token", message=456)
         rest_client._request = mock.AsyncMock(return_value={"id": "456"})
         rest_client._entity_factory.deserialize_message = mock.Mock(return_value=message_obj)
 
-        assert await rest_client.fetch_webhook_message(StubModel(123), "hi, im a token", StubModel(456)) is message_obj
+        assert await rest_client.fetch_webhook_message(webhook, "hi, im a token", StubModel(456)) is message_obj
         rest_client._request.assert_awaited_once_with(expected_route, no_auth=True)
         rest_client._entity_factory.deserialize_message.assert_called_once_with({"id": "456"})
 
@@ -2009,11 +2015,12 @@ async def test_fetch_webhook_message(self, rest_client):
     async def test_edit_webhook_message(self, rest_client):
         ...  # TODO: Implement
 
-    async def test_delete_webhook_message(self, rest_client):
+    @pytest.mark.parametrize("webhook", [mock.Mock(webhooks.ExecutableWebhook, webhook_id=123), 123])
+    async def test_delete_webhook_message(self, rest_client, webhook):
         expected_route = routes.DELETE_WEBHOOK_MESSAGE.compile(webhook=123, token="token", message=456)
         rest_client._request = mock.AsyncMock()
 
-        await rest_client.delete_webhook_message(StubModel(123), "token", StubModel(456))
+        await rest_client.delete_webhook_message(webhook, "token", StubModel(456))
         rest_client._request.assert_awaited_once_with(expected_route, no_auth=True)
 
     async def test_fetch_gateway_url(self, rest_client):
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index 26078818..109957ec 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -85,7 +85,11 @@ def mock_rest_bot(
 
         with stack:
             return hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, slots_=False)(
-                "token", http_settings=mock_http_settings, proxy_settings=mock_proxy_settings, executor=mock_executor
+                "token",
+                http_settings=mock_http_settings,
+                proxy_settings=mock_proxy_settings,
+                executor=mock_executor,
+                max_retries=0,
             )
 
     def test___init__(
@@ -116,6 +120,7 @@ def test___init__(
                 http_settings=mock_http_settings,
                 logs="ERROR",
                 max_rate_limit=32123123,
+                max_retries=0,
                 proxy_settings=mock_proxy_settings,
                 rest_url="hresresres",
             )
@@ -128,6 +133,7 @@ def test___init__(
                 executor=mock_executor,
                 http_settings=mock_http_settings,
                 max_rate_limit=32123123,
+                max_retries=0,
                 proxy_settings=mock_proxy_settings,
                 rest_url="hresresres",
                 token="token",
@@ -176,6 +182,7 @@ def test___init___generates_default_settings(self):
                 executor=None,
                 http_settings=config.HTTPSettings.return_value,
                 max_rate_limit=300.0,
+                max_retries=3,
                 proxy_settings=config.ProxySettings.return_value,
                 rest_url=None,
                 token="token",
diff --git a/tests/hikari/test_applications.py b/tests/hikari/test_applications.py
index 5368fbc3..7e0fe999 100644
--- a/tests/hikari/test_applications.py
+++ b/tests/hikari/test_applications.py
@@ -27,6 +27,8 @@
 from hikari import applications
 from hikari import urls
 from hikari import users
+from hikari.errors import ForbiddenError
+from hikari.errors import UnauthorizedError
 from hikari.internal import routes
 from tests.hikari import hikari_test_helpers
 
@@ -154,6 +156,33 @@ def test_make_cover_image_url_when_hash_is_not_None(self, model):
             urls.CDN_URL, application_id=123, hash="ahashcover", size=1, file_format="jpeg"
         )
 
+    @pytest.mark.asyncio()
+    async def test_fetch_guild(self, model):
+        model.guild_id = 1234
+        model.fetch_guild = mock.AsyncMock()
+
+        model.fetch_guild.return_value.id = model.guild_id
+        assert (await model.fetch_guild()).id == model.guild_id
+
+        model.fetch_guild.side_effect = UnauthorizedError(
+            "blah blah", "interesting", "foo bar", "this is an error", 403
+        )
+        with pytest.raises(UnauthorizedError):
+            await model.fetch_guild()
+
+    @pytest.mark.asyncio()
+    async def test_fetch_guild_preview(self, model):
+        model.fetch_guild_preview = mock.AsyncMock()
+
+        model.fetch_guild_preview.return_value.description = "poggers"
+        assert (await model.fetch_guild_preview()).description == "poggers"
+
+        model.fetch_guild_preview.side_effect = ForbiddenError(
+            "blah blah", "interesting", "foo bar", "this is an error", 403
+        )
+        with pytest.raises(ForbiddenError):
+            await model.fetch_guild_preview()
+
 
 class TestPartialOAuth2Token:
     def test__str__(self):
diff --git a/tests/hikari/test_guilds.py b/tests/hikari/test_guilds.py
index e921371c..28d697fd 100644
--- a/tests/hikari/test_guilds.py
+++ b/tests/hikari/test_guilds.py
@@ -529,6 +529,73 @@ async def test_fetch_emoji(self, model):
         model.app.rest.fetch_emoji.assert_awaited_once_with(model.id, 349)
         assert emoji is model.app.rest.fetch_emoji.return_value
 
+    @pytest.mark.asyncio()
+    async def test_fetch_stickers(self, model):
+        model.app.rest.fetch_guild_stickers = mock.AsyncMock()
+
+        stickers = await model.fetch_stickers()
+
+        model.app.rest.fetch_guild_stickers.assert_awaited_once_with(model.id)
+        assert stickers is model.app.rest.fetch_guild_stickers.return_value
+
+    @pytest.mark.asyncio()
+    async def test_fetch_sticker(self, model):
+        model.app.rest.fetch_guild_sticker = mock.AsyncMock()
+
+        sticker = await model.fetch_sticker(6969)
+
+        model.app.rest.fetch_guild_sticker.assert_awaited_once_with(model.id, 6969)
+        assert sticker is model.app.rest.fetch_guild_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_create_sticker(self, model):
+        model.app.rest.create_sticker = mock.AsyncMock()
+        file = object()
+
+        sticker = await model.create_sticker("NewSticker", "funny", file)
+
+        model.app.rest.create_sticker.assert_awaited_once_with(
+            90210,
+            "NewSticker",
+            "funny",
+            file,
+            description=undefined.UNDEFINED,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.create_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_edit_sticker(self, model):
+        model.app.rest.edit_sticker = mock.AsyncMock()
+
+        sticker = await model.edit_sticker(4567, name="Brilliant", tag="parmesan", description="amazing")
+
+        model.app.rest.edit_sticker.assert_awaited_once_with(
+            90210,
+            4567,
+            name="Brilliant",
+            tag="parmesan",
+            description="amazing",
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.edit_sticker.return_value
+
+    @pytest.mark.asyncio()
+    async def test_delete_sticker(self, model):
+        model.app.rest.delete_sticker = mock.AsyncMock()
+
+        sticker = await model.delete_sticker(951)
+
+        model.app.rest.delete_sticker.assert_awaited_once_with(
+            90210,
+            951,
+            reason=undefined.UNDEFINED,
+        )
+
+        assert sticker is model.app.rest.delete_sticker.return_value
+
     @pytest.mark.asyncio()
     async def test_create_category(self, model):
         model.app.rest.create_guild_category = mock.AsyncMock()