From 9f2814a3d5e32169edf1bad3ece898d0bd7355f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Sun, 6 Jul 2025 04:06:44 +0200 Subject: [PATCH] Add message modification support for interval and cron jobs in modals --- discord_reminder_bot/main.py | 13 +++ discord_reminder_bot/modals.py | 196 ++++++++++++++++++++++++++++++++- 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/discord_reminder_bot/main.py b/discord_reminder_bot/main.py index 4694758..756c2a5 100644 --- a/discord_reminder_bot/main.py +++ b/discord_reminder_bot/main.py @@ -149,6 +149,17 @@ def format_job_for_ui(job: Job) -> str: msg += f"\nData:\n{generate_state(job.__getstate__(), job)}\n" + if isinstance(job.trigger, apscheduler.triggers.interval.IntervalTrigger): + msg += ( + "\nNote: This is an interval job. Due to UI limitations, you can only modify the message, not the trigger settings.\n" + "To change the trigger settings, please delete and recreate the job.\n" + ) + elif isinstance(job.trigger, apscheduler.triggers.cron.CronTrigger): + msg += ( + "\nNote: This is a cron job. Due to UI limitations, you can only modify the message, not the trigger settings.\n" + "To change the trigger settings, please delete and recreate the job.\n" + ) + logger.debug(f"Formatted job for UI: {msg}") return msg @@ -327,6 +338,8 @@ class ReminderListView(discord.ui.View): ephemeral=True, ) + await self.refresh(interaction) + async def handle_pause_unpause(self, interaction: discord.Interaction, job_id: str) -> None: """Handle pausing or unpausing a reminder job. diff --git a/discord_reminder_bot/modals.py b/discord_reminder_bot/modals.py index 89e10ee..3f00d71 100644 --- a/discord_reminder_bot/modals.py +++ b/discord_reminder_bot/modals.py @@ -179,7 +179,7 @@ class DateReminderModifyModal(discord.ui.Modal, title="Modify reminder"): except discord.HTTPException: logger.warning("Failed to send error message via followup") - logger.exception(f"Error in ReminderModifyModal: {error}") + logger.exception(f"Error in {self.__class__.__name__}: {error}") traceback.print_exception(type(error), error, error.__traceback__) @@ -196,6 +196,102 @@ class CronReminderModifyModal(discord.ui.Modal, title="Modify reminder"): self.job = job self.job_id = job.id + # message + self.message_input = discord.ui.TextInput( + label="Reminder message", + default=job.kwargs.get("message", ""), + placeholder="What do you want to be reminded of?", + max_length=200, + ) + + async def _update_message(self, old_message: str, new_message: str) -> bool: + """Update the message of a job. + + Args: + old_message (str): The old message. + new_message (str): The new message. + + Returns: + bool: Whether the message was changed. + """ + if new_message == old_message: + return False + + job: Job | None = scheduler.get_job(self.job_id) + if not job: + return False + + old_kwargs = job.kwargs.copy() + scheduler.modify_job( + self.job_id, + kwargs={ + **old_kwargs, + "message": new_message, + }, + ) + + logger.debug(f"Modified job {self.job_id} with new message: {new_message}") + logger.debug(f"Old kwargs: {old_kwargs}, New kwargs: {job.kwargs}") + return True + + async def on_submit(self, interaction: discord.Interaction) -> None: + """Called when the modal is submitted for a cron-based reminder. + + Args: + interaction (discord.Interaction): The Discord interaction where this modal was triggered from. + """ + old_message: str = self.job.kwargs.get("message", "") + + new_message: str = self.message_input.value + + # Get the job to modify + job_to_modify: Job | None = scheduler.get_job(self.job_id) + if not job_to_modify: + await interaction.response.send_message( + f"Failed to get job.\n{new_message=}", + ephemeral=True, + ) + return + + # Defer early for long operations + await interaction.response.defer(ephemeral=True) + + # Update the message if changed + msg: str = f"Modified job `{escape_markdown(self.job_id)}`:\n" + changes_made = False + + # Update message if changed + message_changed: bool = await self._update_message(old_message, new_message) + if message_changed: + msg += f"Old message: `{escape_markdown(old_message)}`\n" + msg += f"New message: `{escape_markdown(new_message)}`.\n" + changes_made = True + + # Send confirmation message + if changes_made: + await interaction.followup.send(content=msg) + else: + await interaction.followup.send(content=f"No changes made to job `{escape_markdown(self.job_id)}`.", ephemeral=True) + + async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: + """A callback that is called when on_submit fails with an error. + + Args: + interaction (discord.Interaction): The Discord interaction where this modal was triggered from. + error (Exception): The raised exception. + """ + # Check if the interaction has already been responded to + if not interaction.response.is_done(): + await interaction.response.send_message("Oops! Something went wrong.", ephemeral=True) + else: + try: + await interaction.followup.send("Oops! Something went wrong.", ephemeral=True) + except discord.HTTPException: + logger.warning("Failed to send error message via followup") + + logger.exception(f"Error in {self.__class__.__name__}: {error}") + traceback.print_exception(type(error), error, error.__traceback__) + class IntervalReminderModifyModal(discord.ui.Modal, title="Modify reminder"): """A modal for modifying an interval-based reminder.""" @@ -209,3 +305,101 @@ class IntervalReminderModifyModal(discord.ui.Modal, title="Modify reminder"): super().__init__(title="Modify Reminder") self.job = job self.job_id = job.id + + # message + self.message_input = discord.ui.TextInput( + label="Reminder message", + default=job.kwargs.get("message", ""), + placeholder="What do you want to be reminded of?", + max_length=200, + ) + + self.add_item(self.message_input) + + async def _update_message(self, old_message: str, new_message: str) -> bool: + """Update the message of a job. + + Args: + old_message (str): The old message. + new_message (str): The new message. + + Returns: + bool: Whether the message was changed. + """ + if new_message == old_message: + return False + + job: Job | None = scheduler.get_job(self.job_id) + if not job: + return False + + old_kwargs = job.kwargs.copy() + scheduler.modify_job( + self.job_id, + kwargs={ + **old_kwargs, + "message": new_message, + }, + ) + + logger.debug(f"Modified job {self.job_id} with new message: {new_message}") + logger.debug(f"Old kwargs: {old_kwargs}, New kwargs: {job.kwargs}") + return True + + async def on_submit(self, interaction: discord.Interaction) -> None: + """Called when the modal is submitted for an interval-based reminder. + + Args: + interaction (discord.Interaction): The Discord interaction where this modal was triggered from. + """ + old_message: str = self.job.kwargs.get("message", "") + + new_message: str = self.message_input.value + + # Get the job to modify + job_to_modify: Job | None = scheduler.get_job(self.job_id) + if not job_to_modify: + await interaction.response.send_message( + f"Failed to get job.\n{new_message=}", + ephemeral=True, + ) + return + + # Defer early for long operations + await interaction.response.defer(ephemeral=True) + + # Update the message if changed + msg: str = f"Modified job `{escape_markdown(self.job_id)}`:\n" + changes_made = False + + # Update message if changed + message_changed: bool = await self._update_message(old_message, new_message) + if message_changed: + msg += f"Old message: `{escape_markdown(old_message)}`\n" + msg += f"New message: `{escape_markdown(new_message)}`.\n" + changes_made = True + + # Send confirmation message + if changes_made: + await interaction.followup.send(content=msg) + else: + await interaction.followup.send(content=f"No changes made to job `{escape_markdown(self.job_id)}`.", ephemeral=True) + + async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: + """A callback that is called when on_submit fails with an error. + + Args: + interaction (discord.Interaction): The Discord interaction where this modal was triggered from. + error (Exception): The raised exception. + """ + # Check if the interaction has already been responded to + if not interaction.response.is_done(): + await interaction.response.send_message("Oops! Something went wrong.", ephemeral=True) + else: + try: + await interaction.followup.send("Oops! Something went wrong.", ephemeral=True) + except discord.HTTPException: + logger.warning("Failed to send error message via followup") + + logger.exception(f"Error in {self.__class__.__name__}: {error}") + traceback.print_exception(type(error), error, error.__traceback__)