diff options
author | 2020-12-01 23:04:30 +0100 | |
---|---|---|
committer | 2020-12-01 23:04:30 +0100 | |
commit | 3e5cff49f40158acb6417d948c2155daed2e4c29 (patch) | |
tree | 1d99b366e6f58c57ae2f8649bc9cc5fefe65da19 | |
parent | Ensure 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.py | 58 | ||||
-rw-r--r-- | bot/exts/christmas/advent_of_code/_helpers.py | 77 |
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) |