Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bot/exts/advent_of_code/_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def cog_load(self) -> None:
@tasks.loop(minutes=10.0)
async def completionist_task(self) -> None:
"""
Give members who have completed all 50 AoC stars the completionist role.
Give members who have completed all AoC stars for this year the completionist role.

Runs on a schedule, as defined in the task.loop decorator.
"""
Expand All @@ -107,8 +107,8 @@ async def completionist_task(self) -> None:
placement_leaderboard = json.loads(leaderboard["placement_leaderboard"])

for member_aoc_info in placement_leaderboard.values():
if member_aoc_info["stars"] != 50:
# Only give the role to people who have completed all 50 stars
if member_aoc_info["stars"] < _helpers.STARS_THIS_YEAR:
# Only give the role to people who have completed all stars for this year
continue

aoc_name = member_aoc_info["name"] or f"Anonymous #{member_aoc_info['id']}"
Expand Down
27 changes: 19 additions & 8 deletions bot/exts/advent_of_code/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@
# for each star to compute the rank score.
StarResult = collections.namedtuple("StarResult", "member_id completion_time")

# In 2025, AOC was changed to be held from Dec 1 to 12, with 12 days rather than 25.
# This implementation is done in such a way that any arbitary number of days can be supported.
def days_in_year(year: int | None = None) -> int:
"""Return the number of days in the current Advent of Code year."""
if year is None:
year = AdventOfCode.year
return 25 if year < 2025 else 12

DAYS_THIS_YEAR = days_in_year()
STARS_THIS_YEAR = DAYS_THIS_YEAR * 2

class UnexpectedRedirect(aiohttp.ClientError):
"""Raised when an unexpected redirect was detected."""
Expand Down Expand Up @@ -148,7 +158,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict:

# Create summary stats for the stars completed for each day of the event.
daily_stats = {}
for day in range(1, 26):
for day in range(1, DAYS_THIS_YEAR + 1):
day = str(day)
star_one = len(star_results.get((day, "1"), []))
star_two = len(star_results.get((day, "2"), []))
Expand Down Expand Up @@ -427,13 +437,15 @@ async def get_public_join_code(author: discord.Member) -> str | None:

def is_in_advent() -> bool:
"""
Check if we're currently on an Advent of Code day, excluding 25 December.
Check if we're currently on an Advent of Code day, excluding the final day of AOC for that year.

This helper function is used to check whether or not a feature that prepares
something for the next Advent of Code challenge should run. As the puzzle
published on the 25th is the last puzzle, this check excludes that date.
published on the final day is the last puzzle, this check excludes that date.
"""
return arrow.now(EST).day in range(1, 25) and arrow.now(EST).month == 12
# Advent of Code always begins on December 1st, and runs for one month
now = arrow.now(EST)
return now.month == 12 and now.day in range(1, DAYS_THIS_YEAR)


def time_left_to_est_midnight() -> tuple[datetime.datetime, datetime.timedelta]:
Expand Down Expand Up @@ -471,7 +483,7 @@ async def countdown_status(bot: SirRobin) -> None:
# sleeping for the entire year, it will only wait in the currently
# configured year. This means that the task will only start hibernating once
# we start preparing the next event by changing environment variables.
last_challenge = arrow.get(datetime.datetime(AdventOfCode.year, 12, 25, tzinfo=datetime.UTC), EST)
last_challenge = arrow.get(datetime.datetime(AdventOfCode.year, 12, DAYS_THIS_YEAR, tzinfo=datetime.UTC), EST)
end = last_challenge + datetime.timedelta(hours=1)

while arrow.now(EST) < end:
Expand Down Expand Up @@ -516,9 +528,8 @@ async def new_puzzle_notification(bot: SirRobin) -> None:
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 = arrow.get(datetime.datetime(AdventOfCode.year, 12, 25, tzinfo=datetime.UTC), EST)
# Only schedule a reminder if the current day is before the final day December.
end = arrow.get(datetime.datetime(AdventOfCode.year, 12, DAYS_THIS_YEAR, tzinfo=datetime.UTC), EST)
while arrow.now(EST) < end:
log.trace("Started puzzle notification loop.")
tomorrow, time_left = time_left_to_est_midnight()
Expand Down
4 changes: 3 additions & 1 deletion bot/exts/advent_of_code/views/dayandstarview.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import discord

from bot.exts.advent_of_code._helpers import DAYS_THIS_YEAR

AOC_DAY_AND_STAR_TEMPLATE = "{rank: >4} | {name:25.25} | {completion_time: >10}"


Expand Down Expand Up @@ -52,7 +54,7 @@ async def interaction_check(self, interaction: discord.Interaction) -> bool:

@discord.ui.select(
placeholder="Day",
options=[discord.SelectOption(label=str(i)) for i in range(1, 26)],
options=[discord.SelectOption(label=str(i)) for i in range(1, DAYS_THIS_YEAR + 1)],
custom_id="day_select",
)
async def day_select(self, interaction: discord.Interaction, select: discord.ui.Select) -> None:
Expand Down
21 changes: 12 additions & 9 deletions bot/exts/summer_aoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@

from bot.bot import SirRobin
from bot.constants import Bot, Channels, Roles
from bot.exts.advent_of_code._helpers import days_in_year
from bot.utils.time import time_until

log = logging.get_logger(__name__)

# TODO: Add support for different years in accordance with AOC changes
AOC_URL = "https://adventofcode.com/{year}/day/{day}"
LAST_DAY = 25
FIRST_YEAR = 2015
LAST_YEAR = arrow.get().year - 1
PUBLIC_NAME = "Revival of Code"
Expand Down Expand Up @@ -63,8 +64,9 @@ def __init__(self, bot: SirRobin):
self.wait_task: asyncio.Task | None = None
self.loop_task: tasks.Loop | None = None

self.is_running = False
self.is_running: bool = False
self.year: int | None = None
self.days_this_year: int | None = None
self.current_day: int | None = None
self.day_interval: int | None = None
self.post_time = 0
Expand Down Expand Up @@ -145,6 +147,7 @@ async def start(self, ctx: commands.Context, year: int, day_interval: int, post_

self.is_running = True
self.year = year
self.days_this_year = days_in_year(year)
self.current_day = 1
self.day_interval = day_interval
self.post_time = post_time
Expand Down Expand Up @@ -174,8 +177,8 @@ async def force_day(self, ctx: commands.Context, day: int, now: Literal["now"] |
await ctx.send(embed=embed)
return

if not 1 <= day <= LAST_DAY:
raise commands.BadArgument(f"Start day must be between 1 and {LAST_DAY}, inclusive")
if not 1 <= day <= self.days_this_year:
raise commands.BadArgument(f"Start day must be between 1 and {self.days_this_year}, inclusive")

log.info(f"Setting the current day of Summer AoC to {day}")
await self.stop_event()
Expand All @@ -187,7 +190,7 @@ async def force_day(self, ctx: commands.Context, day: int, now: Literal["now"] |

embed = self.get_info_embed()
if now:
if self.current_day > LAST_DAY:
if self.current_day > self.days_this_year:
title = "Puzzle posted and event is now ending"
else:
title = "Puzzle posted and event is now running"
Expand All @@ -197,7 +200,7 @@ async def force_day(self, ctx: commands.Context, day: int, now: Literal["now"] |
embed.title = title
embed.color = discord.Color.green()
await ctx.send(embed=embed)
if self.current_day <= LAST_DAY:
if self.current_day <= self.days_this_year:
await self.start_event()

@summer_aoc_group.command(name="stop")
Expand Down Expand Up @@ -290,7 +293,7 @@ async def stop_event(self) -> bool:

async def post_puzzle(self) -> None:
"""Create a thread for the current day's puzzle."""
if self.current_day > LAST_DAY:
if self.current_day > self.days_this_year:
log.error("Attempted to post puzzle after last day, stopping event")
await self.stop_event()
return
Expand All @@ -305,7 +308,7 @@ async def post_puzzle(self) -> None:

self.current_day += 1
await self.save_event_state()
if self.current_day > LAST_DAY:
if self.current_day > self.days_this_year:
await self.stop_event()

def get_info_embed(self) -> discord.Embed:
Expand All @@ -325,7 +328,7 @@ def get_info_embed(self) -> discord.Embed:

def get_puzzle_embed(self) -> discord.Embed:
"""Generate an embed for the day's puzzle post."""
if self.current_day == LAST_DAY:
if self.current_day == self.days_this_year:
next_puzzle_text = LAST_PUZZLE_TEXT.format(timestamp=int(arrow.get(REAL_AOC_START).timestamp()))
else:
next_puzzle_text = NEXT_PUZZLE_TEXT.format(
Expand Down