Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/browser-tls-fingerprint-bypass' …
Browse files Browse the repository at this point in the history
…into add-gear-support
  • Loading branch information
drewbrew committed Nov 6, 2023
2 parents 52780ec + 9fcde6b commit 6c5ef92
Showing 1 changed file with 47 additions and 6 deletions.
53 changes: 47 additions & 6 deletions garminexport/garminclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,47 @@ def _authenticate(self):
# end up with a 402 response from Garmin.
self.session.headers.update({'NK': 'NT'})

# We need to pass an Authorization oauth token with subsequent requests.
auth_token = self._get_oauth_token()
token_type = auth_token['token_type']
access_token = auth_token['access_token']
self.session.headers.update(
{
'Authorization': f'{token_type} {access_token}',
'Di-Backend': 'connectapi.garmin.com',
})


def _get_oauth_token(self):
"""Retrieve an OAuth token to use for the session.
Typically looks something like the following. The 'access_token'
needs to be passed in the 'Authorization' header for remaining session
requests.
{
"scope": "...",
"jti": "...",
"access_token": "...",
"token_type": "Bearer",
"refresh_token": "...",
"expires_in": 3599,
"refresh_token_expires_in": 7199
}
"""
log.info("getting oauth token ...")
headers = {
'authority': 'connect.garmin.com',
'origin': 'https://connect.garmin.com',
'referer': 'https://connect.garmin.com/modern/',
}
resp = self.session.post('https://connect.garmin.com/modern/di-oauth/exchange',
headers=headers)
if resp.status_code != 200:
raise ValueError(f'get oauth token failed with {resp.status_code}: {resp.text}')
return resp.json()


def _login(self, username, password):
"""Logs in with the supplied account credentials.
The return value is a URL where the created authentication ticket can be claimed.
Expand Down Expand Up @@ -228,7 +269,7 @@ def _fetch_activity_ids_and_ts(self, start_index, max_limit=100):
"""
log.debug("fetching activities %d through %d ...", start_index, start_index + max_limit - 1)
response = self.session.get(
"https://connect.garmin.com/proxy/activitylist-service/activities/search/activities",
"https://connect.garmin.com/activitylist-service/activities/search/activities",
params={"start": start_index, "limit": max_limit})
if response.status_code != 200:
raise Exception(
Expand Down Expand Up @@ -261,7 +302,7 @@ def get_activity_summary(self, activity_id):
:rtype: dict
"""
response = self.session.get(
"https://connect.garmin.com/proxy/activity-service/activity/{}".format(activity_id))
"https://connect.garmin.com/activity-service/activity/{}".format(activity_id))
if response.status_code != 200:
log.error(u"failed to fetch json summary for activity %s: %d\n%s",
activity_id, response.status_code, response.text)
Expand All @@ -282,7 +323,7 @@ def get_activity_details(self, activity_id):
"""
# mounted at xml or json depending on result encoding
response = self.session.get(
"https://connect.garmin.com/proxy/activity-service/activity/{}/details".format(activity_id))
"https://connect.garmin.com/activity-service/activity/{}/details".format(activity_id))
if response.status_code != 200:
raise Exception(u"failed to fetch json activityDetails for {}: {}\n{}".format(
activity_id, response.status_code, response.text))
Expand All @@ -302,7 +343,7 @@ def get_activity_gpx(self, activity_id):
:rtype: str
"""
response = self.session.get(
"https://connect.garmin.com/proxy/download-service/export/gpx/activity/{}".format(activity_id))
"https://connect.garmin.com/download-service/export/gpx/activity/{}".format(activity_id))
# An alternate URL that seems to produce the same results
# and is the one used when exporting through the Garmin
# Connect web page.
Expand Down Expand Up @@ -334,7 +375,7 @@ def get_activity_tcx(self, activity_id):
"""

response = self.session.get(
"https://connect.garmin.com/proxy/download-service/export/tcx/activity/{}".format(activity_id))
"https://connect.garmin.com/download-service/export/tcx/activity/{}".format(activity_id))
if response.status_code == 404:
return None
if response.status_code != 200:
Expand Down Expand Up @@ -378,7 +419,7 @@ def get_original_activity(self, activity_id):
:rtype: (str, str)
"""
response = self.session.get(
"https://connect.garmin.com/proxy/download-service/files/activity/{}".format(activity_id))
"https://connect.garmin.com/download-service/files/activity/{}".format(activity_id))
# A 404 (Not Found) response is a clear indicator of a missing .fit
# file. As of lately, the endpoint appears to have started to
# respond with 500 "NullPointerException" on attempts to download a
Expand Down

0 comments on commit 6c5ef92

Please sign in to comment.