-
-
Save ogajduse/6d13b367f92d2af078704b6777d50c09 to your computer and use it in GitHub Desktop.
Download all workouts from sports-tracker and upload them to Strava
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Description: this script goes through the directory structure where the GPX | |
# files from Sports Tracker are stored and it uploads them to Strava. | |
# The only needed inputs are $TOKEN and $ST_USERNAME which you can edit below. | |
# This tool is using standard GNU tools, jq, curl and Perl. | |
# TODO: use exiftool to modify coordinates and timestamp in the image exif data | |
TOKEN= # Strava token | |
ST_USERNAME= # Sports Tracker username | |
_ACTIVITY_PROCESSING="Your activity is still being processed." | |
_STRAVA_ACTIVITY_URL="https://www.strava.com/activities" | |
_STRAVA_UPLOAD_TIMEOUT=15 # https://developers.strava.com/docs/rate-limits/ | |
_STRAVA_RATE_LIMIT_DELAY=10 | |
_SKIPPED_ACT_COUNT=0 | |
RES_FILE="results-$(date +%s.%N).json" | |
echo '{"uploaded_jobs":{}, "failed_jobs":{}}' >$RES_FILE | |
# Go through all the GPX files, parse the attributes from the filename and upload the GPX file to Strava | |
readarray -d '' FLIST < <(find * -name "*.gpx" -print0) | |
for file in "${FLIST[@]}"; do | |
ID=$(echo $file | perl -pe 's/(.*)\/id--(.*)--activity--(.*)--title--(.*)--file\.gpx/$2/g') | |
ACTIVITY=$(echo $file | perl -pe 's/(.*)\/id--(.*)--activity--(.*)--title--(.*)--file\.gpx/$3/g') | |
NAME=$(echo $file | perl -pe 's/(.*)\/id--(.*)--activity--(.*)--title--(.*)--file\.gpx/$4/g') | |
ST_URL="https://sports-tracker.com/workout/$ST_USERNAME/$ID" | |
printf "\nProcessing the following activity:\nFile: $file\nName: $NAME\nActivity: $ACTIVITY\nID: $ID\n\n" | |
# Check whether the activity was already uploaded | |
if [ -f $(dirname "$file")/.uploaded ]; then | |
echo This activity was already uploaded. Check the activity at: $(cat $(dirname "$file")/.uploaded) | |
((_SKIPPED_ACT_COUNT++)) | |
else # Activity was not uploaded yet | |
UPLOAD_JSON=$( | |
curl -s -X POST "https://www.strava.com/api/v3/uploads" \ | |
-H "accept: application/json" \ | |
-H "authorization: Bearer $TOKEN" \ | |
-H "Content-Type: multipart/form-data" \ | |
-F "file=@$file" \ | |
-F "data_type=gpx" \ | |
-F "description=SportsTracker import. Activity type: $ACTIVITY, Refference URL: $ST_URL" \ | |
-F "name=$NAME" \ | |
-F "external_id=$file" | |
) | |
CURL_RC=$? | |
UPLOAD_ID=$(echo $UPLOAD_JSON | jq -r .id) | |
# Check whether Strava API returned an UPLOAD_ID | |
# if yes, then poll the upload status until it is ready | |
# if not, then mark the upload as failed and store as much info as possible about the failure | |
if [ -z "$UPLOAD_ID" ]; then | |
echo "There was an error uploading the activity" | |
cat <<<$(jq --arg id "${ID}" --arg name "${NAME}" --arg activity "${ACTIVITY}" \ | |
--arg gpx "${file}" --arg api_response "${UPLOAD_JSON}" --arg curl_rc "${CURL_RC}" \ | |
'.failed_jobs[$id].name = $name | .failed_jobs[$id].activity = $activity | | |
.failed_jobs[$id].gpx_file = $gpx | .failed_jobs[$id].api_response = $api_response | | |
.failed_jobs[$id].curl_rc = $curl_rc' $RES_FILE) >$RES_FILE | |
else | |
UPLOAD_ACTIVITY_ID="" | |
UPLOAD_STATUS="" | |
while true; do | |
sleep $_STRAVA_UPLOAD_TIMEOUT | |
# Check the upload status | |
UPLOAD_CHECK_JSON=$( | |
curl -s -X GET "https://www.strava.com/api/v3/uploads/$UPLOAD_ID" \ | |
-H "accept: application/json" \ | |
-H "authorization: Bearer $TOKEN" | |
) | |
UPLOAD_ACTIVITY_ID=$(echo $UPLOAD_CHECK_JSON | jq -r .activity_id) | |
UPLOAD_STATUS=$(echo $UPLOAD_CHECK_JSON | jq -r .status) | |
if [[ $UPLOAD_STATUS == $_ACTIVITY_PROCESSING ]]; then | |
echo $UPLOAD_STATUS | |
continue | |
else | |
if [[ $UPLOAD_ACTIVITY_ID != "null" ]]; then | |
# Activity was uploaded successfully | |
echo $UPLOAD_STATUS Strava activity ID is: $UPLOAD_ACTIVITY_ID | |
echo You can find the activity at $_STRAVA_ACTIVITY_URL/$UPLOAD_ACTIVITY_ID | |
readarray -d '' IMAGE_LIST < <(find * -name "$ID*.jpg") | |
cat <<<$(jq --arg id "${ID}" --arg name "${NAME}" --arg activity "${ACTIVITY}" \ | |
--arg gpx "${file}" --arg status "${UPLOAD_STATUS}" --arg sportstracker_url "${ST_URL}" \ | |
--arg strava_id "${UPLOAD_ACTIVITY_ID}" --arg ilist "${IMAGE_LIST}" \ | |
--arg saurl "${_STRAVA_ACTIVITY_URL}/${UPLOAD_ACTIVITY_ID}" \ | |
'.uploaded_jobs[$id].name = $name | .uploaded_jobs[$id].activity = $activity | | |
.uploaded_jobs[$id].gpx_file = $gpx | .uploaded_jobs[$id].status = $status | | |
.uploaded_jobs[$id].sportstracker_url = $sportstracker_url | | |
.uploaded_jobs[$id].images = ($ilist | split("\n")) | | |
.uploaded_jobs[$id].strava_id = $strava_id | | |
.uploaded_jobs[$id].strava_url = $saurl' $RES_FILE) >$RES_FILE | |
echo $_STRAVA_ACTIVITY_URL/$UPLOAD_ACTIVITY_ID >$(dirname "$file")/.uploaded | |
else | |
# There was an error uploading the activity | |
echo $UPLOAD_STATUS | |
UPLOAD_ERROR=$(echo $UPLOAD_CHECK_JSON | jq -r .error) | |
echo The error is: $UPLOAD_ERROR | |
cat <<<$(jq --arg id "${ID}" --arg name "${NAME}" --arg activity "${ACTIVITY}" \ | |
--arg gpx "${file}" --arg status "${UPLOAD_STATUS}" --arg sportstracker_url "${ST_URL}" \ | |
--arg upload_error "${UPLOAD_ERROR}" \ | |
'.failed_jobs[$id].name = $name | .failed_jobs[$id].activity = $activity | | |
.failed_jobs[$id].gpx_file = $gpx | .failed_jobs[$id].status = $status | | |
.failed_jobs[$id].sportstracker_url = $sportstracker_url | | |
.failed_jobs[$id].upload_error = $upload_error' $RES_FILE) >$RES_FILE | |
fi | |
printf "\n" | |
break | |
fi | |
done | |
sleep $_STRAVA_RATE_LIMIT_DELAY | |
fi | |
fi | |
done # Main loop | |
printf "\n\n\n\n" | |
# Print stats | |
mapfile -t UPLOADED_ACTIVITIES < <(jq -r '.uploaded_jobs | keys[]' $RES_FILE) | |
mapfile -t FAILED_UPLOADS < <(jq -r '.failed_jobs | keys[]' $RES_FILE) | |
printf "Summary:\nTotal activities: %s\nSuccessfully uploaded activities: %s\nFailed uploads: %s\nSkipped activities: %s\n\n" \ | |
${#FLIST[@]} ${#UPLOADED_ACTIVITIES[@]} ${#FAILED_UPLOADS[@]} ${_SKIPPED_ACT_COUNT} | |
# Check whether there are some activities that were downloaded | |
# from Sports Tracker for any activity | |
mapfile -t ACT_W_PHOTOS < <(jq -r '.uploaded_jobs | with_entries(select(.value.images != [])) | |
| to_entries[].key' $RES_FILE) | |
if [ ${#ACT_W_PHOTOS[@]} -ne 0 ]; then | |
echo "There are photos that might be uploaded for the following activities": | |
for id in "${ACT_W_PHOTOS[@]}"; do | |
S_URL=$(jq -r --arg id $id '.uploaded_jobs[$id].strava_url' $RES_FILE) | |
echo $(jq -r --arg id $id '.uploaded_jobs[$id].strava_url' $RES_FILE) | |
echo Upload the following photos to this activity at $S_URL/edit | |
printf "%s\n" $(jq -r --arg id $id '.uploaded_jobs[$id].images | join("\n")' $RES_FILE) | |
echo # empty line | |
done | |
printf "\n" | |
fi | |
# Print out the failed activity IDs | |
if [ ${#FAILED_UPLOADS[@]} -ne 0 ]; then | |
echo "The following activities failed to upload. See $RES_FILE for more details." | |
for upload in "${FAILED_UPLOADS[@]}"; do | |
echo $upload | |
done | |
fi | |
printf "\n\nFull JSON report was saved to $RES_FILE\n" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ogajduse: I added a shell script for the activity upload to my fork. It should be error resistent and it should provide | |
// better reporting about the uploaded activities and failures that happened during the upload. Tested on 2022/01/18. | |
// Useful links: | |
// https://developers.strava.com/docs/getting-started/ | |
// https://developers.strava.com/docs/reference/ | |
// https://www.strava.com/settings/api | |
// https://developers.strava.com/playground/ | |
// | |
// Bilan: My fork fixed some api changes, added images downloading and informative gpx file names. Works on 2020/11/02. | |
// | |
// You can then upload it to Strava using this oneliner: | |
// find * -name '*.gpx' -print0 | while read -d $'\0' i; do ID=`echo $i | sed 's/.*id--//' | sed 's/--activity.*//'`; ACTIVITY=`echo $i | sed 's/.*--activity--//' | sed 's/--title.*//'`; NAME=`echo $i | sed 's/--file.gpx//' | sed 's/.*--title--//'`" ($ID/$ACTIVITY)"; echo "\n$NAME\n"; curl -X POST https://www.strava.com/api/v3/uploads -H "Authorization: Bearer ___TOKEN___" -F file=@"$i" -F data_type="gpx" -F description="SportsTracker import" -F name="$NAME" -F external_id="$i"; sleep 10;done | |
// | |
// Original desc: | |
// based entirely on this blog post: | |
// http://druss.co/2016/04/export-all-workouts-from-sports-tracker/ | |
// unfortunately the original script no longer works, moslty because jQuery is | |
// no longer available on sports-tracker pages. | |
// | |
// I've compiled the changes proposed in the comments in the script below. | |
// to use the script, login to your sports-tracker account | |
// change URL to http://www.sports-tracker.com/diary/workout-list | |
// open browser console (Cmd-Shift-I) | |
// paste the script, hit enter - it'll run and print something like this to the cosole: | |
// curl -o SportsTracker-<..id..>.gpx "http://www.sports-tracker.com/apiserver....." | |
// right-click on the colsole and save the contents to a file, call it download-all-workouts.sh | |
// open terminal, change to the directory where you saved the contents of the console | |
// edit the file - remove the javascript at the beginning of the file leaving only curl commands | |
// fix permissions: | |
// $>chmod +x download-all-workouts.sh | |
// run the script: | |
// $>./download-all-workouts.sh | |
const key = "sessionkey="; | |
const valueStartIndex = document.cookie.indexOf(key) + key.length; | |
const token = document.cookie.substring(valueStartIndex, document.cookie.indexOf(';', valueStartIndex)); | |
const activities = {0:"walk",1:"run",2:"ride",11:"hike",13:"alpineski",14:"rowing",15:"rowing"}; | |
function downloadOne(item) { | |
const href = item.href; | |
const id = href.substr(href.lastIndexOf('/') + 1, 24); | |
const url = 'https://api.sports-tracker.com/apiserver/v1/workout/exportGpx/' + id + '?token=' + token; | |
const activityId = item.querySelector(".activity-icon").getAttribute('activity-icon'); | |
const filename = `id--${id}--activity--${activities[activityId]}--title--${item.querySelector(".description").title.replace('/',',')}--file.gpx`; | |
console.log(`mkdir "${id}"`); | |
console.log(`curl -o "${id}/${filename}" "${url}";`); | |
downloadImages(id); | |
} | |
async function downloadImages(id) { | |
const imagesUrl = 'https://api.sports-tracker.com/apiserver/v1/images/workout/' + id + '?token=' + token; | |
const imageApiResponse = await fetch(imagesUrl); | |
const images = (await imageApiResponse.json()).payload; | |
for (let i = 0; i < images.length; i++) { | |
let image = images[i]; | |
let filename = `${id}-${image.key}-${image.location.x}-${image.location.y}-${image.timestamp}.jpg`; | |
let url = `https://api.sports-tracker.com/apiserver/v1/image/scale/${image.key}.jpg?width=${image.width}&height=${image.height}`; | |
console.log(`curl -o "${id}/${filename}" "${url}";`); | |
} | |
} | |
function loopThroughItems(items) { | |
for (let i = 0; i < items.length; i++) { | |
downloadOne(items[i]); | |
} | |
} | |
const items = document.querySelectorAll("ul.diary-list__workouts li a"); | |
document.body.innerHtml = ''; | |
loopThroughItems(items); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment