Add /remind interval

This commit is contained in:
2025-01-12 23:39:32 +01:00
parent 87e12cd208
commit f1695848ef
2 changed files with 125 additions and 3 deletions

View File

@ -91,6 +91,10 @@ class RemindGroup(discord.app_commands.Group):
user (discord.User, optional): Send reminder as a DM to this user. Defaults to None. user (discord.User, optional): Send reminder as a DM to this user. Defaults to None.
dm_and_current_channel (bool, optional): Send reminder as a DM to the user and in this channel. Defaults to False. dm_and_current_channel (bool, optional): Send reminder as a DM to the user and in this channel. Defaults to False.
""" # noqa: E501 """ # noqa: E501
# TODO(TheLovinator): Add try/except for all of these await calls # noqa: TD003
# TODO(TheLovinator): Add a warning if the interval is too short # noqa: TD003
# TODO(TheLovinator): Check if we have access to the channel and user# noqa: TD003
should_send_channel_reminder = True should_send_channel_reminder = True
await interaction.response.defer() await interaction.response.defer()
@ -210,6 +214,10 @@ class RemindGroup(discord.app_commands.Group):
user (discord.User, optional): Send reminder as a DM to this user. Defaults to None. user (discord.User, optional): Send reminder as a DM to this user. Defaults to None.
dm_and_current_channel (bool, optional): If user is provided, send reminder as a DM to the user and in this channel. Defaults to only the user. dm_and_current_channel (bool, optional): If user is provided, send reminder as a DM to the user and in this channel. Defaults to only the user.
""" # noqa: E501 """ # noqa: E501
# TODO(TheLovinator): Add try/except for all of these await calls # noqa: TD003
# TODO(TheLovinator): Add a warning if the interval is too short # noqa: TD003
# TODO(TheLovinator): Check if we have access to the channel and user# noqa: TD003
# Log kwargs # Log kwargs
logger.info("New cron job from %s (%s) in %s", interaction.user, interaction.user.id, interaction.channel) logger.info("New cron job from %s (%s) in %s", interaction.user, interaction.user.id, interaction.channel)
logger.info("Cron job arguments: %s", {k: v for k, v in locals().items() if k != "self" and v is not None}) logger.info("Cron job arguments: %s", {k: v for k, v in locals().items() if k != "self" and v is not None})
@ -283,6 +291,119 @@ class RemindGroup(discord.app_commands.Group):
f"First run in {calculate(channel_job)} with the message:\n**{message}**.", f"First run in {calculate(channel_job)} with the message:\n**{message}**.",
) )
# /remind interval
@discord.app_commands.command(
name="interval",
description="Create a new reminder that triggers based on an interval.",
)
async def interval( # noqa: PLR0913, PLR0917
self,
interaction: discord.Interaction,
message: str,
weeks: int = 0,
days: int = 0,
hours: int = 0,
minutes: int = 0,
seconds: int = 0,
start_date: str | None = None,
end_date: str | None = None,
timezone: str | None = None,
jitter: int | None = None,
channel: discord.TextChannel | None = None,
user: discord.User | None = None,
dm_and_current_channel: bool | None = None, # noqa: FBT001
) -> None:
"""Create a new reminder that triggers based on an interval.
Args:
interaction (discord.Interaction): The interaction object for the command.
message (str): The content of the reminder.
weeks (int, optional): Number of weeks between each run. Defaults to 0.
days (int, optional): Number of days between each run. Defaults to 0.
hours (int, optional): Number of hours between each run. Defaults to 0.
minutes (int, optional): Number of minutes between each run. Defaults to 0.
seconds (int, optional): Number of seconds between each run. Defaults to 0.
start_date (str, optional): Earliest possible date/time to trigger on (inclusive). Will get parsed.
end_date (str, optional): Latest possible date/time to trigger on (inclusive). Will get parsed.
timezone (str, optional): Time zone to use for the date/time calculations Defaults to scheduler timezone.
jitter (int, optional): Delay the job execution by jitter seconds at most.
channel (discord.TextChannel, optional): The channel to send the reminder to. Defaults to current channel.
user (discord.User, optional): Send reminder as a DM to this user. Defaults to None.
dm_and_current_channel (bool, optional): If user is provided, send reminder as a DM to the user and in this channel. Defaults to only the user.
""" # noqa: E501
# TODO(TheLovinator): Add try/except for all of these await calls # noqa: TD003
# TODO(TheLovinator): Add a warning if the interval is too short # noqa: TD003
# TODO(TheLovinator): Check if we have access to the channel and user# noqa: TD003
logger.info("New interval job from %s (%s) in %s", interaction.user, interaction.user.id, interaction.channel)
logger.info("Interval job arguments: %s", {k: v for k, v in locals().items() if k != "self" and v is not None})
# Get the channel ID
channel_id: int | None = self.get_channel_id(interaction=interaction, channel=channel)
if not channel_id:
await interaction.followup.send(content="Failed to get channel.")
return
# Ensure the guild is valid
guild: discord.Guild | None = interaction.guild or None
if not guild:
await interaction.followup.send(content="Failed to get guild.")
return
# Helper to add a job
def add_job(func: Callable, job_kwargs: dict[str, int | str]) -> Job:
return settings.scheduler.add_job(
func=func,
trigger="interval",
weeks=weeks,
days=days,
hours=hours,
minutes=minutes,
seconds=seconds,
start_date=start_date,
end_date=end_date,
timezone=timezone,
jitter=jitter,
kwargs=job_kwargs,
)
# Create user DM reminder job if user is specified
dm_message: str = ""
if user:
dm_job: Job = add_job(
func=send_to_user,
job_kwargs={
"user_id": user.id,
"guild_id": guild.id,
"message": message,
},
)
dm_message = f" and a DM to {user.display_name} "
if not dm_and_current_channel:
# If only DM is required, notify about the DM job and exit
await interaction.followup.send(
content=f"Hello {interaction.user.display_name},\n"
f"I will send a DM to {user.display_name} at:\n"
f"First run in {calculate(dm_job)} with the message:\n**{message}**.",
)
# Create channel reminder job
channel_job: Job = add_job(
func=send_to_discord,
job_kwargs={
"channel_id": channel_id,
"message": message,
"author_id": interaction.user.id,
},
)
# Compose the final message
await interaction.followup.send(
content=f"Hello {interaction.user.display_name},\n"
f"I will notify you in <#{channel_id}>{dm_message}.\n"
f"First run in {calculate(channel_job)} with the message:\n**{message}**.",
)
@staticmethod @staticmethod
def get_channel_id(interaction: discord.Interaction, channel: discord.TextChannel | None) -> int | None: def get_channel_id(interaction: discord.Interaction, channel: discord.TextChannel | None) -> int | None:
"""Get the channel ID to send the reminder to. """Get the channel ID to send the reminder to.

View File

@ -294,7 +294,7 @@ class JobManagementView(discord.ui.View):
logger.info("Deleting job: %s", self.job.id) logger.info("Deleting job: %s", self.job.id)
if hasattr(self.job, "__getstate__"): if hasattr(self.job, "__getstate__"):
logger.error("State: %s", self.job.__getstate__() if hasattr(self.job, "__getstate__") else "No state") logger.debug("State: %s", self.job.__getstate__() if hasattr(self.job, "__getstate__") else "No state")
# Log extra kwargs # Log extra kwargs
for key, value in job_kwargs.items(): for key, value in job_kwargs.items():
@ -316,7 +316,8 @@ class JobManagementView(discord.ui.View):
Returns: Returns:
str: The deletion message. str: The deletion message.
""" """
msg: str = f"# Job *{job_kwargs.get('message'), 'No message'}* has been deleted.\n" job_msg: str | int = job_kwargs.get("message", "No message found")
msg: str = f"# Job *{job_msg}* has been deleted.\n"
msg += f"**Job ID**: {self.job.id}\n" msg += f"**Job ID**: {self.job.id}\n"
# The time the job was supposed to run # The time the job was supposed to run
@ -390,7 +391,7 @@ class JobManagementView(discord.ui.View):
""" """
logger.info("Modifying job: %s", self.job.id) logger.info("Modifying job: %s", self.job.id)
if hasattr(self.job, "__getstate__"): if hasattr(self.job, "__getstate__"):
logger.error("State: %s", self.job.__getstate__() if hasattr(self.job, "__getstate__") else "No state") logger.debug("State: %s", self.job.__getstate__() if hasattr(self.job, "__getstate__") else "No state")
modal = ModifyJobModal(self.job, self.scheduler) modal = ModifyJobModal(self.job, self.scheduler)
await interaction.response.send_modal(modal) await interaction.response.send_modal(modal)