-
-
Save jamesholcomb/c6fd593a33a8c62a3b8cad6f7a173395 to your computer and use it in GitHub Desktop.
Query Zwift public, upcoming events and build an HTML table of those events (in Python)
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
There are two parts to this: | |
1. The main Python code that uses requests + json to parse the events | |
2. The external Jinja2 template that the data is rendered by, producing the HTML output | |
# ---------------------------------------------------------------------------------------------------------------- | |
#!/usr/bin/env python3 | |
import json | |
import pendulum | |
import requests | |
from jinja2 import Environment, FileSystemLoader | |
env = Environment(loader=FileSystemLoader(".")) | |
event_tpl = env.get_template("events.tmpl") | |
local_tz = "EST" | |
event_url = "https://us-or-rly101.zwift.com/api/public/events/upcoming" | |
############################################################### | |
def fetch_json(event_url): | |
now = pendulum.now().to_rfc3339_string() | |
dt = pendulum.parse(now) | |
# Only fetch the upstream content once every 8 hours | |
# May need to fetch more frequently, if event data | |
# changes more often | |
if dt.hour % 6 == 0: | |
print("Fetching new events from upstream API endpoint...") | |
headers = { | |
"User-Agent": "Public Events Fetcher", | |
"From": "[email protected]", | |
"Content-Type": "application/json", | |
"Connection": "keep-alive", | |
} | |
response = requests.get(event_url, headers=headers) | |
# DEBUG: Print out headers, which indicate compression, gzip encoding | |
# print(response.request.headers) | |
return json.loads(response.text) | |
else: | |
print("Skipping upstream fetch, too soon for new events..") | |
############################################################### | |
def render_events(events): | |
for event in events: | |
event_start = pendulum.parse(event["eventStart"]).in_timezone(local_tz) | |
event["eventStart"] = event_start.strftime("%F %X%z (%Z) %A") | |
event["eventType"] = event["eventType"].replace("_", " ").title() | |
output = event_tpl.render(events=events) | |
# Write the output after rendeirng to 'events.html' | |
with open("events.html", "w") as html: | |
html.write(output) | |
############################################################### | |
def main(): | |
events = fetch_json(event_url) | |
render_events(events) | |
# Save the incoming events as events.json for offline | |
# testing and profiling, if needed, else comment out | |
with open("events.json", "w") as f: | |
f.write(json.dumps(events)) | |
if __name__ == "__main__": | |
main() | |
# ---------------------------------------------------------------------------------------------------------------- | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>Visible to Non-Participants</title> | |
<style> | |
table {font-family: "Courier New", monospace; font-size:0.8em; border-collapse: collapse;} | |
table, th, tr, td {border: 1px solid #ccc; padding:0.3em;} | |
th {text-align: center;} td {text-align:right;} | |
td.l {text-align:left;} | |
tr:nth-child(even) {background: #eee;} | |
</style> | |
</head> | |
<body> | |
<table> | |
<thead> | |
<tr> | |
<th>Kilometers</th> | |
<th>Minutes</th> | |
<th>Laps</th> | |
<th>Activity</th> | |
<th>Start time</th> | |
<th>Link</th></tr> | |
</thead> | |
<tbody> | |
{%- for event in events -%} | |
{%- if event['invisibleToNonParticipants'] == False %} | |
{%- if event['eventType'] == 'Group Ride' or (event['sport'] == 'Running' and event['eventType'] == 'Group Workout') %} | |
<tr> | |
<td>{{ event['distanceInMeters'] / 1000 }}</td> | |
<td>{{ event['durationInSeconds'] / 60 }}</td> | |
<td>{{ event['laps'] }}</td> | |
{%- if 'CYCLING' in event['sport'] %} | |
<td class="l">🚲 Cycling, {{ event['eventType'].title() }}</td> | |
{%- elif 'RUNNING' in event['sport'] %} | |
{%- if 'Ride' in event['eventType'] %} | |
<td class="l">🏃 Running, Group Run</td> | |
{%- else %} | |
<td class="l">🏃 Running, Group Workout</td> | |
{%- endif %} | |
{%- endif %} | |
<td>{{ event['eventStart'] }}</td> | |
<td class="l"><a href="https://zwift.com/events/view/{{ event['id'] }}">{{ event['name'] }}</a></td> | |
</tr> | |
{%- endif %} | |
{%- endif %} | |
{%- endfor %} | |
</tbody> | |
</table> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment