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