aboutsummaryrefslogtreecommitdiffstats
path: root/bot/decorators.py
diff options
context:
space:
mode:
Diffstat (limited to 'bot/decorators.py')
-rw-r--r--bot/decorators.py39
1 files changed, 39 insertions, 0 deletions
diff --git a/bot/decorators.py b/bot/decorators.py
index 2520679a..874c811b 100644
--- a/bot/decorators.py
+++ b/bot/decorators.py
@@ -1,3 +1,4 @@
+import asyncio
import functools
import logging
import random
@@ -13,6 +14,8 @@ from discord.ext.commands import CheckFailure, Context
from bot.constants import ERROR_REPLIES, Month
+ONE_DAY = 24 * 60 * 60
+
log = logging.getLogger(__name__)
@@ -28,6 +31,42 @@ class InMonthCheckFailure(CheckFailure):
pass
+def seasonal_task(*allowed_months: Month, sleep_time: float = ONE_DAY) -> typing.Callable:
+ """
+ Perform the decorated method periodically in `allowed_months`.
+
+ This provides a convenience wrapper to avoid code repetition where some task shall
+ perform an operation repeatedly in a constant interval, but only in specific months.
+
+ The decorated function will be called once every `sleep_time` seconds while
+ the current UTC month is in `allowed_months`. Sleep time defaults to 24 hours.
+ """
+ def decorator(task_body: typing.Callable) -> typing.Callable:
+ @functools.wraps(task_body)
+ async def decorated_task(self: commands.Cog, *args, **kwargs) -> None:
+ """
+ Call `task_body` once every `sleep_time` seconds in `allowed_months`.
+
+ We assume `self` to be a Cog subclass instance carrying a `bot` attr.
+ As some tasks may rely on the client's cache to be ready, we delegate
+ to the bot to wait until it's ready.
+ """
+ await self.bot.wait_until_ready()
+ log.info(f"Starting seasonal task {task_body.__qualname__} ({allowed_months})")
+
+ while True:
+ current_month = Month(datetime.utcnow().month)
+
+ if current_month in allowed_months:
+ await task_body(self, *args, **kwargs)
+ else:
+ log.debug(f"Seasonal task {task_body.__qualname__} sleeps in {current_month.name}")
+
+ await asyncio.sleep(sleep_time)
+ return decorated_task
+ return decorator
+
+
def in_month_listener(*allowed_months: Month) -> typing.Callable:
"""
Shield a listener from being invoked outside of `allowed_months`.