Private
Public Access
1
0
Files
rowsandall/rowers/stravastuff.py
Sander Roosendaal 508ebfd9a1 more pep
2022-02-18 12:59:18 +01:00

405 lines
13 KiB
Python

from rowers.tasks import handle_strava_sync, fetch_strava_workout
from stravalib.exc import ActivityUploadFailed, TimeoutExceeded
from rowers.rower_rules import is_workout_user, ispromember
from rowers.utils import get_strava_stream
from rowers.utils import dologging
from rowers.imports import *
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
SITE_URL
)
import gzip
import rowers.mytypes as mytypes
from rowers.utils import myqueue
from iso8601 import ParseError
import stravalib
from rowers.dataprep import columndict
# All the functionality needed to connect to Strava
from scipy import optimize
from scipy.signal import savgol_filter
import time
from time import strftime
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
try:
from json.decoder import JSONDecodeError
except ImportError: # pragma: no cover
JSONDecodeError = ValueError
webhookverification = "kudos_to_rowing"
webhooklink = SITE_URL+'/rowers/strava/webhooks/'
headers = {'Accept': 'application/json',
'Api-Key': STRAVA_CLIENT_ID,
'Content-Type': 'application/json',
'user-agent': 'sanderroosendaal'}
oauth_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'redirect_uri': STRAVA_REDIRECT_URI,
'autorization_uri': "https://www.strava.com/oauth/authorize",
'content_type': 'application/json',
'tokenname': 'stravatoken',
'refreshtokenname': 'stravarefreshtoken',
'expirydatename': 'stravatokenexpirydate',
'bearer_auth': True,
'base_url': "https://www.strava.com/oauth/token",
'grant_type': 'refresh_token',
'headers': headers,
'scope': 'activity:write,activity:read_all',
}
# Exchange access code for long-lived access token
def get_token(code):
return imports_get_token(code, oauth_data)
def strava_open(user):
t = time.localtime()
timestamp = time.strftime('%b-%d-%Y_%H%M', t)
with open('strava_open.log', 'a') as f:
f.write('\n')
f.write(timestamp)
f.write(' ')
f.write('Getting token for user ')
f.write(str(user.rower.id))
f.write(' token expiry ')
f.write(str(user.rower.stravatokenexpirydate))
f.write(' ')
f.write(json.dumps(oauth_data))
f.write('\n')
token = imports_open(user, oauth_data)
if user.rower.strava_owner_id == 0: # pragma: no cover
_ = set_strava_athlete_id(user)
return token
def do_refresh_token(refreshtoken):
return imports_do_refresh_token(refreshtoken, oauth_data)
def rower_strava_token_refresh(user):
r = Rower.objects.get(user=user)
res = do_refresh_token(r.stravarefreshtoken)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+timedelta(seconds=expires_in)
r.stravatoken = access_token
r.stravatokenexpirydate = expirydatetime
r.stravarefreshtoken = refresh_token
r.save()
return r.stravatoken
# Make authorization URL including random string
def make_authorization_url(request): # pragma: no cover
return imports_make_authorization_url(oauth_data)
def strava_establish_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
post_data = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
'callback_url': webhooklink,
'verify_token': webhookverification,
}
response = requests.post(url, data=post_data)
return response.status_code
def strava_list_push(): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions"
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
return [w['id'] for w in data]
return []
def strava_push_delete(id): # pragma: no cover
url = "https://www.strava.com/api/v3/push_subscriptions/{id}".format(id=id)
params = {
'client_id': STRAVA_CLIENT_ID,
'client_secret': STRAVA_CLIENT_SECRET,
}
response = requests.delete(url, json=params)
return response.status_code
def set_strava_athlete_id(user):
r = Rower.objects.get(user=user)
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate):
_ = imports_open(user, oauth_data)
authorizationstring = str('Bearer ' + r.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete"
response = requests.get(url, headers=headers, params={})
if response.status_code == 200: # pragma: no cover
r.strava_owner_id = response.json()['id']
r.save()
return response.json()['id']
return 0
# Get list of workouts available on Strava
def get_strava_workout_list(user, limit_n=0):
r = Rower.objects.get(user=user)
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401, s)
elif (
r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599) > r.stravatokenexpirydate
): # pragma: no cover
s = "Token expired. Needs to refresh."
return custom_exception_handler(401, s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.stravatoken)
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://www.strava.com/api/v3/athlete/activities"
if limit_n == 0:
params = {}
else: # pragma: no cover
params = {'per_page': limit_n}
s = requests.get(url, headers=headers, params=params)
return s
def async_get_workout(user, stravaid):
try:
_ = strava_open(user)
except NoTokenError: # pragma: no cover
return 0
csvfilename = 'media/{code}_{stravaid}.csv'.format(
code=uuid4().hex[:16], stravaid=stravaid)
job = myqueue(queue,
fetch_strava_workout,
user.rower.stravatoken,
oauth_data,
stravaid,
csvfilename,
user.id,
)
return job
# Get a Strava workout summary data and stroke data by ID
def get_workout(user, stravaid, do_async=True):
return async_get_workout(user, stravaid)
# Generate Workout data for Strava (a TCX file)
def createstravaworkoutdata(w, dozip=True):
filename = w.csvfilename
try:
row = rowingdata(csvfile=filename)
except IOError: # pragma: no cover
data = dataprep.read_df_sql(w.id)
try:
datalength = len(data)
except AttributeError:
datalength = 0
if datalength != 0:
data.rename(columns=columndict, inplace=True)
_ = data.to_csv(w.csvfilename+'.gz',
index_label='index',
compression='gzip')
try:
row = rowingdata(csvfile=filename)
except IOError:
return '', 'Error - could not find rowing data'
else:
return '', 'Error - could not find rowing data'
tcxfilename = filename[:-4]+'.tcx'
try:
newnotes = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
newnotes = 'from '+w.workoutsource+' via rowsandall.com'
row.exporttotcx(tcxfilename, notes=newnotes)
if dozip:
gzfilename = tcxfilename+'.gz'
with open(tcxfilename, 'rb') as inF:
s = inF.read()
with gzip.GzipFile(gzfilename, 'wb') as outF:
outF.write(s)
try:
os.remove(tcxfilename)
except WindowError: # pragma: no cover
pass
return gzfilename, ""
return tcxfilename, ""
# Upload the TCX file to Strava and set the workout activity type
# to rowing on Strava
def handle_stravaexport(f2, workoutname, stravatoken, description='',
activity_type='Rowing', quick=False, asynchron=False):
# w = Workout.objects.get(id=workoutid)
client = stravalib.Client(access_token=stravatoken)
act = client.upload_activity(f2, 'tcx.gz', name=workoutname)
try:
if quick: # pragma: no cover
res = act.wait(poll_interval=2.0, timeout=10)
message = 'Workout successfully synchronized to Strava'
else:
res = act.wait(poll_interval=5.0, timeout=30)
message = 'Workout successfully synchronized to Strava'
except: # pragma: no cover
res = 0
message = 'Strava upload timed out'
# description doesn't work yet. Have to wait for stravalib to update
if res:
try:
act = client.update_activity(
res.id, activity_type=activity_type, description=description, device_name='Rowsandall.com')
except TypeError: # pragma: no cover
act = client.update_activity(
res.id, activity_type=activity_type, description=description)
else: # pragma: no cover
message = 'Strava activity update timed out.'
return (0, message)
return (res.id, message)
def workout_strava_upload(user, w, quick=False, asynchron=True):
try:
_ = strava_open(user)
except NoTokenError: # pragma: no cover
return "Please connect to Strava first", 0
message = "Uploading to Strava"
stravaid = -1
r = Rower.objects.get(user=user)
res = -1
if (r.stravatoken == '') or (r.stravatoken is None): # pragma: no cover
raise NoTokenError("Your hovercraft is full of eels")
if (is_workout_user(user, w)):
if asynchron:
tcxfile, tcxmesg = createstravaworkoutdata(w)
if not tcxfile: # pragma: no cover
return "Failed to create workout data", 0
activity_type = r.stravaexportas
if r.stravaexportas == 'match':
try:
activity_type = mytypes.stravamapping[w.workouttype]
except KeyError: # pragma: no cover
activity_type = 'Rowing'
_ = myqueue(queue,
handle_strava_sync,
r.stravatoken,
w.id,
tcxfile, w.name, activity_type,
w.notes)
dologging('strava_export_log.log', 'Exporting as {t} from {w}'.format(
t=activity_type, w=w.workouttype))
return "Asynchronous sync", -1
try:
tcxfile, tcxmesg = createstravaworkoutdata(w)
if tcxfile:
activity_type = r.stravaexportas
if r.stravaexportas == 'match':
try:
activity_type = mytypes.stravamapping[w.workouttype]
except KeyError: # pragma: no cover
activity_type = 'Rowing'
with open(tcxfile, 'rb') as f:
try:
description = w.notes+'\n from '+w.workoutsource+' via rowsandall.com'
except TypeError:
description = ' via rowsandall.com'
res, mes = handle_stravaexport(
f, w.name,
r.stravatoken,
description=description,
activity_type=activity_type, quick=quick, asynchron=asynchron)
if res == 0: # pragma: no cover
message = mes
w.uploadedtostrava = -1
stravaid = -1
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
return message, stravaid
w.uploadedtostrava = res
w.save()
try:
os.remove(tcxfile)
except WindowsError: # pragma: no cover
pass
message = mes
stravaid = res
return message, stravaid
else: # pragma: no cover
message = "Strava TCX data error "+tcxmesg
w.uploadedtostrava = -1
stravaid = -1
w.save()
return message, stravaid
except ActivityUploadFailed as e: # pragma: no cover
message = "Strava Upload error: %s" % e
w.uploadedtostrava = -1
stravaid = -1
w.save()
os.remove(tcxfile)
return message, stravaid
return message, stravaid # pragma: no cover