|
1
|
|
|
""" |
|
2
|
|
|
Module that contains the command line app. |
|
3
|
|
|
|
|
4
|
|
|
Why does this file exist, and why not put this in __main__? |
|
5
|
|
|
|
|
6
|
|
|
You might be tempted to import things from __main__ later, but that will cause |
|
7
|
|
|
problems: the code will get executed twice: |
|
8
|
|
|
|
|
9
|
|
|
- When you run `python -mgols` python will execute |
|
10
|
|
|
``__main__.py`` as a script. That means there won't be any |
|
11
|
|
|
``gols.__main__`` in ``sys.modules``. |
|
12
|
|
|
- When you import __main__ it will get executed again (as a module) because |
|
13
|
|
|
there's no ``gols.__main__`` in ``sys.modules``. |
|
14
|
|
|
|
|
15
|
|
|
Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration |
|
16
|
|
|
""" |
|
17
|
|
|
|
|
18
|
|
|
import logging |
|
19
|
|
|
import os |
|
20
|
|
|
import shutil |
|
21
|
|
|
|
|
22
|
|
|
import click |
|
23
|
|
|
import requests |
|
24
|
|
|
|
|
25
|
|
|
logger = logging.getLogger(__name__) |
|
26
|
|
|
logging.basicConfig() |
|
27
|
|
|
|
|
28
|
|
|
|
|
29
|
|
|
@click.group() |
|
30
|
|
|
@click.option('--debug/--no_debug', default=False, |
|
31
|
|
|
help='Set to true to see debug logs on top of info') |
|
32
|
|
|
def main(debug): |
|
33
|
|
|
if debug: |
|
34
|
|
|
requests_log = logging.getLogger("requests.packages.urllib3") |
|
35
|
|
|
requests_log.setLevel(logging.DEBUG) |
|
36
|
|
|
requests_log.propagate = True |
|
37
|
|
|
logging.root.setLevel(level=logging.DEBUG) |
|
38
|
|
|
logger.info('Debug level set on') |
|
39
|
|
|
else: |
|
40
|
|
|
logger.setLevel(level=logging.INFO) |
|
41
|
|
|
|
|
42
|
|
|
|
|
43
|
|
|
@main.command(short_help='uploads .fit files to your garmin connect account') |
|
44
|
|
|
@click.option('--directory_fit', '-d', required=True, |
|
45
|
|
|
type=click.Path(exists=True, file_okay=False), |
|
46
|
|
|
help='Path of your .fit files on your watch mount path') |
|
47
|
|
|
@click.option('--move/--no_move', '-m', default=False, |
|
48
|
|
|
help='Move files upon upload') |
|
49
|
|
|
@click.option('--username', '-u', required=True, prompt=True, |
|
50
|
|
|
default=lambda: os.environ.get('GARMINCONNECT_USERNAME', ''), |
|
51
|
|
|
help='The GARMINCONNECT_USERNAME environment variable should you have one set') # noqa |
|
52
|
|
|
@click.option('--password', '-p', required=True, prompt=True, |
|
53
|
|
|
default=lambda: os.environ.get('GARMINCONNECT_PASSWORD', ''), |
|
54
|
|
|
help='The GARMINCONNECT_PASSWORD environment variable should you have one set ') # noqa |
|
55
|
|
|
@click.option('--conf_dir_fit', '-c', required=True, |
|
56
|
|
|
type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True, readable=True)) # noqa |
|
57
|
|
|
def upload(directory_fit, move, username, password, conf_dir_fit): |
|
58
|
|
|
logger.info('Uplading stuff') |
|
59
|
|
|
headers = { |
|
60
|
|
|
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0', |
|
61
|
|
|
} |
|
62
|
|
|
params_login = { |
|
63
|
|
|
'service': 'https://connect.garmin.com/modern/', |
|
64
|
|
|
'webhost': 'olaxpw-conctmodern011', |
|
65
|
|
|
'source': 'https://connect.garmin.com/en-US/signin', |
|
66
|
|
|
'redirectAfterAccountLoginUrl': 'https://connect.garmin.com/modern/', |
|
67
|
|
|
'redirectAfterAccountCreationUrl': 'https://connect.garmin.com/modern/', |
|
68
|
|
|
'gauthHost': 'https://sso.garmin.com/sso', |
|
69
|
|
|
'locale': 'en_US', |
|
70
|
|
|
'id': 'gauth-widget', |
|
71
|
|
|
'cssUrl': 'https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css', |
|
72
|
|
|
'clientId': 'GarminConnect', |
|
73
|
|
|
'rememberMeShown': 'true', |
|
74
|
|
|
'rememberMeChecked': 'false', |
|
75
|
|
|
'createAccountShown': 'true', |
|
76
|
|
|
'openCreateAccount': 'false', |
|
77
|
|
|
'usernameShown': 'false', |
|
78
|
|
|
'displayNameShown': 'true', |
|
79
|
|
|
'consumeServiceTicket': 'false', |
|
80
|
|
|
'initialFocus': 'true', |
|
81
|
|
|
'embedWidget': 'false', |
|
82
|
|
|
'generateExtraServiceTicket': 'false', |
|
83
|
|
|
'globalOptInShown': 'false', |
|
84
|
|
|
'globalOptInChecked': 'false', |
|
85
|
|
|
'connectLegalTerms': 'true', |
|
86
|
|
|
} |
|
87
|
|
|
data_login = { |
|
88
|
|
|
'username': username, |
|
89
|
|
|
'password': password, |
|
90
|
|
|
'embed': 'true', |
|
91
|
|
|
'lt': 'e1s1', |
|
92
|
|
|
'_eventId': 'submit', |
|
93
|
|
|
'displayNameRequired': 'false', |
|
94
|
|
|
'rememberme': 'on', |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
# begin session with headers because, requests client isn't an option, dunno if Icewasel is still banned... |
|
98
|
|
|
logger.info('Login into Garmin connect') |
|
99
|
|
|
s = requests.session() |
|
100
|
|
|
s.headers.update(headers) |
|
101
|
|
|
# we need the cookies from the login page before we can post the user/pass |
|
102
|
|
|
url_login = 'https://sso.garmin.com/sso/login' |
|
103
|
|
|
req_login = s.get(url_login, params=params_login) |
|
104
|
|
|
if req_login.status_code != 200: |
|
105
|
|
|
logger.info('issue with {}, you can turn on debug for more info'.format( |
|
106
|
|
|
req_login)) |
|
107
|
|
|
req_login2 = s.post(url_login, data=data_login) |
|
108
|
|
|
if req_login2.status_code != 200: |
|
109
|
|
|
logger.info('issue with {}, you can turn on debug for more info'.format( |
|
110
|
|
|
req_login2)) |
|
111
|
|
|
# we need that to authenticate further, kind like a weird way to login but... |
|
112
|
|
|
t = req_login2.cookies.get('CASTGC') |
|
113
|
|
|
t = 'ST-0' + t[4:] |
|
114
|
|
|
# now the auth with the cookies we got |
|
115
|
|
|
# url_post_auth = 'https://connect.garmin.com/modern' this one I still don't know how to get it |
|
116
|
|
|
url_post_auth = 'https://connect.garmin.com/post-auth/login' |
|
117
|
|
|
params_post_auth = {'ticket': t} |
|
118
|
|
|
req_post_auth = s.get(url_post_auth, params=params_post_auth) |
|
119
|
|
|
if req_post_auth.status_code != 200: |
|
120
|
|
|
logger.info('issue with {}, you can turn on debug for more info'.format( |
|
121
|
|
|
req_post_auth)) |
|
122
|
|
|
logger.info('Let\'s upload stuff now') |
|
123
|
|
|
# login should be done we upload now |
|
124
|
|
|
|
|
125
|
|
|
# url_upload = 'https://connect.garmin.com/proxy/upload-service-1.1/json/upload/.fit' |
|
126
|
|
|
url_upload = 'https://connect.garmin.com/modern/proxy/upload-service/upload/.fit' |
|
127
|
|
|
if len(os.listdir(directory_fit)): |
|
128
|
|
|
logger.debug([f for f in os.listdir(directory_fit) if os.path.isfile(os.path.join(directory_fit, f))]) |
|
129
|
|
|
for filename in [f for f in os.listdir(directory_fit) if os.path.isfile(os.path.join(directory_fit, f))]: |
|
130
|
|
|
logger.info('uploading: {}'.format(filename)) |
|
131
|
|
|
files = {'data': (filename, |
|
132
|
|
|
open(os.path.join(directory_fit, filename), 'rb'), |
|
133
|
|
|
'application/octet-stream') |
|
134
|
|
|
} |
|
135
|
|
|
s.headers.update({'Referer': 'https://connect.garmin.com/modern/import-data', 'NK': 'NT'}) |
|
136
|
|
|
req5 = s.post(url_upload, files=files) |
|
137
|
|
|
if req5.status_code != 200: |
|
138
|
|
|
logger.info( |
|
139
|
|
|
'issue with {}, you can turn on debug for more info'.format( |
|
140
|
|
|
req5)) |
|
141
|
|
|
|
|
142
|
|
|
# fn = req5.json()['detailedImportResult']['fileName'] |
|
143
|
|
|
if 'failures' in req5.json()['detailedImportResult']: |
|
144
|
|
|
for failure in req5.json()['detailedImportResult']['failures']: |
|
145
|
|
|
m_failures = failure['messages'][0]['content'] |
|
146
|
|
|
logger.info(m_failures) |
|
147
|
|
|
if 'successes' in req5.json()['detailedImportResult']: |
|
148
|
|
|
for successes in req5.json()['detailedImportResult']['successes']: |
|
149
|
|
|
m_success = 'https://connect.garmin.com/modern/activity/' + str( |
|
150
|
|
|
successes['internalId']) |
|
151
|
|
|
logger.info(m_success) |
|
152
|
|
|
|
|
153
|
|
|
if move: |
|
154
|
|
|
shutil.move(os.path.join(directory_fit, filename), |
|
155
|
|
|
os.path.join(conf_dir_fit, filename)) |
|
156
|
|
|
|
|
157
|
|
|
logger.info('Done uploading') |
|
158
|
|
|
else: |
|
159
|
|
|
logger.info('No file found in {}'.format(directory_fit)) |
|
160
|
|
|
logger.info('Finished') |
|
161
|
|
|
|