Skip to content

Commit 8b12757

Browse files
authored
Merge pull request #35 from jhurl3y/master
New APIs + cleanup
2 parents 02d2e3c + bad703f commit 8b12757

1 file changed

Lines changed: 109 additions & 38 deletions

File tree

garminconnect/__init__.py

Lines changed: 109 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,31 @@
1313
MODERN_URL = 'https://connect.garmin.com/modern'
1414
SIGNIN_URL = 'https://sso.garmin.com/sso/signin'
1515

16+
1617
class Garmin(object):
1718
"""
1819
Object using Garmin Connect 's API-method.
1920
See https://connect.garmin.com/
2021
"""
2122
url_user_summary = MODERN_URL + '/proxy/usersummary-service/usersummary/daily/'
22-
url_user_summary_chart = MODERN_URL + '/proxy/wellness-service/wellness/dailySummaryChart/'
23+
url_user_summary_chart = MODERN_URL + \
24+
'/proxy/wellness-service/wellness/dailySummaryChart/'
2325
url_heartrates = MODERN_URL + '/proxy/wellness-service/wellness/dailyHeartRate/'
2426
url_sleepdata = MODERN_URL + '/proxy/wellness-service/wellness/dailySleepData/'
25-
url_body_composition = MODERN_URL + '/proxy/weight-service/weight/daterangesnapshot'
26-
url_activities = MODERN_URL + '/proxy/activitylist-service/activities/search/activities'
27-
url_exercise_sets = MODERN_URL + '/proxy/activity-service/activity/'
27+
url_body_composition = MODERN_URL + \
28+
'/proxy/weight-service/weight/daterangesnapshot'
29+
url_activities = MODERN_URL + \
30+
'/proxy/activitylist-service/activities/search/activities'
31+
url_activity = MODERN_URL + '/proxy/activity-service/activity/'
32+
url_personal_record = MODERN_URL + '/proxy/personalrecord-service/personalrecord/'
2833
url_tcx_download = MODERN_URL + "/proxy/download-service/export/tcx/activity/"
2934
url_gpx_download = MODERN_URL + "/proxy/download-service/export/gpx/activity/"
3035
url_kml_download = MODERN_URL + "/proxy/download-service/export/kml/activity/"
3136
url_fit_download = MODERN_URL + "/proxy/download-service/files/activity/"
3237
url_csv_download = MODERN_URL + "/proxy/download-service/export/csv/activity/"
3338
url_device_list = MODERN_URL + '/proxy/device-service/deviceregistration/devices'
34-
url_device_settings = MODERN_URL + '/proxy/device-service/deviceservice/device-info/settings/'
39+
url_device_service = MODERN_URL + \
40+
'/proxy/device-service/deviceservice/'
3541

3642
headers = {
3743
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36',
@@ -50,7 +56,6 @@ def __init__(self, email, password):
5056
self.full_name = ""
5157
self.unit_system = ""
5258

53-
5459
def login(self):
5560
"""
5661
Login to portal
@@ -87,9 +92,11 @@ def login(self):
8792
'displayNameRequired': 'false'
8893
}
8994

90-
self.logger.debug("Login to Garmin Connect using POST url %s", SIGNIN_URL)
95+
self.logger.debug(
96+
"Login to Garmin Connect using POST url %s", SIGNIN_URL)
9197
try:
92-
response = self.req.post(SIGNIN_URL, headers=self.headers, params=params, data=data)
98+
response = self.req.post(
99+
SIGNIN_URL, headers=self.headers, params=params, data=data)
93100
if response.status_code == 429:
94101
raise GarminConnectTooManyRequestsError("Too many requests")
95102

@@ -99,7 +106,8 @@ def login(self):
99106
raise GarminConnectConnectionError("Error connecting") from err
100107

101108
self.logger.debug("Response is %s", response.text)
102-
response_url = re.search(r'"(https:[^"]+?ticket=[^"]+)"', response.text)
109+
response_url = re.search(
110+
r'"(https:[^"]+?ticket=[^"]+)"', response.text)
103111

104112
if not response_url:
105113
raise GarminConnectAuthenticationError("Authentication error")
@@ -117,17 +125,18 @@ def login(self):
117125

118126
self.logger.debug("Profile info is %s", response.text)
119127

120-
self.user_prefs = self.parse_json(response.text, 'VIEWER_USERPREFERENCES')
128+
self.user_prefs = self.parse_json(
129+
response.text, 'VIEWER_USERPREFERENCES')
121130
self.unit_system = self.user_prefs['measurementSystem']
122131
self.logger.debug("Unit system is %s", self.unit_system)
123132

124-
self.social_profile = self.parse_json(response.text, 'VIEWER_SOCIAL_PROFILE')
133+
self.social_profile = self.parse_json(
134+
response.text, 'VIEWER_SOCIAL_PROFILE')
125135
self.display_name = self.social_profile['displayName']
126136
self.full_name = self.social_profile['fullName']
127137
self.logger.debug("Display name is %s", self.display_name)
128138
self.logger.debug("Fullname is %s", self.full_name)
129139

130-
131140
def parse_json(self, html, key):
132141
"""
133142
Find and return json data
@@ -137,7 +146,6 @@ def parse_json(self, html, key):
137146
text = found.group(1).replace('\\"', '"')
138147
return json.loads(text)
139148

140-
141149
def fetch_data(self, url):
142150
"""
143151
Fetch and return data
@@ -150,57 +158,59 @@ def fetch_data(self, url):
150158
self.logger.debug("Fetch response code %s", response.status_code)
151159
response.raise_for_status()
152160
except requests.exceptions.HTTPError as err:
153-
self.logger.debug("Exception occurred during data retrieval - perhaps session expired - trying relogin: %s" % err)
161+
self.logger.debug(
162+
"Exception occurred during data retrieval - perhaps session expired - trying relogin: %s" % err)
154163
self.login()
155164
try:
156165
response = self.req.get(url, headers=self.headers)
157166
if response.status_code == 429:
158-
raise GarminConnectTooManyRequestsError("Too many requests")
167+
raise GarminConnectTooManyRequestsError(
168+
"Too many requests")
159169

160-
self.logger.debug("Fetch response code %s", response.status_code)
170+
self.logger.debug("Fetch response code %s",
171+
response.status_code)
161172
response.raise_for_status()
162173
except requests.exceptions.HTTPError as err:
163-
self.logger.debug("Exception occurred during data retrieval, relogin without effect: %s" % err)
174+
self.logger.debug(
175+
"Exception occurred during data retrieval, relogin without effect: %s" % err)
164176
raise GarminConnectConnectionError("Error connecting") from err
165177

166178
resp_json = response.json()
167179
self.logger.debug("Fetch response json %s", resp_json)
168180
return resp_json
169181

170-
171182
def get_full_name(self):
172183
"""
173184
Return full name
174185
"""
175186
return self.full_name
176187

177-
178188
def get_unit_system(self):
179189
"""
180190
Return unit system
181191
"""
182192
return self.unit_system
183193

184-
185194
def get_stats_and_body(self, cdate):
186195
"""
187196
Return activity data and body composition
188197
"""
189198
return ({**self.get_stats(cdate), **self.get_body_composition(cdate)['totalAverage']})
190199

191-
192200
def get_stats(self, cdate): # cDate = 'YYY-mm-dd'
193201
"""
194202
Fetch available activity data
195203
"""
196-
summaryurl = self.url_user_summary + self.display_name + '?' + 'calendarDate=' + cdate
204+
summaryurl = self.url_user_summary + \
205+
self.display_name + '?' + 'calendarDate=' + cdate
197206
self.logger.debug("Fetching statistics %s", summaryurl)
198207
try:
199208
response = self.req.get(summaryurl, headers=self.headers)
200209
if response.status_code == 429:
201210
raise GarminConnectTooManyRequestsError("Too many requests")
202211

203-
self.logger.debug("Statistics response code %s", response.status_code)
212+
self.logger.debug("Statistics response code %s",
213+
response.status_code)
204214
response.raise_for_status()
205215
except requests.exceptions.HTTPError as err:
206216
raise GarminConnectConnectionError("Error connecting") from err
@@ -212,20 +222,22 @@ def get_stats(self, cdate): # cDate = 'YYY-mm-dd'
212222
try:
213223
response = self.req.get(summaryurl, headers=self.headers)
214224
if response.status_code == 429:
215-
raise GarminConnectTooManyRequestsError("Too many requests")
225+
raise GarminConnectTooManyRequestsError(
226+
"Too many requests")
216227

217-
self.logger.debug("Statistics response code %s", response.status_code)
228+
self.logger.debug(
229+
"Statistics response code %s", response.status_code)
218230
response.raise_for_status()
219231
except requests.exceptions.HTTPError as err:
220-
self.logger.debug("Exception occurred during statistics retrieval, relogin without effect: %s" % err)
232+
self.logger.debug(
233+
"Exception occurred during statistics retrieval, relogin without effect: %s" % err)
221234
raise GarminConnectConnectionError("Error connecting") from err
222235
else:
223236
resp_json = response.json()
224237

225238
self.logger.debug("Statistics response json %s", resp_json)
226239
return resp_json
227240

228-
229241
def get_heart_rates(self, cdate): # cDate = 'YYYY-mm-dd'
230242
"""
231243
Fetch available heart rates data
@@ -235,7 +247,6 @@ def get_heart_rates(self, cdate): # cDate = 'YYYY-mm-dd'
235247

236248
return self.fetch_data(hearturl)
237249

238-
239250
def get_sleep_data(self, cdate): # cDate = 'YYYY-mm-dd'
240251
"""
241252
Fetch available sleep data
@@ -254,26 +265,28 @@ def get_steps_data(self, cdate): # cDate = 'YYYY-mm-dd'
254265

255266
return self.fetch_data(steps_url)
256267

257-
258268
def get_body_composition(self, cdate): # cDate = 'YYYY-mm-dd'
259269
"""
260270
Fetch available body composition data (only for cDate)
261271
"""
262-
bodycompositionurl = self.url_body_composition + '?startDate=' + cdate + '&endDate=' + cdate
263-
self.logger.debug("Fetching body composition with url %s", bodycompositionurl)
272+
bodycompositionurl = self.url_body_composition + \
273+
'?startDate=' + cdate + '&endDate=' + cdate
274+
self.logger.debug(
275+
"Fetching body composition with url %s", bodycompositionurl)
264276

265277
return self.fetch_data(bodycompositionurl)
266278

267279
def get_activities(self, start, limit):
268280
"""
269281
Fetch available activities
270282
"""
271-
activitiesurl = self.url_activities + '?start=' + str(start) + '&limit=' + str(limit)
283+
activitiesurl = self.url_activities + '?start=' + \
284+
str(start) + '&limit=' + str(limit)
272285
self.logger.debug("Fetching activities with url %s", activitiesurl)
273286

274287
return self.fetch_data(activitiesurl)
275288

276-
def get_activities_by_date(self, startdate: str, enddate: str, activitytype = "" ) -> list[json]:
289+
def get_activities_by_date(self, startdate, enddate, activitytype):
277290
"""
278291
Fetch available activities between specific dates
279292
@@ -297,7 +310,7 @@ def get_activities_by_date(self, startdate: str, enddate: str, activitytype = ""
297310
activityslug = ""
298311
while returndata:
299312
activitiesurl = self.url_activities + '?startDate=' + str(startdate) + '&endDate=' + str(
300-
enddate) + '&start=' + str(start) + '&limit=' + str(limit) + activityslug
313+
enddate) + '&start=' + str(start) + '&limit=' + str(limit) + activityslug
301314
self.logger.debug("Fetching activities with url %s", activitiesurl)
302315
act = self.fetch_data(activitiesurl)
303316
if act:
@@ -310,28 +323,86 @@ def get_activities_by_date(self, startdate: str, enddate: str, activitytype = ""
310323

311324
def get_excercise_sets(self, activity_id):
312325
activity_id = str(activity_id)
313-
exercisesetsurl = f"{self.url_exercise_sets}{activity_id}/exerciseSets"
314-
self.logger.debug(f"Fetching exercise sets for activity_id {activity_id}")
326+
exercisesetsurl = f"{self.url_activity}{activity_id}/exerciseSets"
327+
self.logger.debug(
328+
f"Fetching exercise sets for activity_id {activity_id}")
315329

316330
return self.fetch_data(exercisesetsurl)
317331

332+
def get_activity_splits(self, activity_id):
333+
activity_id = str(activity_id)
334+
splits_url = f"{self.url_activity}{activity_id}/splits"
335+
self.logger.debug(
336+
f"Fetching splits for activity_id {activity_id}")
337+
338+
return self.fetch_data(splits_url)
339+
340+
def get_activity_split_summaries(self, activity_id):
341+
activity_id = str(activity_id)
342+
split_summaries_url = f"{self.url_activity}{activity_id}/split_summaries"
343+
self.logger.debug(
344+
f"Fetching split summaries for activity_id {activity_id}")
345+
346+
return self.fetch_data(split_summaries_url)
347+
348+
def get_activity_weather(self, activity_id):
349+
activity_id = str(activity_id)
350+
activity_weather_url = f"{self.url_activity}{activity_id}/weather"
351+
self.logger.debug(
352+
f"Fetching weather for activity_id {activity_id}")
353+
354+
return self.fetch_data(activity_weather_url)
355+
356+
def get_activity_hr_in_timezones(self, activity_id):
357+
activity_id = str(activity_id)
358+
activity_hr_timezone_url = f"{self.url_activity}{activity_id}/hrTimeInZones"
359+
self.logger.debug(
360+
f"Fetching split summaries for activity_id {activity_id}")
361+
362+
return self.fetch_data(activity_hr_timezone_url)
363+
364+
def get_activity_details(self, activity_id, maxChartSize=2000, maxPolylineSize=4000):
365+
activity_id = str(activity_id)
366+
params = f"maxChartSize={maxChartSize}&maxPolylineSize={maxPolylineSize}"
367+
details_url = f"{self.url_activity}{activity_id}/details?{params}"
368+
self.logger.debug(
369+
f"Fetching details for activity_id {activity_id}")
370+
371+
return self.fetch_data(details_url)
372+
373+
def get_personal_record(self, owner_display_name):
374+
personal_records_url = f"{self.url_personal_record}prs/{owner_display_name}"
375+
self.logger.debug(
376+
f"Fetching prs for owner {owner_display_name}")
377+
378+
return self.fetch_data(personal_records_url)
379+
318380
def get_devices(self):
319381
"""
320382
Fetch available devices for the current account
321383
"""
322384
devicesurl = self.url_device_list
323-
self.logger.debug("Fetching available devices for the current account with url %s", devicesurl)
385+
self.logger.debug(
386+
"Fetching available devices for the current account with url %s", devicesurl)
324387

325388
return self.fetch_data(devicesurl)
326389

327390
def get_device_settings(self, device_id):
328391
"""
329392
Fetch device settings for current device
330393
"""
331-
devicesurl = f"{self.url_device_settings}{device_id}"
394+
devicesurl = f"{self.url_device_service}device-info/settings/{device_id}"
332395
self.logger.debug("Fetching device settings with url %s", devicesurl)
333396
return self.fetch_data(devicesurl)
334397

398+
def get_device_last_used(self):
399+
"""
400+
Fetch device last used
401+
"""
402+
device_last_used_url = f"{self.url_device_service}mylastused"
403+
self.logger.debug(
404+
"Fetching device last used with url %s", device_last_used_url)
405+
return self.fetch_data(device_last_used_url)
335406

336407
class ActivityDownloadFormat(Enum):
337408
ORIGINAL = auto()

0 commit comments

Comments
 (0)