diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index dd0f055..2941de0 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -6,7 +6,7 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: psf/black@stable with: options: "-S -l 132" diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 55fa003..8e747b6 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.x - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.version }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 102ce3d..6b73c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ This changelog is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 4.4.0 - 2024-07-12 + +- changed: allow session directory to work on first go + ([Pull Request #108](https://github.com/pe-st/garmin-connect-export/pull/108) by @rjl6789) +- fixed: processing of old activities with missing activity name (#113) or elevation correction flag (#112) + + +## 4.3.0 - 2024-03-02 + +- added: option `-ss` to store OAuth token between sessions + ([Pull Request #99](https://github.com/pe-st/garmin-connect-export/pull/99) by @embear) +- added: typeId filtering (option `-tf`) + ([Pull Request #72](https://github.com/pe-st/garmin-connect-export/pull/72) by @joetimmerman) + + ## 4.2.0 - 2023-10-08 - fixed: Use [garth](https://github.com/matin/garth) for OAuth to fix #95 diff --git a/README.md b/README.md index eef03a0..33a88ea 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,8 @@ read all your data in Garmin Connect (e.g. your health data), maybe even change ### Examples -- `python gcexport.py --count all` - will download all of your data to a dated directory. +- `python gcexport.py -ss ~/.garth --count all` + will download all of your data to a dated directory and save your OAuth tokens in the directory `.garth` in your home directory (from the second run on you will not be asked for your username/password anymore) - `python gcexport.py -c all -f gpx -ot --desc 20` will export all of your data in GPX format, set the timestamp of the GPX files to the start time of the activity and append the 20 first characters of the activity's description to the file name. @@ -195,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 2024-03 (Hope I didn't forget anyone, +Contributors as of 2024-07 (Hope I didn't forget anyone, see also [Contributors](https://github.com/pe-st/garmin-connect-export/graphs/contributors)): - Kyle Krafka @kjkjava @@ -226,6 +226,8 @@ see also [Contributors](https://github.com/pe-st/garmin-connect-export/graphs/co - Simon Ă…gren @agrensimon - @embear - Joe Timmerman @joetimmerman +- Rob @rjl6789 +- @gustav-b ## License diff --git a/gcexport.py b/gcexport.py index 931664f..b5d5384 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' +SCRIPT_VERSION = '4.4.0' # This version here should correspond to what is written in CONTRIBUTING.md#python-3x-versions MINIMUM_PYTHON_VERSION = (3, 8) @@ -500,7 +500,7 @@ def login_to_garmin_connect(args): except GarthException as ex: logging.debug("Could not resume session, error: %s", ex) login_required = True - except Exception as ex: + except FileNotFoundError as ex: logging.debug("Could not resume session, (non-garth) error: %s", ex) login_required = True try: @@ -508,8 +508,8 @@ def login_to_garmin_connect(args): except GarthException as ex: logging.debug("Session expired, error: %s", ex) login_required = True - except Exception as ex: - logging.debug("Session expired, (non-garth) error: %s", ex) + except AssertionError as ex: + logging.debug("Token not found, (non-garth) error: %s", ex) login_required = True logging.info("Authenticating using OAuth token from %s", garth_session_directory) else: @@ -520,14 +520,12 @@ def login_to_garmin_connect(args): password = args.password if args.password else getpass() garth.login(username, password) - # try to store data if a session directory is given + # try to store data if a session directory was given if garth_session_directory: try: garth.save(garth_session_directory) except GarthException as ex: logging.warning("Unable to store session data to %s, error: %s", garth_session_directory, ex) - except Exception as ex: - logging.warning("Unable to store session data to %s, (non-garth) error: %s", garth_session_directory, ex) except Exception as ex: raise GarminException(f'Authentication failure ({ex}). Did you enter correct credentials?') from ex @@ -799,6 +797,7 @@ def export_data_file(activity_id, activity_details, args, file_time, append_desc file_mode = 'wb' elif args.format == 'json': data_filename = os.path.join(directory, f'{prefix}activity_{activity_id}{append_desc}.json') + download_url = None file_mode = 'w' else: raise ValueError('Unrecognized format.') @@ -1100,7 +1099,7 @@ def copy_details_to_summary(summary, details): The choice of which properties are copied is determined by the properties used by the 'csv_write_record' method. - This particularly useful for childs of multisport activities, as I don't + This particularly useful for children of multisport activities, as I don't know how to get these activity summaries otherwise :param summary: summary dict, will be modified in-place :param details: details dict @@ -1321,7 +1320,11 @@ def main(argv): item, len(action_list), device_dict, type_filter, activity_type_name, event_type_name, csv_filter, args ) except Exception as ex_item: - activity_id = item['activity']['activityId'] if present('activity', item) and present('activityId', item['activity']) else "(unknown id)" + activity_id = ( + item['activity']['activityId'] + if present('activity', item) and present('activityId', item['activity']) + else "(unknown id)" + ) logging.error("Error during processing of activity '%s': %s/%s", activity_id, type(ex_item), ex_item) raise @@ -1341,6 +1344,6 @@ def main(argv): except KeyboardInterrupt: print('Interrupted') sys.exit(0) - except Exception as ex: # pylint: disable=broad-except + except Exception as abort_exception: # pylint: disable=broad-except logging.error("Processing aborted.") - logging.exception(ex) + logging.exception(abort_exception)