Private
Public Access
1
0
Files
rowsandall/rowers/stravastuff.py
Sander Roosendaal bdb0e55ca4 some changes
2021-12-16 16:13:58 +01:00

409 lines
14 KiB
Python

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
# 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')
from rowers.dataprep import columndict
from rowers.rower_rules import is_workout_user,ispromember
import stravalib
from stravalib.exc import ActivityUploadFailed,TimeoutExceeded
from iso8601 import ParseError
from rowers.utils import myqueue
import rowers.mytypes as mytypes
import gzip
from rowers.tasks import handle_strava_sync,fetch_strava_workout
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
)
try:
from json.decoder import JSONDecodeError
except ImportError: # pragma: no cover
JSONDecodeError = ValueError
from rowers.imports import *
from rowers.utils import dologging
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
strava_owner_id = 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,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': oauth_data['content_type']}
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):
token = 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
from rowers.utils import get_strava_stream
def async_get_workout(user,stravaid):
try:
token = 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)
res = 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:
thetoken = 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
s = "Token doesn't exist. Need to authorize"
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'
job = 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