Last active
December 1, 2024 14:32
-
-
Save madr/67618ae6880a7a4d0f87f996ebec8ccf to your computer and use it in GitHub Desktop.
Advent of Code timeline
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pip install flask | |
mkdir templates | |
cp main.jinja2 templates/ | |
AOC_TOKEN=<adventofcode.com session cookie> FLASK_APP=app flask run | |
# or | |
podman build . | |
podman run \ | |
--replace \ | |
--name aoc-leaderboard \ | |
-d -it \ | |
-p 8080:8080 \ | |
-e AOC_TOKEN=<adventofcode.com session cookie> \ | |
<container id> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import collections | |
from datetime import datetime | |
import json | |
import os | |
import urllib | |
from flask import Flask, render_template, request | |
os.environ["TZ"] = "Europe/Stockholm" | |
app = Flask(__name__) | |
def get_data(token, calendar, leaderboard, dummy_data=True): | |
if dummy_data: | |
with open("leaderboard.json") as lb: | |
data = lb.read() | |
else: | |
try: | |
url = f"https://adventofcode.com/{calendar}/leaderboard/private/view/{leaderboard}.json" | |
headers = {"Cookie": f"session={token}"} | |
req = urllib.request.Request(url, headers=headers) | |
with urllib.request.urlopen(req) as response: | |
data = response.read() | |
except TypeError: | |
data = "[]" | |
except urllib.error.HTTPError: | |
data = "[]" | |
return json.loads(data) | |
@app.route("/") | |
def hello_world(): | |
calendar = request.args.get("calendar", "2021") | |
leaderboard = request.args.get("board") | |
token = os.environ.get("AOC_TOKEN") | |
if not token: | |
return "Missing AOC_TOKEN environment variable. Use the session cookie on adventofcode.com" | |
if not leaderboard: | |
return "Missing board get parameter." | |
data = get_data(token, calendar, leaderboard, False) | |
if len(data) == 0: | |
return "Token expired or no access to board" | |
leaderboard = sorted( | |
[ | |
(int(member["local_score"]), int(member["stars"]), member["name"]) | |
for _, member in data["members"].items() | |
if int(member["stars"]) > 0 | |
], | |
key=lambda x: x[0], | |
reverse=True, | |
) | |
timeline = collections.defaultdict(lambda: []) | |
for _id, m in data["members"].items(): | |
for d, stars in m["completion_day_level"].items(): | |
for p, tsd in stars.items(): | |
ts = datetime.fromtimestamp(tsd["get_star_ts"]) | |
timeline[d].append( | |
(datetime.strftime(ts, "%H:%M:%S (%d/%m)"), d, p, m["name"]) | |
) | |
timeline = sorted(timeline.items(), key=lambda x: int(x[0]), reverse=True) | |
timeline = [ | |
( | |
d, | |
sorted( | |
tl, key=lambda entry: datetime.strptime(entry[0], "%H:%M:%S (%d/%m)") | |
), | |
) | |
for d, tl in timeline | |
] | |
timeline = [ | |
( | |
d, | |
[ | |
te + (get_score(time_entries, te, len(data["members"])),) | |
for te in time_entries | |
], | |
) | |
for d, time_entries in timeline | |
] | |
return render_template("main.jinja2", leaderboard=leaderboard, timeline=timeline) | |
def get_score(time_entries, current_te, nr_competitors): | |
list_for_current_star = [te for te in time_entries if te[2] == current_te[2]] | |
pos = [i for i, te in enumerate(list_for_current_star) if te[3] == current_te[3]][0] | |
return nr_competitors - pos |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM python:3-alpine3.20 AS base | |
WORKDIR /app | |
FROM base AS reqs | |
RUN pip install flask==3.1.0 | |
RUN pip install waitress==3.0.2 | |
FROM reqs AS app | |
RUN mkdir /app/templates | |
COPY *.jinja2 /app/templates | |
COPY app.py app.py | |
ENV AOC_TOKEN= | |
EXPOSE 8080 | |
VOLUME images | |
ENTRYPOINT waitress-serve app:app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<style> | |
html { | |
font-family: monospace; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
} | |
body { | |
display: flex; | |
gap: 1em; | |
flex-direction: column; | |
padding: 1em; | |
} | |
h1, h2 { | |
font-size: 1em; | |
font-weight: normal; | |
text-transform: uppercase; | |
} | |
h1::after { | |
display: block; | |
content: '===========' | |
} | |
h2::after { | |
display: block; | |
content: '------' | |
} | |
h3 { | |
font-size: 1em; | |
font-weight: normal; | |
} | |
h3::after { | |
content: ':' | |
} | |
ul, ol { | |
list-style: none; | |
} | |
</style> | |
<h1>Leaderboard</h1> | |
<table> | |
{% for local_score, stars, name in leaderboard %} | |
<tr> | |
<td>{{ local_score }}</td> | |
<td>{{ stars }}</td> | |
<td>{{ name }}</td> | |
</tr> | |
{% endfor %} | |
</table> | |
<h2>Events</h2> | |
{% for day, item in timeline %} | |
<h3>Day {{ day }}</h3> | |
<ol> | |
{% for ts, day, star, name, points in item %} | |
<li> | |
{% if star == '2' %} | |
{{ ts }} - {{ name }} finished | |
{% else %} | |
{{ ts }} - {{ name }} solved part 1 | |
{% endif %} | |
and got {{ points }} points! | |
</li> | |
{% endfor %} | |
</ol> | |
{% endfor %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment