#!/usr/bin/env python
import re
import requests
from bs4 import BeautifulSoup, NavigableString, Tag
YEAR = 2024
s = requests.session()
with open(".cache/session") as f:
session = s.cookies["session"] = f.read()
me = re.search(r"((\d+)-[\da-f]+)
", s.get("https://adventofcode.com/leaderboard/private", cookies={"session": session}).text)[1].split("-")[0] # type: ignore
years = {}
try:
with open(".ranks.csv", "r") as file:
for line in file.readlines():
years[int(line.split(",")[0])] = line
except FileNotFoundError:
pass
file = open(".ranks.csv", "w")
for year in range(2015, YEAR + 1):
if year in years and year != YEAR:
file.write(years[year])
continue
scores = {}
names = {}
links = {}
imgs = {}
resp = s.get(f"https://adventofcode.com/{year}/stats")
if resp.status_code == 404:
break
resp.encoding = "utf-8"
bs = BeautifulSoup(resp.text, "html.parser")
stats = bs.select_one(".stats")
total = 0
for x in stats:
if type(x) is not Tag:
continue
firstonly = int(x.select_one(".stats-firstonly").text.strip())
both = int(x.select_one(".stats-both").text.strip())
total = max(total, firstonly + both)
for day in range(1, 26):
print(f"\r{year}/{day:02} ", end="")
resp = s.get(f"https://adventofcode.com/{year}/leaderboard/day/{day}")
if resp.status_code == 404:
break
resp.encoding = "utf-8"
bs = BeautifulSoup(resp.text, "html.parser")
for entry in bs.select(".leaderboard-entry"):
user_id = entry["data-user-id"]
rank = int(re.search(r"\d+", entry.select_one(".leaderboard-position").text)[0]) # type: ignore
name = [
str(c).strip()
for c in entry.descendants
if type(c) is NavigableString
and str(c).strip()
and not ({*c.parent.get("class", [])} & {"supporter-badge", "sponsor-badge"}) # type: ignore
][-1]
score = 101 - rank
scores[user_id] = scores.get(user_id, 0) + score
names[user_id] = name
if (link := entry.select_one("a")) and not (
{*link.get("class", [])} & {"supporter-badge", "sponsor-badge"}
):
links[user_id] = link.get("href")
if img := entry.select_one("img"):
imgs[user_id] = img.get("src")
lbfile = open(f"leaderboards/{year}.csv", "w")
lbmd = open(f"leaderboards/{year}.md", "w")
lbmd.write(f"# Advent of Code {year} Global Leaderboard\n\n")
lbmd.write(f"|Rank|Score|Username|\n")
lbmd.write(f"|-|-|-|\n")
leaderboard = sorted(scores, key=scores.get, reverse=True) # type: ignore
ranks = {}
last = None
for i, user_id in enumerate(leaderboard):
ranks[user_id] = i + 1 if last is None or scores[user_id] != scores[last] else ranks[last]
last = user_id
lbfile.write(",".join(map(str, [ranks[user_id], scores[user_id], names[user_id]])) + "\n")
name = names[user_id]
if user_id in links:
name = f"[{name}]({links[user_id]})"
if user_id in imgs:
name = f'
{name}'
lbmd.write(f"|**{ranks[user_id]}**|{scores[user_id]}|{name}|\n")
print(f"\r{year}: score={scores.get(me, 0)}, rank={ranks.get(me)}, leaderboard={len(ranks)}, total={total}")
file.write(",".join(map(str, [year, scores.get(me, 0), ranks.get(me), len(ranks), total])) + "\n")
lbfile.flush()
lbfile.close()
lbmd.flush()
lbmd.close()
file.flush()
file.close()