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
1 change: 1 addition & 0 deletions bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class Colours:
python_yellow = 0xFFD43B
grass_green = 0x66FF00
gold = 0xE6C200
aoc_violet = 0x43439D


# Git SHA for Sentry
Expand Down
77 changes: 54 additions & 23 deletions bot/exts/advent_of_code/_cog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Literal

import arrow
import discord
Expand Down Expand Up @@ -52,8 +53,13 @@ def __init__(self, bot: SirRobin):
self._base_url = f"https://adventofcode.com/{AocConfig.year}"
self.global_leaderboard_url = f"https://adventofcode.com/{AocConfig.year}/leaderboard"

self.about_aoc_filepath = Path("./bot/exts/advent_of_code/about.json")
self.cached_about_aoc = self._build_about_embed()
self.aoc_files = Path("./bot/exts/advent_of_code/")
self.cached_about_aoc = self._build_embed_from_json("about")
self.cached_changes = self._build_embed_from_json("changes_2025")
self.cached_no_global = self._build_embed_from_json(
"changes_2025", only_field="What happened to the global leaderboard?"
)
self.cached_no_global.colour = Colours.soft_red

self.scheduler = scheduling.Scheduler(self.__class__.__name__)

Expand Down Expand Up @@ -410,22 +416,27 @@ async def aoc_leaderboard(self, ctx: commands.Context, *, aoc_name: str | None =
await ctx.send(content=f"{header}\n\n{table}", embed=info_embed)
return

@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
@adventofcode_group.command(
name="changes",
brief="Frequently Asked Questions about Advent of Code changes in 2025",
)
@in_whitelist(channels=AOC_WHITELIST_RESTRICTED, redirect=AOC_REDIRECT)
async def aoc_changes(self, ctx: commands.Context) -> None:
"""Get answers to frequently asked questions about Advent of Code changes in 2025."""
await ctx.send(embed=self.cached_changes)

@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
@adventofcode_group.command(
name="global",
aliases=("globalboard", "gb"),
hidden=True, # Global leaderboard no longer exists
brief="Get a link to the global leaderboard",
)
@in_whitelist(channels=AOC_WHITELIST_RESTRICTED, redirect=AOC_REDIRECT)
async def aoc_global_leaderboard(self, ctx: commands.Context) -> None:
"""Get a link to the global Advent of Code leaderboard."""
url = self.global_leaderboard_url
global_leaderboard = discord.Embed(
title="Advent of Code — Global Leaderboard",
description=f"You can find the global leaderboard [here]({url})."
)
global_leaderboard.set_thumbnail(url=_helpers.AOC_EMBED_THUMBNAIL)
await ctx.send(embed=global_leaderboard)
# Same behaviour as aoc changes, but change the title to "where's the global leaderboard"
await ctx.send(embed=self.cached_no_global)

@in_month(Month.DECEMBER, Month.JANUARY, Month.FEBRUARY)
@adventofcode_group.command(
Expand Down Expand Up @@ -479,19 +490,39 @@ async def refresh_leaderboard(self, ctx: commands.Context) -> None:
else:
await ctx.send("\N{OK Hand Sign} Refreshed leaderboard cache!")

def _build_about_embed(self) -> discord.Embed:
def _build_embed_from_json(
self, file_type: Literal["about","changes_2025"], *, only_field: str | None = None
) -> discord.Embed:
"""Build and return the informational "About AoC" embed from the resources file."""
embed_fields = json.loads(self.about_aoc_filepath.read_text("utf8"))
if file_type == "about":
filepath = self.aoc_files / "about.json"
title = "Advent of Code"
url = self._base_url
colour = Colours.soft_green

elif file_type == "changes_2025":
filepath = self.aoc_files / "changes_2025.json"
title = "2025 Changes"
url = None
colour = Colours.aoc_violet
else:
raise ValueError("file_type must be either 'about' or 'changes_2025'")

about_embed = discord.Embed(
title=self._base_url,
colour=Colours.soft_green,
url=self._base_url,
timestamp=datetime.now(tz=UTC)
)
about_embed.set_author(name="Advent of Code", url=self._base_url)
for field in embed_fields:
about_embed.add_field(**field)
embed = discord.Embed(title=title, url=url, colour=colour, timestamp=datetime.now(tz=UTC))

embed_fields = json.loads(filepath.read_text("utf8"))

if only_field:
embed_fields = [field for field in embed_fields if field["name"] == only_field]
if not embed_fields:
raise ValueError(f"No field named '{only_field}' found in {file_type}.json")
embed.title = only_field
embed.description = embed_fields[0]["value"]
else:
for field in embed_fields:
embed.add_field(**field)

embed.set_author(name="Advent of Code", url=self._base_url)
embed.set_footer(text="Last Updated")

about_embed.set_footer(text="Last Updated")
return about_embed
return embed
4 changes: 2 additions & 2 deletions bot/exts/advent_of_code/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

AOC_EMBED_THUMBNAIL = (
"https://raw.githubusercontent.com/python-discord"
"/branding/main/seasonal/christmas/server_icons/festive_256.gif"
"/branding/main/events/christmas/server_icons/festive_256.gif"
)

# Create an easy constant for the EST timezone
Expand Down Expand Up @@ -188,7 +188,7 @@ def _format_leaderboard(leaderboard: dict[str, dict], self_placement_name: str |
raise commands.BadArgument(
"Sorry, your profile does not exist in this leaderboard."
"\n\n"
"To join our leaderboard, run the command `/aoc join`."
"To join our leaderboard, run the command </aoc join:1312458389388787853>."
" If you've joined recently, please wait up to 30 minutes for our leaderboard to refresh."
)
return "\n".join(leaderboard_lines)
Expand Down
8 changes: 4 additions & 4 deletions bot/exts/advent_of_code/about.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"inline": true
},
{
"name": "How does scoring work?",
"value": "For the [global leaderboard](https://adventofcode.com/leaderboard), the first person to get a star first gets 100 points, the second person gets 99 points, and so on down to 1 point at 100th place.\n\nFor private leaderboards, the first person to get a star gets N points, where N is the number of people on the leaderboard. The second person to get the star gets N-1 points and so on and so forth.",
"name": "Is it a competition?",
"value": "In prior years, AoC had a global leaderboard which ranked all participants. Beginning in 2025, the only leaderboards available are private leaderboards. In these private leaderboards, the first person to get a star gets N points, where N is the number of people on the leaderboard. The second person to get the star gets N-1 points and so on and so forth.",
"inline": false
},
{
"name": "Join our private leaderboard!",
"value": "Come join the Python Discord private leaderboard and compete against other people in the community! Get the join code using `.aoc join` and visit the [private leaderboard page](https://adventofcode.com/leaderboard/private) to join our leaderboard.",
"name": "Join our leaderboard!",
"value": "Come join the Python Discord leaderboard and compete against other people in the community! Get the join code using </aoc join:1312458389388787853> and visit the [leaderboard page](https://adventofcode.com/leaderboard/private) to join our leaderboard.",
"inline": false
}
]
26 changes: 26 additions & 0 deletions bot/exts/advent_of_code/changes_2025.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"name": "What changed in Advent of Code 2025?",
"value": "In 2025, Advent of Code made significant changes to its leaderboard system. The global leaderboard was removed, and now only private leaderboards are available. This change aims to create a more inclusive and supportive environment for all participants."
},
{
"name": "Why did the number of days per event change?",
"value": "[Eric needed a break.](https://hachyderm.io/@ericwastl/115415473413415697) The puzzles still start on December 1st so that the day numbers make sense (Day 1 = Dec 1), and puzzles come out every day (ending mid-December).",
"inline": false
},
{
"name": "What happened to the global leaderboard?",
"value": "The global leaderboard has been removed starting in 2025 due to DDoS attacks and other issues. Private leaderboards are now the only option for competition.\nYou can join the Python Discord leaderboard with </aoc join:1312458389388787853>.",
"inline": false
},
{
"name": "Is there still a way to compete with others?",
"value": "We have our own private leaderboard for the Python Discord community! You can join it using the </aoc join:1312458389388787853> command and compete with other members of the community.",
"inline": false
},
{
"name": "Where can I find more information?",
"value": "For more details about the changes in Advent of Code 2025, you can refer to the [official announcement on Reddit](https://www.reddit.com/r/adventofcode/comments/1ocwh04/changes_to_advent_of_code_starting_this_december/) and the [FAQ on the Advent of Code website](https://adventofcode.com/2025/about).",
"inline": false
}
]