Skip to content

Commit

Permalink
Pep8
Browse files Browse the repository at this point in the history
  • Loading branch information
La0 committed Dec 26, 2016
1 parent 7d3c207 commit e1bb2f1
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 106 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Expand Up @@ -6,4 +6,6 @@ install:
- pip install .
- pip install -r requirements.txt
- pip install -r requirements-tests.txt
script: pytest
script:
- pytest
- flake8
96 changes: 49 additions & 47 deletions garmin_uploader/api.py
Expand Up @@ -28,16 +28,17 @@
URL_HOST_SSO = 'sso.garmin.com'
URL_HOST_CONNECT = 'connect.garmin.com'
URL_UPLOAD = 'https://connect.garmin.com/proxy/upload-service-1.1/json/upload'
URL_ACTIVITY_NAME = 'https://connect.garmin.com/proxy/activity-service-1.0/json/name'
URL_ACTIVITY_TYPE = 'https://connect.garmin.com/proxy/activity-service-1.2/json/type'
URL_ACTIVITY_TYPES = 'https://connect.garmin.com/proxy/activity-service-1.2/json/activity_types'
URL_ACTIVITY_NAME = 'https://connect.garmin.com/proxy/activity-service-1.0/json/name' # noqa
URL_ACTIVITY_TYPE = 'https://connect.garmin.com/proxy/activity-service-1.2/json/type' # noqa
URL_ACTIVITY_TYPES = 'https://connect.garmin.com/proxy/activity-service-1.2/json/activity_types' # noqa


class GarminAPIException(Exception):
"""
An Exception occured in Garmin API
"""


class GarminAPI:
"""
Low level Garmin Connect api connector
Expand All @@ -55,70 +56,72 @@ def authenticate(self, username, password):
# TODO: use several UA picked randomly
session = requests.Session()
session.headers.update({
'User-Agent' : 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/50.0',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/50.0', # noqa
})

# Request sso hostname
sso_hostname = None
resp = session.get(URL_HOSTNAME)
if not resp.ok:
raise Exception('Invalid SSO first request status code {}'.format(resp.status_code))
raise Exception('Invalid SSO first request status code {}'.format(resp.status_code)) # noqa

sso_hostname = resp.json().get('host', None).rstrip('.garmin.com')
# Load login page to get login ticket
params = {
'clientId' : 'GarminConnect',
'webhost' : sso_hostname,
'clientId': 'GarminConnect',
'webhost': sso_hostname,

# Full parameters from Firebug
# Fuck this shit. Who needs mandatory urls in a request parameters !
'consumeServiceTicket' : 'false',
'createAccountShown' : 'true',
'cssUrl' : 'https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css',
'displayNameShown' : 'false',
'embedWidget' : 'false',
'gauthHost' : 'https://sso.garmin.com/sso',
'generateExtraServiceTicket' : 'false',
# Fuck this shit.
# Who needs mandatory urls in a request parameters !
'consumeServiceTicket': 'false',
'createAccountShown': 'true',
'cssUrl': 'https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css', # noqa
'displayNameShown': 'false',
'embedWidget': 'false',
'gauthHost': 'https://sso.garmin.com/sso',
'generateExtraServiceTicket': 'false',
'globalOptInChecked': 'false',
'globalOptInShown': 'false',
'id' : 'gauth-widget',
'initialFocus' : 'true',
'locale' : 'fr',
'openCreateAlcount' : 'false',
'redirectAfterAccountCreationUrl' : 'https://connect.garmin.com/post-auth/login',
'redirectAfterAccountLoginUrl' : 'https://connect.garmin.com/post-auth/login',
'rememberMeChecked' : 'false',
'rememberMeShown' : 'true',
'service' : 'https://connect.garmin.com/post-auth/login',
'source' : 'https://connect.garmin.com/fr-FR/signin',
'usernameShown' : 'false',
'id': 'gauth-widget',
'initialFocus': 'true',
'locale': 'fr',
'openCreateAlcount': 'false',
'redirectAfterAccountCreationUrl': 'https://connect.garmin.com/post-auth/login', # noqa
'redirectAfterAccountLoginUrl': 'https://connect.garmin.com/post-auth/login', # noqa
'rememberMeChecked': 'false',
'rememberMeShown': 'true',
'service': 'https://connect.garmin.com/post-auth/login',
'source': 'https://connect.garmin.com/fr-FR/signin',
'usernameShown': 'false',
}
res = session.get(URL_LOGIN, params=params)
if res.status_code != 200:
raise Exception('No login form')
raise Exception('No login form')

# Get the login ticket value
regex = '<input\s+type="hidden"\s+name="lt"\s+value="(?P<lt>\w+)"\s+/>'
res = re.search(regex, res.text)
if not res:
raise Exception('No login ticket')
raise Exception('No login ticket')
login_ticket = res.group('lt')
logger.debug('Found login ticket %s', login_ticket)

# Login/Password with login ticket
data = {
# All parameters are needed
'_eventId' : 'submit',
'displayNameRequired' : 'false',
'embed' : 'true',
'lt' : login_ticket,
'username' : username,
'password' : password,
'_eventId': 'submit',
'displayNameRequired': 'false',
'embed': 'true',
'lt': login_ticket,
'username': username,
'password': password,
}
headers = {
'Host' : URL_HOST_SSO,
'Host': URL_HOST_SSO,
}
res = session.post(URL_LOGIN, params=params, data=data, headers=headers)
res = session.post(URL_LOGIN, params=params, data=data,
headers=headers)
if res.status_code != 200:
raise Exception('Authentification failed.')

Expand All @@ -134,7 +137,7 @@ def authenticate(self, username, password):
# Second auth step
# Needs a service ticket from previous response
headers = {
'Host' : URL_HOST_CONNECT,
'Host': URL_HOST_CONNECT,
}
res = session.get(URL_POST_LOGIN, params=params, headers=headers)
if res.status_code != 200 and not res.history:
Expand All @@ -144,12 +147,11 @@ def authenticate(self, username, password):
res = session.get(URL_CHECK_LOGIN)
garmin_user = res.json()
if not garmin_user.get('username', None):
raise Exception("Login check failed.")
raise Exception("Login check failed.")
logger.info('Logged in as %s' % (garmin_user['username']))

return session


def upload_activity(self, session, activity):
"""
Upload an activity on Garmin
Expand All @@ -172,7 +174,7 @@ def upload_activity(self, session, activity):
# Activity already exists
return response["failures"][0]["internalId"], False
else:
raise GarminAPIException(response["failures"][0]["messages"])
raise GarminAPIException(response["failures"][0]["messages"]) # noqa
else:
raise GarminAPIException('Unknown error: {}'.format(response))
else:
Expand All @@ -188,15 +190,15 @@ def set_activity_name(self, session, activity):

url = '{}/{}'.format(URL_ACTIVITY_NAME, activity.id)
data = {
'value' : activity.name,
'value': activity.name,
}
res = session.post(url, data=data)
if not res.ok:
raise GarminAPIException('Activity name not set: {}'.format(res.content))
raise GarminAPIException('Activity name not set: {}'.format(res.content)) # noqa

new_name = res.json()["display"]["value"]
if new_name != activity.name:
raise GarminAPIException('Activity name not set: {}'.format(res.content))
raise GarminAPIException('Activity name not set: {}'.format(res.content)) # noqa

def load_activity_types(self):
"""
Expand All @@ -218,7 +220,7 @@ def load_activity_types(self):
out = dict(out)
self.activity_types = out

logger.debug('Fetched {} activity types'.format(len(self.activity_types)))
logger.debug('Fetched {} activity types'.format(len(self.activity_types))) # noqa
return self.activity_types

def set_activity_type(self, session, activity):
Expand All @@ -237,12 +239,12 @@ def set_activity_type(self, session, activity):

url = '{}/{}'.format(URL_ACTIVITY_TYPE, activity.id)
data = {
'value' : type_key,
'value': type_key,
}
res = session.post(url, data)
if not res.ok:
raise GarminAPIException('Activity type not set: {}'.format(res.content))
raise GarminAPIException('Activity type not set: {}'.format(res.content)) # noqa

res = res.json()
if "activityType" not in res or res["activityType"]["key"] != type_key:
raise GarminAPIException('Activity type not set: {}'.format(res.content))
raise GarminAPIException('Activity type not set: {}'.format(res.content)) # noqa
19 changes: 13 additions & 6 deletions garmin_uploader/cli.py
Expand Up @@ -29,27 +29,31 @@ def main():
base_dir = os.path.realpath(os.path.dirname(__file__))
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description='A script to upload .TCX, .GPX, and .FIT files to the Garmin Connect web site.',
description='A script to upload .TCX, .GPX, and .FIT'
'files to the Garmin Connect web site.',
epilog=open(os.path.join(base_dir, 'help.txt')).read(),
)

parser.add_argument(
'paths',
type=str,
nargs='+',
help='Path and name of file(s) to upload, list file name, or directory name containing fitness files.')
help='Path and name of file(s) to upload, list file name, or directory'
'name containing fitness files.')
parser.add_argument(
'-a',
'--name',
dest='activity_name',
type=str,
help='Sets the activity name for the upload file. This option is ignored if multiple upload files are given.')
help='Sets the activity name for the upload file. This option is'
'ignored if multiple upload files are given.')
parser.add_argument(
'-t',
'--type',
dest='activity_type',
type=str,
help='Sets activity type for ALL files in filename list, except files described inside a csv list file.')
help='Sets activity type for ALL files in filename list, except files'
'described inside a csv list file.')
parser.add_argument(
'-u',
'--username',
Expand All @@ -68,13 +72,16 @@ def main():
dest='verbose',
type=int,
default=2,
choices=[1, 2, 3, 4, 5] ,
help='Verbose - select level of verbosity. 1=DEBUG(most verbose), 2=INFO, 3=WARNING, 4=ERROR, 5= CRITICAL(least verbose). [default=2]')
choices=[1, 2, 3, 4, 5],
help='Verbose - select level of verbosity. 1=DEBUG(most verbose),'
' 2=INFO, 3=WARNING, 4=ERROR, 5= CRITICAL(least verbose).'
' [default=2]')

# Run workflow with these options
options = parser.parse_args()
workflow = Workflow(**vars(options))
workflow.run()


if __name__ == '__main__':
main()
29 changes: 17 additions & 12 deletions garmin_uploader/user.py
Expand Up @@ -15,18 +15,23 @@ class User(object):
Authenticates through web api as a browser
"""
def __init__(self, username=None, password=None):
"""
---- GC login credential order of precedence ----
1) Credentials given on command line
2) Credentials given in config file in current working directory
3) Credentials given in config file in user's home directory
Command line overrides all, config in cwd overrides config in home dir
"""
# Authenticated API session
self.session = None

# ---- GC login credential order of precedence ----
# 1) Credentials given on command line
# 2) Credentials given in config file in current working directory
# 3) Credentials given in config file in user's home directory
#
# Command line overrides all, config in cwd overrides config in home dir
#
configCurrentDir = os.path.abspath(os.path.normpath('./' + CONFIG_FILE))
configHomeDir = os.path.expanduser(os.path.normpath('~/' + CONFIG_FILE))
configCurrentDir = os.path.abspath(
os.path.normpath('./' + CONFIG_FILE)
)
configHomeDir = os.path.expanduser(
os.path.normpath('~/' + CONFIG_FILE)
)

if username and password:
logger.debug('Using credentials from command line.')
Expand All @@ -47,9 +52,9 @@ def __init__(self, username=None, password=None):
else:
cwd = os.path.abspath(os.path.normpath('./'))
homepath = os.path.expanduser(os.path.normpath('~/'))
msg = '\'%s\' file does not exist in current directory (%s) or home directory (%s). Use -l option.' % (CONFIG_FILE, cwd, homepath)
logger.critical(msg)
raise IOError(msg)
raise Exception("'{}' file does not exist in current directory {}"
"or home directory {}. Use login options.".format(
CONFIG_FILE, cwd, homepath))

def authenticate(self):
"""
Expand Down

0 comments on commit e1bb2f1

Please sign in to comment.