aboutsummaryrefslogtreecommitdiffstats
path: root/botcore/utils/monkey_patches.py
blob: abbb37a5c6de3ea2f70c6578e116e86326c04f9f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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()