aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--botcore/utils/__init__.py3
-rw-r--r--botcore/utils/monkey_patches.py83
2 files changed, 85 insertions, 1 deletions
diff --git a/botcore/utils/__init__.py b/botcore/utils/__init__.py
index 71354334..671ef49f 100644
--- a/botcore/utils/__init__.py
+++ b/botcore/utils/__init__.py
@@ -1,6 +1,6 @@
"""Useful utilities and tools for discord bot development."""
-from botcore.utils import (caching, channel, extensions, logging, members, regex, scheduling)
+from botcore.utils import (caching, channel, extensions, logging, members, monkey_patches, regex, scheduling)
__all__ = [
caching,
@@ -8,6 +8,7 @@ __all__ = [
extensions,
logging,
members,
+ monkey_patches,
regex,
scheduling,
]
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()