mirror of
https://github.com/zoffline/zwift-offline.git
synced 2026-08-03 09:21:33 -07:00
Merge pull request #41 from oldnapalm/master
Fix issues with Mac launcher 1.0.23 (issue #27) Update instructions to configure Mac client Fix issues with Windows launcher 1.0.50 Add support for multiple profiles (tested with Windows and Mac, untested with Docker) Remove free trial limit from profile (mentioned in issue #25) Add Garmin Connect activity upload Fix multi profile on Android (issue #44) Update to Zwift 1.0.48969
This commit is contained in:
@@ -65,9 +65,9 @@ dependencies listed below. The necessary Apache configuration is inside the
|
||||
<details><summary>Windows 10 Instructions</summary>
|
||||
|
||||
* Install Zwift
|
||||
* If your Zwift version is newer than 1.0.48307 you may have to uninstall, then reinstall after installing zoffline.
|
||||
* If your Zwift version is 1.0.48307, you're all set.
|
||||
* If Zwift is not installed install it after installing zoffline (1.0.48307 will be installed instead of the latest).
|
||||
* If your Zwift version is newer than 1.0.48969 you may have to uninstall, then reinstall after installing zoffline.
|
||||
* If your Zwift version is 1.0.48969, you're all set.
|
||||
* If Zwift is not installed install it after installing zoffline (1.0.48969 will be installed instead of the latest).
|
||||
* On your Windows machine running Zwift, copy the following files in this repo to a known location:
|
||||
* ``ssl/cert-zwift-com.p12``
|
||||
* ``ssl/cert-zwift-com.pem``
|
||||
@@ -77,7 +77,7 @@ dependencies listed below. The necessary Apache configuration is inside the
|
||||
* Open Notepad as an admin and open ``C:\Program Files (x86)\Zwift\data\cacert.pem``
|
||||
* Append the contents of ``ssl/cert-zwift-com.pem`` to cacert.pem
|
||||
* Open Notepad as an admin and open ``C:\Windows\System32\Drivers\etc\hosts``
|
||||
* Append this line: ``<zoffline ip> us-or-rly101.zwift.com secure.zwift.com cdn.zwift.com``
|
||||
* Append this line: ``<zoffline ip> us-or-rly101.zwift.com secure.zwift.com cdn.zwift.com launcher.zwift.com``
|
||||
<br />(Where ``<zoffline ip>`` is the ip address of the machine running zoffline. If
|
||||
it's running on the same machine as Zwift, use ``127.0.0.1`` as the ip.)
|
||||
|
||||
@@ -90,9 +90,9 @@ to generate your own certificates and do the same.
|
||||
<details><summary>Mac OS X Instructions (Thanks @oldnapalm!)</summary>
|
||||
|
||||
* Install Zwift
|
||||
* If your Zwift version is newer than 1.0.48307 you may have to uninstall, then reinstall after installing zoffline.
|
||||
* If your Zwift version is 1.0.48307, you're all set.
|
||||
* If Zwift is not installed install it after installing zoffline (1.0.48307 will be installed instead of the latest).
|
||||
* If your Zwift version is newer than 1.0.48969 you may have to uninstall, then reinstall after installing zoffline.
|
||||
* If your Zwift version is 1.0.48969, you're all set.
|
||||
* If Zwift is not installed install it after installing zoffline (1.0.48969 will be installed instead of the latest).
|
||||
* On your Mac machine running Zwift, copy the following files in this repo to a known location:
|
||||
* ``ssl/cert-zwift-com.p12``
|
||||
* ``ssl/cert-zwift-com.pem``
|
||||
@@ -102,8 +102,25 @@ to generate your own certificates and do the same.
|
||||
* If you're prompted for a password, just leave it blank. There is no password.
|
||||
* Using a text editor open ``~/Library/Application Support/Zwift/data/cacert.pem``
|
||||
* Append the contents of ``ssl/cert-zwift-com.pem`` to cacert.pem
|
||||
* Using a text editor (with admin privileges) open ``/Applications/Zwift.app/Contents/Info.plist``
|
||||
* Append these keys:
|
||||
```
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>zwift.com</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
```
|
||||
* Using a text editor (with admin privileges) open ``/etc/hosts``
|
||||
* Append this line: ``<zoffline ip> us-or-rly101.zwift.com secure.zwift.com cdn.zwift.com``
|
||||
* Append this line: ``<zoffline ip> us-or-rly101.zwift.com secure.zwift.com cdn.zwift.com launcher.zwift.com``
|
||||
<br />(Where ``<zoffline ip>`` is the ip address of the machine running zoffline. If
|
||||
it's running on the same machine as Zwift, use ``127.0.0.1`` as the ip.)
|
||||
|
||||
@@ -132,6 +149,7 @@ to generate your own certificates and do the same.
|
||||
* (modify on PC)
|
||||
* ``adb push hosts /etc/hosts``
|
||||
* Start Zwift and sign in using any email/password
|
||||
* To change profile, sign in using ``player_id`` (numeric, default is 1000) and any password (Thanks @kienkhuat!)
|
||||
|
||||
Why: We need to redirect Zwift to use zoffline and convince Zwift to
|
||||
accept zoffline's self signed certificates for Zwift's domain names. Feel free
|
||||
@@ -160,13 +178,26 @@ To obtain your current profile:
|
||||
|
||||
### Step 4 [OPTIONAL]: Obtain Strava API token
|
||||
|
||||
* Install dependencies: stravalib, six
|
||||
* e.g., on Linux/Mac: ``pip install stravalib six``
|
||||
* e.g., on Windows in command prompt: ``C:\Python27\Scripts\pip.exe install stravalib six``
|
||||
* Install dependencies: stravalib
|
||||
* e.g., on Linux/Mac: ``pip install stravalib``
|
||||
* e.g., on Windows in command prompt: ``C:\Python27\Scripts\pip.exe install stravalib``
|
||||
* Get CLIENT_ID and CLIENT_SECRET from https://www.strava.com/settings/api
|
||||
* Run ``scripts/strava_auth.py --client-id CLIENT_ID --client-secret CLIENT_SECRET``
|
||||
* Open http://localhost:8000/ and authorize.
|
||||
* Move the resulting strava_token.txt (saved in whatever directory you ran strava_auth.py in) into the ``storage`` directory.
|
||||
* Move the resulting strava_token.txt (saved in whatever directory you ran strava_auth.py in) into the ``storage/<player_id>`` directory.
|
||||
|
||||
|
||||
### Step 5 [OPTIONAL]: Upload activities to Garmin Connect
|
||||
|
||||
* Install dependencies: garmin-uploader
|
||||
* e.g., on Linux/Mac: ``pip install garmin-uploader``
|
||||
* e.g., on Windows in command prompt: ``C:\Python27\Scripts\pip.exe install garmin-uploader``
|
||||
* Create a file garmin_credentials.txt in the ``storage/<player_id>`` directory containing your login credentials
|
||||
```
|
||||
<username>
|
||||
<password>
|
||||
```
|
||||
* Note: this is not secure. Only do this if you are comfortable with your login credentials being stored in a clear text file.
|
||||
|
||||
|
||||
## Dependencies
|
||||
@@ -182,6 +213,8 @@ Docker
|
||||
* ``pip install protobuf_to_dict``
|
||||
* OPTIONAL: stravalib (https://github.com/hozn/stravalib)
|
||||
* ``pip install stravalib``
|
||||
* OPTIONAL: garmin-uploader (https://github.com/La0/garmin-uploader)
|
||||
* ``pip install garmin-uploader``
|
||||
|
||||
|
||||
## Known issues
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Zwift
|
||||
version="1.0.48307"
|
||||
version="1.0.48969"
|
||||
GAME_URL="https://us-or-rly101.zwift.com"
|
||||
manifest="ZwiftMac_1.0.48307_5019d0f8_manifest.xml"
|
||||
manifest_checksum="-683988237"
|
||||
manifest="ZwiftMac_1.0.48969_ec6cdfaf_manifest.xml"
|
||||
manifest_checksum="1897373237"
|
||||
mandatory_version="0.0.0"
|
||||
ver_cur_checksum="933528709"/>
|
||||
ver_cur_checksum="1271531551"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Zwift
|
||||
version="1.0.48307"
|
||||
version="1.0.48969"
|
||||
GAME_URL="https://us-or-rly101.zwift.com"
|
||||
manifest="Zwift_1.0.48307_5019d0f8_manifest.xml"
|
||||
manifest_checksum="-2074220869"
|
||||
manifest="Zwift_1.0.48969_ec6cdfaf_manifest.xml"
|
||||
manifest_checksum="-567094760"
|
||||
mandatory_version="0.0.0"
|
||||
ver_cur_checksum="-1992170625"/>
|
||||
ver_cur_checksum="317148842"/>
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Loading Zwift</title>
|
||||
<style>
|
||||
a, a:visited {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>zoffline Launcher</h1>
|
||||
|
||||
</br>
|
||||
<h2><a href="https://secure.zwift.com/ride">Click here to start Zwift</a></h2>
|
||||
</br>
|
||||
|
||||
<h3>Launch into a specific map*:
|
||||
<table border=0 cellpadding=15><tr>
|
||||
<td><a href="http://cdn.zwift.com/INNSBRUCK">Innsbruck</a></td>
|
||||
<td><a href="http://cdn.zwift.com/LONDON">London</a></td>
|
||||
<td><a href="http://cdn.zwift.com/NEWYORK">New York</a></td>
|
||||
<td><a href="http://cdn.zwift.com/RICHMOND">Richmond</a></td>
|
||||
<td><a href="http://cdn.zwift.com/WATOPIA">Watopia</a></td>
|
||||
<td><a href="http://cdn.zwift.com/YORKSHIRE">Yorkshire</a></td>
|
||||
</tr></table>
|
||||
</h3>
|
||||
<form method="POST" action="/start-zwift">
|
||||
<table border=0 cellpadding=15>
|
||||
<tr><td><p>Select profile:</p>
|
||||
<select name="id">
|
||||
{% for profile in profiles %}
|
||||
<option value="{{ profile.id }}">{{ profile.f4 + ' ' + profile.f5 }}</option>
|
||||
{% endfor %}
|
||||
</select></td>
|
||||
<td><p>Select map*:</p>
|
||||
<select name="map">
|
||||
<option value="CALENDAR">Calendar</option>
|
||||
<option value="INNSBRUCK">Innsbruck</option>
|
||||
<option value="LONDON">London</option>
|
||||
<option value="NEWYORK">New York</option>
|
||||
<option value="RICHMOND">Richmond</option>
|
||||
<option value="WATOPIA">Watopia</option>
|
||||
<option value="YORKSHIRE">Yorkshire</option>
|
||||
</select></td></tr>
|
||||
<tr><td><button type="submit">Start Zwift</button></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</br>
|
||||
To automatically launch Zwift and skip this step create a file called "auto_launch.txt" inside zoffline's storage directory.
|
||||
|
||||
+180
-46
@@ -14,7 +14,7 @@ from datetime import timedelta
|
||||
from io import BytesIO
|
||||
from shutil import copyfile
|
||||
|
||||
from flask import Flask, request, jsonify, g, redirect
|
||||
from flask import Flask, request, jsonify, g, redirect, render_template
|
||||
from google.protobuf.descriptor import FieldDescriptor
|
||||
from protobuf_to_dict import protobuf_to_dict, TYPE_CALLABLE_MAP
|
||||
|
||||
@@ -29,7 +29,7 @@ import protobuf.world_pb2 as world_pb2
|
||||
import protobuf.zfiles_pb2 as zfiles_pb2
|
||||
|
||||
|
||||
logging.basicConfig()
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
logger = logging.getLogger('zoffline')
|
||||
logger.setLevel(logging.WARN)
|
||||
|
||||
@@ -56,12 +56,11 @@ DATABASE_CUR_VER = 1
|
||||
|
||||
# For auth server
|
||||
AUTOLAUNCH_FILE = "%s/auto_launch.txt" % STORAGE_DIR
|
||||
NOAUTO_EMBED = "http://cdn.zwift.com/static/web/launcher/embed-noauto.html"
|
||||
from tokens import *
|
||||
|
||||
|
||||
# Android uses https for cdn
|
||||
app = Flask(__name__, static_folder='%s/cdn/gameassets' % SCRIPT_DIR, static_url_path='/gameassets')
|
||||
app = Flask(__name__, static_folder='%s/cdn/gameassets' % SCRIPT_DIR, static_url_path='/gameassets', template_folder='%s/cdn/static/web/launcher' % SCRIPT_DIR)
|
||||
|
||||
|
||||
####
|
||||
@@ -73,6 +72,10 @@ type_callable_map[FieldDescriptor.TYPE_BYTES] = str
|
||||
type_callable_map[FieldDescriptor.TYPE_UINT64] = str
|
||||
|
||||
|
||||
profiles = list()
|
||||
selected_profile = 1000
|
||||
|
||||
|
||||
def insert_protobuf_into_db(table_name, msg):
|
||||
cur = g.db.cursor()
|
||||
msg_dict = protobuf_to_dict(msg, type_callable_map=type_callable_map)
|
||||
@@ -135,7 +138,7 @@ def world_time():
|
||||
|
||||
@app.route('/api/auth', methods=['GET'])
|
||||
def api_auth():
|
||||
return '{"realm":"zwift","url":"https://secure.zwift.com/auth/"}'
|
||||
return '{"realm":"zwift","launcher":"https://launcher.zwift.com/launcher","url":"https://secure.zwift.com/auth/"}'
|
||||
|
||||
|
||||
@app.route('/api/users/login', methods=['POST'])
|
||||
@@ -155,6 +158,8 @@ def api_users_login():
|
||||
|
||||
@app.route('/api/users/logout', methods=['POST'])
|
||||
def api_users_logout():
|
||||
# update profiles list in case new user was created
|
||||
list_profiles()
|
||||
return '', 204
|
||||
|
||||
|
||||
@@ -200,26 +205,45 @@ def api_private_event_feed():
|
||||
|
||||
@app.route('/api/profiles/me', methods=['GET'])
|
||||
def api_profiles_me():
|
||||
profile_dir = '%s/%s' % (STORAGE_DIR, selected_profile)
|
||||
try:
|
||||
if not os.path.isdir(profile_dir):
|
||||
os.makedirs(profile_dir)
|
||||
except IOError, e:
|
||||
logger.error("failed to create profile dir (%s): %s", profile_dir, str(e))
|
||||
sys.exit(1)
|
||||
profile = profile_pb2.Profile()
|
||||
profile_file = '%s/profile.bin' % STORAGE_DIR
|
||||
profile_file = '%s/profile.bin' % profile_dir
|
||||
if not os.path.isfile(profile_file):
|
||||
profile.id = 1000
|
||||
profile.id = selected_profile
|
||||
profile.is_connected_to_strava = True
|
||||
profile.f3 = 'user@email.com'
|
||||
return profile.SerializeToString(), 200
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile.ParseFromString(fd.read())
|
||||
# ensure profile.id = directory (in case directory is renamed)
|
||||
if profile.id != selected_profile:
|
||||
logger.warn('player_id is different from profile directory, updating database...')
|
||||
cur = g.db.cursor()
|
||||
cur.execute('UPDATE activity SET player_id = ? WHERE player_id = ?', (str(selected_profile), str(profile.id)))
|
||||
cur.execute('UPDATE goal SET player_id = ? WHERE player_id = ?', (str(selected_profile), str(profile.id)))
|
||||
cur.execute('UPDATE segment_result SET player_id = ? WHERE player_id = ?', (str(selected_profile), str(profile.id)))
|
||||
g.db.commit()
|
||||
profile.id = selected_profile
|
||||
if not profile.f3:
|
||||
profile.f3 = 'user@email.com'
|
||||
# clear f60 to remove free trial limit
|
||||
if profile.f60:
|
||||
logger.warn('Profile contains bytes related to subscription/billing, removing...')
|
||||
del profile.f60[:]
|
||||
return profile.SerializeToString(), 200
|
||||
|
||||
|
||||
# FIXME (not going to fix unless really bored): only supports 1 profile
|
||||
@app.route('/api/profiles/<int:player_id>', methods=['PUT'])
|
||||
def api_profiles_id(player_id):
|
||||
if not request.stream:
|
||||
return '', 400
|
||||
with open('%s/profile.bin' % STORAGE_DIR, 'wb') as f:
|
||||
with open('%s/%s/profile.bin' % (STORAGE_DIR, player_id), 'wb') as f:
|
||||
f.write(request.stream.read())
|
||||
return '', 204
|
||||
|
||||
@@ -246,6 +270,70 @@ def api_profiles_activities(player_id):
|
||||
return activities.SerializeToString(), 200
|
||||
|
||||
|
||||
def strava_upload(player_id, activity):
|
||||
try:
|
||||
from stravalib.client import Client
|
||||
except ImportError:
|
||||
logger.warn("stravalib is not installed. Skipping Strava upload attempt.")
|
||||
return
|
||||
profile_dir = '%s/%s' % (STORAGE_DIR, player_id)
|
||||
strava = Client()
|
||||
try:
|
||||
with open('%s/strava_token.txt' % profile_dir, 'r') as f:
|
||||
client_id = f.readline().rstrip('\r\n')
|
||||
client_secret = f.readline().rstrip('\r\n')
|
||||
strava.access_token = f.readline().rstrip('\r\n')
|
||||
refresh_token = f.readline().rstrip('\r\n')
|
||||
expires_at = f.readline().rstrip('\r\n')
|
||||
except:
|
||||
logger.warn("Failed to read %s/strava_token.txt. Skipping Strava upload attempt." % profile_dir)
|
||||
return
|
||||
try:
|
||||
if time.time() > int(expires_at):
|
||||
refresh_response = strava.refresh_access_token(client_id=client_id, client_secret=client_secret,
|
||||
refresh_token=refresh_token)
|
||||
with open('%s/strava_token.txt' % profile_dir, 'w') as f:
|
||||
f.write(client_id + '\n')
|
||||
f.write(client_secret + '\n')
|
||||
f.write(refresh_response['access_token'] + '\n')
|
||||
f.write(refresh_response['refresh_token'] + '\n')
|
||||
f.write(str(refresh_response['expires_at']) + '\n')
|
||||
except:
|
||||
logger.warn("Failed to refresh token. Skipping Strava upload attempt.")
|
||||
return
|
||||
try:
|
||||
# See if there's internet to upload to Strava
|
||||
strava.upload_activity(BytesIO(activity.fit), data_type='fit', name=activity.name)
|
||||
# XXX: assume the upload succeeds on strava's end. not checking on it.
|
||||
except:
|
||||
logger.warn("Strava upload failed. No internet?")
|
||||
|
||||
def garmin_upload(player_id, activity):
|
||||
try:
|
||||
from garmin_uploader.workflow import Workflow
|
||||
except ImportError:
|
||||
logger.warn("garmin_uploader is not installed. Skipping Garmin upload attempt.")
|
||||
return
|
||||
profile_dir = '%s/%s' % (STORAGE_DIR, player_id)
|
||||
try:
|
||||
with open('%s/garmin_credentials.txt' % profile_dir, 'r') as f:
|
||||
username = f.readline().rstrip('\r\n')
|
||||
password = f.readline().rstrip('\r\n')
|
||||
except:
|
||||
logger.warn("Failed to read %s/garmin_credentials.txt. Skipping Garmin upload attempt." % profile_dir)
|
||||
return
|
||||
try:
|
||||
with open('%s/last_activity.fit' % profile_dir, 'wb') as f:
|
||||
f.write(activity.fit)
|
||||
except:
|
||||
logger.warn("Failed to save fit file. Skipping Garmin upload attempt.")
|
||||
return
|
||||
try:
|
||||
w = Workflow(['%s/last_activity.fit' % profile_dir], activity_name=activity.name, username=username, password=password)
|
||||
w.run()
|
||||
except:
|
||||
logger.warn("Garmin upload failed. No internet?")
|
||||
|
||||
# With 64 bit ids Zwift can pass negative numbers due to overflow, which the flask int
|
||||
# converter does not handle so it's a string argument
|
||||
@app.route('/api/profiles/<int:player_id>/activities/<string:activity_id>', methods=['PUT'])
|
||||
@@ -260,41 +348,8 @@ def api_profiles_activities_id(player_id, activity_id):
|
||||
response = '{"id":%s}' % activity_id
|
||||
if request.args.get('upload-to-strava') != 'true':
|
||||
return response, 200
|
||||
try:
|
||||
from stravalib.client import Client
|
||||
except ImportError:
|
||||
logger.warn("stravalib is not installed. Skipping Strava upload attempt.")
|
||||
return response, 200
|
||||
strava = Client()
|
||||
try:
|
||||
with open('%s/strava_token.txt' % STORAGE_DIR, 'r') as f:
|
||||
client_id = f.readline().rstrip('\n')
|
||||
client_secret = f.readline().rstrip('\n')
|
||||
strava.access_token = f.readline().rstrip('\n')
|
||||
refresh_token = f.readline().rstrip('\n')
|
||||
expires_at = f.readline().rstrip('\n')
|
||||
except:
|
||||
logger.warn("Failed to read %s/strava_token.txt. Skipping Strava upload attempt." % STORAGE_DIR)
|
||||
return response, 200
|
||||
try:
|
||||
if time.time() > int(expires_at):
|
||||
refresh_response = strava.refresh_access_token(client_id=client_id, client_secret=client_secret,
|
||||
refresh_token=refresh_token)
|
||||
with open('%s/strava_token.txt' % STORAGE_DIR, 'w') as f:
|
||||
f.write(client_id + '\n');
|
||||
f.write(client_secret + '\n');
|
||||
f.write(refresh_response['access_token'] + '\n');
|
||||
f.write(refresh_response['refresh_token'] + '\n');
|
||||
f.write(str(refresh_response['expires_at']) + '\n');
|
||||
except:
|
||||
logger.warn("Failed to refresh token. Skipping Strava upload attempt.")
|
||||
return response, 200
|
||||
try:
|
||||
# See if there's internet to upload to Strava
|
||||
strava.upload_activity(BytesIO(activity.fit), data_type='fit', name=activity.name)
|
||||
# XXX: assume the upload succeeds on strava's end. not checking on it.
|
||||
except:
|
||||
logger.warn("Strava upload failed. No internet?")
|
||||
strava_upload(player_id, activity)
|
||||
garmin_upload(player_id, activity)
|
||||
return response, 200
|
||||
|
||||
|
||||
@@ -574,7 +629,51 @@ def teardown_request(exception):
|
||||
g.db.close()
|
||||
|
||||
|
||||
@app.before_first_request
|
||||
def move_old_profile():
|
||||
profile_file = '%s/profile.bin' % STORAGE_DIR
|
||||
if os.path.isfile(profile_file):
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile = profile_pb2.Profile()
|
||||
profile.ParseFromString(fd.read())
|
||||
profile_dir = '%s/%s' % (STORAGE_DIR, profile.id)
|
||||
try:
|
||||
if not os.path.isdir(profile_dir):
|
||||
os.makedirs(profile_dir)
|
||||
except IOError, e:
|
||||
logger.error("failed to create profile dir (%s): %s", profile_dir, str(e))
|
||||
sys.exit(1)
|
||||
os.rename(profile_file, '%s/profile.bin' % profile_dir)
|
||||
strava_file = '%s/strava_token.txt' % STORAGE_DIR
|
||||
if os.path.isfile(strava_file):
|
||||
os.rename(strava_file, '%s/strava_token.txt' % profile_dir)
|
||||
|
||||
|
||||
def list_profiles():
|
||||
global profiles
|
||||
global selected_profile
|
||||
del profiles[:]
|
||||
for (root, dirs, files) in os.walk(STORAGE_DIR):
|
||||
dirs.sort()
|
||||
for profile_id in dirs:
|
||||
profile = profile_pb2.Profile()
|
||||
profile_file = '%s/%s/profile.bin' % (STORAGE_DIR, profile_id)
|
||||
if os.path.isfile(profile_file):
|
||||
with open(profile_file, 'rb') as fd:
|
||||
profile.ParseFromString(fd.read())
|
||||
# ensure profile.id = directory (in case directory is renamed)
|
||||
profile.id = int(profile_id)
|
||||
profiles.append(profile)
|
||||
profile = profile_pb2.Profile()
|
||||
if profiles:
|
||||
profile.id = profiles[-1].id + 1
|
||||
# select first profile for auto launch
|
||||
selected_profile = profiles[0].id
|
||||
else:
|
||||
profile.id = 1000
|
||||
profile.f4 = 'New profile'
|
||||
profiles.append(profile)
|
||||
|
||||
|
||||
def init_database():
|
||||
conn = connect_db()
|
||||
cur = conn.cursor()
|
||||
@@ -612,6 +711,13 @@ def init_database():
|
||||
conn.close()
|
||||
|
||||
|
||||
@app.before_first_request
|
||||
def before_first_request():
|
||||
move_old_profile()
|
||||
list_profiles()
|
||||
init_database()
|
||||
|
||||
|
||||
####################
|
||||
#
|
||||
# Auth server (secure.zwift.com) routes below here
|
||||
@@ -624,6 +730,8 @@ def auth_rb():
|
||||
|
||||
|
||||
@app.route('/launcher', methods=['GET'])
|
||||
@app.route('/launcher/realms/zwift/protocol/openid-connect/auth', methods=['GET'])
|
||||
@app.route('/launcher/realms/zwift/protocol/openid-connect/registrations', methods=['GET'])
|
||||
@app.route('/auth/realms/zwift/protocol/openid-connect/auth', methods=['GET'])
|
||||
@app.route('/auth/realms/zwift/login-actions/request/login', methods=['GET', 'POST'])
|
||||
@app.route('/auth/realms/zwift/protocol/openid-connect/registrations', methods=['GET'])
|
||||
@@ -634,21 +742,47 @@ def auth_rb():
|
||||
def launch_zwift():
|
||||
# Zwift client has switched to calling https://launcher.zwift.com/launcher/ride
|
||||
if request.path != "/ride" and not os.path.exists(AUTOLAUNCH_FILE):
|
||||
return redirect(NOAUTO_EMBED, 302)
|
||||
return render_template("embed-noauto.html", profiles=profiles)
|
||||
else:
|
||||
return redirect("http://zwift/?code=zwift_refresh_token%s" % REFRESH_TOKEN, 302)
|
||||
|
||||
|
||||
@app.route('/auth/realms/zwift/protocol/openid-connect/token', methods=['POST'])
|
||||
def auth_realms_zwift_protocol_openid_connect_token():
|
||||
# select profile on Android
|
||||
global selected_profile
|
||||
profile_id = None
|
||||
username = request.form.get('username')
|
||||
if username:
|
||||
try:
|
||||
profile_id = int(username)
|
||||
except ValueError:
|
||||
pass
|
||||
if profile_id:
|
||||
selected_profile = profile_id
|
||||
return FAKE_JWT, 200
|
||||
|
||||
|
||||
@app.route("/start-zwift" , methods=['POST'])
|
||||
def start_zwift():
|
||||
global selected_profile
|
||||
selected_profile = int(request.form['id'])
|
||||
selected_map = request.form['map']
|
||||
if selected_map == 'CALENDAR':
|
||||
return redirect("/ride", 302)
|
||||
else:
|
||||
return redirect("http://cdn.zwift.com/%s" % selected_map, 302)
|
||||
|
||||
|
||||
# Called by Mac, but not Windows
|
||||
@app.route('/auth/realms/zwift/tokens/access/codes', methods=['POST'])
|
||||
def auth_realms_zwift_tokens_access_codes():
|
||||
return FAKE_JWT, 200
|
||||
|
||||
@app.route('/static/web/launcher/<filename>', methods=['GET'])
|
||||
def static_web_launcher(filename):
|
||||
return render_template(filename)
|
||||
|
||||
|
||||
def run_standalone():
|
||||
app.run(ssl_context=('%s/cert-zwift-com.pem' % SSL_DIR, '%s/key-zwift-com.pem' % SSL_DIR),
|
||||
|
||||
Reference in New Issue
Block a user