diff --git a/README.md b/README.md
index 1373b29..1bd8bbf 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,10 @@ Get weight from Nokia Health and update to Garmin Connect or Smashrun.
2. Satisfy the following requirements:
- Python 3.X
- - Python libraries: arrow, requests, requests-oauthlib
+ - Set up a virtual env with requirements:
+ - `python3 -m venv venv`
+ - `source venv/bin/activate`
+ - `pip install -r requirements.txt`
3. [Register](https://account.withings.com/partner/add_oauth2) an application with Nokia Health and obtain a consumer key and secret.
1. logo: the requirements are quite strict, [feel free to use this one](https://github.com/magnific0/nokia-weight-sync/blob/master/logo256w.png)
@@ -60,7 +63,6 @@ nokia-weight-sync includes components the following open-source projects:
* ```fit.py``` from [ikasamah/withings-garmin](https://github.com/ikasamah/withings-garmin), MIT License (c) 2013 Masayuki Hamasaki, adapted for Python 3.
* ```garmin.py``` from [jaroslawhartman/withings-garmin-v2](https://github.com/jaroslawhartman/withings-garmin-v2), MIT License (c) 2013 Masayuki Hamasaki, adapted for Python 3.
-* ```nokia.py``` from [python-nokia](https://github.com/orcasgit/python-nokia), MIT License (c) 2012 Maxime Bouroumeau-Fuseau, 2017 ORCAS, unmodified.
* ```sessioncache.py``` from [cpfair/tapiriik](https://github.com/cpfair/tapiriik/blob/187d1b97ce73cc35b5e2194eb4631ceff20499e3/tapiriik/services/sessioncache.py), Apache License 2.0, unmodified.
* ```smashrun.py``` from [campbellr/smashrun-client](https://github.com/campbellr/smashrun-client), Apache License 2.0, several fixes.
diff --git a/nokia.py b/nokia.py
deleted file mode 100644
index 7ea299a..0000000
--- a/nokia.py
+++ /dev/null
@@ -1,281 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-"""
-Python library for the Nokia Health API
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Nokia Health API
-
-
-Uses Oauth 2.0 to authentify. You need to obtain a consumer key
-and consumer secret from Nokia by creating an application
-here:
-
-Usage:
-
-auth = NokiaAuth(CLIENT_ID, CONSUMER_SECRET, CALLBACK_URL)
-authorize_url = auth.get_authorize_url()
-print("Go to %s allow the app and copy the url you are redirected to." % authorize_url)
-authorization_response = raw_input('Please enter your full authorization response url: ')
-creds = auth.get_credentials(authorization_response)
-
-client = NokiaApi(creds)
-measures = client.get_measures(limit=1)
-print("Your last measured weight: %skg" % measures[0].weight)
-
-creds = client.get_credentials()
-
-"""
-
-from __future__ import unicode_literals
-
-__title__ = 'nokia'
-__version__ = '0.4.0'
-__author__ = 'Maxime Bouroumeau-Fuseau, and ORCAS'
-__license__ = 'MIT'
-__copyright__ = 'Copyright 2012-2017 Maxime Bouroumeau-Fuseau, and ORCAS'
-
-__all__ = [str('NokiaCredentials'), str('NokiaAuth'), str('NokiaApi'),
- str('NokiaMeasures'), str('NokiaMeasureGroup')]
-
-import arrow
-import datetime
-import json
-
-from arrow.parser import ParserError
-from requests_oauthlib import OAuth2Session
-from oauthlib.oauth2 import WebApplicationClient
-
-class NokiaCredentials(object):
- def __init__(self, access_token=None, token_expiry=None, token_type=None,
- refresh_token=None, user_id=None,
- client_id=None, consumer_secret=None):
- self.access_token = access_token
- self.token_expiry = token_expiry
- self.token_type = token_type
- self.refresh_token = refresh_token
- self.user_id = user_id
- self.client_id = client_id
- self.consumer_secret = consumer_secret
-
-
-class NokiaAuth(object):
- URL = 'https://account.withings.com'
-
- def __init__(self, client_id, consumer_secret, callback_uri,
- scope='user.metrics'):
- self.client_id = client_id
- self.consumer_secret = consumer_secret
- self.callback_uri = callback_uri
- self.scope = scope
-
- def _oauth(self):
- return OAuth2Session(self.client_id,
- redirect_uri=self.callback_uri,
- scope=self.scope)
-
- def get_authorize_url(self):
- return self._oauth().authorization_url(
- '%s/oauth2_user/authorize2'%self.URL
- )[0]
-
- def get_credentials(self, code):
- tokens = self._oauth().fetch_token(
- '%s/oauth2/token' % self.URL,
- code=code,
- timeout=2,
- client_secret=self.consumer_secret)
-
- return NokiaCredentials(
- access_token=tokens['access_token'],
- token_expiry=str(ts()+int(tokens['expires_in'])),
- token_type=tokens['token_type'],
- refresh_token=tokens['refresh_token'],
- user_id=tokens['userid'],
- client_id=self.client_id,
- consumer_secret=self.consumer_secret,
- )
-
-
-def is_date(key):
- return 'date' in key
-
-
-def is_date_class(val):
- return isinstance(val, (datetime.date, datetime.datetime, arrow.Arrow, ))
-
-
-# Calculate seconds since 1970-01-01 (timestamp) in a way that works in
-# Python 2 and Python3
-# https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp
-def ts():
- return int((
- datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)
- ).total_seconds())
-
-
-class NokiaApi(object):
- URL = 'https://wbsapi.withings.net'
-
- def __init__(self, credentials):
- self.credentials = credentials
- self.token = {
- 'access_token': credentials.access_token,
- 'refresh_token': credentials.refresh_token,
- 'token_type': credentials.token_type,
- 'expires_in': str(int(credentials.token_expiry) - ts()),
- }
- oauth_client = WebApplicationClient(credentials.client_id,
- token=self.token, default_token_placement='query')
- self.client = OAuth2Session(
- credentials.client_id,
- token=self.token,
- client=oauth_client,
- auto_refresh_url='{}/oauth2/token'.format(NokiaAuth.URL),
- auto_refresh_kwargs={
- 'client_id': credentials.client_id,
- 'client_secret': credentials.consumer_secret,
- },
- token_updater=self.set_token
- )
-
- def get_credentials(self):
- return self.credentials
-
- def set_token(self, token):
- self.token = token
- self.credentials.token_expiry = str(
- ts() + int(self.token['expires_in'])
- )
- self.credentials.access_token = self.token['access_token']
- self.credentials.refresh_token = self.token['refresh_token']
-
- def request(self, service, action, params=None, method='GET',
- version=None):
- params = params or {}
- params['userid'] = self.credentials.user_id
- params['action'] = action
- for key, val in params.items():
- if is_date(key) and is_date_class(val):
- params[key] = arrow.get(val).timestamp
- url_parts = filter(None, [self.URL, version, service])
- r = self.client.request(method, '/'.join(url_parts), params=params,timeout=10)
- response = json.loads(r.content.decode())
- if response['status'] != 0:
- raise Exception("Error code %s" % response['status'])
- return response.get('body', None)
-
- def get_user(self):
- return self.request('user', 'getbyuserid')
-
- def get_activities(self, **kwargs):
- r = self.request('measure', 'getactivity', params=kwargs, version='v2')
- activities = r['activities'] if 'activities' in r else [r]
- return [NokiaActivity(act) for act in activities]
-
- def get_measures(self, **kwargs):
- r = self.request('measure', 'getmeas', kwargs)
- return NokiaMeasures(r)
-
- def get_sleep(self, **kwargs):
- r = self.request('sleep', 'get', params=kwargs, version='v2')
- return NokiaSleep(r)
-
- def subscribe(self, callback_url, comment, **kwargs):
- params = {'callbackurl': callback_url, 'comment': comment}
- params.update(kwargs)
- self.request('notify', 'subscribe', params)
-
- def unsubscribe(self, callback_url, **kwargs):
- params = {'callbackurl': callback_url}
- params.update(kwargs)
- self.request('notify', 'revoke', params)
-
- def is_subscribed(self, callback_url, appli=1):
- params = {'callbackurl': callback_url, 'appli': appli}
- try:
- self.request('notify', 'get', params)
- return True
- except:
- return False
-
- def list_subscriptions(self, appli=1):
- r = self.request('notify', 'list', {'appli': appli})
- return r['profiles']
-
-
-class NokiaObject(object):
- def __init__(self, data):
- self.set_attributes(data)
-
- def set_attributes(self, data):
- self.data = data
- for key, val in data.items():
- try:
- setattr(self, key, arrow.get(val) if is_date(key) else val)
- except ParserError:
- setattr(self, key, val)
-
-
-class NokiaActivity(NokiaObject):
- pass
-
-
-class NokiaMeasures(list, NokiaObject):
- def __init__(self, data):
- super(NokiaMeasures, self).__init__(
- [NokiaMeasureGroup(g) for g in data['measuregrps']])
- self.set_attributes(data)
-
-
-class NokiaMeasureGroup(NokiaObject):
- MEASURE_TYPES = (
- ('weight', 1),
- ('height', 4),
- ('fat_free_mass', 5),
- ('fat_ratio', 6),
- ('fat_mass_weight', 8),
- ('diastolic_blood_pressure', 9),
- ('systolic_blood_pressure', 10),
- ('heart_pulse', 11),
- ('temperature', 12),
- ('spo2', 54),
- ('body_temperature', 71),
- ('skin_temperature', 72),
- ('muscle_mass', 76),
- ('hydration', 77),
- ('bone_mass', 88),
- ('pulse_wave_velocity', 91)
- )
-
- def __init__(self, data):
- super(NokiaMeasureGroup, self).__init__(data)
- for n, t in self.MEASURE_TYPES:
- self.__setattr__(n, self.get_measure(t))
-
- def is_ambiguous(self):
- return self.attrib == 1 or self.attrib == 4
-
- def is_measure(self):
- return self.category == 1
-
- def is_target(self):
- return self.category == 2
-
- def get_measure(self, measure_type):
- for m in self.measures:
- if m['type'] == measure_type:
- return m['value'] * pow(10, m['unit'])
- return None
-
-
-class NokiaSleepSeries(NokiaObject):
- def __init__(self, data):
- super(NokiaSleepSeries, self).__init__(data)
- self.timedelta = self.enddate - self.startdate
-
-
-class NokiaSleep(NokiaObject):
- def __init__(self, data):
- super(NokiaSleep, self).__init__(data)
- self.series = [NokiaSleepSeries(series) for series in self.series]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ab58911
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+nokia