From e7780bd8890aa16f2f701e387442f5c45d0bffc1 Mon Sep 17 00:00:00 2001 From: hedy Date: Sun, 24 Mar 2024 14:59:10 +0800 Subject: Reminders: Add a button for others to opt-in to a ping This is the initial implementation - it's currently _far_ from perfect and is very susceptible to errors. Notable features beyond the basic requirements: - Fails safely when max mentions reached (we're limited by the 2000-character limit when sending the reminder with the pings), and disables the button. - Adds an additional embed to the initial confirmation message to show who clicked on the notify button. - Edits the additional embed and disables the button when the reminder is sent. In many ways, this implementation is quite bad: - Uses an async callback to delegate the task of PATCH-ing the API to edit mentions to the `new_reminders` method. - Edits to the opt-in list embed relies on the fact that the reminder is not edited (using !remind edit) before someone clicks on the button. A trivial way to fix this would be to add another field to the site schema to store the `notification_view` in some way. - The button is neither disabled nor any edits to the embed made when the reminder is deleted before someone clicks on the button. - String splitting is used which relies on the exact format of the embed message when editing the embed to disable the button. We have to reminder to update this piece of code when adjusting its format in the future. The UX can also be improved. Currently, I can't think of a way to concisely phrase the button embed message so that it is clear that the button is for people _other than_ the reminder author. Notes: - Max reminder mentions: - Mentions are pinged directly in a discord message when the reminder is sent. This means we're limited by the 2000-char limit. If we take each User ID snowflake to be 18-characters, and considering each mention to be formated as "<@ID> " (with extra space), it results in about 90 mentions max. I've set the constant to 80 just in case. - This is not an issue when the mentions are added in through other means than the button we're adding in this commit, because the user has to use @-mentions when sending the `!remind edit` command, which is already under the discord's character limit. - Log messages are added when something unexpected occurs within the code. Hopefully this is unlikely to happen after the implementation issues listed above are solved. - The opt-in list in the second embed is separate from mentions added in the original reminder creation, or any further edits, because mentions are added by the to-be-mentioned-user, rather than by the reminder author in this way. (Even though they are stored the same way.) --- bot/exts/utils/reminders.py | 137 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 7 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index c79e0499c..c09e427c0 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -39,6 +39,9 @@ LOCK_NAMESPACE = "reminder" WHITELISTED_CHANNELS = Guild.reminder_whitelist MAXIMUM_REMINDERS = 5 REMINDER_EDIT_CONFIRMATION_TIMEOUT = 60 +# The number of mentions that can be sent when a reminder arrives is limited by +# the 2000-character message limit. +MAXIMUM_REMINDER_MENTION_OPT_INS = 80 Mentionable = discord.Member | discord.Role ReminderMention = UnambiguousUser | discord.Role @@ -75,6 +78,99 @@ class ModifyReminderConfirmationView(discord.ui.View): self.stop() +class GetNotificationView(discord.ui.View): + """A button to get notified of someone else's reminder.""" + + def __init__( + self, + reminder: dict, + callback: t.Callable[[list], t.Awaitable[t.Any]], + ): + super().__init__() + self.reminder = reminder + self.callback = callback + self.first_click: bool = True + self.success_message: discord.Message | None = None + self.button: discord.ui.Button | None = None + + @discord.ui.button(label="Notify me", style=discord.ButtonStyle.green) + async def button_callback(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """The button callback.""" + + self.button = button + + if interaction.user.id in self.reminder['mentions']: + await interaction.response.send_message( + "You are already in the list of mentions for that reminder!", + ephemeral=True, + ) + return + + if interaction.user.id == self.reminder['author']: + await interaction.response.send_message( + "As the author of that reminder, you will already be notified when the reminder arrives.", + ephemeral=True, + ) + return + + if len(self.reminder['mentions']) >= MAXIMUM_REMINDER_MENTION_OPT_INS: + await interaction.response.send_message( + "Sorry, this reminder has reached the maximum number of allowed mentions.", + ephemeral=True, + ) + await self.disable_button("Maximum number of allowed mentions reached!") + return + + new_mentions = self.reminder['mentions'] + [interaction.user.id] + await self.callback(new_mentions) + + await interaction.response.send_message( + "You were successfully added to the list of mentions for that reminder.", + ephemeral=True + ) + + # Edit original message to show the opt-ins + if not self.success_message or len(self.success_message.embeds) != 2: + log.trace(f"Unable to update the message for the list of member opt-ins for reminder #{self.reminder['id']}.") + return + + embeds = self.success_message.embeds + + if self.first_click: + embeds[1].description += "\n\nThe following member(s) opted-in to be notified:\n" + self.first_click = False + embeds[1].description += f"{interaction.user.mention} " + + await self.success_message.edit(embeds=embeds) + + async def disable_button(self, reason: str | None = None) -> None: + """Disable the button.""" + if not self.success_message or not self.button: + log.trace("Unable to disable the button for opting-in for the reminder notification.") + return + + self.button.disabled = True + + if not reason: + await self.success_message.edit(view=self) + return + + embeds = self.success_message.embeds + + if len(embeds) != 2: + log.trace("Unable to get the button instruction embed for editing reason of disabling the reminder notification button.") + return + + description = embeds[1].description + if "\n\n" in description: + # The button was clicked at least once. Don't remove the text that + # shows the list of opt-ins. + embeds[1].description = reason + "\n\n" + embeds[1].description += description.split("\n\n", maxsplit=2)[1] + + await self.success_message.edit(embeds=embeds, view=self) + + class Reminders(Cog): """Provide in-channel reminder functionality.""" @@ -127,10 +223,12 @@ class Reminders(Cog): async def _send_confirmation( ctx: Context, on_success: str, - reminder_id: str | int - ) -> None: + reminder_id: str | int, + embed: discord.Embed | None = None, + view: discord.ui.View | None = None, + ) -> discord.Message: """Send an embed confirming the reminder change was made successfully.""" - embed = discord.Embed( + success_embed = discord.Embed( description=on_success, colour=discord.Colour.green(), title=random.choice(POSITIVE_REPLIES) @@ -138,9 +236,12 @@ class Reminders(Cog): footer_str = f"ID: {reminder_id}" - embed.set_footer(text=footer_str) + success_embed.set_footer(text=footer_str) - await ctx.send(embed=embed) + if embed: + return await ctx.send(embeds=[success_embed, embed], view=view) + else: + return await ctx.send(embed=success_embed, view=view) @staticmethod async def _check_mentions(ctx: Context, mentions: t.Iterable[Mentionable]) -> tuple[bool, str]: @@ -251,6 +352,11 @@ class Reminders(Cog): log.debug(f"Deleting reminder #{reminder['id']} (the user has been reminded).") await self.bot.api_client.delete(f"bot/reminders/{reminder['id']}") + # Remove the button that lets others opt-in to pings. + view = reminder.get("notification_view") + if view: + await view.disable_button() + @staticmethod async def try_get_content_from_reply(ctx: Context) -> str | None: """ @@ -373,13 +479,30 @@ class Reminders(Cog): mention_string += f" and will mention {len(mentions)} other(s)" mention_string += "!" + # Add a button for others to also get notified. + button_embed = discord.Embed( + description= "Click on the button to also get a ping when the reminder arrives.", + ) + + async def callback(new_mentions: list): + reminder['mentions'] = new_mentions + await self._edit_reminder(reminder['id'], { 'mentions': new_mentions }) + + view = GetNotificationView(reminder, callback) + # Confirm to the user that it worked. - await self._send_confirmation( + message = await self._send_confirmation( ctx, on_success=mention_string, - reminder_id=reminder["id"] + reminder_id=reminder["id"], + embed=button_embed, + view=view, ) + view.success_message = message + + reminder['notification_view'] = view + self.schedule_reminder(reminder) @remind_group.command(name="list") -- cgit v1.2.3 From cdaa59fa5ee05a2f47e003c289d57a4e3098c507 Mon Sep 17 00:00:00 2001 From: hedy Date: Mon, 25 Mar 2024 22:19:19 +0800 Subject: Reminders: More robust implementation of mention opt-in button This solves most, if not all issues from the previous commit. - A timeout of 5 minutes is enforced - this means the button can no longer be used either when the reminder arrives or 5 minutes passes since creation, whichever comes first. - Reminder edits in between creation and button clicks will be handled responsibly - This includes both edits of duration, mentions, and deleting reminders altogether. - UX is improved. This list of to-be-mentioned users is sent up-front with the author included. Instructions to click the button comes right after the list. - No updates to the API or site schema required, as the button message will disable itself when it encounters any sort of errors. - Implementation is also somewhat simplified. There are probably more improvements, maybe one caveat, but it's like almost midnight and I want to sleep :/ I sure hope the list above covers most of it. Further testing will be done. Now `.remind 10s test` is ingrained in my muscle memory... --- bot/exts/utils/reminders.py | 220 ++++++++++++++++++++++++++------------------ 1 file changed, 131 insertions(+), 89 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index c09e427c0..277d8a3af 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -39,6 +39,7 @@ LOCK_NAMESPACE = "reminder" WHITELISTED_CHANNELS = Guild.reminder_whitelist MAXIMUM_REMINDERS = 5 REMINDER_EDIT_CONFIRMATION_TIMEOUT = 60 +REMINDER_MENTION_BUTTON_TIMEOUT = 5*60 # The number of mentions that can be sent when a reminder arrives is limited by # the 2000-character message limit. MAXIMUM_REMINDER_MENTION_OPT_INS = 80 @@ -78,38 +79,52 @@ class ModifyReminderConfirmationView(discord.ui.View): self.stop() -class GetNotificationView(discord.ui.View): - """A button to get notified of someone else's reminder.""" +class OptInReminderMentionView(discord.ui.View): + """A button to opt-in to get notified of someone else's reminder.""" - def __init__( - self, - reminder: dict, - callback: t.Callable[[list], t.Awaitable[t.Any]], - ): + def __init__(self, cog: "Reminders", reminder: dict): super().__init__() + self.cog = cog self.reminder = reminder - self.callback = callback - self.first_click: bool = True - self.success_message: discord.Message | None = None - self.button: discord.ui.Button | None = None - @discord.ui.button(label="Notify me", style=discord.ButtonStyle.green) + async def get_embed_description(self) -> str: + """Return a string for use in the embed that shows the button.""" + + description = "The following user(s) will be notified when the reminder arrives:\n" + description += " ".join([ + mentionable.mention async for mentionable in self.cog.get_mentionables( + [self.reminder["author"]] + self.reminder["mentions"] + ) + ]) + description += "\n\nClick on the button to add yourself to the list of mentions." + + return description + + @discord.ui.button(emoji="🔔", label="Notify me", style=discord.ButtonStyle.green) async def button_callback(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: """The button callback.""" - self.button = button + # This is required in case the reminder was edited/deleted between + # creation and the opt-in button click. + try: + self.reminder = await self.cog.bot.api_client.get(f"bot/reminders/{self.reminder['id']}") + except ResponseCodeError as e: + await self.handle_api_error(interaction, button, e) + return - if interaction.user.id in self.reminder['mentions']: + # Check whether the user should be added. + if interaction.user.id == self.reminder['author']: await interaction.response.send_message( - "You are already in the list of mentions for that reminder!", + "As the author of that reminder, you will already be notified when the reminder arrives.", ephemeral=True, ) return - if interaction.user.id == self.reminder['author']: + if interaction.user.id in self.reminder['mentions']: await interaction.response.send_message( - "As the author of that reminder, you will already be notified when the reminder arrives.", + "You are already in the list of mentions for that reminder.", ephemeral=True, + delete_after=5, ) return @@ -117,58 +132,91 @@ class GetNotificationView(discord.ui.View): await interaction.response.send_message( "Sorry, this reminder has reached the maximum number of allowed mentions.", ephemeral=True, + delete_after=5, ) - await self.disable_button("Maximum number of allowed mentions reached!") + await self.disable(interaction, button, "Maximum number of allowed mentions reached!") return - new_mentions = self.reminder['mentions'] + [interaction.user.id] - await self.callback(new_mentions) + # Add the user to the list of mentions. + try: + self.reminder = await self.cog.add_mention_opt_in(self.reminder, interaction.user.id) + except ResponseCodeError as e: + await self.handle_api_error(interaction, button, e) + return + # Confirm that it was successful. await interaction.response.send_message( "You were successfully added to the list of mentions for that reminder.", - ephemeral=True + ephemeral=True, + delete_after=5, ) - # Edit original message to show the opt-ins - if not self.success_message or len(self.success_message.embeds) != 2: - log.trace(f"Unable to update the message for the list of member opt-ins for reminder #{self.reminder['id']}.") - return + # Update the embed to show the new list of mentions. + try: + embed = interaction.message.embeds[0] + embed.description = await self.get_embed_description() + await interaction.message.edit(embed=embed) + except: + log.trace(f"Unable to edit the interaction message for reminder #{self.reminder['id']}.") - embeds = self.success_message.embeds + async def handle_api_error( + self, + interaction: discord.Interaction, + button: discord.ui.Button, + error: ResponseCodeError + ) -> None: + """Handle a ResponseCodeError from the API responsibly.""" - if self.first_click: - embeds[1].description += "\n\nThe following member(s) opted-in to be notified:\n" - self.first_click = False - embeds[1].description += f"{interaction.user.mention} " + log.trace(f"API returned {error.status} for reminder #{self.reminder['id']}.") - await self.success_message.edit(embeds=embeds) + if error.status == 404: + # This might happen if the reminder was edited to arrive before the + # button was initially scheduled to timeout. + await interaction.response.send_message( + "This reminder was either deleted or has already arrived.", + ephemeral=True, + delete_after=5, + ) + # Don't delete the whole interaction message here or the user will + # see the above response message seemingly without context. + await self.disable(interaction, button) - async def disable_button(self, reason: str | None = None) -> None: - """Disable the button.""" - if not self.success_message or not self.button: - log.trace("Unable to disable the button for opting-in for the reminder notification.") - return + else: + await interaction.response.send_message( + "Sorry, an unexpected error occurred when performing this operation.\n" + "Please create your own reminder instead.", + ephemeral=True, + delete_after=5, + ) + await self.disable( + interaction, + button, + "An unexpected error occurred when attempting to add users." + ) - self.button.disabled = True + async def disable( + self, + interaction: discord.Interaction, + button: discord.ui.Button, + reason: str | None = None, + ) -> None: + """Disable the button and add an optional reason to the original interaction message.""" - if not reason: - await self.success_message.edit(view=self) - return + button.disabled = True - embeds = self.success_message.embeds + embeds = interaction.message.embeds - if len(embeds) != 2: - log.trace("Unable to get the button instruction embed for editing reason of disabling the reminder notification button.") - return + try: + embed = embeds[0] + embed.description = embed.description.split("\n\n", maxsplit=2)[0] + if reason: + embed.description += "\n\n" + reason - description = embeds[1].description - if "\n\n" in description: - # The button was clicked at least once. Don't remove the text that - # shows the list of opt-ins. - embeds[1].description = reason + "\n\n" - embeds[1].description += description.split("\n\n", maxsplit=2)[1] + await interaction.message.edit(embed=embed, view=self) - await self.success_message.edit(embeds=embeds, view=self) + except: + log.trace("Unable to disable the reminder notification button.") + await interaction.message.edit(embeds=embeds, view=None) class Reminders(Cog): @@ -223,12 +271,10 @@ class Reminders(Cog): async def _send_confirmation( ctx: Context, on_success: str, - reminder_id: str | int, - embed: discord.Embed | None = None, - view: discord.ui.View | None = None, - ) -> discord.Message: + reminder_id: str | int + ) -> None: """Send an embed confirming the reminder change was made successfully.""" - success_embed = discord.Embed( + embed = discord.Embed( description=on_success, colour=discord.Colour.green(), title=random.choice(POSITIVE_REPLIES) @@ -236,12 +282,9 @@ class Reminders(Cog): footer_str = f"ID: {reminder_id}" - success_embed.set_footer(text=footer_str) + embed.set_footer(text=footer_str) - if embed: - return await ctx.send(embeds=[success_embed, embed], view=view) - else: - return await ctx.send(embed=success_embed, view=view) + await ctx.send(embed=embed) @staticmethod async def _check_mentions(ctx: Context, mentions: t.Iterable[Mentionable]) -> tuple[bool, str]: @@ -308,6 +351,19 @@ class Reminders(Cog): log.trace(f"Scheduling new task #{reminder['id']}") self.schedule_reminder(reminder) + @lock_arg(LOCK_NAMESPACE, "reminder", itemgetter("id"), raise_error=True) + async def add_mention_opt_in(self, reminder: dict, user_id: int) -> dict: + """Add an opt-in user to a reminder's mentions and return the edited reminder.""" + + if user_id in reminder['mentions'] or user_id == reminder['author']: + return reminder + + reminder['mentions'].append(user_id) + reminder = await self._edit_reminder(reminder['id'], {'mentions': reminder['mentions']}) + + await self._reschedule_reminder(reminder) + return reminder + @lock_arg(LOCK_NAMESPACE, "reminder", itemgetter("id"), raise_error=True) async def send_reminder(self, reminder: dict, expected_time: time.Timestamp | None = None) -> None: """Send the reminder.""" @@ -352,11 +408,6 @@ class Reminders(Cog): log.debug(f"Deleting reminder #{reminder['id']} (the user has been reminded).") await self.bot.api_client.delete(f"bot/reminders/{reminder['id']}") - # Remove the button that lets others opt-in to pings. - view = reminder.get("notification_view") - if view: - await view.disable_button() - @staticmethod async def try_get_content_from_reply(ctx: Context) -> str | None: """ @@ -473,35 +524,26 @@ class Reminders(Cog): ) formatted_time = time.discord_timestamp(expiration, time.TimestampFormats.DAY_TIME) - mention_string = f"Your reminder will arrive on {formatted_time}" - - if mentions: - mention_string += f" and will mention {len(mentions)} other(s)" - mention_string += "!" - - # Add a button for others to also get notified. - button_embed = discord.Embed( - description= "Click on the button to also get a ping when the reminder arrives.", - ) - - async def callback(new_mentions: list): - reminder['mentions'] = new_mentions - await self._edit_reminder(reminder['id'], { 'mentions': new_mentions }) - - view = GetNotificationView(reminder, callback) + success_message = f"Your reminder will arrive on {formatted_time}!" # Confirm to the user that it worked. - message = await self._send_confirmation( + await self._send_confirmation( ctx, - on_success=mention_string, - reminder_id=reminder["id"], - embed=button_embed, - view=view, + on_success=success_message, + reminder_id=reminder["id"] ) - view.success_message = message + # Add a button for others to also get notified. + view = OptInReminderMentionView(self, reminder) - reminder['notification_view'] = view + button_embed = discord.Embed( + description=await view.get_embed_description(), + ) + button_timeout = min( + (expiration - datetime.now(UTC)).total_seconds(), + REMINDER_MENTION_BUTTON_TIMEOUT + ) + await ctx.send(embed=button_embed, view=view, delete_after=button_timeout) self.schedule_reminder(reminder) -- cgit v1.2.3 From e753586ee59b9ae4f7c4a42c5b51422c158dc50d Mon Sep 17 00:00:00 2001 From: hedy Date: Tue, 26 Mar 2024 11:48:30 +0800 Subject: Reminders: Refactor all opt-in button related logic into View class - More resilient handling of API errors. - Don't rely on string manipulation to disable the button. --- bot/exts/utils/reminders.py | 78 +++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 277d8a3af..eafe70484 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -82,45 +82,55 @@ class ModifyReminderConfirmationView(discord.ui.View): class OptInReminderMentionView(discord.ui.View): """A button to opt-in to get notified of someone else's reminder.""" - def __init__(self, cog: "Reminders", reminder: dict): + def __init__(self, cog: "Reminders", reminder: dict, expiration: Duration): super().__init__() + self.cog = cog self.reminder = reminder - async def get_embed_description(self) -> str: - """Return a string for use in the embed that shows the button.""" + self.timeout = min( + (expiration - datetime.now(UTC)).total_seconds(), + REMINDER_MENTION_BUTTON_TIMEOUT + ) + async def get_embed_description( + self, + message: str = "Click on the button to add yourself to the list of mentions." + ) -> str: + """Return a string for use in the embed that shows the button.""" description = "The following user(s) will be notified when the reminder arrives:\n" description += " ".join([ mentionable.mention async for mentionable in self.cog.get_mentionables( [self.reminder["author"]] + self.reminder["mentions"] ) ]) - description += "\n\nClick on the button to add yourself to the list of mentions." + if message: + description += f"\n\n{message}" return description @discord.ui.button(emoji="🔔", label="Notify me", style=discord.ButtonStyle.green) - async def button_callback(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + async def button_callback(self, interaction: Interaction, button: discord.ui.Button) -> None: """The button callback.""" - # This is required in case the reminder was edited/deleted between # creation and the opt-in button click. try: - self.reminder = await self.cog.bot.api_client.get(f"bot/reminders/{self.reminder['id']}") + api_response = await self.cog.bot.api_client.get(f"bot/reminders/{self.reminder['id']}") except ResponseCodeError as e: await self.handle_api_error(interaction, button, e) return + self.reminder = api_response + # Check whether the user should be added. - if interaction.user.id == self.reminder['author']: + if interaction.user.id == self.reminder["author"]: await interaction.response.send_message( "As the author of that reminder, you will already be notified when the reminder arrives.", ephemeral=True, ) return - if interaction.user.id in self.reminder['mentions']: + if interaction.user.id in self.reminder["mentions"]: await interaction.response.send_message( "You are already in the list of mentions for that reminder.", ephemeral=True, @@ -128,7 +138,7 @@ class OptInReminderMentionView(discord.ui.View): ) return - if len(self.reminder['mentions']) >= MAXIMUM_REMINDER_MENTION_OPT_INS: + if len(self.reminder["mentions"]) >= MAXIMUM_REMINDER_MENTION_OPT_INS: await interaction.response.send_message( "Sorry, this reminder has reached the maximum number of allowed mentions.", ephemeral=True, @@ -139,11 +149,13 @@ class OptInReminderMentionView(discord.ui.View): # Add the user to the list of mentions. try: - self.reminder = await self.cog.add_mention_opt_in(self.reminder, interaction.user.id) + api_response = await self.cog.add_mention_opt_in(self.reminder, interaction.user.id) except ResponseCodeError as e: await self.handle_api_error(interaction, button, e) return + self.reminder = api_response + # Confirm that it was successful. await interaction.response.send_message( "You were successfully added to the list of mentions for that reminder.", @@ -156,17 +168,16 @@ class OptInReminderMentionView(discord.ui.View): embed = interaction.message.embeds[0] embed.description = await self.get_embed_description() await interaction.message.edit(embed=embed) - except: + except Exception: log.trace(f"Unable to edit the interaction message for reminder #{self.reminder['id']}.") async def handle_api_error( self, - interaction: discord.Interaction, + interaction: Interaction, button: discord.ui.Button, error: ResponseCodeError ) -> None: """Handle a ResponseCodeError from the API responsibly.""" - log.trace(f"API returned {error.status} for reminder #{self.reminder['id']}.") if error.status == 404: @@ -194,29 +205,19 @@ class OptInReminderMentionView(discord.ui.View): "An unexpected error occurred when attempting to add users." ) - async def disable( - self, - interaction: discord.Interaction, - button: discord.ui.Button, - reason: str | None = None, - ) -> None: + async def disable(self, interaction: Interaction, button: discord.ui.Button, reason: str = "") -> None: """Disable the button and add an optional reason to the original interaction message.""" - button.disabled = True embeds = interaction.message.embeds try: embed = embeds[0] - embed.description = embed.description.split("\n\n", maxsplit=2)[0] - if reason: - embed.description += "\n\n" + reason - + embed.description = await self.get_embed_description(reason) await interaction.message.edit(embed=embed, view=self) - - except: + except Exception: log.trace("Unable to disable the reminder notification button.") - await interaction.message.edit(embeds=embeds, view=None) + await interaction.message.edit(embeds=[], view=None) class Reminders(Cog): @@ -354,12 +355,11 @@ class Reminders(Cog): @lock_arg(LOCK_NAMESPACE, "reminder", itemgetter("id"), raise_error=True) async def add_mention_opt_in(self, reminder: dict, user_id: int) -> dict: """Add an opt-in user to a reminder's mentions and return the edited reminder.""" - - if user_id in reminder['mentions'] or user_id == reminder['author']: + if user_id in reminder["mentions"] or user_id == reminder["author"]: return reminder - reminder['mentions'].append(user_id) - reminder = await self._edit_reminder(reminder['id'], {'mentions': reminder['mentions']}) + reminder["mentions"].append(user_id) + reminder = await self._edit_reminder(reminder["id"], {"mentions": reminder["mentions"]}) await self._reschedule_reminder(reminder) return reminder @@ -534,16 +534,12 @@ class Reminders(Cog): ) # Add a button for others to also get notified. - view = OptInReminderMentionView(self, reminder) - - button_embed = discord.Embed( - description=await view.get_embed_description(), - ) - button_timeout = min( - (expiration - datetime.now(UTC)).total_seconds(), - REMINDER_MENTION_BUTTON_TIMEOUT + view = OptInReminderMentionView(self, reminder, expiration) + await ctx.send( + view=view, + delete_after=view.timeout, + embed=discord.Embed(description=await view.get_embed_description()) ) - await ctx.send(embed=button_embed, view=view, delete_after=button_timeout) self.schedule_reminder(reminder) -- cgit v1.2.3 From 8efc22daacbc3f56575fc3a920030b0e4b35cc6e Mon Sep 17 00:00:00 2001 From: hedy Date: Wed, 27 Mar 2024 13:27:20 +0800 Subject: Reminders: Simplify helper function to get button embed This removes the need of any form of error handling from `embeds` list indexing, and makes the code more concise and readable. The drawback is that a whole new embed object have to be allocated each time, rather than editing the description like before. --- bot/exts/utils/reminders.py | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index eafe70484..26e45a0ac 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -93,21 +93,22 @@ class OptInReminderMentionView(discord.ui.View): REMINDER_MENTION_BUTTON_TIMEOUT ) - async def get_embed_description( + async def get_embed( self, message: str = "Click on the button to add yourself to the list of mentions." - ) -> str: - """Return a string for use in the embed that shows the button.""" + ) -> discord.Embed: + """Return an embed to show the button together with.""" description = "The following user(s) will be notified when the reminder arrives:\n" description += " ".join([ mentionable.mention async for mentionable in self.cog.get_mentionables( [self.reminder["author"]] + self.reminder["mentions"] ) ]) + if message: description += f"\n\n{message}" - return description + return discord.Embed(description=description) @discord.ui.button(emoji="🔔", label="Notify me", style=discord.ButtonStyle.green) async def button_callback(self, interaction: Interaction, button: discord.ui.Button) -> None: @@ -164,12 +165,7 @@ class OptInReminderMentionView(discord.ui.View): ) # Update the embed to show the new list of mentions. - try: - embed = interaction.message.embeds[0] - embed.description = await self.get_embed_description() - await interaction.message.edit(embed=embed) - except Exception: - log.trace(f"Unable to edit the interaction message for reminder #{self.reminder['id']}.") + await interaction.message.edit(embed=await self.get_embed()) async def handle_api_error( self, @@ -208,16 +204,10 @@ class OptInReminderMentionView(discord.ui.View): async def disable(self, interaction: Interaction, button: discord.ui.Button, reason: str = "") -> None: """Disable the button and add an optional reason to the original interaction message.""" button.disabled = True - - embeds = interaction.message.embeds - - try: - embed = embeds[0] - embed.description = await self.get_embed_description(reason) - await interaction.message.edit(embed=embed, view=self) - except Exception: - log.trace("Unable to disable the reminder notification button.") - await interaction.message.edit(embeds=[], view=None) + await interaction.message.edit( + embed=await self.get_embed(reason), + view=self, + ) class Reminders(Cog): @@ -535,11 +525,7 @@ class Reminders(Cog): # Add a button for others to also get notified. view = OptInReminderMentionView(self, reminder, expiration) - await ctx.send( - view=view, - delete_after=view.timeout, - embed=discord.Embed(description=await view.get_embed_description()) - ) + await ctx.send(embed=await view.get_embed(), view=view, delete_after=view.timeout) self.schedule_reminder(reminder) -- cgit v1.2.3 From 0f18d7090d2c540bb0f1f8ad565f428f0a025b65 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sat, 29 Mar 2025 20:10:43 +0000 Subject: Correct dependabot config --- .github/dependabot.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2f9f77909..665c591b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,9 +5,10 @@ updates: schedule: interval: "daily" ignore: - update-types: - - sem-ver:patch - - sem-ver:minor + - dependency-name: "*" + update-types: + - version-update:semver-patch + - version-update:semver-minor - package-ecosystem: "github-actions" directory: "/" schedule: -- cgit v1.2.3