Skip to content

Commit 170de58

Browse files
committed
Major changes, added calls
1 parent 0155564 commit 170de58

File tree

4 files changed

+247
-156
lines changed

4 files changed

+247
-156
lines changed

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,41 @@ pip install garminconnect
1717
## Usage
1818

1919
```python
20-
import garminconnect
20+
from datetime import date
21+
22+
from garminconnect import (
23+
Garmin,
24+
GarminConnectConnectionError,
25+
GarminConnectTooManyRequestsError,
26+
GarminConnectAuthenticationError,
27+
)
28+
29+
today = date.today()
30+
2131

2232
"""Login to portal using specified credentials"""
23-
client = garminconnect.Garmin(YOUR_EMAIL, YOUR_PASSWORD)
33+
try:
34+
client = Garmin(YOUR_EMAIL, YOUR_PASSWORD)
35+
except (
36+
GarminConnectConnectionError,
37+
GarminConnectAuthenticationError,
38+
GarminConnectTooManyRequestsError,
39+
) as err:
40+
print("Error occured during Garmin Connect Client setup: %s", err)
41+
return
42+
except Exception: # pylint: disable=broad-except
43+
print("Unknown error occured during Garmin Connect Client setup")
44+
return
45+
46+
"""Get Full name"""
47+
print(client.get_full_name()
48+
49+
"""Get Unit system"""
50+
print(client.get_unit_system()
2451

2552
"""Fetch your activities data"""
26-
print(client.fetch_stats('2020-01-04'))
53+
print(client.get_stats(today.isoformat())
2754

2855
"""Fetch your logged heart rates"""
29-
print(client.fetch_heart_rates('2020-01-04'))
56+
print(client.get_heart_rates(today.isoformat())
3057
```

garminconnect/__init__.py

Lines changed: 215 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,215 @@
1-
from .data import Garmin
1+
# -*- coding: utf-8 -*-
2+
"""Python 3 API wrapper for Garmin Connect to get your statistics."""
3+
import logging
4+
import json
5+
import re
6+
import requests
7+
8+
from .__version__ import __version__
9+
10+
BASE_URL = 'https://connect.garmin.com'
11+
SSO_URL = 'https://sso.garmin.com/sso'
12+
MODERN_URL = 'https://connect.garmin.com/modern'
13+
SIGNIN_URL = 'https://sso.garmin.com/sso/signin'
14+
15+
class Garmin(object):
16+
"""
17+
Object using Garmin Connect 's API-method.
18+
See https://connect.garmin.com/
19+
"""
20+
url_activities = MODERN_URL + '/proxy/usersummary-service/usersummary/daily/'
21+
url_heartrates = MODERN_URL + '/proxy/wellness-service/wellness/dailyHeartRate/'
22+
headers = {
23+
'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',
24+
'origin': 'https://sso.garmin.com'
25+
}
26+
27+
def __init__(self, email, password):
28+
"""
29+
Init module
30+
"""
31+
self.email = email
32+
self.password = password
33+
self.req = requests.session()
34+
self.logger = logging.getLogger(__name__)
35+
36+
self.login(self.email, self.password)
37+
38+
def login(self, email, password):
39+
"""
40+
Login to portal
41+
"""
42+
params = {
43+
'webhost': BASE_URL,
44+
'service': MODERN_URL,
45+
'source': SIGNIN_URL,
46+
'redirectAfterAccountLoginUrl': MODERN_URL,
47+
'redirectAfterAccountCreationUrl': MODERN_URL,
48+
'gauthHost': SSO_URL,
49+
'locale': 'en_US',
50+
'id': 'gauth-widget',
51+
'cssUrl': 'https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css',
52+
'clientId': 'GarminConnect',
53+
'rememberMeShown': 'true',
54+
'rememberMeChecked': 'false',
55+
'createAccountShown': 'true',
56+
'openCreateAccount': 'false',
57+
'usernameShown': 'false',
58+
'displayNameShown': 'false',
59+
'consumeServiceTicket': 'false',
60+
'initialFocus': 'true',
61+
'embedWidget': 'false',
62+
'generateExtraServiceTicket': 'false'
63+
}
64+
65+
data = {
66+
'username': email,
67+
'password': password,
68+
'embed': 'true',
69+
'lt': 'e1s1',
70+
'_eventId': 'submit',
71+
'displayNameRequired': 'false'
72+
}
73+
74+
self.logger.debug("Login to Garmin Connect using POST url %s", SIGNIN_URL)
75+
try:
76+
response = self.req.post(SIGNIN_URL, headers=self.headers, params=params, data=data)
77+
except requests.exceptions.HTTPError as err:
78+
raise GarminConnectConnectionError("Error connecting")
79+
80+
if response.status_code == 429:
81+
raise GarminConnectTooManyRequestsError("Too many requests")
82+
83+
self.logger.debug("Login response code %s", response.status_code)
84+
response.raise_for_status()
85+
86+
response_url = re.search(r'"(https:[^"]+?ticket=[^"]+)"', response.text)
87+
self.logger.debug("Response is %s", response.text)
88+
if response.status_code == 429:
89+
raise GarminConnectTooManyRequestsError("Too many requests")
90+
91+
if not response_url:
92+
raise GarminConnectAuthenticationError("Authentication error")
93+
94+
response_url = re.sub(r'\\', '', response_url.group(1))
95+
self.logger.debug("Fetching profile info using found response url")
96+
try:
97+
response = self.req.get(response_url)
98+
except requests.exceptions.HTTPError as err:
99+
raise GarminConnectConnectionError("Error connecting")
100+
101+
if response.status_code == 429:
102+
raise GarminConnectTooManyRequestsError("Too many requests")
103+
104+
self.user_prefs = self.parse_json(response.text, 'VIEWER_USERPREFERENCES')
105+
self.unit_system = self.user_prefs['measurementSystem']
106+
self.logger.debug("Unit system is %s", self.unit_system)
107+
108+
self.social_profile = self.parse_json(response.text, 'VIEWER_SOCIAL_PROFILE')
109+
self.display_name = self.social_profile['displayName']
110+
self.full_name = self.social_profile['fullName']
111+
self.logger.debug("Display name is %s", self.display_name)
112+
self.logger.debug("Fullname is %s", self.full_name)
113+
response.raise_for_status()
114+
115+
def parse_json(self, html, key):
116+
"""
117+
Find and return json data
118+
"""
119+
found = re.search(key + r" = JSON.parse\(\"(.*)\"\);", html, re.M)
120+
if found:
121+
text = found.group(1).replace('\\"', '"')
122+
return json.loads(text)
123+
124+
def get_full_name(self):
125+
"""
126+
Return full name
127+
"""
128+
return self.full_name
129+
130+
def get_unit_system(self):
131+
"""
132+
Return unit system
133+
"""
134+
return self.unit_system
135+
136+
def get_stats(self, cdate): # cDate = 'YYY-mm-dd'
137+
"""
138+
Fetch available activity data
139+
"""
140+
acturl = self.url_activities + self.display_name + '?' + 'calendarDate=' + cdate
141+
self.logger.debug("Fetching activities %s", acturl)
142+
try:
143+
response = self.req.get(acturl, headers=self.headers)
144+
self.logger.debug("Activities response code %s, and json %s", response.status_code, response.json())
145+
response.raise_for_status()
146+
except requests.exceptions.HTTPError as err:
147+
raise GarminConnectConnectionError("Error connecting")
148+
149+
if response.status_code == 429:
150+
raise GarminConnectTooManyRequestsError("Too many requests")
151+
152+
if response.json()['privacyProtected'] is True:
153+
self.logger.debug("Session expired - trying relogin")
154+
self.login(self.email, self.password)
155+
try:
156+
response = self.req.get(acturl, headers=self.headers)
157+
self.logger.debug("Activities response code %s, and json %s", response.status_code, response.json())
158+
response.raise_for_status()
159+
except requests.exceptions.HTTPError as err:
160+
self.logger.debug("Exception occured during stats retrieval, relogin without effect: %s" % err)
161+
raise GarminConnectConnectionError("Error connecting")
162+
163+
return response.json()
164+
165+
def get_heart_rates(self, cdate): # cDate = 'YYYY-mm-dd'
166+
"""
167+
Fetch available heart rates data
168+
"""
169+
hearturl = self.url_heartrates + self.display_name + '?date=' + cdate
170+
self.logger.debug("Fetching heart rates with url %s", hearturl)
171+
try:
172+
response = self.req.get(hearturl, headers=self.headers)
173+
self.logger.debug("Heart Rates response code %s, and json %s", response.status_code, response.json())
174+
response.raise_for_status()
175+
except requests.exceptions.HTTPError as err:
176+
self.logger.debug("Exception occured during heart rate retrieval - perhaps session expired - trying relogin: %s" % err)
177+
self.login(self.email, self.password)
178+
try:
179+
response = self.req.get(hearturl, headers=self.headers)
180+
self.logger.debug("Heart Rates response code %s, and json %s", response.status_code, response.json())
181+
response.raise_for_status()
182+
except requests.exceptions.HTTPError as err:
183+
self.logger.debug("Exception occured during stats retrieval, relogin without effect: %s" % err)
184+
raise GarminConnectConnectionError("Error connecting")
185+
186+
if response.status_code == 429:
187+
raise GarminConnectTooManyRequestsError("Too many requests")
188+
189+
return response.json()
190+
191+
192+
class GarminConnectConnectionError(Exception):
193+
"""Raised when communication ended in error."""
194+
195+
def __init__(self, status):
196+
"""Initialize."""
197+
super(GarminConnectConnectionError, self).__init__(status)
198+
self.status = status
199+
200+
class GarminConnectTooManyRequestsError(Exception):
201+
"""Raised when rate limit is exceeded."""
202+
203+
def __init__(self, status):
204+
"""Initialize."""
205+
super(GarminConnectTooManyRequestsError, self).__init__(status)
206+
self.status = status
207+
208+
class GarminConnectAuthenticationError(Exception):
209+
"""Raised when login returns wrong result."""
210+
211+
def __init__(self, status):
212+
"""Initialize."""
213+
super(GarminConnectAuthenticationError, self).__init__(status)
214+
self.status = status
215+

garminconnect/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# -*- coding: utf-8 -*-
22
"""Python 3 API wrapper for Garmin Connect to get your statistics."""
33

4-
__version__ = "0.1.3"
4+
__version__ = "0.1.7"

0 commit comments

Comments
 (0)