-
-
Save bryder/f0b388cb65e77cf9fab7 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 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