Skip to content

Instantly share code, notes, and snippets.

@ogajduse
Forked from bilan/sports-tracker-download.js
Last active January 18, 2022 09:53
Show Gist options
  • Save ogajduse/6d13b367f92d2af078704b6777d50c09 to your computer and use it in GitHub Desktop.
Save ogajduse/6d13b367f92d2af078704b6777d50c09 to your computer and use it in GitHub Desktop.
Download all workouts from sports-tracker and upload them to Strava
#!/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"
// 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