Merge pull request #29 from zoffline/master

This commit is contained in:
oldnapalm
2020-12-19 13:27:57 -03:00
committed by GitHub
5 changed files with 129 additions and 65 deletions
+21
View File
@@ -0,0 +1,21 @@
# Only breaking changes in releases will be documented here.
Update to Zwift_1.0.60239:
* With the update to this Zwift release, zoffline has included significant changes
to fully support a multiplayer mode, pace partners, and more.
* New dependencies required if running from source: pyJWT, flask-login, FlaskSQLAlchemy, gevent
* Install these with ``pip install pyjwt flask-login flask_sqlalchemy gevent``
* Multi-profile support has been deprecated in favor of full multiplayer support.
* See Step 6 in README.md for how to enable multiplayer support.
* Create an account for each profile you had and upload their profile.bin
and credential files via the new Zwift launcher.
* Do not "upgrade" from an existing single player installation to multiplayer and then
"downgrade" back to single player without backing up your original storage directory.
You will lose your activities, segment results, and goals if you do this and don't
have a backup to restore from.
Update to Zwift_1.0.51959:
* Changes were made to Zwift's cacert.pem. You will likely need to re-append
cert-zwift-com.pem to Zwift's cacert.pem in a specific way.
See https://github.com/zoffline/zwift-offline/issues/62 for details.
+1 -1
View File
@@ -3,7 +3,7 @@ MAINTAINER zoffline <zoffline@tutanota.com>
WORKDIR /usr/src/app
RUN apk add --no-cache git
RUN apk add --no-cache git gcc musl-dev libffi-dev openssl-dev file make
RUN pip install flask flask_sqlalchemy flask-login pyjwt gevent protobuf protobuf3_to_dict stravalib garmin-uploader cryptography
RUN git clone --depth 1 https://github.com/zoffline/zwift-offline
+45 -21
View File
@@ -1,6 +1,6 @@
# zoffline
zoffline enables the use of [Zwift](http://zwift.com) offline by acting as a partial implementation of a Zwift server.
zoffline enables the use of [Zwift](http://zwift.com) offline by acting as a partial implementation of a Zwift server. By default zoffline is only for a single player. See [Step 6: Enable Multiplayer](#step-6-optional-enable-multiplayer) for how to enable support for multiple users/profiles.
zoffline also offers riding against ghosts (your previous rides). Enable this feature by checking "Enable ghosts" in zoffline's launcher. See https://github.com/zoffline/zwift-offline/issues/56 for extra details.
@@ -27,19 +27,20 @@ To install zoffline on Windows:
<details><summary>Linux, Windows, or Mac OS X (from source)</summary>
To install zoffline on Linux, Windows, or Mac OS X:
* Install Python 2 or 3 (https://www.python.org/downloads/) if not already installed
* Install Python 3 if not already installed
* On Windows, installing Python via the Microsoft Store is highly recommend! If using a Python installer, ensure that in the first Python installer screen "Add Python 3.x to PATH" is checked.
* Install dependencies: flask, flask_sqlalchemy, flask-login, pyjwt, gevent, python-protobuf, protobuf3_to_dict, stravalib (optional)
* e.g., on Linux/Mac: ``pip install flask flask_sqlalchemy flask-login pyjwt gevent protobuf protobuf3_to_dict stravalib``
* e.g., on Windows in command prompt: ``C:\Python27\Scripts\pip.exe install flask flask_sqlalchemy flask-login pyjwt gevent protobuf protobuf3_to_dict stravalib``
* Python 3 is installed by default in ``C:\Users\<username>\AppData\Local\Programs\Python\Python38-32`` instead of ``C:\Python27``
* e.g., on Windows in command prompt: ``pip install flask flask_sqlalchemy flask-login pyjwt gevent protobuf protobuf3_to_dict stravalib``
* You may need to use ``C:\Users\<username>\AppData\Local\Programs\Python\Python39\Scripts\pip.exe`` instead of just ``pip``
* Clone or download this repo
* If you are not running zoffline on the same PC that Zwift is running: create a ``server-ip.txt`` file in the ``storage`` directory containing the IP address of the PC running zoffline.
* Run standalone.py before starting Zwift
* e.g., on Linux/Mac: ``sudo ./standalone.py``
* sudo is needed because we're binding to the privileged ports 80 and 443.
* If using Python 3, but Python 3 is not your system default run ``sudo python3 standalone.py``
* e.g., on Windows in command prompt: ``C:\Python27\python.exe standalone.py``
* For Python 3 the command will likely be ``C:\Users\<username>\AppData\Local\Programs\Python\Python38-32\python.exe standalone.py``
* e.g., on Windows in command prompt: ``python standalone.py``
* You may need to use ``C:\Users\<username>\AppData\Local\Programs\Python\Python39\python.exe`` instead of just ``python``
* Start Zwift with standalone.py running (__after completing step 2__)
* Note: When upgrading zoffline, be sure to retain the ``storage`` directory. It contains your Zwift progress state.
@@ -171,6 +172,8 @@ the Zwift system tray icon and exit) and restart Zwift.
### Step 3 [OPTIONAL]: Obtain current Zwift profile
<details><summary>Expand</summary>
If you don't obtain your current Zwift profile before first starting Zwift with
zoffline enabled, you will be prompted to create a new profile (height, weight,
gender). Your profile can be further customized and changed via the in game
@@ -185,27 +188,35 @@ To obtain your current profile:
* If multiplayer is enabled, use the upload button in the launcher window to import your file.
* If using Docker, move profile.bin into the path you passed to ``-v``
</details>
### Step 4 [OPTIONAL]: Obtain Strava API token
### Step 4 [OPTIONAL]: Upload activities to Strava
<details><summary>Expand</summary>
* 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``
* Or, if using the Windows zoffline.exe version without Python installed you can run strava_auth.exe obtained from https://github.com/zoffline/zwift-offline/releases/tag/zoffline_helper in place of ``scripts/strava_auth.py`` below.
* e.g., on Windows in command prompt: ``pip install stravalib``
* You may need to use ``C:\Users\<username>\AppData\Local\Programs\Python\Python39\Scripts\pip.exe`` instead of just ``pip``
* Or, if using the Windows zoffline.exe version without Python installed you can run ``strava_auth.exe`` obtained from https://github.com/zoffline/zwift-offline/releases/tag/zoffline_helper in place of ``scripts/strava_auth.py`` below.
* [OPTIONAL] 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``
* Run without arguments to use default values.
* 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/<player_id>`` directory.
* Move the resulting ``strava_token.txt`` (saved in whatever directory you ran ``strava_auth.py`` in) into the ``storage/<player_id>`` directory.
* If multiplayer is enabled, use the upload button in the launcher window to import your file.
</details>
### Step 5 [OPTIONAL]: Upload activities to Garmin Connect
<details><summary>Expand</summary>
* Install dependencies: garmin-uploader, cryptography (optional)
* e.g., on Linux/Mac: ``pip install garmin-uploader cryptography``
* e.g., on Windows in command prompt: ``C:\Python27\Scripts\pip.exe install garmin-uploader cryptography``
* Create a file garmin_credentials.txt in the ``storage/<player_id>`` directory containing your login credentials
* e.g., on Windows in command prompt: ``pip install garmin-uploader cryptography``
* You may need to use ``C:\Users\<username>\AppData\Local\Programs\Python\Python39\Scripts\pip.exe`` instead of just ``pip``
* Create a file ``garmin_credentials.txt`` in the ``storage/<player_id>`` directory containing your login credentials
```
<username>
<password>
@@ -213,16 +224,27 @@ To obtain your current profile:
* Note: this is not secure. Only do this if you are comfortable with your login credentials being stored in a clear text file.
* If multiplayer is enabled, use the upload button in the launcher window to encrypt the credentials file.
</details>
### Step 6 [OPTIONAL]: Enable multiplayer
* Create a multiplayer.txt file in the ``storage`` directory.
* If you are not running zoffline on the same PC that Zwift is running: create a server-ip.txt file in the ``storage`` directory containing the IP address of the PC running zoffline.
<details><summary>Expand</summary>
<details><summary>Extra optional steps</summary>
To enable support for multiple users perform the steps below. zoffline's previous multi-profile support has been superceded by full multiplayer support. If you were previously using multiple profiles with zoffline you will need to enable multiplayer to continue supporting multiple users.
* To obtain the official map schedule and update files from Zwift server: create a cdn-proxy.txt file in the ``storage`` directory. This can only work if you are running zoffline on a different machine than the Zwift client.
* To enable the password reset feature: create a gmail_credentials.txt file in the ``storage`` directory containing the login credentials of a Gmail account. You need to enable the "Less secure app access" in the account settings and you may need to access https://accounts.google.com/DisplayUnlockCaptcha to allow the login from the server.
* Create a ``multiplayer.txt`` file in the ``storage`` directory.
* If you are not running zoffline on the same PC that Zwift is running: create a ``server-ip.txt`` file in the ``storage`` directory containing the IP address of the PC running zoffline.
* TCP ports 80, 443, 3023 and UDP port 3022 will need to be open on the PC running zoffline if its running remotely.
* Start Zwift and create an account in the new Zwift launcher and upload your ``profile.bin``, ``strava_credentials.txt``, and/or ``garmin_credentials.txt`` if you have them.
* This account will only exist on your zoffline server and has no relation with your actual Zwift account.
</details>
### Extra optional steps
<details><summary>Expand</summary>
* To obtain the official map schedule and update files from Zwift server: create a ``cdn-proxy.txt`` file in the ``storage`` directory. This can only work if you are running zoffline on a different machine than the Zwift client.
* To enable the password reset feature when multiplayer is enabled: create a gmail_credentials.txt file in the ``storage`` directory containing the login credentials of a Gmail account. You need to enable the "Less secure app access" in the account settings and you may need to access https://accounts.google.com/DisplayUnlockCaptcha to allow the login from the server.
* If the Zwift client is having issues connecting to the Linux server ("The request was aborted: Could not create SSL/TLS secure channel." or "The underlying connection was closed: An unexpected error occurred on a send. Received an unexpected EOF or 0 bytes from the transport stream."): change MinProtocol in /etc/ssl/openssl.cnf to TLSv1.0
```
[system_default_sect]
@@ -231,9 +253,9 @@ To obtain your current profile:
```
</details>
## Discord and zoffline (online) server
## Community Discord and zoffline (online) server
Please join the [Discord](https://discord.gg/GMdn8F8) and our enhanced version of zoffline, hosted Online!
Please join the community supported [Discord](https://discord.gg/GMdn8F8) and their enhanced version of zoffline, hosted Online!
Follow the guide in #instructions to create your account and join other Zwifters.
@@ -243,7 +265,8 @@ Docker
-or-
* Python 2 or 3 (https://www.python.org/downloads/)
* Python 3 (https://www.python.org/downloads/)
* On Windows, installing Python via the Microsoft Store is highly recommend! If using a Python installer, ensure that in the first Python installer screen "Add Python 3.x to PATH" is checked.
* Flask (http://flask.pocoo.org/)
* ``pip install flask``
* python-protobuf (https://pypi.org/project/protobuf/)
@@ -269,7 +292,8 @@ Docker
## Note
Future Zwift updates may break zoffline until it's updated. While zoffline is
enabled Zwift updates will not be installed.
enabled Zwift updates will not be installed. If a zoffline update broke
something, check the ``CHANGELOG`` for possible changes that need to be made.
Don't expose zoffline to the internet, it was not designed with that in mind.
+61 -43
View File
@@ -3,36 +3,38 @@
#
# Adapted from https://github.com/jlemon/zlogger/blob/master/get_riders.py
#
#The MIT License (MIT)
# The MIT License (MIT)
#
#Copyright (c) 2016 Jonathan Lemon
# Copyright (c) 2016 Jonathan Lemon
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, argparse, getpass
import requests
import argparse
import getpass
import json
import os, time, stat
from collections import namedtuple
import os
import requests
import sys
if getattr(sys, 'frozen', False):
# If we're running as a pyinstaller bundle
# If we're running as a py installer bundle
SCRIPT_DIR = os.path.dirname(sys.executable)
else:
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
@@ -45,6 +47,7 @@ except NameError:
global args
global dbh
def post_credentials(session, username, password):
# Credentials POSTing and tokens retrieval
# POST https://secure.zwift.com/auth/realms/zwift/tokens/access/codes
@@ -67,8 +70,8 @@ def post_credentials(session, username, password):
"password": password,
"grant_type": "password",
},
allow_redirects = False,
verify = args.verifyCert,
allow_redirects=False,
verify=args.verifyCert,
)
if args.verbose:
@@ -84,6 +87,11 @@ def post_credentials(session, username, password):
except requests.exceptions.RequestException as e:
print('HTTP Request failed: %s' % e)
except KeyError as e:
print('Invalid uname and/or password')
exit(-1)
def query_player_profile(session, access_token):
# Query Player Profile
# GET https://us-or-rly101.zwift.com/api/profiles/<player_id>
@@ -99,7 +107,7 @@ def query_player_profile(session, access_token):
"Authorization": "Bearer %s" % access_token,
"Accept-Language": "en-us",
},
verify = args.verifyCert,
verify=args.verifyCert,
)
if args.verbose:
@@ -131,7 +139,7 @@ def logout(session, refresh_token):
"client_id": "Zwift_Mobile_Link",
"refresh_token": refresh_token,
},
verify = args.verifyCert,
verify=args.verifyCert,
)
if args.verbose:
print('Response HTTP Status Code: {status_code}'.format(
@@ -141,10 +149,12 @@ def logout(session, refresh_token):
except requests.exceptions.RequestException as e:
print('HTTP Request failed: %s' % e)
def login(session, user, password):
access_token, refresh_token, expired_in = post_credentials(session, user, password)
return access_token, refresh_token
def main(argv):
global args
global dbh
@@ -152,32 +162,39 @@ def main(argv):
access_token = None
cookies = None
parser = argparse.ArgumentParser(description = 'Zwift Profile Fetcher')
parser = argparse.ArgumentParser(description='Zwift Profile Fetcher')
parser.add_argument('-v', '--verbose', action='store_true',
help='Verbose output')
help='Verbose output')
parser.add_argument('--dont-check-certificates', action='store_false',
dest='verifyCert', default=True)
dest='verifyCert', default=True)
parser.add_argument('-u', '--user', help='Zwift user name')
args = parser.parse_args()
# if args.user:
# password = getpass.getpass("Password for %s? " % args.user)
# else:
# file = os.environ['HOME'] + '/.zwift_cred.json'
# with open(file) as f:
# try:
# cred = json.load(f)
# except ValueError, se:
# sys.exit('"%s": %s' % (args.output, se))
# f.close
# args.user = cred['user']
# password = cred['pass']
# if args.user:
# password = getpass.getpass("Password for %s? " % args.user)
# else:
# file = os.environ['HOME'] + '/.zwift_cred.json'
# with open(file) as f:
# try:
# cred = json.load(f)
# except ValueError, se:
# sys.exit('"%s": %s' % (args.output, se))
# f.close
# args.user = cred['user']
# password = cred['pass']
if args.user:
username = args.user
else:
username = input("Enter Zwift login (e-mail): ")
password = getpass.getpass("Enter password: ")
if not sys.stdin.isatty(): # This terminal cannot support input without displaying text
print(f'*WARNING* The current shell ({os.name}) cannot support hidden text entry.')
print(f'Your password entry WILL BE VISIBLE.')
print(f'If you are running a bash shell under windows, try executing this program via winpty:')
print(f'>winpty python {argv[0]}')
password = input("Enter password (will be shown):")
else:
password = getpass.getpass("Enter password: ")
session = requests.session()
@@ -188,10 +205,11 @@ def main(argv):
logout(session, refresh_token)
if __name__ == '__main__':
try:
main(sys.argv)
except KeyboardInterrupt:
pass
except SystemExit as se:
print("ERROR:",se)
print("ERROR:", se)
+1
View File
@@ -1547,6 +1547,7 @@ def before_first_request():
move_old_profile()
init_database()
db.create_all(app=app)
db.session.commit() # in case create_all created a table
check_columns()
db.session.close()
send_message_thread = threading.Thread(target=send_server_back_online_message)