aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar TizzySaurus <[email protected]>2023-06-10 19:48:48 +0100
committerGravatar GitHub <[email protected]>2023-06-10 19:48:48 +0100
commitc57fae8198cadcab3f9c0b09d1189d6b4eb9daf3 (patch)
treeb318ec25f2ea2ecd7da3cd733ec8d1891e03be82
parentAdd paste service utility (#179) (diff)
Handle discord.Forbidden 90001 errors by default in `create_task()` (#177)v9.7.0
Co-authored-by: Chris Lovering <[email protected]>
-rw-r--r--docs/changelog.rst1
-rw-r--r--pydis_core/utils/__init__.py5
-rw-r--r--pydis_core/utils/error_handling.py35
-rw-r--r--pydis_core/utils/scheduling.py21
4 files changed, 55 insertions, 7 deletions
diff --git a/docs/changelog.rst b/docs/changelog.rst
index dda1012e..471f7be6 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -5,6 +5,7 @@ Changelog
=========
- :release:`9.7.0 <10th June 2023>`
- :feature:`179` Add paste service utility to upload text to our paste service.
+- :feature:`177` Automatically handle discord.Forbidden 90001 errors in all schedules
- :feature:`176` Migrate repo to use ruff for linting
diff --git a/pydis_core/utils/__init__.py b/pydis_core/utils/__init__.py
index 0ca265c9..ac5760a8 100644
--- a/pydis_core/utils/__init__.py
+++ b/pydis_core/utils/__init__.py
@@ -1,8 +1,8 @@
"""Useful utilities and tools for Discord bot development."""
from pydis_core.utils import (
- _monkey_patches, caching, channel, commands, cooldown, function, interactions, logging, members, paste_service,
- regex, scheduling
+ _monkey_patches, caching, channel, commands, cooldown, error_handling, function, interactions, logging, members,
+ paste_service, regex, scheduling
)
from pydis_core.utils._extensions import unqualify
@@ -29,6 +29,7 @@ __all__ = [
channel,
commands,
cooldown,
+ error_handling,
function,
interactions,
logging,
diff --git a/pydis_core/utils/error_handling.py b/pydis_core/utils/error_handling.py
new file mode 100644
index 00000000..51d23ca0
--- /dev/null
+++ b/pydis_core/utils/error_handling.py
@@ -0,0 +1,35 @@
+from discord import Forbidden, Message
+
+from pydis_core.utils import logging
+
+log = logging.get_logger(__name__)
+
+
+async def handle_forbidden_from_block(error: Forbidden, message: Message | None = None) -> None:
+ """
+ Handles ``discord.Forbidden`` 90001 errors, or re-raises if ``error`` isn't a 90001 error.
+
+ Args:
+ error: The raised ``discord.Forbidden`` to check.
+ message: The message to reply to and include in logs, if error is 90001 and message is provided.
+ """
+ if error.code != 90001:
+ # The error ISN'T caused by the bot attempting to add a reaction
+ # to a message whose author has blocked the bot, so re-raise it
+ raise error
+
+ if not message:
+ log.info("Failed to add reaction(s) to a message since the message author has blocked the bot")
+ return
+
+ if message:
+ log.info(
+ "Failed to add reaction(s) to message %d-%d since the message author (%d) has blocked the bot",
+ message.channel.id,
+ message.id,
+ message.author.id,
+ )
+ await message.channel.send(
+ f":x: {message.author.mention} failed to add reaction(s) to your message as you've blocked me.",
+ delete_after=30
+ )
diff --git a/pydis_core/utils/scheduling.py b/pydis_core/utils/scheduling.py
index 1e2e25e7..aa90b1e1 100644
--- a/pydis_core/utils/scheduling.py
+++ b/pydis_core/utils/scheduling.py
@@ -8,7 +8,10 @@ from collections import abc
from datetime import datetime, timezone
from functools import partial
+from discord.errors import Forbidden
+
from pydis_core.utils import logging
+from pydis_core.utils.error_handling import handle_forbidden_from_block
_background_tasks: set[asyncio.Task] = set()
@@ -77,7 +80,7 @@ class Scheduler:
coroutine.close()
return
- task = asyncio.create_task(coroutine, name=f"{self.name}_{task_id}")
+ task = asyncio.create_task(_coro_wrapper(coroutine), name=f"{self.name}_{task_id}")
task.add_done_callback(partial(self._task_done_callback, task_id))
self._scheduled_tasks[task_id] = task
@@ -238,9 +241,9 @@ def create_task(
asyncio.Task: The wrapped task.
"""
if event_loop is not None:
- task = event_loop.create_task(coro, **kwargs)
+ task = event_loop.create_task(_coro_wrapper(coro), **kwargs)
else:
- task = asyncio.create_task(coro, **kwargs)
+ task = asyncio.create_task(_coro_wrapper(coro), **kwargs)
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
@@ -248,11 +251,19 @@ def create_task(
return task
+async def _coro_wrapper(coro: abc.Coroutine[typing.Any, typing.Any, TASK_RETURN]) -> None:
+ """Wraps `coro` in a try/except block that will handle 90001 Forbidden errors."""
+ try:
+ await coro
+ except Forbidden as e:
+ await handle_forbidden_from_block(e)
+
+
def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: tuple[type[Exception], ...]) -> None:
- """Retrieve and log the exception raised in ``task`` if one exists."""
+ """Retrieve and log the exception raised in ``task``, if one exists and it's not suppressed."""
with contextlib.suppress(asyncio.CancelledError):
exception = task.exception()
- # Log the exception if one exists.
+ # Log the exception if one exists and it's not suppressed/handled.
if exception and not isinstance(exception, suppressed_exceptions):
log = logging.get_logger(__name__)
log.error(f"Error in task {task.get_name()} {id(task)}!", exc_info=exception)