diff --git a/cdn/static/web/launcher/user_home.html b/cdn/static/web/launcher/user_home.html
index 3170543..812f590 100644
--- a/cdn/static/web/launcher/user_home.html
+++ b/cdn/static/web/launcher/user_home.html
@@ -15,6 +15,10 @@
Upload
Change password
+
Strava auth
+ {% if server_ip != "localhost" and server_ip != "127.0.0.1" %}
+
Get Zwift profile
+ {% endif %}
Logout
{% if is_admin and not restarting %}
Restart server
diff --git a/scripts/strava_auth.py b/scripts/strava_auth.py
index 9043ac1..5210e47 100755
--- a/scripts/strava_auth.py
+++ b/scripts/strava_auth.py
@@ -62,10 +62,11 @@ class RequestHandler(BaseHTTPRequestHandler):
if request_path.startswith('/authorization'):
self.send_response(200)
- self.send_header(six.b("Content-type"), six.b("text/plain"))
+ self.send_header("Content-Type", "application/octet-stream")
+ self.send_header("Content-Disposition", "attachment; filename=strava_token.txt")
self.end_headers()
- self.wfile.write(six.b("Authorization Handler\n\n"))
+ #self.wfile.write(six.b("Authorization Handler\n\n"))
code = urlparse.parse_qs(parsed_path.query).get('code')
if code:
code = code[0]
@@ -75,22 +76,18 @@ class RequestHandler(BaseHTTPRequestHandler):
access_token = token_response['access_token']
refresh_token = token_response['refresh_token']
expires_at = token_response['expires_at']
- self.server.logger.info("Exchanged code {} for access token {}".format(code, access_token))
- self.wfile.write(six.b("Access Token: {}\n".format(access_token)))
- self.wfile.write(six.b("Refresh Token: {}\n".format(refresh_token)))
- self.wfile.write(six.b("Expires at: {}\n".format(expires_at)))
- with open('%s/strava_token.txt' % SCRIPT_DIR, 'w') as f:
- f.write(self.server.client_id + '\n');
- f.write(self.server.client_secret + '\n');
- f.write(access_token + '\n');
- f.write(refresh_token + '\n');
- f.write(str(expires_at) + '\n');
+
+ self.wfile.write(six.b("{}\n".format(self.server.client_id)))
+ self.wfile.write(six.b("{}\n".format(self.server.client_secret)))
+ self.wfile.write(six.b("{}\n".format(access_token)))
+ self.wfile.write(six.b("{}\n".format(refresh_token)))
+ self.wfile.write(six.b("{}\n".format(expires_at)))
else:
self.server.logger.error("No code param received.")
self.wfile.write(six.b("ERROR: No code param recevied.\n"))
else:
url = client.authorization_url(client_id=self.server.client_id,
- redirect_uri='http://localhost:{}/authorization'.format(self.server.server_port),
+ redirect_uri='http://18.133.120.5:{}/authorization'.format(self.server.server_port),
scope='activity:write')
self.send_response(302)
@@ -119,9 +116,9 @@ if __name__ == "__main__":
action='store', type=int, default=8000)
parser.add_argument('--client-id', help='Strava API Client ID',
- action='store', default='28117')
+ action='store', default='65392')
parser.add_argument('--client-secret', help='Strava API Client Secret',
- action='store', default='41b7b7b76d8cfc5dc12ad5f020adfea17da35468')
+ action='store', default='c6a2af6ebb6306d9b6c9974356bf63be80659ac8')
args = parser.parse_args()
main(port=args.port, client_id=args.client_id, client_secret=args.client_secret)
diff --git a/zwift_offline.py b/zwift_offline.py
index 44c0694..1adda9b 100644
--- a/zwift_offline.py
+++ b/zwift_offline.py
@@ -14,6 +14,13 @@ import math
import threading
import re
import smtplib, ssl
+
+import subprocess
+import requests
+import protobuf.activity_pb2 as activity_pb2
+import protobuf.profile_pb2 as profile_pb2
+import scripts.online_sync as online_sync
+
from copy import copy
from functools import wraps
from io import BytesIO
@@ -44,6 +51,7 @@ import protobuf.zfiles_pb2 as zfiles_pb2
import protobuf.hash_seeds_pb2 as hash_seeds_pb2
import protobuf.events_pb2 as events_pb2
+
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
logger = logging.getLogger('zoffline')
logger.setLevel(logging.DEBUG)
@@ -92,7 +100,7 @@ else:
SECRET_KEY_FILE = "%s/secret-key.txt" % STORAGE_DIR
ENABLEGHOSTS_FILE = "%s/enable_ghosts.txt" % STORAGE_DIR
MULTIPLAYER = False
-garmin_key = None
+credentials_key = None
if os.path.exists("%s/multiplayer.txt" % STORAGE_DIR):
MULTIPLAYER = True
try:
@@ -111,12 +119,12 @@ if os.path.exists("%s/multiplayer.txt" % STORAGE_DIR):
logger.warn("cryptography is not installed. Uploaded garmin_credentials.txt will not be encrypted.")
encrypt = False
if encrypt:
- GARMIN_KEY_FILE = "%s/garmin-key.txt" % STORAGE_DIR
+ GARMIN_KEY_FILE = "%s/credentials-key.txt" % STORAGE_DIR
if not os.path.exists(GARMIN_KEY_FILE):
with open(GARMIN_KEY_FILE, 'wb') as f:
f.write(Fernet.generate_key())
with open(GARMIN_KEY_FILE, 'rb') as f:
- garmin_key = f.read()
+ credentials_key = f.read()
from tokens import *
@@ -456,12 +464,42 @@ def reset(username):
return render_template("reset.html", username=current_user.username)
+@app.route("/profile/
/", methods=["GET", "POST"])
+@login_required
+def profile(username):
+ if request.method == "POST":
+ if request.form['username'] == "" or request.form['password'] == "":
+ flash("Zwift credentials can't be empty")
+ return render_template("profile.html", username=current_user.username)
+ exit(-1);
+
+ username = request.form['username']
+ password = request.form['password']
+ session = requests.session()
+
+ try:
+ access_token, refresh_token = online_sync.login(session, username, password)
+ try:
+ profile = online_sync.query_player_profile(session, access_token)
+ with open('%s/profile.bin' % SCRIPT_DIR, 'wb') as f:
+ f.write(profile)
+ online_sync.logout(session, refresh_token)
+ player_id = current_user.player_id
+ profile_dir = '%s/%s' % (STORAGE_DIR, str(player_id))
+ os.rename('%s/profile.bin' % SCRIPT_DIR, '%s/profile.bin' % profile_dir)
+ flash("Zwift profile installed locally.")
+ except:
+ flash("Error downloading profile")
+ except:
+ flash("Error invalid Username or password")
+
+ return render_template("profile.html", username=current_user.username)
@app.route("/user//")
@login_required
def user_home(username):
return render_template("user_home.html", username=current_user.username, enable_ghosts=bool(current_user.enable_ghosts),
- online=get_online(), is_admin=current_user.is_admin, restarting=restarting, restarting_in_minutes=restarting_in_minutes)
+ online=get_online(), is_admin=current_user.is_admin, restarting=restarting, restarting_in_minutes=restarting_in_minutes,server_ip=server_ip)
def send_message_to_all_online(message, sender='Server'):
@@ -553,19 +591,26 @@ def upload(username):
except IOError as e:
logger.error("failed to create profile dir (%s): %s", profile_dir, str(e))
return '', 500
-
+
if request.method == 'POST':
uploaded_file = request.files['file']
- if uploaded_file.filename in ['profile.bin', 'strava_token.txt', 'garmin_credentials.txt']:
+ if uploaded_file.filename in ['profile.bin', 'strava_token.txt', 'garmin_credentials.txt', 'zwift_credentials.txt']:
file_path = os.path.join(profile_dir, uploaded_file.filename)
uploaded_file.save(file_path)
- if uploaded_file.filename == 'garmin_credentials.txt' and garmin_key is not None:
+ if uploaded_file.filename == 'garmin_credentials.txt' and credentials_key is not None:
with open(file_path, 'rb') as fr:
garmin_credentials = fr.read()
- cipher_suite = Fernet(garmin_key)
+ cipher_suite = Fernet(credentials_key)
ciphered_text = cipher_suite.encrypt(garmin_credentials)
with open(file_path, 'wb') as fw:
fw.write(ciphered_text)
+ if uploaded_file.filename == 'zwift_credentials.txt' and credentials_key is not None:
+ with open(file_path, 'rb') as fr:
+ garmin_credentials = fr.read()
+ cipher_suite = Fernet(credentials_key)
+ ciphered_text = cipher_suite.encrypt(garmin_credentials)
+ with open(file_path, 'wb') as fw:
+ fw.write(ciphered_text)
flash("File %s uploaded." % uploaded_file.filename)
else:
flash("Invalid file name.")
@@ -590,8 +635,13 @@ def upload(username):
if os.path.isfile(garmin_file):
stat = os.stat(garmin_file)
garmin = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime))
+ zwift = None
+ zwift_file = os.path.join(profile_dir, 'zwift_credentials.txt')
+ if os.path.isfile(zwift_file):
+ stat = os.stat(zwift_file)
+ zwift = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime))
- return render_template("upload.html", username=current_user.username, profile=profile, name=name, token=token, garmin=garmin)
+ return render_template("upload.html", username=current_user.username, profile=profile, name=name, token=token, garmin=garmin, zwift=zwift)
@app.route("/download/profile.bin", methods=["GET"])
@@ -603,6 +653,17 @@ def download():
if os.path.isfile(profile_file):
return send_file(profile_file, attachment_filename='profile.bin')
+@app.route("/delete/", methods=["GET"])
+@login_required
+def delete(filename):
+ player_id = current_user.player_id
+ profile_dir = os.path.join(STORAGE_DIR, str(player_id))
+ delete_file = os.path.join(profile_dir, filename)
+ if os.path.isfile(delete_file):
+ os.remove("%s" %delete_file)
+ return redirect(url_for('upload', username=current_user))
+
+
@app.route("/logout/")
@login_required
@@ -770,8 +831,7 @@ def api_events_search():
critccw_cat.startLocation = cat
critccw_cat.label = cat
- return events.SerializeToString(), 200
-
+ return '', 200
@app.route('/api/events/subgroups/signup/', methods=['POST'])
def api_events_subgroups_signup_id(event_id):
@@ -791,8 +851,6 @@ def api_events_subgroups_entrants_id(event_id):
@app.route('/relay/race/event_starting_line/', methods=['POST'])
def relay_race_event_starting_line_id(event_id):
return '', 204
-
-
@app.route('/api/zfiles', methods=['POST'])
def api_zfiles():
# Don't care about zfiles, but shuts up some errors in Zwift log.
@@ -1030,7 +1088,6 @@ def strava_upload(player_id, activity):
except:
logger.warn("Strava upload failed. No internet?")
-
def garmin_upload(player_id, activity):
try:
from garmin_uploader.workflow import Workflow
@@ -1040,8 +1097,8 @@ def garmin_upload(player_id, activity):
profile_dir = '%s/%s' % (STORAGE_DIR, player_id)
try:
with open('%s/garmin_credentials.txt' % profile_dir, 'r') as f:
- if garmin_key is not None:
- cipher_suite = Fernet(garmin_key)
+ if credentials_key is not None:
+ cipher_suite = Fernet(credentials_key)
ciphered_text = f.read()
unciphered_text = (cipher_suite.decrypt(ciphered_text.encode(encoding='UTF-8')))
unciphered_text = unciphered_text.decode(encoding='UTF-8')
@@ -1066,6 +1123,51 @@ def garmin_upload(player_id, activity):
except:
logger.warn("Garmin upload failed. No internet?")
+def zwift_upload(player_id):
+ profile_dir = '%s/%s' % (STORAGE_DIR, player_id)
+ try:
+ with open('%s/zwift_credentials.txt' % profile_dir, 'r') as f:
+ if credentials_key is not None:
+ cipher_suite = Fernet(credentials_key)
+ ciphered_text = f.read()
+ unciphered_text = (cipher_suite.decrypt(ciphered_text.encode(encoding='UTF-8')))
+ unciphered_text = unciphered_text.decode(encoding='UTF-8')
+ split_credentials = unciphered_text.splitlines()
+ username = split_credentials[0]
+ password = split_credentials[1]
+ else:
+ username = f.readline().rstrip('\r\n')
+ password = f.readline().rstrip('\r\n')
+ except:
+ logger.warn("Failed to read %s/zwift_credentials.txt. Skipping Zwift upload attempt." % profile_dir)
+ return
+
+ try:
+ session = requests.session()
+ try:
+ activity = activity_pb2.Activity()
+ access_token, refresh_token = online_sync.login(session, username, password)
+ activity.player_id = online_sync.get_player_id(session, access_token)
+ player_id = current_user.player_id
+ profile_dir = '%s/%s' % (STORAGE_DIR, str(player_id))
+ activity_file = '%s/last_activity.bin' % profile_dir
+ if not os.path.isfile(activity_file):
+ print('Activity file not found')
+ with open(activity_file, 'rb') as fd:
+ try:
+ activity.ParseFromString(fd.read())
+ except:
+ print('Could not parse activity file')
+ res = online_sync.upload_activity(session, access_token, activity)
+ if res == 200:
+ logger.info("Zwift activity upload succesfull")
+ else:
+ logger.warn("Zwift activity upload failed:%s:" %res)
+ online_sync.logout(session, refresh_token)
+ except:
+ logger.warn("Error uploading activity to Zwift Server")
+ except:
+ logger.warn("Zwift 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
@@ -1099,6 +1201,9 @@ def api_profiles_activities_id(player_id, activity_id):
# For using with upload_activity.py (to upload zoffline activity to Zwift server)
with open('%s/%s/last_activity.bin' % (STORAGE_DIR, player_id), 'wb') as f:
f.write(activity.SerializeToString())
+ if server_ip != "localhost" and server_ip != "127.0.0.1":
+ zwift_upload(player_id)
+
return response, 200
@app.route('/api/profiles//activities/0/rideon', methods=['POST']) #activity_id Seem to always be 0, even when giving ride on to ppl with 30km+