Skip to content

Instantly share code, notes, and snippets.

@wadecourtney
Created January 14, 2013 04:22
Show Gist options
  • Save wadecourtney/4527743 to your computer and use it in GitHub Desktop.
Save wadecourtney/4527743 to your computer and use it in GitHub Desktop.
A tool that allows you to download activities from Garmin Connect. Features include downloading in kml, gpx, and tcx format, selecting the number of activities to download, which activity to begin the activity search, and where to save the activities. Very little error checking is done.
#!/usr/bin/env python3
import http.client
http.client.HTTPConnection.debuglevel = 0
#import urllib.request
from urllib.request import Request, urlopen, HTTPCookieProcessor
from urllib.parse import urlencode
import argparse
import sys,os
import errno
import json
GC_LOGIN = "https://connect.garmin.com/signin"
GC_ACTIVITIES = "http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?start=%d"
GC_DL = {
'kml': "http://connect.garmin.com/proxy/activity-service-1.0/kml/activity/%d?full=true",
'tcx': "http://connect.garmin.com/proxy/activity-service-1.1/tcx/activity/%d?full=true",
'gpx': "http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/%d?full=true"
}
def login(username, password):
# Fields from the login form on:
# https://connect.garmin.com/signin
data = {
'login': 'login',
'login:loginUsernameField': username,
'login:password': password,
'login:rememberMe': 'on',
'login:signInButton': 'Sign In',
'javax.faces.ViewState': 'j_id1'
}
# Make an initial request to the login page to obtain cookies. These
# cookies MUST be included with the login request.
loginResponse = urlopen(GC_LOGIN)
cookies = get_cookies(loginResponse)
# POST headers
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookies
}
# Create a login request
loginRequest = Request(GC_LOGIN, data=bytes(urlencode(data), 'utf-8'),
headers=headers)
# Send request
response = urlopen(loginRequest)
return cookies
def get_cookies(response):
cookies = []
for header in response.getheaders():
if header[0] == 'Set-Cookie':
cookies.append(header[1].split(';')[0])
return bytes(';'.join(cookies), 'utf-8')
def get_activities(cookies, args):
counter = int(args.START)
numDownloaded = 0
loop = True
while loop:
# make a request to the search service
activitySearch = Request(GC_ACTIVITIES % counter,
headers={'Cookie': cookies})
response = urlopen(activitySearch)
data = response.read()
# Parse json data
results = json.loads(data.decode('utf-8'))
if 'activities' not in results['results']:
break
for activity in results['results']['activities']:
print(str(counter) + "\tDownloading: "
+ activity['activity']['activityName']['value'])
if args.TYPE == 'kml':
if 'endLatitude' in activity['activity']:
# Make sure that a file exists by checking
# a gps coord
download(cookies, activity['activity']['activityId'])
numDownloaded += 1
else:
print("\t\t* No KML file exists for this activity.")
else:
download(cookies, activity['activity']['activityId'])
numDownloaded += 1
counter += 1
if (int(args.NUM_DOWNLOAD) != -1) and \
(numDownloaded >= int(args.NUM_DOWNLOAD)):
loop = False
break
if (int(results['results']['search']['totalFound']) == counter):
loop = False
break
if numDownloaded == 1:
print("\nDownloaded " + str(numDownloaded) + " activity.")
else:
print("\nDownloaded " + str(numDownloaded) + " activities.")
def download(cookies, activityid):
# Download request
request = Request(GC_DL[args.TYPE] % int(activityid),
headers={'Cookie': cookies})
response = urlopen(request)
data = response.read()
# Write data to file
f = open(args.DIRECTORY + activityid + '.' + args.TYPE,
'wt', encoding='utf-8')
f.write(data.decode('utf-8'))
f.close()
if __name__ == "__main__":
print("Garmin Connect Activity Downloader")
print("This program has essentially no error checking; it is not guaranteed to fail gracefully! Use at your own rsik\n")
parser = argparse.ArgumentParser(
description="Download Garmin Connect Activities")
parser.add_argument('-u', '--user', required = True,
dest='GC_USERNAME', help='Garmin Connnect Username')
parser.add_argument('-N', dest='NUM_DOWNLOAD', default='-1',
help='Number of activities to download')
parser.add_argument('-D', '--dir', dest='DIRECTORY', default='',
help='Directory to install activities to. Defaults to current directory.')
parser.add_argument('-t', '--type', dest='TYPE', default='kml',
help='Download type: kml, tcx, gpx')
parser.add_argument('--start', dest="START", default=0,
help='Activity number to start with')
args = parser.parse_args()
# Check arguments
# Download file type
if args.TYPE not in ['kml', 'tcx', 'gpx']:
print("Invalid download format.")
sys.exit(0)
# Download directory
if args.DIRECTORY:
if ((args.DIRECTORY.rfind('/') == 0) or \
(args.DIRECTORY.rfind('/') > 0)):
if args.DIRECTORY[-1] != '/':
args.DIRECTORY = args.DIRECTORY + '/'
if not os.path.exists(args.DIRECTORY):
try:
os.makedirs(args.DIRECTORY)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
else:
print("Invalid directory")
sys.exit(0)
print("Logging in with: " + args.GC_USERNAME)
GC_PASSWORD = input("Password: ")
cookies = login(args.GC_USERNAME, GC_PASSWORD)
print("Downloading activities...")
get_activities(cookies, args)
print("\nFinished!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment