From b450dcb7e13a4880c6bbd17b43de6e83674e82a8 Mon Sep 17 00:00:00 2001 From: Wiktor Jaworski Date: Sun, 18 Feb 2024 14:06:26 +0100 Subject: [PATCH 1/5] Print error to console if sending reminder fails --- sggwbot/calendar.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/sggwbot/calendar.py b/sggwbot/calendar.py index b6ea83a..09f8727 100644 --- a/sggwbot/calendar.py +++ b/sggwbot/calendar.py @@ -1437,10 +1437,25 @@ def _reminder_methods( ) -> Generator[Coroutine[Any, Any, None], None, None]: current_time = datetime.datetime.now() guild: Guild = self._bot.get_default_guild() # type: ignore + + async def send_reminder(event: Event) -> None: + reminder = event.reminder + assert reminder is not None + try: + await reminder.send(ReminderGenerator(event, guild)) + except (InvalidSettingsFile, ValueError, DiscordException) as e: + Console.specific( + f"An error occurred while sending the reminder for the event " + f"'{event.full_info}': {e}", + "Calendar", + FontColour.RED, + bold_type=True, + ) + for event in self._calendar_model.calendar_data: reminder = event.reminder if reminder and reminder.datetime <= current_time and not reminder.is_sent: - yield reminder.send(ReminderGenerator(event, guild)) + yield send_reminder(event) @dataclass(slots=True, frozen=True) @@ -1842,9 +1857,17 @@ def get_channel(self, guild: Guild) -> TextChannel | None: ------- :class:`nextcord.TextChannel` | `None` The channel to send the reminder to. + + Raises + ------ + ValueError + The channel with the ID is not found or is not a text channel. """ channel = guild.get_channel(self.channel_id) - assert channel is None or isinstance(channel, TextChannel) + if channel is None: + raise ValueError(f"Channel with ID {self.channel_id} not found") + if not isinstance(channel, TextChannel): + raise ValueError(f"Channel with ID {self.channel_id} is not a text channel") return channel def get_roles(self, guild: Guild) -> list[Role]: From 7f1058349c72ffda3361c188418337b16bd3197d Mon Sep 17 00:00:00 2001 From: Wiktor Jaworski Date: Sun, 18 Feb 2024 14:06:39 +0100 Subject: [PATCH 2/5] Add information about missing permission to send reminders --- sggwbot/calendar.py | 68 ++++++++++++++++++++++++++++++++++----------- sggwbot/errors.py | 5 ++++ 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/sggwbot/calendar.py b/sggwbot/calendar.py index 09f8727..da463bd 100644 --- a/sggwbot/calendar.py +++ b/sggwbot/calendar.py @@ -37,9 +37,11 @@ from nextcord.utils import format_dt from sggwbot.console import Console, FontColour -from sggwbot.errors import ExceptionData, InvalidSettingsFile, UpdateEmbedError +from sggwbot.errors import (ExceptionData, InvalidSettingsFile, + MissingPermission, UpdateEmbedError) from sggwbot.models import ControllerWithEmbed, EmbedModel, Model -from sggwbot.utils import InteractionUtils, Matcher, SmartDict, wait_until_midnight +from sggwbot.utils import (InteractionUtils, Matcher, SmartDict, + wait_until_midnight) if TYPE_CHECKING: from nextcord.guild import Guild @@ -2064,7 +2066,17 @@ def __init__(self, event: Event, guild: Guild): ) self.add_item(self.more_info_input) - @InteractionUtils.with_info(catch_exceptions=[ValueError, UpdateEmbedError]) + @InteractionUtils.with_info( + catch_exceptions=[ + ValueError, + UpdateEmbedError, + ExceptionData( + MissingPermission, + with_traceback_in_log=False, + with_traceback_in_response=False, + ), + ] + ) async def callback(self, interaction: Interaction) -> None: """The callback for the modal. @@ -2077,8 +2089,22 @@ async def callback(self, interaction: Interaction) -> None: assert isinstance(member, Member) await interaction.response.defer() + roles = self._find_roles(self.roles_to_ping_input.value or "") + roles.sort(key=lambda i: i.position, reverse=True) + + channel = self._find_channel(self.channel_to_send_input.value or "") + self._check_permissions(channel) + + if (datetime_value := self.datetime_input.value) is None: + raise ValueError("The datetime is invalid.") + dt = datetime.datetime.strptime(datetime_value, Reminder.DT_FORMAT) + self._validate_datetime(dt) + + content = self.content_input.value or self.event.description + more_info = self.more_info_input.value or "" + old_reminder = self.event.reminder - reminder = self._create_new_reminder() + reminder = self._create_new_reminder(dt, content, more_info, channel, roles) self.event.reminder = reminder self._send_info_to_console(member, old_reminder, reminder) @@ -2106,19 +2132,29 @@ def _validate_datetime(self, dt: datetime.datetime) -> None: if dt.date() > self.event.datetime.date(): raise ValueError("The datetime must be before the event.") - def _create_new_reminder(self) -> Reminder: - roles = self._find_roles(self.roles_to_ping_input.value or "") - roles.sort(key=lambda i: i.position, reverse=True) - channel = self._find_channel(self.channel_to_send_input.value or "") - content = self.content_input.value or self.event.description - more_info = self.more_info_input.value or "" - - if (datetime_value := self.datetime_input.value) is None: - raise ValueError("The datetime is invalid.") - - dt = datetime.datetime.strptime(datetime_value, Reminder.DT_FORMAT) - self._validate_datetime(dt) + @staticmethod + def _check_permissions(channel: TextChannel) -> None: + needed_permissions = [ + "view_channel", + "send_messages", + "embed_links", + "read_message_history", + "read_messages", + ] + for permission in needed_permissions: + if not getattr(channel.permissions_for(channel.guild.me), permission): + raise MissingPermission( + f"Missing '{permission}' permission in {channel.mention}." + ) + def _create_new_reminder( # pylint: disable=too-many-arguments + self, + dt: datetime.datetime, + content: str, + more_info: str, + channel: TextChannel, + roles: list[Role], + ) -> Reminder: return Reminder( dt.isoformat(), content, diff --git a/sggwbot/errors.py b/sggwbot/errors.py index 6a9612e..6f6fac6 100644 --- a/sggwbot/errors.py +++ b/sggwbot/errors.py @@ -25,10 +25,15 @@ class AttachmentError(SGGWBotError): class NoVoiceConnection(SGGWBotError): """No voice connection.""" + class InvalidSettingsFile(SGGWBotError): """Invalid settings file.""" +class MissingPermission(SGGWBotError): + """Missing permission.""" + + @dataclass class ExceptionData: """Exception data with attributes to be passed to the error handler. From c3c0c16d802274c0c966ccc79218ad0cda278df4 Mon Sep 17 00:00:00 2001 From: Wiktor Jaworski Date: Sun, 18 Feb 2024 14:07:40 +0100 Subject: [PATCH 3/5] Update to 0.8.0-beta.1 --- sggwbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sggwbot/__init__.py b/sggwbot/__init__.py index 39d9341..467886b 100644 --- a/sggwbot/__init__.py +++ b/sggwbot/__init__.py @@ -19,7 +19,7 @@ __author__ = "Wiktor Jaworski" __license__ = "MIT" __copyright__ = "Copyright 2023, 2024 Wiktor Jaworski" -__version__ = "0.8.0-beta" +__version__ = "0.8.0-beta.1" from . import console, errors, utils from .sggw_bot import SGGWBot From 1daa3048d7992b42c38ca232939ab359a6720617 Mon Sep 17 00:00:00 2001 From: Wiktor Jaworski Date: Tue, 20 Feb 2024 13:08:09 +0100 Subject: [PATCH 4/5] Fix unnecessary reminder deletion after editing event --- sggwbot/calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sggwbot/calendar.py b/sggwbot/calendar.py index da463bd..6548c0c 100644 --- a/sggwbot/calendar.py +++ b/sggwbot/calendar.py @@ -1250,6 +1250,7 @@ async def callback(self, interaction: Interaction) -> None: if old_event is not None: self._controller.model.remove_event_from_json(old_event) + event.reminder = old_event.reminder self._send_info_to_console(old_event, event, member) From 16626f694602e649b169164843af80f67d876796 Mon Sep 17 00:00:00 2001 From: Wiktor Jaworski Date: Tue, 20 Feb 2024 13:09:45 +0100 Subject: [PATCH 5/5] Update to 0.8.0-beta.2 --- sggwbot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sggwbot/__init__.py b/sggwbot/__init__.py index 467886b..ab850d9 100644 --- a/sggwbot/__init__.py +++ b/sggwbot/__init__.py @@ -19,7 +19,7 @@ __author__ = "Wiktor Jaworski" __license__ = "MIT" __copyright__ = "Copyright 2023, 2024 Wiktor Jaworski" -__version__ = "0.8.0-beta.1" +__version__ = "0.8.0-beta.2" from . import console, errors, utils from .sggw_bot import SGGWBot