diff options
author | 2018-12-02 05:38:53 +0000 | |
---|---|---|
committer | 2018-12-02 05:38:53 +0000 | |
commit | 325074c2b4928ef52bf34490e153dc254fef700b (patch) | |
tree | 425b8c8deb1e426cde26b87a6bbf0bb4437a8725 | |
parent | Fix global lb display to respect ties (#79) (diff) |
AoC countdown (#81)
* Add countdown status and notifications
* Remove debug print
* flake8 my ass
* Import order fixes
* while is_in_advent() instead of while True
* 2 * 60 => 120
* while is_in_advent() instead of while True in notifier
-rw-r--r-- | Pipfile | 1 | ||||
-rw-r--r-- | Pipfile.lock | 10 | ||||
-rw-r--r-- | bot/constants.py | 2 | ||||
-rw-r--r-- | bot/seasons/christmas/adventofcode.py | 120 |
4 files changed, 131 insertions, 2 deletions
@@ -9,6 +9,7 @@ arrow = "*" beautifulsoup4 = "*" aiodns = "*" pillow = "*" +pytz = "*" [dev-packages] "flake8" = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 5cc764a4..1f693364 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3a42819305aee3c0cebc74cc6ba939e0744d1f4ac4db6d78659a67c569ac9dec" + "sha256": "37690a44eef1762372759be11694f873af63f032de0264280ad1762d917c2b89" }, "pipfile-spec": 6, "requires": { @@ -113,6 +113,14 @@ ], "version": "==2.7.5" }, + "pytz": { + "hashes": [ + "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", + "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" + ], + "index": "pypi", + "version": "==2018.7" + }, "six": { "hashes": [ "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", diff --git a/bot/constants.py b/bot/constants.py index 6020f1c1..1294912a 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -87,6 +87,8 @@ class AdventOfCode: leaderboard_join_code = "363275-442b6939" leaderboard_max_displayed_members = 10 year = 2018 + channel_id = int(environ.get("AOC_CHANNEL_ID", 517745814039166986)) + role_id = int(environ.get("AOC_ROLE_ID", 518565788744024082)) bot = SeasonalBot(command_prefix=Client.prefix) diff --git a/bot/seasons/christmas/adventofcode.py b/bot/seasons/christmas/adventofcode.py index 59467f20..9cdb7b4e 100644 --- a/bot/seasons/christmas/adventofcode.py +++ b/bot/seasons/christmas/adventofcode.py @@ -1,7 +1,8 @@ +import asyncio import json import logging import re -from datetime import datetime +from datetime import datetime, timedelta from pathlib import Path from typing import List @@ -9,6 +10,7 @@ import aiohttp import discord from bs4 import BeautifulSoup from discord.ext import commands +from pytz import timezone from bot.constants import AdventOfCode as AocConfig from bot.constants import Colours, Emojis, Tokens @@ -18,6 +20,84 @@ log = logging.getLogger(__name__) AOC_REQUEST_HEADER = {"user-agent": "PythonDiscord AoC Event Bot"} AOC_SESSION_COOKIE = {"session": Tokens.aoc_session_cookie} +EST = timezone("EST") + + +def is_in_advent() -> bool: + """ + Utility function to check if we are between December 1st + and December 25th. + """ + return datetime.now(EST).day in range(1, 26) and datetime.now(EST).month == 12 + + +def time_left_to_aoc_midnight() -> timedelta: + """ + This calculates the amount of time left until midnight in + UTC-5 (Advent of Code maintainer timezone). + """ + # Change all time properties back to 00:00 + todays_midnight = datetime.now(EST).replace(microsecond=0, + second=0, + minute=0, + hour=0) + + # We want tomorrow so add a day on + tomorrow = todays_midnight + timedelta(days=1) + + # Calculate the timedelta between the current time and midnight + return tomorrow, tomorrow - datetime.now(EST) + + +async def countdown_status(bot: commands.Bot): + """ + Every 2 minutes set the playing status of the bot to + the number of minutes & hours left until the next day + release. + """ + while is_in_advent(): + _, time_left = time_left_to_aoc_midnight() + + hours, minutes = time_left.seconds // 3600, time_left.seconds // 60 % 60 + + if hours == 0: + game = discord.Game(f"in {minutes} minutes") + else: + game = discord.Game(f"in {hours} hours and {minutes} minutes") + + # Status will look like "Playing in 5 hours and 30 minutes" + await bot.change_presence(activity=game) + + # Sleep 2 minutes + await asyncio.sleep(120) + + +async def day_countdown(bot: commands.Bot): + """ + 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 task is + ready. + """ + while is_in_advent(): + tomorrow, time_left = time_left_to_aoc_midnight() + + await asyncio.sleep(time_left.seconds) + + channel = bot.get_channel(AocConfig.channel_id) + + 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!") + + # 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: def __init__(self, bot: commands.Bot): @@ -33,6 +113,15 @@ class AdventOfCode: self.cached_global_leaderboard = None self.cached_private_leaderboard = None + self.countdown_task = None + self.status_task = None + + countdown_coro = day_countdown(self.bot) + self.countdown_task = asyncio.ensure_future(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)) + @commands.group(name="adventofcode", aliases=("aoc",), invoke_without_command=True) async def adventofcode_group(self, ctx: commands.Context): """ @@ -41,6 +130,35 @@ class AdventOfCode: await ctx.invoke(self.bot.get_command("help"), "adventofcode") + @adventofcode_group.command(name="notifications", aliases=("notify", "notifs"), brief="Notifications for new days") + async def aoc_notifications(self, ctx: commands.Context): + """ + Assign the role for notifications about new days being ready. + + Call the same command again to end notifications and remove the role. + """ + role = ctx.guild.get_role(AocConfig.role_id) + + if role in ctx.author.roles: + await ctx.author.remove_roles(role) + await ctx.send("Okay! You have been unsubscribed from notifications. If in future you want to" + " resubscribe just run this command again.") + else: + await ctx.author.add_roles(role) + await ctx.send("Okay! You have been subscribed to notifications about new Advent of Code tasks." + " To unsubscribe in future run the same command again.") + + @adventofcode_group.command(name="countdown", aliases=("count", "c"), brief="Return time left until next day") + async def aoc_countdown(self, ctx: commands.Context): + """ + Return time left until next day + """ + tomorrow, time_left = time_left_to_aoc_midnight() + + hours, minutes = time_left.seconds // 3600, time_left.seconds // 60 % 60 + + await ctx.send(f"There are {hours} hours and {minutes} minutes left until day {tomorrow.day}.") + @adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code") async def about_aoc(self, ctx: commands.Context): """ |