diff options
author | 2023-06-10 19:48:48 +0100 | |
---|---|---|
committer | 2023-06-10 19:48:48 +0100 | |
commit | c57fae8198cadcab3f9c0b09d1189d6b4eb9daf3 (patch) | |
tree | b318ec25f2ea2ecd7da3cd733ec8d1891e03be82 | |
parent | Add 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.rst | 1 | ||||
-rw-r--r-- | pydis_core/utils/__init__.py | 5 | ||||
-rw-r--r-- | pydis_core/utils/error_handling.py | 35 | ||||
-rw-r--r-- | pydis_core/utils/scheduling.py | 21 |
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) |