mirror of
				https://github.com/TheLovinator1/discord-reminder-bot.git
				synced 2025-11-04 01:59:48 +01:00 
			
		
		
		
	Enhance /remind list and add calculate()
				
					
				
			This commit is contained in:
		@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user