From 54b52af4a7b8be5748931102fb171b6c2128c497 Mon Sep 17 00:00:00 2001 From: Peter Steiner Date: Sat, 2 Mar 2024 19:23:49 +0100 Subject: [PATCH] Prepare for release 4.3.0 --- README.md | 33 +++++++++++++++++++++++---------- gcexport.py | 19 ++++++++++++------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e393446..eef03a0 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,11 @@ If you have many activities, you may find that this script crashes with an "Oper You will need a little experience running things from the command line to use this script. That said, here are the usage details from the `--help` flag: ``` -usage: gcexport.py [-h] [--version] [-v] [--username USERNAME] - [--password PASSWORD] [-c COUNT] - [-sd START_DATE] [-ed END_DATE] - [-e EXTERNAL] [-a ARGS] - [-f {gpx,tcx,original,json}] [-d DIRECTORY] [-s SUBDIR] - [-lp LOGPATH] [-u] [-ot] [--desc [DESC]] [-t TEMPLATE] - [-fp] [-sa START_ACTIVITY_NO] [-ex FILE] - [-ss DIRECTORY] +usage: gcexport.py [-h] [--version] [-v] [--username USERNAME] [--password PASSWORD] + [-c COUNT] [-sd START_DATE] [-ed END_DATE] [-e EXTERNAL] [-a ARGS] + [-f {gpx,tcx,original,json}] [-d DIRECTORY] [-s SUBDIR] [-lp LOGPATH] + [-u] [-ot] [--desc [DESC]] [-t TEMPLATE] [-fp] [-sa START_ACTIVITY_NO] + [-ex FILE] [-tf TYPE_FILTER] [-ss DIRECTORY] Garmin Connect Exporter @@ -95,12 +92,26 @@ optional arguments: -sa START_ACTIVITY_NO, --start_activity_no START_ACTIVITY_NO give index for first activity to import, i.e. skipping the newest activities -ex FILE, --exclude FILE - JSON file with Array of activity IDs to exclude from download. + JSON file with array of activity IDs to exclude from download. Format example: {"ids": ["6176888711"]} + -tf TYPE_FILTER, --type_filter TYPE_FILTER + comma-separated list of activity type IDs to allow. Format example: 3,9 -ss DIRECTORY, --session DIRECTORY enable loading and storing SSO information from/to given directory ``` +### Authentication + +You have to authenticate with username and password, and possibly an MFA code, at least for an initial login. + +The script is then using OAuth tokens (thanks to the [garth](https://github.com/matin/garth) library). +You can persist the OAuth token by giving a session directory, removing the need to provide username/password/MFA +for every script run. + +But keep the persistent tokens safe; if somebody gets hold of your tokens, they might be able to +read all your data in Garmin Connect (e.g. your health data), maybe even change or delete it. + + ### Examples - `python gcexport.py --count all` @@ -184,7 +195,7 @@ For the history of this fork see the [CHANGELOG](CHANGELOG.md) Contributions are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md) -Contributors as of 2023-10 (Hope I didn't forget anyone, +Contributors as of 2024-03 (Hope I didn't forget anyone, see also [Contributors](https://github.com/pe-st/garmin-connect-export/graphs/contributors)): - Kyle Krafka @kjkjava @@ -213,6 +224,8 @@ see also [Contributors](https://github.com/pe-st/garmin-connect-export/graphs/co - @geraudloup - @app4g - Simon Ă…gren @agrensimon +- @embear +- Joe Timmerman @joetimmerman ## License diff --git a/gcexport.py b/gcexport.py index 8885af7..c054030 100644 --- a/gcexport.py +++ b/gcexport.py @@ -54,7 +54,7 @@ COOKIE_JAR = http.cookiejar.CookieJar() OPENER = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(COOKIE_JAR), urllib.request.HTTPSHandler(debuglevel=0)) -SCRIPT_VERSION = '4.3.0-Beta' +SCRIPT_VERSION = '4.3.0' # This version here should correspond to what is written in CONTRIBUTING.md#python-3x-versions MINIMUM_PYTHON_VERSION = (3, 8) @@ -473,9 +473,9 @@ def parse_arguments(argv): parser.add_argument('-sa', '--start_activity_no', type=int, default=1, help='give index for first activity to import, i.e. skipping the newest activities') parser.add_argument('-ex', '--exclude', metavar='FILE', - help='JSON file with Array of activity IDs to exclude from download. Format example: {"ids": ["6176888711"]}') + help='JSON file with array of activity IDs to exclude from download. Format example: {"ids": ["6176888711"]}') parser.add_argument('-tf', '--type_filter', - help="comma-seperated list of activity type IDs to allow.") + help='comma-separated list of activity type IDs to allow. Format example: 3,9') parser.add_argument('-ss', '--session', metavar='DIRECTORY', help='enable loading and storing SSO information from/to given directory') # fmt: on @@ -973,6 +973,7 @@ def annotate_activity_list(activities, start, exclude_list, type_filter): :param start: One-based index of the first non-skipped activity (i.e. with 1 no activity gets skipped, with 2 the first activity gets skipped etc) :param exclude_list: List of activity ids that have to be skipped explicitly + :param type_filter: list of activity types to include in the output :return: List of action tuples """ @@ -1117,6 +1118,7 @@ def process_activity_item(item, number_of_items, device_dict, type_filter, activ :param item: activity item tuple, see `annotate_activity_list()` :param number_of_items: total number of items (for progress output) :param device_dict: cache (dict) of already known devices + :param type_filter: list of activity types to include in the output :param activity_type_name: lookup table for activity type descriptions :param event_type_name: lookup table for event type descriptions :param csv_filter: object encapsulating CSV file access @@ -1140,10 +1142,11 @@ def process_activity_item(item, number_of_items, device_dict, type_filter, activ print(f"({current_index}/{number_of_items}) [{actvty['activityId']}]") return - # Action: Filtered out by typeID + # Action: Filtered out by typeId if action == 'f': # Display which entry we're skipping. - print(f"Filtering out due to typeID, {actvty['activityType']['typeId']} not in {type_filter} : Garmin Connect activity ", end='') + type_id = actvty['activityType']['typeId'] + print(f"Filtering out due to type ID {type_id} not in {type_filter}: Garmin Connect activity ", end='') print(f"({current_index}/{number_of_items}) [{actvty['activityId']}]") return @@ -1280,7 +1283,7 @@ def main(argv): activities = fetch_activity_list(args, total_to_download) - type_filter = list(map(int,args.type_filter.split(','))) if args.type_filter is not None else None + type_filter = list(map(int, args.type_filter.split(','))) if args.type_filter is not None else None action_list = annotate_activity_list(activities, args.start_activity_no, exclude_list, type_filter) @@ -1297,7 +1300,9 @@ def main(argv): # Process each activity. for item in action_list: - process_activity_item(item, len(action_list), device_dict, type_filter, activity_type_name, event_type_name, csv_filter, args) + process_activity_item( + item, len(action_list), device_dict, type_filter, activity_type_name, event_type_name, csv_filter, args + ) logging.info('CSV file written.')