support customizing the 'User-Agent' value
This commit is contained in:
parent
cbe9e74704
commit
33b8354867
@ -18,6 +18,10 @@ log = logging.getLogger(__name__)
|
||||
DEFAULT_MAX_RETRIES = 7
|
||||
"""The default maximum number of retries to make when fetching a single activity."""
|
||||
|
||||
DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
|
||||
"""The default `User-Agent` to use for HTTP requests when none is supplied by
|
||||
the user.
|
||||
"""
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
"""Parse CLI arguments.
|
||||
@ -59,6 +63,9 @@ def parse_args() -> argparse.Namespace:
|
||||
help=("The maximum number of retries to make on failed attempts to fetch an activity. "
|
||||
"Exponential backoff will be used, meaning that the delay between successive attempts "
|
||||
"will double with every retry, starting at one second. DEFAULT: {}").format(DEFAULT_MAX_RETRIES))
|
||||
parser.add_argument(
|
||||
"--user-agent", type=str, default=DEFAULT_USER_AGENT,
|
||||
help="A value to use for the `User-Agent` request header. Use an authentic browser agent string to prevent being blocked by Garmin. A tool such as `user_agent` (`ua`) can be used to generate such values.")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
@ -70,6 +77,7 @@ def main():
|
||||
try:
|
||||
incremental_backup(username=args.username,
|
||||
password=args.password,
|
||||
user_agent_fn=lambda:DEFAULT_USER_AGENT,
|
||||
backup_dir=args.backup_dir,
|
||||
export_formats=args.format,
|
||||
ignore_errors=args.ignore_errors,
|
||||
|
@ -81,18 +81,27 @@ class GarminClient(object):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, username, password):
|
||||
def __init__(self, username, password, user_agent_fn=None):
|
||||
"""Initialize a :class:`GarminClient` instance.
|
||||
|
||||
:param username: Garmin Connect user name or email address.
|
||||
:type username: str
|
||||
:param password: Garmin Connect account password.
|
||||
:type password: str
|
||||
:keyword user_agent_fn: A function that, when called, produces a
|
||||
`User-Agent` string to be used as `User-Agent` for the remainder of the
|
||||
session. If set to None, the default user agent of the http request
|
||||
library is used.
|
||||
:type user_agent_fn: Callable[[], str]
|
||||
|
||||
"""
|
||||
self.username = username
|
||||
self.password = password
|
||||
self._user_agent_fn = user_agent_fn
|
||||
|
||||
self.session = None
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
self.connect()
|
||||
return self
|
||||
@ -118,7 +127,15 @@ class GarminClient(object):
|
||||
"embed": "false",
|
||||
"_csrf": self._get_csrf_token(),
|
||||
}
|
||||
headers = {'origin': 'https://sso.garmin.com'}
|
||||
headers = {
|
||||
'origin': 'https://sso.garmin.com',
|
||||
}
|
||||
if self._user_agent_fn:
|
||||
user_agent = self._user_agent_fn()
|
||||
if not user_agent:
|
||||
raise ValueError("user_agent_fn didn't produce a value")
|
||||
headers['User-Agent'] = user_agent
|
||||
|
||||
auth_response = self.session.post(
|
||||
SSO_SIGNIN_URL, headers=headers, params=self._auth_params(), data=form_data)
|
||||
log.debug("got auth response: %s", auth_response.text)
|
||||
|
@ -3,7 +3,7 @@ import getpass
|
||||
import logging
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from typing import List
|
||||
from typing import Callable, List
|
||||
|
||||
import garminexport.backup
|
||||
from garminexport.backup import supported_export_formats
|
||||
@ -15,6 +15,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
def incremental_backup(username: str,
|
||||
password: str = None,
|
||||
user_agent_fn: Callable[[],str] = None,
|
||||
backup_dir: str = os.path.join(".", "activities"),
|
||||
export_formats: List[str] = None,
|
||||
ignore_errors: bool = False,
|
||||
@ -23,6 +24,11 @@ def incremental_backup(username: str,
|
||||
|
||||
:param username: Garmin Connect user name
|
||||
:param password: Garmin Connect user password. Default: None. If not provided, would be asked interactively.
|
||||
:keyword user_agent_fn: A function that, when called, produces a
|
||||
`User-Agent` string to be used as `User-Agent` for the remainder of the
|
||||
session. If set to None, the default user agent of the http request
|
||||
library is used.
|
||||
:type user_agent_fn: Callable[[], str]
|
||||
:param backup_dir: Destination directory for downloaded activities. Default: ./activities/".
|
||||
:param export_formats: List of desired output formats (json_summary, json_details, gpx, tcx, fit).
|
||||
Default: `None` which means all supported formats will be backed up.
|
||||
@ -34,6 +40,7 @@ def incremental_backup(username: str,
|
||||
The activities are stored in a local directory on the user's computer.
|
||||
The backups are incremental, meaning that only activities that aren't already
|
||||
stored in the backup directory will be downloaded.
|
||||
|
||||
"""
|
||||
# if no --format was specified, all formats are to be backed up
|
||||
export_formats = export_formats if export_formats else supported_export_formats
|
||||
@ -50,7 +57,7 @@ def incremental_backup(username: str,
|
||||
delay_strategy=ExponentialBackoffDelayStrategy(initial_delay=timedelta(seconds=1)),
|
||||
stop_strategy=MaxRetriesStopStrategy(max_retries))
|
||||
|
||||
with GarminClient(username, password) as client:
|
||||
with GarminClient(username, password, user_agent_fn) as client:
|
||||
# get all activity ids and timestamps from Garmin account
|
||||
log.info("scanning activities for %s ...", username)
|
||||
activities = set(retryer.call(client.list_activities))
|
||||
|
Loading…
Reference in New Issue
Block a user