aboutsummaryrefslogtreecommitdiffstats
path: root/botcore/utils/monkey_patches.py
diff options
context:
space:
mode:
Diffstat (limited to 'botcore/utils/monkey_patches.py')
-rw-r--r--botcore/utils/monkey_patches.py83
1 files changed, 83 insertions, 0 deletions
diff --git a/botcore/utils/monkey_patches.py b/botcore/utils/monkey_patches.py
new file mode 100644
index 00000000..abbb37a5
--- /dev/null
+++ b/botcore/utils/monkey_patches.py
@@ -0,0 +1,83 @@
+"""Contains all common monkey patches, used to alter disnake to fit our needs."""
+
+import logging
+from datetime import datetime, timedelta
+from functools import partial, partialmethod
+
+from disnake import Forbidden, http
+from disnake.ext import commands
+
+log = logging.getLogger(__name__)
+
+
+class _Command(commands.Command):
+ """
+ A :obj:`disnake.ext.commands.Command` subclass which supports root aliases.
+
+ A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as
+ top-level commands rather than being aliases of the command's group. It's stored as an attribute
+ also named ``root_aliases``.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.root_aliases = kwargs.get("root_aliases", [])
+
+ if not isinstance(self.root_aliases, (list, tuple)):
+ raise TypeError("Root aliases of a command must be a list or a tuple of strings.")
+
+
+class _Group(commands.Group, _Command):
+ """
+ A :obj:`disnake.ext.commands.Group` subclass which supports root aliases.
+
+ A ``root_aliases`` keyword argument is added, which is a sequence of alias names that will act as
+ top-level groups rather than being aliases of the command's group. It's stored as an attribute
+ also named ``root_aliases``.
+ """
+
+
+def _patch_typing() -> None:
+ """
+ Sometimes Discord turns off typing events by throwing 403s.
+
+ Handle those issues by patching disnake's internal ``send_typing`` method so it ignores 403s in general.
+ """
+ log.debug("Patching send_typing, which should fix things breaking when Discord disables typing events. Stay safe!")
+
+ original = http.HTTPClient.send_typing
+ last_403 = None
+
+ async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001
+ nonlocal last_403
+ if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5):
+ log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.")
+ return
+ try:
+ await original(self, channel_id)
+ except Forbidden:
+ last_403 = datetime.utcnow()
+ log.warning("Got a 403 from typing event!")
+
+ http.HTTPClient.send_typing = honeybadger_type
+
+
+def apply_monkey_patches() -> None:
+ """
+ Applies all common monkey patches for our bots.
+
+ Patches :obj:`disnake.ext.commands.Command` and :obj:`disnake.ext.commands.Group` to support root aliases.
+ A ``root_aliases`` keyword argument is added to these two objects, which is a sequence of alias names
+ that will act as top-level groups rather than being aliases of the command's group.
+
+ It's stored as an attribute also named ``root_aliases``
+
+ Patches disnake's internal ``send_typing`` method so that it ignores 403 errors from Discord.
+ When under heavy load Discord has added a CloudFlare worker to this route, which causes 403 errors to be thrown.
+ """
+ commands.command = partial(commands.command, cls=_Command)
+ commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=_Command)
+
+ commands.group = partial(commands.group, cls=_Group)
+ commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=_Group)
+ _patch_typing()