From 1601a373b3f25a510dde58d295ac17e9a6861dda Mon Sep 17 00:00:00 2001 From: uneasyguy Date: Thu, 11 Apr 2019 17:47:53 -0400 Subject: [PATCH 1/3] Add files via upload --- fitbit/api.py | 52 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/fitbit/api.py b/fitbit/api.py index ba9d037..86b31ce 100644 --- a/fitbit/api.py +++ b/fitbit/api.py @@ -2,6 +2,8 @@ import datetime import json import requests +import time +from sys import stdout try: from urllib.parse import urlencode @@ -95,7 +97,16 @@ def make_request(self, url, data=None, method=None, **kwargs): client_secret=self.client_secret, **kwargs ) - + resp_headers = response.headers + if response.status_code == 429: + reset_at = resp_headers['Fitbit-Rate-Limit-Reset'] + print('You have reached the API rate limit, your quota will refresh in {} seconds. Sleeping until then.'.format(str(reset_at))) + for i in range((int(reset_at)+15),0,-1): + time.sleep(1) + stdout.write(str(i)+' ') + stdout.flush() + new_response = self._request(method,url,data=data,client_id=self.client_id,client_secret=self.client_secret,**kwargs) + return new_response exceptions.detect_and_raise_error(response) return response @@ -127,7 +138,6 @@ def authorize_token_url(self, scope=None, redirect_uri=None, **kwargs): if redirect_uri: self.session.redirect_uri = redirect_uri - return self.session.authorization_url(self.authorization_url, **kwargs) def fetch_access_token(self, code, redirect_uri=None): @@ -253,7 +263,15 @@ def make_request(self, *args, **kwargs): method = kwargs.get('method', 'POST' if 'data' in kwargs else 'GET') response = self.client.make_request(*args, **kwargs) - + resp_headers = response.headers + rl_remaining = resp_headers['Fitbit-Rate-Limit-Remaining'] + reset_at = resp_headers['Fitbit-Rate-Limit-Reset'] + if str(rl_remaining) == '0': + print('You have reached the API rate limit, your quota will refresh in {} seconds. Sleeping until then.'.format(str(reset_at))) + for i in range(int(reset_at),0,-1): + time.sleep(1) + stdout.write(str(i)+' ') + stdout.flush() if response.status_code == 202: return True if method == 'DELETE': @@ -529,22 +547,19 @@ def time_series(self, resource, user_id=None, base_date='today', """ if period and end_date: raise TypeError("Either end_date or period can be specified, not both") - - if end_date: - end = self._get_date_string(end_date) + if resource != 'sleep': + if end_date: + end = self._get_date_string(end_date) + else: + if not period in Fitbit.PERIODS: + raise ValueError("Period must be one of %s" + % ','.join(Fitbit.PERIODS)) + end = period + url = "{0}/{1}/user/{2}/{resource}/date/{base_date}/{end}.json".format(*self._get_common_args(user_id),resource=resource,base_date=self._get_date_string(base_date),end=end) + return self.make_request(url) else: - if not period in Fitbit.PERIODS: - raise ValueError("Period must be one of %s" - % ','.join(Fitbit.PERIODS)) - end = period - - url = "{0}/{1}/user/{2}/{resource}/date/{base_date}/{end}.json".format( - *self._get_common_args(user_id), - resource=resource, - base_date=self._get_date_string(base_date), - end=end - ) - return self.make_request(url) + url = "{0}/1.2/user/{2}/{resource}/date/{base_date}.json".format(*self._get_common_args(user_id),resource=resource,base_date=self._get_date_string(base_date)) + return self.make_request(url) def intraday_time_series(self, resource, base_date='today', detail_level='1min', start_time=None, end_time=None): """ @@ -616,6 +631,7 @@ def activity_stats(self, user_id=None, qualifier=''): *self._get_common_args(user_id), qualifier=qualifier ) + print (str(url)) return self.make_request(url) def _food_stats(self, user_id=None, qualifier=''): From dbb63f0fee68f3c1f44a27fcedb802cdc1eaf705 Mon Sep 17 00:00:00 2001 From: uneasyguy Date: Thu, 11 Apr 2019 17:59:21 -0400 Subject: [PATCH 2/3] Add files via upload --- gather_keys_oauth2.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gather_keys_oauth2.py b/gather_keys_oauth2.py index aade911..148e7cb 100755 --- a/gather_keys_oauth2.py +++ b/gather_keys_oauth2.py @@ -5,11 +5,16 @@ import threading import traceback import webbrowser +from subprocess import Popen from base64 import b64encode from fitbit.api import Fitbit from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError +cherrypy.log.screen = None + +def open_url(url): + p1 = Popen(['google-chrome-stable',str(url)]) class OAuth2Server: def __init__(self, client_id, client_secret, @@ -35,8 +40,11 @@ def browser_authorize(self): """ url, _ = self.fitbit.client.authorize_token_url() # Open the web browser in a new thread for command-line browser support - threading.Timer(1, webbrowser.open, args=(url,)).start() + # threading.Timer(1, webbrowser.open, args=(url,)).start() + # threading.Timer(1, open_url, args=(url,)).start() + webbrowser.open(url) cherrypy.quickstart(self) + # return p1.pid @cherrypy.expose def index(self, state, code=None, error=None): @@ -68,7 +76,8 @@ def _fmt_failure(self, message): def _shutdown_cherrypy(self): """ Shutdown cherrypy in one second, if it's running """ if cherrypy.engine.state == cherrypy.engine.states.STARTED: - threading.Timer(1, cherrypy.engine.exit).start() + cherrypy.engine.exit() + # threading.Timer(1, cherrypy.engine.exit).start() if __name__ == '__main__': @@ -86,4 +95,4 @@ def _shutdown_cherrypy(self): print('TOKEN\n=====\n') for key, value in server.fitbit.client.session.token.items(): - print('{} = {}'.format(key, value)) + print('{} = {}'.format(key, value)) \ No newline at end of file From 5b7620fc0b2881b5590519b33fd2a9f0050c786d Mon Sep 17 00:00:00 2001 From: uneasyguy Date: Thu, 11 Apr 2019 20:18:34 -0400 Subject: [PATCH 3/3] Add files via upload --- fitbit/gather_keys_oauth2.py | 98 ++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 fitbit/gather_keys_oauth2.py diff --git a/fitbit/gather_keys_oauth2.py b/fitbit/gather_keys_oauth2.py new file mode 100644 index 0000000..148e7cb --- /dev/null +++ b/fitbit/gather_keys_oauth2.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +import cherrypy +import os +import sys +import threading +import traceback +import webbrowser +from subprocess import Popen + +from base64 import b64encode +from fitbit.api import Fitbit +from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError + +cherrypy.log.screen = None + +def open_url(url): + p1 = Popen(['google-chrome-stable',str(url)]) + +class OAuth2Server: + def __init__(self, client_id, client_secret, + redirect_uri='http://127.0.0.1:8080/'): + """ Initialize the FitbitOauth2Client """ + self.success_html = """ +

You are now authorized to access the Fitbit API!

+

You can close this window

""" + self.failure_html = """ +

ERROR: %s


You can close this window

%s""" + + self.fitbit = Fitbit( + client_id, + client_secret, + redirect_uri=redirect_uri, + timeout=10, + ) + + def browser_authorize(self): + """ + Open a browser to the authorization url and spool up a CherryPy + server to accept the response + """ + url, _ = self.fitbit.client.authorize_token_url() + # Open the web browser in a new thread for command-line browser support + # threading.Timer(1, webbrowser.open, args=(url,)).start() + # threading.Timer(1, open_url, args=(url,)).start() + webbrowser.open(url) + cherrypy.quickstart(self) + # return p1.pid + + @cherrypy.expose + def index(self, state, code=None, error=None): + """ + Receive a Fitbit response containing a verification code. Use the code + to fetch the access_token. + """ + error = None + if code: + try: + self.fitbit.client.fetch_access_token(code) + except MissingTokenError: + error = self._fmt_failure( + 'Missing access token parameter.
Please check that ' + 'you are using the correct client_secret') + except MismatchingStateError: + error = self._fmt_failure('CSRF Warning! Mismatching state') + else: + error = self._fmt_failure('Unknown error while authenticating') + # Use a thread to shutdown cherrypy so we can return HTML first + self._shutdown_cherrypy() + return error if error else self.success_html + + def _fmt_failure(self, message): + tb = traceback.format_tb(sys.exc_info()[2]) + tb_html = '
%s
' % ('\n'.join(tb)) if tb else '' + return self.failure_html % (message, tb_html) + + def _shutdown_cherrypy(self): + """ Shutdown cherrypy in one second, if it's running """ + if cherrypy.engine.state == cherrypy.engine.states.STARTED: + cherrypy.engine.exit() + # threading.Timer(1, cherrypy.engine.exit).start() + + +if __name__ == '__main__': + + if not (len(sys.argv) == 3): + print("Arguments: client_id and client_secret") + sys.exit(1) + + server = OAuth2Server(*sys.argv[1:]) + server.browser_authorize() + + profile = server.fitbit.user_profile_get() + print('You are authorized to access data for the user: {}'.format( + profile['user']['fullName'])) + + print('TOKEN\n=====\n') + for key, value in server.fitbit.client.session.token.items(): + print('{} = {}'.format(key, value)) \ No newline at end of file