Enhance /remind list
and add calculate()
This commit is contained in:
@ -2,12 +2,16 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import textwrap
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import dateparser
|
import dateparser
|
||||||
import discord
|
import discord
|
||||||
from apscheduler.job import Job
|
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.abc import PrivateChannel
|
||||||
from discord.ui import Button, Select
|
from discord.ui import Button, Select
|
||||||
from discord_webhook import DiscordWebhook
|
from discord_webhook import DiscordWebhook
|
||||||
@ -137,7 +141,7 @@ class RemindGroup(discord.app_commands.Group):
|
|||||||
where_and_when = ""
|
where_and_when = ""
|
||||||
channel_id: int | None = self.get_channel_id(interaction, channel)
|
channel_id: int | None = self.get_channel_id(interaction, channel)
|
||||||
if user:
|
if user:
|
||||||
_user_reminder: Job = settings.scheduler.add_job(
|
user_reminder: Job = settings.scheduler.add_job(
|
||||||
send_to_user,
|
send_to_user,
|
||||||
run_date=run_date,
|
run_date=run_date,
|
||||||
kwargs={
|
kwargs={
|
||||||
@ -150,9 +154,11 @@ class RemindGroup(discord.app_commands.Group):
|
|||||||
dm_message = f"and a DM to {user.display_name} "
|
dm_message = f"and a DM to {user.display_name} "
|
||||||
if not dm_and_current_channel:
|
if not dm_and_current_channel:
|
||||||
should_send_channel_reminder = False
|
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:
|
if should_send_channel_reminder:
|
||||||
_reminder: Job = settings.scheduler.add_job(
|
reminder: Job = settings.scheduler.add_job(
|
||||||
send_to_discord,
|
send_to_discord,
|
||||||
run_date=run_date,
|
run_date=run_date,
|
||||||
kwargs={
|
kwargs={
|
||||||
@ -161,7 +167,9 @@ class RemindGroup(discord.app_commands.Group):
|
|||||||
"author_id": interaction.user.id,
|
"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}**."
|
final_message: str = f"Hello {interaction.user.display_name}, {where_and_when}With the message:\n**{message}**."
|
||||||
await interaction.followup.send(final_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)
|
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:
|
def create_job_embed(job: Job) -> discord.Embed:
|
||||||
"""Create an embed for a job.
|
"""Create an embed for a job.
|
||||||
|
|
||||||
@ -266,9 +316,18 @@ def create_job_embed(job: Job) -> discord.Embed:
|
|||||||
Returns:
|
Returns:
|
||||||
discord.Embed: The embed for the job.
|
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(
|
return discord.Embed(
|
||||||
title=f"Job: {job.name}",
|
title=embed_title,
|
||||||
description=f"Next run: {job.next_run_time.strftime('%Y-%m-%d %H:%M:%S') if job.next_run_time else 'Paused'}",
|
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(),
|
color=discord.Color.blue(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -283,9 +342,32 @@ class JobSelector(Select):
|
|||||||
scheduler: The scheduler to get the jobs from.
|
scheduler: The scheduler to get the jobs from.
|
||||||
"""
|
"""
|
||||||
self.scheduler: settings.AsyncIOScheduler = scheduler
|
self.scheduler: settings.AsyncIOScheduler = scheduler
|
||||||
options: list[discord.SelectOption] = [
|
options: list[discord.SelectOption] = []
|
||||||
discord.SelectOption(label=job.name, value=job.id) for job in settings.scheduler.get_jobs()
|
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)
|
super().__init__(placeholder="Select a job...", options=options)
|
||||||
|
|
||||||
async def callback(self, interaction: discord.Interaction) -> None:
|
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])
|
job: Job | None = self.scheduler.get_job(self.values[0])
|
||||||
if job:
|
if job:
|
||||||
embed = create_job_embed(job)
|
embed: discord.Embed = create_job_embed(job)
|
||||||
view = JobManagementView(job, self.scheduler)
|
view = JobManagementView(job, self.scheduler)
|
||||||
await interaction.response.edit_message(embed=embed, view=view)
|
await interaction.response.edit_message(embed=embed, view=view)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user