Skip to content

Instantly share code, notes, and snippets.

@desrod
Last active November 7, 2022 13:36
Show Gist options
  • Save desrod/4faf98adfdd13aeb6af4ec1095efaade to your computer and use it in GitHub Desktop.
Save desrod/4faf98adfdd13aeb6af4ec1095efaade to your computer and use it in GitHub Desktop.
Query Zwift public, upcoming events and build an HTML table of those events (in Python)
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": "setuid@gmail.com",
"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">&#128690; Cycling, {{ event['eventType'].title() }}</td>
{%- elif 'RUNNING' in event['sport'] %}
{%- if 'Ride' in event['eventType'] %}
<td class="l">&#127939; Running, Group Run</td>
{%- else %}
<td class="l">&#127939; 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