include CSRF-token in auth and drop 'modern' prefix in paths (#72)
This fix is needed to address internal changes in the Garmin API, where service endpoints that previously worked started returning 402 responses. Appears like dropping the `modern` prefix from the endpoint paths fixes the issue. Also, the authentication sequence has been updated to include a CSRF (Cross-Site Request Forgery) token to closer mimic the behavior of the login sequence on the official web page. Co-authored-by: kfollesdal <kristoffer.follesdal@bkk.no>
This commit is contained in:
parent
3886c314c9
commit
b62e2ac1e5
2
Pipfile
2
Pipfile
@ -4,7 +4,7 @@ url = "https://pypi.org/simple"
|
|||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
"garminexport" = {path = ".", editable = true}
|
garminexport = {path = ".",editable = true}
|
||||||
requests = ">=2.0,<3"
|
requests = ">=2.0,<3"
|
||||||
python-dateutil = ">=2.0,<3"
|
python-dateutil = ">=2.0,<3"
|
||||||
|
|
||||||
|
122
Pipfile.lock
generated
122
Pipfile.lock
generated
@ -16,60 +16,18 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
|
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||||
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
|
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||||
],
|
],
|
||||||
"version": "==2020.4.5.1"
|
"version": "==2020.12.5"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||||
],
|
],
|
||||||
"version": "==3.0.4"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
},
|
"version": "==4.0.0"
|
||||||
"coverage": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
|
|
||||||
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
|
|
||||||
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
|
|
||||||
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
|
|
||||||
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
|
|
||||||
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
|
|
||||||
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
|
|
||||||
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
|
|
||||||
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
|
|
||||||
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
|
|
||||||
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
|
|
||||||
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
|
|
||||||
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
|
|
||||||
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
|
|
||||||
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
|
|
||||||
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
|
|
||||||
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
|
|
||||||
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
|
|
||||||
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
|
|
||||||
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
|
|
||||||
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
|
|
||||||
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
|
|
||||||
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
|
|
||||||
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
|
|
||||||
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
|
|
||||||
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
|
|
||||||
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
|
|
||||||
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
|
|
||||||
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
|
|
||||||
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
|
|
||||||
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
|
||||||
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
|
||||||
],
|
|
||||||
"version": "==4.5.4"
|
|
||||||
},
|
|
||||||
"future": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
|
||||||
],
|
|
||||||
"version": "==0.18.2"
|
|
||||||
},
|
},
|
||||||
"garminexport": {
|
"garminexport": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
@ -77,32 +35,11 @@
|
|||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
|
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||||
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
|
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||||
],
|
],
|
||||||
"version": "==2.9"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
},
|
"version": "==2.10"
|
||||||
"mock": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
|
||||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
|
||||||
],
|
|
||||||
"version": "==2.0.0"
|
|
||||||
},
|
|
||||||
"nose": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
|
|
||||||
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
|
||||||
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
|
||||||
],
|
|
||||||
"version": "==1.3.7"
|
|
||||||
},
|
|
||||||
"pbr": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
|
|
||||||
"sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
|
|
||||||
],
|
|
||||||
"version": "==5.4.5"
|
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -114,25 +51,27 @@
|
|||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
|
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||||
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
|
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.23.0"
|
"version": "==2.25.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
],
|
],
|
||||||
"version": "==1.14.0"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.15.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
"sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
|
||||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
"sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
|
||||||
],
|
],
|
||||||
"version": "==1.25.8"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
|
"version": "==1.26.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@ -171,6 +110,7 @@
|
|||||||
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
||||||
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==4.5.4"
|
"version": "==4.5.4"
|
||||||
},
|
},
|
||||||
"mock": {
|
"mock": {
|
||||||
@ -178,6 +118,7 @@
|
|||||||
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
"sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
|
||||||
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
"sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==2.0.0"
|
"version": "==2.0.0"
|
||||||
},
|
},
|
||||||
"nose": {
|
"nose": {
|
||||||
@ -186,21 +127,24 @@
|
|||||||
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
"sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
|
||||||
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
"sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==1.3.7"
|
"version": "==1.3.7"
|
||||||
},
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
|
"sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
|
||||||
"sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
|
"sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
|
||||||
],
|
],
|
||||||
"version": "==5.4.5"
|
"markers": "python_version >= '2.6'",
|
||||||
|
"version": "==5.5.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||||
],
|
],
|
||||||
"version": "==1.14.0"
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.15.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,11 @@ log = logging.getLogger(__name__)
|
|||||||
# reduce logging noise from requests library
|
# reduce logging noise from requests library
|
||||||
logging.getLogger("requests").setLevel(logging.ERROR)
|
logging.getLogger("requests").setLevel(logging.ERROR)
|
||||||
|
|
||||||
SSO_LOGIN_URL = "https://sso.garmin.com/sso/signin"
|
SSO_LOGIN_URL = "https://sso.garmin.com/sso/login"
|
||||||
"""The Garmin Connect Single-Sign On login URL."""
|
"""Garmin Connect's Single-Sign On login URL."""
|
||||||
|
SSO_SIGNIN_URL = "https://sso.garmin.com/sso/signin"
|
||||||
|
"""The Garmin Connect Single-Sign On sign-in URL. This is where the login form
|
||||||
|
gets POSTed."""
|
||||||
|
|
||||||
|
|
||||||
def require_session(client_function):
|
def require_session(client_function):
|
||||||
@ -108,17 +111,16 @@ class GarminClient(object):
|
|||||||
|
|
||||||
def _authenticate(self):
|
def _authenticate(self):
|
||||||
log.info("authenticating user ...")
|
log.info("authenticating user ...")
|
||||||
|
|
||||||
form_data = {
|
form_data = {
|
||||||
"username": self.username,
|
"username": self.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
"embed": "false"
|
"embed": "false",
|
||||||
}
|
"_csrf": self._get_csrf_token(),
|
||||||
request_params = {
|
|
||||||
"service": "https://connect.garmin.com/modern"
|
|
||||||
}
|
}
|
||||||
headers = {'origin': 'https://sso.garmin.com'}
|
headers = {'origin': 'https://sso.garmin.com'}
|
||||||
auth_response = self.session.post(
|
auth_response = self.session.post(
|
||||||
SSO_LOGIN_URL, headers=headers, params=request_params, data=form_data)
|
SSO_SIGNIN_URL, headers=headers, params=self._auth_params(), data=form_data)
|
||||||
log.debug("got auth response: %s", auth_response.text)
|
log.debug("got auth response: %s", auth_response.text)
|
||||||
if auth_response.status_code != 200:
|
if auth_response.status_code != 200:
|
||||||
raise ValueError("authentication failure: did you enter valid credentials?")
|
raise ValueError("authentication failure: did you enter valid credentials?")
|
||||||
@ -132,9 +134,35 @@ class GarminClient(object):
|
|||||||
"auth failure: failed to claim auth ticket: {}: {}\n{}".format(
|
"auth failure: failed to claim auth ticket: {}: {}\n{}".format(
|
||||||
auth_ticket_url, response.status_code, response.text))
|
auth_ticket_url, response.status_code, response.text))
|
||||||
|
|
||||||
# appears like we need to touch base with the old API to initiate
|
# appears like we need to touch base with the main page to complete the
|
||||||
# some form of legacy session. otherwise certain downloads will fail.
|
# login ceremony.
|
||||||
self.session.get('https://connect.garmin.com/legacy/session')
|
self.session.get('https://connect.garmin.com/modern')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_csrf_token(self):
|
||||||
|
"""Retrieves a Cross-Site Request Forgery (CSRF) token from Garmin's login
|
||||||
|
page. The token is passed along in the login form for increased
|
||||||
|
security."""
|
||||||
|
log.info("fetching CSRF token ...")
|
||||||
|
resp = self.session.get(SSO_LOGIN_URL, params=self._auth_params())
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise ValueError("auth failure: could not load {}".format(SSO_LOGIN_URL))
|
||||||
|
# extract CSRF token
|
||||||
|
csrf_token = re.search(r'<input type="hidden" name="_csrf" value="(\w+)"',
|
||||||
|
resp.content.decode('utf-8'))
|
||||||
|
if not csrf_token:
|
||||||
|
raise ValueError("auth failure: no CSRF token in {}".format(SSO_LOGIN_URL))
|
||||||
|
return csrf_token.group(1)
|
||||||
|
|
||||||
|
def _auth_params(self):
|
||||||
|
"""A set of request query parameters that need to be present for Garmin to
|
||||||
|
accept our login attempt.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"service": "https://connect.garmin.com/modern/",
|
||||||
|
"gauthHost": "https://sso.garmin.com/sso",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_auth_ticket_url(auth_response):
|
def _extract_auth_ticket_url(auth_response):
|
||||||
@ -190,7 +218,7 @@ class GarminClient(object):
|
|||||||
"""
|
"""
|
||||||
log.debug("fetching activities %d through %d ...", start_index, start_index + max_limit - 1)
|
log.debug("fetching activities %d through %d ...", start_index, start_index + max_limit - 1)
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/activitylist-service/activities/search/activities",
|
"https://connect.garmin.com/proxy/activitylist-service/activities/search/activities",
|
||||||
params={"start": start_index, "limit": max_limit})
|
params={"start": start_index, "limit": max_limit})
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@ -223,7 +251,7 @@ class GarminClient(object):
|
|||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/activity-service/activity/{}".format(activity_id))
|
"https://connect.garmin.com/proxy/activity-service/activity/{}".format(activity_id))
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
log.error(u"failed to fetch json summary for activity %s: %d\n%s",
|
log.error(u"failed to fetch json summary for activity %s: %d\n%s",
|
||||||
activity_id, response.status_code, response.text)
|
activity_id, response.status_code, response.text)
|
||||||
@ -244,7 +272,7 @@ class GarminClient(object):
|
|||||||
"""
|
"""
|
||||||
# mounted at xml or json depending on result encoding
|
# mounted at xml or json depending on result encoding
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/activity-service/activity/{}/details".format(activity_id))
|
"https://connect.garmin.com/proxy/activity-service/activity/{}/details".format(activity_id))
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise Exception(u"failed to fetch json activityDetails for {}: {}\n{}".format(
|
raise Exception(u"failed to fetch json activityDetails for {}: {}\n{}".format(
|
||||||
activity_id, response.status_code, response.text))
|
activity_id, response.status_code, response.text))
|
||||||
@ -264,7 +292,7 @@ class GarminClient(object):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/download-service/export/gpx/activity/{}".format(activity_id))
|
"https://connect.garmin.com/proxy/download-service/export/gpx/activity/{}".format(activity_id))
|
||||||
# An alternate URL that seems to produce the same results
|
# An alternate URL that seems to produce the same results
|
||||||
# and is the one used when exporting through the Garmin
|
# and is the one used when exporting through the Garmin
|
||||||
# Connect web page.
|
# Connect web page.
|
||||||
@ -296,7 +324,7 @@ class GarminClient(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/download-service/export/tcx/activity/{}".format(activity_id))
|
"https://connect.garmin.com/proxy/download-service/export/tcx/activity/{}".format(activity_id))
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
return None
|
return None
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
@ -317,7 +345,7 @@ class GarminClient(object):
|
|||||||
:rtype: (str, str)
|
:rtype: (str, str)
|
||||||
"""
|
"""
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
"https://connect.garmin.com/modern/proxy/download-service/files/activity/{}".format(activity_id))
|
"https://connect.garmin.com/proxy/download-service/files/activity/{}".format(activity_id))
|
||||||
# A 404 (Not Found) response is a clear indicator of a missing .fit
|
# A 404 (Not Found) response is a clear indicator of a missing .fit
|
||||||
# file. As of lately, the endpoint appears to have started to
|
# file. As of lately, the endpoint appears to have started to
|
||||||
# respond with 500 "NullPointerException" on attempts to download a
|
# respond with 500 "NullPointerException" on attempts to download a
|
||||||
@ -373,7 +401,7 @@ class GarminClient(object):
|
|||||||
:obj:`None` if upload is still processing.
|
:obj:`None` if upload is still processing.
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
response = self.session.get("https://connect.garmin.com/modern/proxy/activity-service/activity/status/{}/{}?_={}".format(
|
response = self.session.get("https://connect.garmin.com/proxy/activity-service/activity/status/{}/{}?_={}".format(
|
||||||
creation_date[:10], uuid.replace("-",""), int(datetime.now().timestamp()*1000)), headers={"nk": "NT"})
|
creation_date[:10], uuid.replace("-",""), int(datetime.now().timestamp()*1000)), headers={"nk": "NT"})
|
||||||
if response.status_code == 201 and response.headers["location"]:
|
if response.status_code == 201 and response.headers["location"]:
|
||||||
# location should be https://connectapi.garmin.com/activity-service/activity/ACTIVITY_ID
|
# location should be https://connectapi.garmin.com/activity-service/activity/ACTIVITY_ID
|
||||||
@ -416,7 +444,7 @@ class GarminClient(object):
|
|||||||
|
|
||||||
# upload it
|
# upload it
|
||||||
files = dict(data=(fn, file))
|
files = dict(data=(fn, file))
|
||||||
response = self.session.post("https://connect.garmin.com/modern/proxy/upload-service/upload/.{}".format(format),
|
response = self.session.post("https://connect.garmin.com/proxy/upload-service/upload/.{}".format(format),
|
||||||
files=files, headers={"nk": "NT"})
|
files=files, headers={"nk": "NT"})
|
||||||
|
|
||||||
# check response and get activity ID
|
# check response and get activity ID
|
||||||
|
Loading…
Reference in New Issue
Block a user