aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2020-12-01 23:04:30 +0100
committerGravatar Sebastiaan Zeeff <[email protected]>2020-12-01 23:04:30 +0100
commit3e5cff49f40158acb6417d948c2155daed2e4c29 (patch)
tree1d99b366e6f58c57ae2f8649bc9cc5fefe65da19
parentEnsure status countdown waits for bot start-up (diff)
Let puzzle notification sleep until AoC starts
Instead of cancelling the task when it starts up outside of the boundaries of the Advent of Code, the task will now hibernate until just before the event starts if starts up before December. This allows it to actually announce the first puzzle. After the announcement for the last day of the current event is made, it will terminate itself. It will only start hibernating again when we've updated the environment variables for next year's event, ensuring that it does not run unnecessarily. To prevent issues with the guild cache not being available, I've added our new `wait_until_guild_available` waiting function. I've also moved the calls that get teh role/channel to before the loop, as there's no need to get them each time the loop goes around. I've also changed the way we calculate the time we need to sleep, as the old way used truncated seconds, meaning that we would always wake up relatively early. Instead, I'm now using fractional seconds, which means we can reduce the safety padding to a fraction of second. More accurate announcement timing!
-rw-r--r--bot/exts/christmas/advent_of_code/_cog.py58
-rw-r--r--bot/exts/christmas/advent_of_code/_helpers.py77
2 files changed, 77 insertions, 58 deletions
diff --git a/bot/exts/christmas/advent_of_code/_cog.py b/bot/exts/christmas/advent_of_code/_cog.py
index 57043454..902391b5 100644
--- a/bot/exts/christmas/advent_of_code/_cog.py
+++ b/bot/exts/christmas/advent_of_code/_cog.py
@@ -1,4 +1,3 @@
-import asyncio
import json
import logging
from datetime import datetime, timedelta
@@ -21,61 +20,6 @@ AOC_REQUEST_HEADER = {"user-agent": "PythonDiscord AoC Event Bot"}
AOC_WHITELIST = WHITELISTED_CHANNELS + (Channels.advent_of_code,)
-async def day_countdown(bot: commands.Bot) -> None:
- """
- Calculate the number of seconds left until the next day of Advent.
-
- Once we have calculated this we should then sleep that number and when the time is reached, ping
- the Advent of Code role notifying them that the new challenge is ready.
- """
- while _helpers.is_in_advent():
- tomorrow, time_left = _helpers.time_left_to_aoc_midnight()
-
- # Prevent bot from being slightly too early in trying to announce today's puzzle
- await asyncio.sleep(time_left.seconds + 1)
-
- channel = bot.get_channel(Channels.advent_of_code)
-
- if not channel:
- log.error("Could not find the AoC channel to send notification in")
- break
-
- aoc_role = channel.guild.get_role(AocConfig.role_id)
- if not aoc_role:
- log.error("Could not find the AoC role to announce the daily puzzle")
- break
-
- puzzle_url = f"https://adventofcode.com/{AocConfig.year}/day/{tomorrow.day}"
-
- # Check if the puzzle is already available to prevent our members from spamming
- # the puzzle page before it's available by making a small HEAD request.
- for retry in range(1, 5):
- log.debug(f"Checking if the puzzle is already available (attempt {retry}/4)")
- async with bot.http_session.head(puzzle_url, raise_for_status=False) as resp:
- if resp.status == 200:
- log.debug("Puzzle is available; let's send an announcement message.")
- break
- log.debug(f"The puzzle is not yet available (status={resp.status})")
- await asyncio.sleep(10)
- else:
- log.error("The puzzle does does not appear to be available at this time, canceling announcement")
- break
-
- await channel.send(
- f"{aoc_role.mention} Good morning! Day {tomorrow.day} is ready to be attempted. "
- f"View it online now at {puzzle_url}. Good luck!",
- allowed_mentions=discord.AllowedMentions(
- everyone=False,
- users=False,
- roles=[discord.Object(AocConfig.role_id)],
- )
- )
-
- # Wait a couple minutes so that if our sleep didn't sleep enough
- # time we don't end up announcing twice.
- await asyncio.sleep(120)
-
-
class AdventOfCode(commands.Cog):
"""Advent of Code festivities! Ho Ho Ho!"""
@@ -91,7 +35,7 @@ class AdventOfCode(commands.Cog):
self.countdown_task = None
self.status_task = None
- countdown_coro = day_countdown(self.bot)
+ countdown_coro = _helpers.day_countdown(self.bot)
self.countdown_task = self.bot.loop.create_task(countdown_coro)
status_coro = _helpers.countdown_status(self.bot)
diff --git a/bot/exts/christmas/advent_of_code/_helpers.py b/bot/exts/christmas/advent_of_code/_helpers.py
index 9ba4d9be..1c4a01ed 100644
--- a/bot/exts/christmas/advent_of_code/_helpers.py
+++ b/bot/exts/christmas/advent_of_code/_helpers.py
@@ -13,7 +13,7 @@ import discord
import pytz
from bot.bot import Bot
-from bot.constants import AdventOfCode, Colours
+from bot.constants import AdventOfCode, Channels, Colours
from bot.exts.christmas.advent_of_code import _caches
log = logging.getLogger(__name__)
@@ -447,3 +447,78 @@ async def countdown_status(bot: Bot) -> None:
delay = time_left.seconds % COUNTDOWN_STEP or COUNTDOWN_STEP
log.trace(f"The countdown status task will sleep for {delay} seconds.")
await asyncio.sleep(delay)
+
+
+async def day_countdown(bot: Bot) -> None:
+ """
+ Calculate the number of seconds left until the next day of Advent.
+
+ Once we have calculated this we should then sleep that number and when the time is reached, ping
+ the Advent of Code role notifying them that the new challenge is ready.
+ """
+ # We wake up one hour before the event starts to prepare the announcement
+ # of the release of the first puzzle.
+ await wait_for_advent_of_code(hours_before=1)
+
+ log.info("The Advent of Code has started or will start soon, waking up notification task.")
+
+ # Ensure that the guild cache is loaded so we can get the Advent of Code
+ # channel and role.
+ await bot.wait_until_guild_available()
+ aoc_channel = bot.get_channel(Channels.advent_of_code)
+ aoc_role = aoc_channel.guild.get_role(AdventOfCode.role_id)
+
+ if not aoc_channel:
+ log.error("Could not find the AoC channel to send notification in")
+ return
+
+ if not aoc_role:
+ log.error("Could not find the AoC role to announce the daily puzzle")
+ return
+
+ # The last event day is 25 December, so we only have to schedule
+ # a reminder if the current day is before 25 December.
+ end = datetime.datetime(AdventOfCode.year, 12, 25, tzinfo=EST)
+ while datetime.datetime.now(EST) < end:
+ log.trace("Started puzzle notification loop.")
+ tomorrow, time_left = time_left_to_aoc_midnight()
+
+ # Use fractional `total_seconds` to wake up very close to our target, with
+ # padding of 0.1 seconds to ensure that we actually pass midnight.
+ sleep_seconds = time_left.total_seconds() + 0.1
+ log.trace(f"The puzzle notification task will sleep for {sleep_seconds} seconds")
+ await asyncio.sleep(sleep_seconds)
+
+ puzzle_url = f"https://adventofcode.com/{AdventOfCode.year}/day/{tomorrow.day}"
+
+ # Check if the puzzle is already available to prevent our members from spamming
+ # the puzzle page before it's available by making a small HEAD request.
+ for retry in range(1, 5):
+ log.debug(f"Checking if the puzzle is already available (attempt {retry}/4)")
+ async with bot.http_session.head(puzzle_url, raise_for_status=False) as resp:
+ if resp.status == 200:
+ log.debug("Puzzle is available; let's send an announcement message.")
+ break
+ log.debug(f"The puzzle is not yet available (status={resp.status})")
+ await asyncio.sleep(10)
+ else:
+ log.error(
+ "The puzzle does does not appear to be available "
+ "at this time, canceling announcement"
+ )
+ break
+
+ await aoc_channel.send(
+ f"{aoc_role.mention} Good morning! Day {tomorrow.day} is ready to be attempted. "
+ f"View it online now at {puzzle_url}. Good luck!",
+ allowed_mentions=discord.AllowedMentions(
+ everyone=False,
+ users=False,
+ roles=[aoc_role],
+ )
+ )
+
+ # Ensure that we don't send duplicate announcements by sleeping to well
+ # over midnight. This means we're certain to calculate the time to the
+ # next midnight at the top of the loop.
+ await asyncio.sleep(120)