from .integrations import SyncIntegration, NoTokenError, create_or_update_syncrecord, get_known_ids from rowers.models import User, Rower, Workout, TombStone from django.db.utils import IntegrityError from rowers import mytypes from rowers.nkimportutils import * from rowers.tasks import handle_nk_async_workout from rowsandall_app.settings import ( NK_CLIENT_ID, NK_REDIRECT_URI, NK_CLIENT_SECRET, SITE_URL, NK_API_LOCATION, NK_OAUTH_LOCATION, UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET, ) import time from time import strftime import urllib import requests import arrow from json.decoder import JSONDecodeError from django.utils import timezone from rowers.utils import dologging, uniqify, custom_exception_handler, myqueue from requests_oauthlib import OAuth2Session from requests.auth import HTTPBasicAuth import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') class NKIntegration(SyncIntegration): def __init__(self, *args, **kwargs): super(NKIntegration, self).__init__(*args, **kwargs) self.oauth_data = { 'client_id': NK_CLIENT_ID, 'client_secret': NK_CLIENT_SECRET, 'redirect_uri': NK_REDIRECT_URI, 'authorization_uri': NK_OAUTH_LOCATION+"/oauth/authorize", 'content_type': 'application/json', 'tokenname': 'nktoken', 'refreshtokenname': 'nkrefreshtoken', 'expirydatename': 'nktokenexpirydate', 'bearer_auth': True, 'base_url': NK_OAUTH_LOCATION+"/oauth/token", 'scope': 'read', } def get_name(self): return "NK Logbook" def get_shortname(self): return "nk" def createworkoutdata(self, w, *args, **kwargs): return None def workout_export(self, workout, *args, **kwargs) -> str: return "" # there is no export def get_workouts(self, *args, **kwargs) -> int: before = kwargs.get('before',0) after = kwargs.get('after',0) if not before: # pragma: no cover before = arrow.now()+timedelta(days=1) before = str(int(before.timestamp())*1000) if not after: # pragma: no cover after = arrow.now()-timedelta(days=7) after = str(int(after.timestamp())*1000) try: _ = self.open() except NoTokenError: # pragma: no cover dologging("nklog.log","NK Token error for user {id}".format(id=self.rower.user.id)) return 0 workouts = self.get_workout_list(before=before, after=after) count = 0 for workout in workouts: nkid = workout['id'] if workout['new'] == 'NEW': dologging('nklog.log','Queueing {id}'.format(id=nkid)) self.get_workout(nkid, before=before, after=after) count += 1 return count def get_workout(self, id, *args, **kwargs) -> int: _ = self.open() r = self.rower record = create_or_update_syncrecord(r, None, nkid=id) before = kwargs.get('before',0) after = kwargs.get('after',0) if not before: startdate = kwargs.get('startdate','') enddate = kwargs.get('enddate','') before = 0 after = 0 if startdate: # pragma: no cover startdate = arrow.get(startdate) after = str(int(startdate.timestamp())*1000) if enddate: # pragma: no cover enddate = arrow.get(enddate) before = str(int(enddate.timestamp())*1000) jsondata = self.get_workout_list_json(before=before, after=after) alldata = {} for item in jsondata: alldata[item['id']] = item res = myqueue( queuehigh, handle_nk_async_workout, alldata, r.user.id, r.nktoken, id, 0, r.defaulttimezone, ) return 1 def get_workout_list_json(self, *args, **kwargs) -> dict: before = kwargs.get('before',0) after = kwargs.get('after',0) if after > before: temp = before before = after after = temp r = self.rower authorizationstring = "Bearer {token}".format(token = r.nktoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json', } url = NK_API_LOCATION+"api/v1/sessions" params = { 'after': after, 'before': before, } # start / end time res = requests.get(url, headers=headers, params=params) if (res.status_code != 200): # pragma: no cover dologging('nklog.log',res.status_code) dologging('nklog.log',res.text) raise NoTokenError("No NK Token") return res.json() # need to unify workout list def get_workout_list(self, *args, **kwargs) -> list: try: _ = self.open() except NoTokenError: return [] r = self.rower before = kwargs.get('before',0) after = kwargs.get('after',0) # ready to fetch. Hurray if not before: # pragma: no cover before = arrow.now()+timedelta(days=1) before = str(int(before.timestamp())*1000) if not after: # pragma: no cover after = arrow.now()-timedelta(days=7) after = str(int(after.timestamp())*1000) jsondata = self.get_workout_list_json(before=before,after=after) # get NK IDs nkids = [item['id'] for item in jsondata] knownnkids = get_known_ids(r, 'nkid') workouts = [] for item in jsondata: d = int(float(item['totalDistanceGps'])) # could also be Impeller i = item['id'] n = item['name'] if i in knownnkids: nnn = '' else: # pragma: no cover nnn = 'NEW' ttot = str(datetime.timedelta( seconds=int(float(item['elapsedTime'])/1000.))) s = arrow.get(item['startTime'], tzinfo=r.defaulttimezone).format( arrow.FORMAT_RFC850) keys = ['id', 'distance', 'duration', 'starttime', 'rowtype', 'source', 'name','new'] values = [i, d, ttot, s, None, None, n, nnn] rs = dict(zip(keys, values)) workouts.append(rs) workouts = workouts[::-1] return workouts def make_authorization_url(self, *args, **kwargs) -> str: # pragma: no cover state = str(uuid4()) scope = "read" params = { "grant_type": "authorization_code", "response_type": "code", "client_id": NK_CLIENT_ID, "scope": scope, "state": state, "redirect_uri": NK_REDIRECT_URI, } s = 'Authorizing NK for user {u}'.format(u = self.user.id) dologging('nklog.log',s) url = NK_OAUTH_LOCATION+"/oauth/authorize?"+urllib.parse.urlencode(params) return url def get_token(self, code, *args, **kwargs) -> (str, int, str): url = self.oauth_data['base_url'] post_data = {"client_id": self.oauth_data['client_id'], "grant_type": "authorization_code", "redirect_uri": self.oauth_data['redirect_uri'], "code": code, } try: response = requests.post( url, auth=HTTPBasicAuth(self.oauth_data['client_id'], self.oauth_data['client_secret']), data=post_data ) except ConnectionError: dologging('nklog.log','Connection error for user {u}'.format(u=self.user.id)) raise NoTokenError("Failed to obtain token") if response.status_code != 200: dologging('nklog.log','Response Status Code {c} for user {u}: {e}'.format( u = self.user.id, c = response.status_code, e = response.text )) raise NoTokenError("Failed to obtain token") token_json = response.json() access_token = token_json['access_token'] refresh_token = token_json['refresh_token'] expires_in = token_json['expires_in'] nk_owner_id = token_json['user_id'] return [access_token, expires_in, refresh_token] #, nk_owner_id] def open(self, *args, **kwargs) -> str: r = self.rower if not r.nktoken: # pragma: no cover raise NoTokenError("User has no token") else: if (timezone.now() > r.nktokenexpirydate): thetoken = self.token_refresh() if thetoken is None: # pragma: no cover raise NoTokenError("User has no token") return thetoken else: thetoken = r.nktoken return thetoken def do_refresh_token(self, *args, **kwargs): post_data = {"grant_type": "refresh_token", # "client_id":NK_CLIENT_ID, "refresh_token": self.rower.nkrefreshtoken, } url = self.oauth_data['base_url'] response = requests.post( url, data=post_data, auth=HTTPBasicAuth( self.oauth_data['client_id'], self.oauth_data['client_secret'] ) ) if response.status_code != 200: # pragma: no cover return [0, 0, 0] token_json = response.json() access_token = token_json['access_token'] refresh_token = token_json['refresh_token'] expires_in = token_json['expires_in'] return access_token, expires_in, refresh_token def token_refresh(self, *args, **kwargs) -> str: r = self.rower access_token, expires_in, refresh_token = self.do_refresh_token() expirydatetime = timezone.now()+timedelta(seconds=expires_in) r.nktoken = access_token r.nktokenexpirydate = expirydatetime r.nkrefreshtoken = refresh_token r.save() return r.nktoken