{"id":17184806,"url":"https://github.com/liskin/strava-offline","last_synced_at":"2025-08-14T12:33:51.667Z","repository":{"id":57471699,"uuid":"298531865","full_name":"liskin/strava-offline","owner":"liskin","description":"Keep a local mirror of Strava activities for further analysis/processing","archived":false,"fork":false,"pushed_at":"2024-06-16T15:32:11.000Z","size":164,"stargazers_count":68,"open_issues_count":1,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-11-23T16:06:28.524Z","etag":null,"topics":["gpx","gpx-tracks","gpx-writer","hacktoberfest","liskin-cookiecutter-python-cli","python","python3","strava","strava-activities","strava-api","strava-bulk-export","strava-data","strava-offline"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liskin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-09-25T09:45:44.000Z","updated_at":"2024-08-26T10:27:52.000Z","dependencies_parsed_at":"2024-05-20T21:27:38.602Z","dependency_job_id":"0a2979c6-0526-445f-b3b2-69b1d74035ed","html_url":"https://github.com/liskin/strava-offline","commit_stats":{"total_commits":141,"total_committers":2,"mean_commits":70.5,"dds":"0.028368794326241176","last_synced_commit":"080cf6a3c9b9b16f508fd2f154e98d428d21778d"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liskin%2Fstrava-offline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liskin%2Fstrava-offline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liskin%2Fstrava-offline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liskin%2Fstrava-offline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liskin","download_url":"https://codeload.github.com/liskin/strava-offline/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229827770,"owners_count":18130394,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gpx","gpx-tracks","gpx-writer","hacktoberfest","liskin-cookiecutter-python-cli","python","python3","strava","strava-activities","strava-api","strava-bulk-export","strava-data","strava-offline"],"created_at":"2024-10-15T00:44:27.197Z","updated_at":"2024-12-15T14:07:03.637Z","avatar_url":"https://github.com/liskin.png","language":"Python","funding_links":["https://www.paypal.me/lisknisi/10EUR","https://github.com/sponsors/liskin"],"categories":[],"sub_categories":[],"readme":"# strava-offline\n\n[![PyPI Python Version badge](https://img.shields.io/pypi/pyversions/strava-offline)](https://pypi.org/project/strava-offline/)\n[![PyPI Version badge](https://img.shields.io/pypi/v/strava-offline)](https://pypi.org/project/strava-offline/)\n![License badge](https://img.shields.io/github/license/liskin/strava-offline)\n\n## Overview\n\nstrava-offline is a tool to keep a local mirror of Strava activities for\nfurther analysis/processing:\n\n* synchronizes metadata about your bikes and activities to an [SQLite][]\n  database\n\n* downloads all your activities as [GPX][] (and supports not downloading [bulk\n  exported][strava-bulk-export] activities again)\n\n[SQLite]: https://www.sqlite.org/\n[GPX]: https://en.wikipedia.org/wiki/GPS_Exchange_Format\n\nExample of what you can do with the data:\n\n![sample-reports](https://user-images.githubusercontent.com/300342/132984440-3a45365a-0ad2-4310-a02b-6adb669d892e.png)\n\n## Installation\n\nUsing [pipx][]:\n\n```\npipx ensurepath\npipx install strava-offline\n```\n\nTo keep a local git clone around:\n\n```\ngit clone https://github.com/liskin/strava-offline\nmake -C strava-offline pipx\n```\n\nAlternatively, if you don't need the isolated virtualenv that [pipx][]\nprovides, feel free to just:\n\n```\npip install strava-offline\n```\n\n[pipx]: https://github.com/pypa/pipx\n\n## Setup and usage\n\n* Run `strava-offline sqlite`. The first time you do this, it will open Strava\n  in a browser and ask for permissions. The token is then saved and it\n  proceeds to sync activities metadata (this may take a couple dozen seconds\n  the first time). Next time you run this, it uses the saved token and\n  incrementally syncs latest activities (this takes a few seconds).\n\n* Now you can use [sqlite3][] to query the activity database, which is placed\n  at `~/.local/share/strava_offline/strava.sqlite` by default. Try for example:\n\n  ```\n  sqlite3 ~/.local/share/strava_offline/strava.sqlite \\\n    \"SELECT CAST(SUM(distance)/1000 AS INT) || ' km' FROM activity\"\n  ```\n\n* For GPX downloading, you'll need to get the `_strava4_session` cookie from\n  your web browser session. Open \u003chttps://strava.com/\u003e in your browser and\n  then follow a guide for your browser to obtain the cookie value:\n\n  * [Chrome](https://developers.google.com/web/tools/chrome-devtools/storage/cookies)\n  * [Firefox](https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector)\n  * [Edge](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/storage/cookies)\n\n* You may also need to obtain your own Client ID and Client Secret from\n  \u003chttps://www.strava.com/settings/api\u003e because the built-in ID/Secret is\n  shared with other users and may hit [rate limits][] (HTTP 429 Too Many\n  Requests). Pass these as `--client-id` and `--client-secret` command line\n  arguments or export as `STRAVA_CLIENT_ID` and `STRAVA_CLIENT_SECRET`\n  environment variables.\n\n  Alternatively, you may just wait a couple minutes and try again, but the\n  rate limits are rather strict, so in the unlikely event this tool gets\n  popular, serious users will have to get their own API application\n  registered.\n\n  (That settings page also lists Your Access Token but this won't let you\n  download private activities or see names of bikes. Therefore its use is not\n  supported in strava-offline.)\n\n[sqlite3]: https://manpages.debian.org/buster/sqlite3/sqlite3.1.en.html\n[rate limits]: http://developers.strava.com/docs/rate-limits/\n\n### Mirror activities metadata\n\n\u003c!-- include tests/readme/help-sqlite.md --\u003e\n    $ strava-offline sqlite --help\n    Usage: strava-offline sqlite [OPTIONS]\n    \n      Synchronize bikes and activities metadata to local sqlite3 database. Unless\n      --full is given, the sync is incremental, i.e. only new activities are\n      synchronized and deletions aren't detected.\n    \n    Options:\n      Sync options: \n        --full / --no-full    Perform full sync instead of incremental  [default:\n                              no-full]\n      Strava API: \n        --client-id TEXT      Strava OAuth 2 client id  [env var:\n                              STRAVA_CLIENT_ID]\n        --client-secret TEXT  Strava OAuth 2 client secret  [env var:\n                              STRAVA_CLIENT_SECRET]\n        --token-file FILE     Strava OAuth 2 token store  [default:\n                              /home/user/.config/strava_offline/token.json]\n        --http-host TEXT      OAuth 2 HTTP server host  [default: 127.0.0.1]\n        --http-port INTEGER   OAuth 2 HTTP server port  [default: 12345]\n      Database: \n        --database FILE       Sqlite database file  [default: /home/user/.local/sh\n                              are/strava_offline/strava.sqlite]\n      -v, --verbose           Logging verbosity (0 = WARNING, 1 = INFO, 2 = DEBUG)\n      --config FILE           Read configuration from FILE.  [default:\n                              /home/user/.config/strava_offline/config.yaml]\n      --help                  Show this message and exit.\n\u003c!-- end include tests/readme/help.md --\u003e\n\n### Mirror activities as GPX\n\n**Important:** To avoid overloading Strava servers (and possibly getting\nnoticed), first download all your existing activities using the [Bulk Export\nfeature of Strava][strava-bulk-export]. Then use `--dir-activities-backup` at\nleast once to let strava-offline reuse these downloaded files.\n\n[strava-bulk-export]: https://support.strava.com/hc/en-us/articles/216918437-Exporting-your-Data-and-Bulk-Export#Bulk\n\n\u003c!-- include tests/readme/help-gpx.md --\u003e\n    $ strava-offline gpx --help\n    Usage: strava-offline gpx [OPTIONS]\n    \n      Download known (previously synced using the \"sqlite\" command) activities in\n      GPX format. It's recommended to only use this incrementally to download the\n      latest activities every day or week, and download the bulk of your historic\n      activities directly from Strava. Use --dir-activities-backup to avoid\n      downloading activities already downloaded in the bulk.\n    \n    Options:\n      GPX storage: \n        --dir-activities DIRECTORY    Directory to store gpx files indexed by\n                                      activity id  [default: /home/user/.local/sha\n                                      re/strava_offline/activities]\n        --dir-activities-backup DIRECTORY\n                                      Optional path to activities in Strava backup\n                                      (no need to redownload these)\n      Strava web: \n        --strava4-session TEXT        '_strava4_session' cookie value  [env var:\n                                      STRAVA_COOKIE_STRAVA4_SESSION; required]\n      Database: \n        --database FILE               Sqlite database file  [default: /home/user/.\n                                      local/share/strava_offline/strava.sqlite]\n      -v, --verbose                   Logging verbosity (0 = WARNING, 1 = INFO, 2\n                                      = DEBUG)\n      --config FILE                   Read configuration from FILE.  [default: /ho\n                                      me/user/.config/strava_offline/config.yaml]\n      --help                          Show this message and exit.\n\u003c!-- end include tests/readme/help-gpx.md --\u003e\n\n### Reports\n\n\u003c!-- include tests/readme/help-report.md --\u003e\n    $ strava-offline --help | grep report-\n      report-bikes         Show all-time report by bike\n      report-yearly        Show yearly report by activity type\n      report-yearly-bikes  Show yearly report by bike\n\u003c!-- end include tests/readme/help-report.md --\u003e\n\n```\n$ strava-offline report-yearly 2020\nActivity type      Distance (km)    Moving time (hour)\n---------------  ---------------  --------------------\nRide                        4888                   243\nInlineSkate                   76                     4\nWalk                          59                    13\nHike                          38                     9\nStandUpPaddling                9                     1\nCanoeing                       2                     1\n```\n\n### Configuration file\n\nSecrets (and other options) can be set permanently in a config file,\nwhich is located at `~/.config/strava_offline/config.yaml` by default\n(on Linux; on other platforms see output of `--help`).\n\nSample config file can be generated using the `--config-sample` flag:\n\n\u003c!-- include tests/readme/config-sample.md --\u003e\n    $ strava-offline --config-sample\n    # Perform full sync instead of incremental\n    full: false\n    \n    # Strava OAuth 2 client id\n    strava_client_id: '12345'\n    \n    # Strava OAuth 2 client secret\n    strava_client_secret: SECRET\n    \n    # Strava OAuth 2 token store\n    strava_token_filename: /home/user/.config/strava_offline/token.json\n    \n    # OAuth 2 HTTP server host\n    http_host: 127.0.0.1\n    \n    # OAuth 2 HTTP server port\n    http_port: 12345\n    \n    # Sqlite database file\n    strava_sqlite_database: /home/user/.local/share/strava_offline/strava.sqlite\n    \n    # Logging verbosity (0 = WARNING, 1 = INFO, 2 = DEBUG)\n    verbose: 0\n    \n    # Directory to store gpx files indexed by activity id\n    dir_activities: /home/user/.local/share/strava_offline/activities\n    \n    # Optional path to activities in Strava backup (no need to redownload these)\n    dir_activities_backup: DIRECTORY\n    \n    # '_strava4_session' cookie value\n    strava_cookie_strava4_session: TEXT\n\u003c!-- end include tests/readme/config-sample.md --\u003e\n\n### Note about incremental synchronization\n\nSynchronization of activities (`strava-offline sqlite`) performs an\nincremental sync by default. We request recent activities from the Strava API\nand stop processing or asking for more as soon as we've seen 10 activities\nthat had already been in the local database.\n\nThis means that if you change an older activity, it may not be synced unless\nyou ask for a `--full` sync. The upside is that the incremental sync is faster\nand makes fewer API calls.\n\n(You may be wondering why other tools like VeloViewer don't need this. Strava\nAPI supports webhooks so that a service can subscribe to be notified of new\nactivities and changes to existing activities, but `strava-offline` is not a\nweb service, it's a local tool, so it can't do that.)\n\n## Contributing\n\n### Code\n\nWe welcome bug fixes, (reasonable) new features, documentation improvements,\nand more. Submit these as GitHub pull requests. Use GitHub issues to report\nbugs and discuss non-trivial code improvements; alternatively, get in touch\nvia [IRC/Matrix/Fediverse](https://work.lisk.in/contact/).\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for more details about the code base\n(including running tests locally).\n\nNote that this project was born out of a desire to solve a problem I was\nfacing. While I'm excited to share it with the world, keep in mind that I'll\nbe prioritizing features and bug fixes that align with my personal use cases.\nThere may be times when I'm busy with other commitments and replies to\ncontributions might be delayed, or even occasionally missed. Progress may come\nin bursts. Adjust your expectations accordingly.\n\n### Donations (♥ = €)\n\nIf you like this tool and wish to support its development and maintenance,\nplease consider [a small donation](https://www.paypal.me/lisknisi/10EUR) or\n[recurrent support through GitHub Sponsors](https://github.com/sponsors/liskin).\n\nBy donating, you'll also support the development of my other projects. You\nmight like these:\n\n* [strava-gear](https://github.com/liskin/strava-gear) – Rule based tracker of gear and component wear primarily for Strava\n* [strava-map-switcher](https://github.com/liskin/strava-map-switcher) – Map switcher for Strava website\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliskin%2Fstrava-offline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliskin%2Fstrava-offline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliskin%2Fstrava-offline/lists"}