Add leaderboard http service
This commit is contained in:
parent
dd756cb9eb
commit
2d51468eb1
3 changed files with 196 additions and 0 deletions
4
leaderboard/README
Normal file
4
leaderboard/README
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pip install flask
|
||||
mkdir templates
|
||||
mv main.jinja2 templates/
|
||||
AOC_TOKEN=<adventofcode.com session cookie> FLASK_APP=app flask run
|
||||
87
leaderboard/app.py
Normal file
87
leaderboard/app.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
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", datetime.now().year)
|
||||
leaderboard = request.args.get("board")
|
||||
token = request.args.get("token")
|
||||
if not token:
|
||||
return "Missing token get parameter. 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, "%Y-%m-%dT%H:%M:%S"), 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], "%Y-%m-%dT%H:%M:%S")
|
||||
),
|
||||
)
|
||||
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
|
||||
105
leaderboard/templates/main.jinja2
Normal file
105
leaderboard/templates/main.jinja2
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<!DOCTYPE html>
|
||||
<style>
|
||||
html {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
flex-direction: column;
|
||||
padding: 1em;
|
||||
width: 50em;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
table {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
th::after {
|
||||
display: block;
|
||||
content: "----------------------"
|
||||
}
|
||||
|
||||
th + th::after {
|
||||
content: "-------------"
|
||||
}
|
||||
|
||||
th + th + th::after {
|
||||
content: "--------"
|
||||
}
|
||||
</style>
|
||||
<h1>Leaderboard</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Local score</th>
|
||||
<th>Stars</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for local_score, stars, name in leaderboard %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ local_score }}</td>
|
||||
<td>{{ stars }}</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 %}
|
||||
Loading…
Add table
Reference in a new issue