From d55f1993e8a3a77095afb65fef76d81ebce95f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Tue, 8 Apr 2025 17:57:50 +0200 Subject: [PATCH] Add remove command for reminders --- discord_reminder_bot/main.py | 70 +++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/discord_reminder_bot/main.py b/discord_reminder_bot/main.py index 1b98732..28df82b 100644 --- a/discord_reminder_bot/main.py +++ b/discord_reminder_bot/main.py @@ -17,6 +17,7 @@ import sentry_sdk from apscheduler import events from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_MISSED, JobExecutionEvent from apscheduler.job import Job +from apscheduler.jobstores.base import JobLookupError from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger @@ -27,8 +28,9 @@ from discord_webhook import DiscordWebhook from dotenv import load_dotenv from loguru import logger +from interactions.api.models.misc import Snowflake + if TYPE_CHECKING: - from apscheduler.job import Job from discord.guild import GuildChannel from discord.interactions import InteractionChannel from discord.types.channel import _BaseChannel @@ -46,6 +48,42 @@ sentry_sdk.init( ) +def generate_markdown_state(state: dict[str, Any]) -> str: + """Format the __getstate__ dictionary for Discord markdown. + + Args: + state (dict): The __getstate__ dictionary. + + Returns: + str: The formatted string. + """ + if not state: + return "```json\nNo state found.\n```" + + # discord.app_commands.errors.CommandInvokeError: Command 'remove' raised an exception: TypeError: Object of type IntervalTrigger is not JSON serializable + + # Convert the IntervalTrigger to a string representation + for key, value in state.items(): + if isinstance(value, IntervalTrigger): + state[key] = "IntervalTrigger" + elif isinstance(value, DateTrigger): + state[key] = "DateTrigger" + elif isinstance(value, Job): + state[key] = "Job" + elif isinstance(value, Snowflake): + state[key] = str(value) + + try: + msg: str = json.dumps(state, indent=4, default=str) + except TypeError as e: + e.add_note("This is likely due to a non-serializable object in the state. Please check the state for any non-serializable objects.") + e.add_note(f"{state=}") + logger.error(f"Failed to serialize state: {e}") + return "```json\nFailed to serialize state.\n```" + + return "```json\n" + msg + "\n```" + + def parse_time(date_to_parse: str | None, timezone: str | None = os.getenv("TIMEZONE")) -> datetime.datetime | None: """Parse a date string into a datetime object. @@ -1023,6 +1061,36 @@ class RemindGroup(discord.app_commands.Group): else: await interaction.followup.send(content="No new reminders were added.") + # /remind remove + @discord.app_commands.command(name="remove", description="Remove a reminder") + async def remove(self, interaction: discord.Interaction, job_id: str) -> None: + """Remove a scheduled reminder. + + Args: + interaction (discord.Interaction): The interaction object for the command. + job_id (str): The identifier of the job to remove. + """ + await interaction.response.defer() + + logger.debug(f"Removing reminder with ID {job_id} for {interaction.user} ({interaction.user.id}) in {interaction.channel}") + logger.debug(f"Arguments: {locals()}") + + try: + job: Job | None = scheduler.get_job(job_id) + if not job: + await interaction.followup.send(content=f"Reminder with ID {job_id} not found.", ephemeral=True) + return + scheduler.remove_job(job_id) + logger.info(f"Removed job {job_id}. {job.__getstate__()}") + await interaction.followup.send( + content=f"Reminder with ID {job_id} removed successfully.\n{generate_markdown_state(job.__getstate__())}", + ) + except JobLookupError as e: + logger.exception(f"Failed to remove job {job_id}") + await interaction.followup.send(content=f"Failed to remove reminder with ID {job_id}. {e}", ephemeral=True) + + logger.info(f"Job {job_id} removed from the scheduler.") + intents: discord.Intents = discord.Intents.default() intents.guild_scheduled_events = True