aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joseph <[email protected]>2018-12-02 05:38:53 +0000
committerGravatar GitHub <[email protected]>2018-12-02 05:38:53 +0000
commit325074c2b4928ef52bf34490e153dc254fef700b (patch)
tree425b8c8deb1e426cde26b87a6bbf0bb4437a8725
parentFix 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--Pipfile1
-rw-r--r--Pipfile.lock10
-rw-r--r--bot/constants.py2
-rw-r--r--bot/seasons/christmas/adventofcode.py120
4 files changed, 131 insertions, 2 deletions
diff --git a/Pipfile b/Pipfile
index fb49b687..6ffc2136 100644
--- a/Pipfile
+++ b/Pipfile
@@ -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):
"""