aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2021-10-04 20:26:59 +0100
committerGravatar Chris Lovering <[email protected]>2021-10-04 20:38:49 +0100
commit6fbd7883fee19e62898eacea08b4d7b7f7fc74db (patch)
tree9b6002d6dad498882050e2046857b13d7562595d
parentMerge pull request #890 from python-discord/better-bookmark-error-message (diff)
Monkey patch http.send_typing to catch 403s
Sometimes discord turns off typing events by throwing 403's, so we should catch those
-rw-r--r--bot/__init__.py26
-rw-r--r--bot/command.py18
-rw-r--r--bot/group.py18
-rw-r--r--bot/monkey_patches.py78
4 files changed, 85 insertions, 55 deletions
diff --git a/bot/__init__.py b/bot/__init__.py
index c6a48105..db576cb2 100644
--- a/bot/__init__.py
+++ b/bot/__init__.py
@@ -15,28 +15,15 @@ from pathlib import Path
import arrow
from discord.ext import commands
-from bot.command import Command
+from bot import monkey_patches
from bot.constants import Client
-from bot.group import Group
# Configure the "TRACE" logging level (e.g. "log.trace(message)")
logging.TRACE = 5
logging.addLevelName(logging.TRACE, "TRACE")
-
-def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
- """
- Log 'msg % args' with severity 'TRACE'.
-
- To pass exception information, use the keyword argument exc_info with a true value, e.g.
- logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
- """
- if self.isEnabledFor(logging.TRACE):
- self._log(logging.TRACE, msg, args, **kwargs)
-
-
-logging.Logger.trace = monkeypatch_trace
+logging.Logger.trace = monkey_patches.trace_log
# Set timestamp of when execution started (approximately)
start_time = arrow.utcnow()
@@ -84,11 +71,12 @@ logging.getLogger().info("Logging initialization complete")
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+monkey_patches.patch_typing()
# Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases.
# Must be patched before any cogs are added.
-commands.command = partial(commands.command, cls=Command)
-commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command)
+commands.command = partial(commands.command, cls=monkey_patches.Command)
+commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=monkey_patches.Command)
-commands.group = partial(commands.group, cls=Group)
-commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=Group)
+commands.group = partial(commands.group, cls=monkey_patches.Group)
+commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=monkey_patches.Group)
diff --git a/bot/command.py b/bot/command.py
deleted file mode 100644
index 0fb900f7..00000000
--- a/bot/command.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from discord.ext import commands
-
-
-class Command(commands.Command):
- """
- A `discord.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.")
diff --git a/bot/group.py b/bot/group.py
deleted file mode 100644
index a7bc59b7..00000000
--- a/bot/group.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from discord.ext import commands
-
-
-class Group(commands.Group):
- """
- A `discord.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 __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 group must be a list or a tuple of strings.")
diff --git a/bot/monkey_patches.py b/bot/monkey_patches.py
new file mode 100644
index 00000000..fe81f2e3
--- /dev/null
+++ b/bot/monkey_patches.py
@@ -0,0 +1,78 @@
+import logging
+from datetime import datetime, timedelta
+
+from discord import Forbidden, http
+from discord.ext import commands
+
+log = logging.getLogger(__name__)
+
+
+def trace_log(self: logging.Logger, msg: str, *args, **kwargs) -> None:
+ """
+ Log 'msg % args' with severity 'TRACE'.
+
+ To pass exception information, use the keyword argument exc_info with a true value, e.g.
+ logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
+ """
+ if self.isEnabledFor(logging.TRACE):
+ self._log(logging.TRACE, msg, args, **kwargs)
+
+
+class Command(commands.Command):
+ """
+ A `discord.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):
+ """
+ A `discord.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 __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 group must be a list or a tuple of strings.")
+
+
+def patch_typing() -> None:
+ """
+ Sometimes discord turns off typing events by throwing 403's.
+
+ Handle those issues by patching the trigger_typing method so it ignores 403's 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!")
+ pass
+
+ http.HTTPClient.send_typing = honeybadger_type