Enhance /remind list and add calculate()

This commit is contained in:
2025-01-03 23:09:47 +01:00
parent 8337a20b50
commit 649cf1b4a3

View File

@ -2,12 +2,16 @@ from __future__ import annotations
import datetime
import logging
import textwrap
from typing import TYPE_CHECKING
from zoneinfo import ZoneInfo
import dateparser
import discord
from apscheduler.job import Job
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from apscheduler.triggers.interval import IntervalTrigger
from discord.abc import PrivateChannel
from discord.ui import Button, Select
from discord_webhook import DiscordWebhook
@ -137,7 +141,7 @@ class RemindGroup(discord.app_commands.Group):
where_and_when = ""
channel_id: int | None = self.get_channel_id(interaction, channel)
if user:
_user_reminder: Job = settings.scheduler.add_job(
user_reminder: Job = settings.scheduler.add_job(
send_to_user,
run_date=run_date,
kwargs={
@ -150,9 +154,11 @@ class RemindGroup(discord.app_commands.Group):
dm_message = f"and a DM to {user.display_name} "
if not dm_and_current_channel:
should_send_channel_reminder = False
where_and_when: str = f"I will send a DM to {user.display_name} at:\n**{run_date}** (in )\n"
where_and_when: str = (
f"I will send a DM to {user.display_name} at:\n**{run_date}** (in {calculate(user_reminder)})\n"
)
if should_send_channel_reminder:
_reminder: Job = settings.scheduler.add_job(
reminder: Job = settings.scheduler.add_job(
send_to_discord,
run_date=run_date,
kwargs={
@ -161,7 +167,9 @@ class RemindGroup(discord.app_commands.Group):
"author_id": interaction.user.id,
},
)
where_and_when = f"I will notify you in <#{channel_id}> {dm_message}at:\n**{run_date}** (in)\n"
where_and_when = (
f"I will notify you in <#{channel_id}> {dm_message}at:\n**{run_date}** (in {calculate(reminder)})\n"
)
final_message: str = f"Hello {interaction.user.display_name}, {where_and_when}With the message:\n**{message}**."
await interaction.followup.send(final_message)
@ -257,6 +265,48 @@ class RemindGroup(discord.app_commands.Group):
await interaction.followup.send(embed=embed, view=view)
def calculate(job: Job) -> str:
"""Get trigger time from a reminder and calculate how many days, hours and minutes till trigger.
Days/Minutes will not be included if 0.
Args:
job: The job. Can be cron, interval or normal.
Returns:
Returns days, hours and minutes till the reminder. Returns "Couldn't calculate time" if no job is found.
"""
trigger_time: datetime.datetime | None = (
job.trigger.run_date if isinstance(job.trigger, DateTrigger) else job.next_run_time
)
if trigger_time is None:
logger.error("Couldn't calculate time for job: %s: %s", job.id, job.name)
return "Couldn't calculate time"
countdown_time: datetime.timedelta = trigger_time - datetime.datetime.now(tz=ZoneInfo(settings.config_timezone))
days, hours, minutes = (
countdown_time.days,
countdown_time.seconds // 3600,
countdown_time.seconds // 60 % 60,
)
# Return seconds if only seconds are left.
if days == 0 and hours == 0 and minutes == 0:
seconds: int = countdown_time.seconds % 60
return f"{seconds} second" + ("s" if seconds != 1 else "")
return ", ".join(
f"{x} {y}{'s' * (x != 1)}"
for x, y in (
(days, "day"),
(hours, "hour"),
(minutes, "minute"),
)
if x
)
def create_job_embed(job: Job) -> discord.Embed:
"""Create an embed for a job.
@ -266,9 +316,18 @@ def create_job_embed(job: Job) -> discord.Embed:
Returns:
discord.Embed: The embed for the job.
"""
next_run_time: datetime.datetime | str = (
job.next_run_time.strftime("%Y-%m-%d %H:%M:%S") if job.next_run_time else "Paused"
)
job_kwargs: dict = job.kwargs or {}
channel_id: int = job_kwargs.get("channel_id", 0)
message: str = job_kwargs.get("message", "N/A")
author_id: int = job_kwargs.get("author_id", 0)
embed_title: str = textwrap.shorten(f"{message}", width=256, placeholder="...")
return discord.Embed(
title=f"Job: {job.name}",
description=f"Next run: {job.next_run_time.strftime('%Y-%m-%d %H:%M:%S') if job.next_run_time else 'Paused'}",
title=embed_title,
description=f"ID: {job.id}\nNext run: {next_run_time}\nTime left: {calculate(job)}\nChannel: <#{channel_id}>\nAuthor: <@{author_id}>", # noqa: E501
color=discord.Color.blue(),
)
@ -283,9 +342,32 @@ class JobSelector(Select):
scheduler: The scheduler to get the jobs from.
"""
self.scheduler: settings.AsyncIOScheduler = scheduler
options: list[discord.SelectOption] = [
discord.SelectOption(label=job.name, value=job.id) for job in settings.scheduler.get_jobs()
]
options: list[discord.SelectOption] = []
jobs: list[Job] = scheduler.get_jobs()
# Only 25 options are allowed in a select menu.
# TODO(TheLovinator): Add pagination for more than 25 jobs. # noqa: TD003
max_jobs: int = 25
if len(jobs) > max_jobs:
jobs = jobs[:max_jobs]
for job in jobs:
job_kwargs: dict = job.kwargs or {}
label_prefix: str = ""
if job.next_run_time is None:
label_prefix = "Paused: "
# Cron job
elif isinstance(job.trigger, CronTrigger):
label_prefix = "Cron: "
# Interval job
elif isinstance(job.trigger, IntervalTrigger):
label_prefix = "Interval: "
message: str = job_kwargs.get("message", f"{job.id}")
message: str = textwrap.shorten(f"{label_prefix}{message}", width=100, placeholder="...")
options.append(discord.SelectOption(label=message, value=job.id))
super().__init__(placeholder="Select a job...", options=options)
async def callback(self, interaction: discord.Interaction) -> None:
@ -296,7 +378,7 @@ class JobSelector(Select):
"""
job: Job | None = self.scheduler.get_job(self.values[0])
if job:
embed = create_job_embed(job)
embed: discord.Embed = create_job_embed(job)
view = JobManagementView(job, self.scheduler)
await interaction.response.edit_message(embed=embed, view=view)