Brandon209-Red-bot-Cogs/scheduler/tasks.py
2021-06-14 16:17:13 -04:00

153 lines
5.4 KiB
Python

from __future__ import annotations
import contextlib
from datetime import datetime, timedelta, timezone
from typing import Optional, cast
import attr
import discord
from redbot.core.utils.chat_formatting import humanize_timedelta
from .message import SchedulerMessage
@attr.s(auto_attribs=True, slots=True)
class Task:
nicename: str
uid: str
author: discord.Member
content: str
channel: discord.TextChannel
initial: datetime
recur: Optional[timedelta] = None
extern_cog: Optional[str] = None
def __attrs_post_init__(self):
if self.initial.tzinfo is None:
self.initial = self.initial.replace(tzinfo=timezone.utc)
def __hash__(self):
return hash(self.uid)
async def get_message(self, bot):
pfx = (await bot.get_prefix(self.channel))[0]
content = f"{pfx}{self.content}"
return SchedulerMessage(content=content, author=self.author, channel=self.channel)
def to_config(self):
return {
self.uid: {
"nicename": self.nicename,
"author": self.author.id,
"content": self.content,
"channel": self.channel.id,
"initial": self.initial.timestamp(),
"recur": self.recur.total_seconds() if self.recur else None,
"extern_cog": self.extern_cog,
}
}
@classmethod
def bulk_from_config(cls, bot: discord.Client, **entries):
for uid, data in entries.items():
cid = data.pop("channel", 0)
aid = data.pop("author", 0)
initial_ts = data.pop("initial", 0)
initial = datetime.fromtimestamp(initial_ts, tz=timezone.utc)
recur_raw = data.pop("recur", None)
recur = timedelta(seconds=recur_raw) if recur_raw else None
channel = cast(Optional[discord.TextChannel], bot.get_channel(cid))
if not channel:
continue
author = channel.guild.get_member(aid)
if not author:
continue
with contextlib.suppress(AttributeError, ValueError):
yield cls(
initial=initial,
recur=recur,
channel=channel,
author=author,
uid=uid,
**data,
)
@property
def next_call_delay(self) -> float:
now = datetime.now(timezone.utc)
if self.recur and now >= self.initial:
raw_interval = self.recur.total_seconds()
return raw_interval - ((now - self.initial).total_seconds() % raw_interval)
return (self.initial - now).total_seconds()
def to_embed(self, index: int, page_count: int, color: discord.Color):
now = datetime.now(timezone.utc)
next_run_at = now + timedelta(seconds=self.next_call_delay)
embed = discord.Embed(color=color, timestamp=next_run_at)
embed.title = f"Now viewing {index} of {page_count} selected tasks"
embed.add_field(name="Command", value=f"[p]{self.content[:990]}")
embed.add_field(name="Channel", value=self.channel.mention)
embed.add_field(name="Creator", value=self.author.mention)
embed.add_field(name="Task ID", value=self.uid)
try:
fmt_date = self.initial.strftime("%A %B %-d, %Y at %-I%p %Z")
except ValueError: # Windows
# This looks less natural, but I'm not doing this piecemeal to emulate.
fmt_date = self.initial.strftime("%A %B %d, %Y at %I%p %Z")
if self.recur:
try:
fmt_date = self.initial.strftime("%A %B %-d, %Y at %-I%p %Z")
except ValueError: # Windows
# This looks less natural, but I'm not doing this piecemeal to emulate.
fmt_date = self.initial.strftime("%A %B %d, %Y at %I%p %Z")
if self.initial > now:
description = (
f"{self.nicename} starts running on {fmt_date}."
f"\nIt repeats every {humanize_timedelta(timedelta=self.recur)}"
)
else:
description = (
f"{self.nicename} started running on {fmt_date}."
f"\nIt repeats every {humanize_timedelta(timedelta=self.recur)}"
)
footer = "Next runtime:"
else:
try:
fmt_date = next_run_at.strftime("%A %B %-d, %Y at %-I%p %Z")
except ValueError: # Windows
# This looks less natural, but I'm not doing this piecemeal to emulate.
fmt_date = next_run_at.strftime("%A %B %d, %Y at %I%p %Z")
description = f"{self.nicename} will run at {fmt_date}."
footer = "Runtime:"
embed.set_footer(text=footer)
embed.description = description
return embed
def update_objects(self, bot):
"""Updates objects or throws an AttributeError"""
guild_id = self.author.guild.id
author_id = self.author.id
channel_id = self.channel.id
guild = bot.get_guild(guild_id)
self.author = guild.get_member(author_id)
self.channel = guild.get_channel(channel_id)
if not hasattr(self.channel, "id"):
raise AttributeError()
# Yes, this is slower than an inline `self.channel.id`
# It's also not slow anywhere important, and I prefer the clear intent