aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sebastiaan Zeeff <[email protected]>2019-12-04 17:50:08 +0100
committerGravatar GitHub <[email protected]>2019-12-04 17:50:08 +0100
commit54cae73ab735de9f06169bb29e668b7732e6d184 (patch)
treef21aaa356a6dca4545f69e7ca95baa617f9afaa1
parentMerge pull request #305 from python-discord/exclude-draft-prs (diff)
parentUnlock AoC role to make announcements actually ping the users (diff)
Merge pull request #328 from python-discord/aoc-announcement-mention-fix
Make the daily Advent of Code subscription service actually ping subscribers
-rw-r--r--bot/constants.py2
-rw-r--r--bot/seasons/christmas/adventofcode.py41
-rw-r--r--bot/utils/__init__.py23
3 files changed, 58 insertions, 8 deletions
diff --git a/bot/constants.py b/bot/constants.py
index c09d8369..ce650b80 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -23,7 +23,7 @@ class AdventOfCode:
class Channels(NamedTuple):
admins = 365960823622991872
- advent_of_code = 517745814039166986
+ advent_of_code = int(environ.get("AOC_CHANNEL_ID", 517745814039166986))
announcements = int(environ.get("CHANNEL_ANNOUNCEMENTS", 354619224620138496))
big_brother_logs = 468507907357409333
bot = 267659945086812160
diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py
index 71da8d94..f2ec83df 100644
--- a/bot/seasons/christmas/adventofcode.py
+++ b/bot/seasons/christmas/adventofcode.py
@@ -15,6 +15,7 @@ from pytz import timezone
from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Tokens, WHITELISTED_CHANNELS
from bot.decorators import override_in_channel
+from bot.utils import unlocked_role
log = logging.getLogger(__name__)
@@ -85,17 +86,42 @@ async def day_countdown(bot: commands.Bot) -> None:
while is_in_advent():
tomorrow, time_left = time_left_to_aoc_midnight()
- await asyncio.sleep(time_left.seconds)
+ # Correct `time_left.seconds` for the sleep we have after unlocking the role (-5) and adding
+ # a second (+1) as the bot is consistently ~0.5 seconds early in announcing the puzzles.
+ await asyncio.sleep(time_left.seconds - 4)
- channel = bot.get_channel(Channels.seasonalbot_chat)
+ channel = bot.get_channel(Channels.advent_of_code)
if not channel:
log.error("Could not find the AoC channel to send notification in")
break
- await channel.send(f"<@&{AocConfig.role_id}> Good morning! Day {tomorrow.day} is ready to be attempted. "
- f"View it online now at https://adventofcode.com/{AocConfig.year}/day/{tomorrow.day}"
- f" (this link could take a few minutes to start working). Good luck!")
+ 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
+
+ async with unlocked_role(aoc_role, delay=5):
+ 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!"
+ )
# Wait a couple minutes so that if our sleep didn't sleep enough
# time we don't end up announcing twice.
@@ -122,10 +148,10 @@ class AdventOfCode(commands.Cog):
self.status_task = None
countdown_coro = day_countdown(self.bot)
- self.countdown_task = asyncio.ensure_future(self.bot.loop.create_task(countdown_coro))
+ self.countdown_task = self.bot.loop.create_task(countdown_coro)
status_coro = countdown_status(self.bot)
- self.status_task = asyncio.ensure_future(self.bot.loop.create_task(status_coro))
+ self.status_task = self.bot.loop.create_task(status_coro)
@commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True)
@override_in_channel(AOC_WHITELIST)
@@ -422,6 +448,7 @@ class AdventOfCode(commands.Cog):
def cog_unload(self) -> None:
"""Cancel season-related tasks on cog unload."""
+ log.debug("Unloading the cog and canceling the background task.")
self.countdown_task.cancel()
self.status_task.cancel()
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index 0aa50af6..25fd4b96 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -1,4 +1,5 @@
import asyncio
+import contextlib
import re
import string
from typing import List
@@ -127,3 +128,25 @@ def replace_many(
return replacement.lower()
return regex.sub(_repl, sentence)
+
+
+async def unlocked_role(role: discord.Role, delay: int = 5) -> None:
+ """
+ Create a context in which `role` is unlocked, relocking it automatically after use.
+
+ A configurable `delay` is added before yielding the context and directly after exiting the
+ context to allow the role settings change to properly propagate at Discord's end. This
+ prevents things like role mentions from failing because of synchronization issues.
+
+ Usage:
+ >>> async with unlocked_role(role, delay=5):
+ ... await ctx.send(f"Hey {role.mention}, free pings for everyone!")
+ """
+ await role.edit(mentionable=True)
+ await asyncio.sleep(delay)
+ try:
+ yield
+ finally:
+ await asyncio.sleep(delay)
+ await role.edit(mentionable=False)