Files
unbelievaselfbot/bot/timings.py

68 lines
2.6 KiB
Python

"""
timings.py
Holds classes related to maintaining consistent cooldowns for various commands and API operations.
"""
import asyncio
import logging
from datetime import datetime
from typing import Union, Optional
from bot import exceptions, constants
logger = logging.getLogger(__file__)
logger.setLevel(constants.LOGGING_LEVEL)
class Cooldown(object):
"""
A cooldown object helps users manage a minimum time passed between activations of something.
"""
def __init__(self, cooldown: float, now: bool = False, last_hit: Union[float, int] = None):
self.cooldown: float = max(0.0, cooldown)
self.hot_until: Optional[float] = float(last_hit) if last_hit is not None else None
if now:
self.hot_until = datetime.utcnow().timestamp()
def hit(self, safe: bool = False):
"""Activate the cooldown. Raises an exception if Safe is set to True and the cooldown has not passed."""
if safe and not self.ready:
raise exceptions.CooldownRequired('The cooldown duration has not passed. {}')
self.hot_until = datetime.utcnow().timestamp() + self.cooldown
def change_expiration(self, timestamp: Union[float, int]) -> None:
"""
Change the epoch timestamp at which the cooldown's hot period expires.
:param timestamp: A epoch timestamp.
"""
if self.hot_until is not None:
change = round(abs(timestamp - self.hot_until), 2)
change_word = 'longer' if timestamp > self.hot_until else 'sooner'
logger.debug(f'Changing cooldown timestamp to {round(timestamp, 2)} ({change}s {change_word})')
else:
change = round(abs(timestamp - datetime.utcnow().timestamp()), 2)
change_word = 'in the future' if timestamp >= datetime.utcnow().timestamp() else 'ago'
logger.debug(f'Setting cooldown timestamp to {round(timestamp, 2)} ({change}s {change_word})')
self.hot_until = timestamp
async def sleep(self) -> None:
if self.ready:
return
logger.debug(f'Sleeping for {round(self.time_left, 2)}s before sending a command.')
await asyncio.sleep(self.time_left)
@property
def time_left(self) -> float:
"""Returns the non-negative time left until the cooldown is ready."""
return max(0.0, self.hot_until - datetime.utcnow().timestamp())
@property
def ready(self, now: Union[float, int] = None) -> bool:
"""Returns True if the cooldown has passed."""
if self.hot_until:
return now or datetime.utcnow().timestamp() >= self.hot_until
return True