-
-
Save tmcw/1098861 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env ruby | |
| ## disconnect | |
| # ./disconnect.rb -u yourusername | |
| # | |
| # This is a command-line utility for the bulk-downloading of run data from | |
| # the connect.garmin.com web application, which has lackluster export | |
| # capabilities. | |
| # | |
| # Using this code is a matter of your own relationship with Garmin Connect | |
| # and their TOS. I can't imagine this being very destructive to their service, | |
| # and it's just filling in a hole in their existing service. | |
| # | |
| # It's built against Garmin Connect as of July 22, 2011. It's a scraper: | |
| # thus if Garmin changes, this **will break**. | |
| # | |
| # This script requires all of the utilities on the line below: install them | |
| # with rubygems | |
| %w{rubygems json fileutils mechanize choice highline/import}.map{|x| require x} | |
| LOGIN_PAGE = "https://connect.garmin.com/signin" | |
| ACTIVITIES_SEARCH = "http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?_dc=1220170621856&start=0&limit=1000" | |
| GPX_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/%d?full=true" | |
| KML_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.0/kml/activity/%d?full=true" | |
| TCX_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.0/tcx/activity/%d?full=true" | |
| Choice.options do | |
| header '' | |
| header 'Specific options:' | |
| option :user, :required => true do | |
| short '-u' | |
| long '--user=USER' | |
| desc 'connect.garmin.com username. Required' | |
| end | |
| option :dir do | |
| short '-o' | |
| long '--output-dir=OUTPUT' | |
| desc 'the directory to save .tcx files' | |
| default 'tcx' | |
| end | |
| end | |
| password = ask("Enter your password: " ) { |q| q.echo = "*" } | |
| def login(agent, user, password) | |
| page = agent.get(LOGIN_PAGE) | |
| login_form = page.form('login') | |
| login_form['login:loginUsernameField'] = user | |
| login_form['login:password'] = password | |
| page = agent.submit(login_form, login_form.buttons.first) | |
| raise "Login incorrect!" if page.title().match('Sign In') | |
| page | |
| end | |
| def download_run(agent, id) | |
| print "." | |
| # This downloads TCX files: you can swap out the constant, or add | |
| # more lines that download the different kinds of exports. I prefer TCX, | |
| # because despite being a 'private standard,' it includes all data, | |
| # including heart rate data. | |
| agent.get(TCX_EXPORT % (id).to_i).save_as(File.join(Choice[:dir], "%d.tcx" % id)) | |
| end | |
| def activities(agent) | |
| j = agent.get(ACTIVITIES_SEARCH) | |
| search = JSON.parse(j.content) | |
| runs = search['results']['activities'].map {|r| | |
| # Get each activity id to insert into the download URL | |
| r['activity']['activityId'] | |
| }.map {|id| | |
| # Download a run. | |
| download_run(agent, id) | |
| } | |
| end | |
| agent = Mechanize.new | |
| # One needs to log in to get access to private runs. Mechanize will store | |
| # the session data for the API call that cames next. | |
| home_page = login(agent, Choice[:user], password) | |
| FileUtils.mkdir_p(Choice[:dir]) if not File.directory?(Choice[:dir]) | |
| puts "Downloading runs..." | |
| activities(agent) |
Ok. help me out here. Where and how do I copy and paste the above code? Thanks.
Thanks a lot for this, but.. I get Net::HTTPBadRequest even befgore I get one activity, and Garmin's "Activity Search Service 1.0" is deprecated: http://connect.garmin.com/proxy/activity-search-service-1.0/ Is it over..?
@cloveras: try @gljeremy's fork: https://gist.github.com/1232102
@dano11 you'd copy & paste it into a file called disconnect.rb, and you'll need to gem install the dependencies on the top and run it with Ruby in a terminal.
Yes! Works perfectly, thanks a lot for the quick reply!
I note the TCX file downloaded does not contain the name or description associated with and activity on the garmin connect website, even though the file format supports that. :-( Can this script be extend to down loads as well?
Yes, thanks a lot for sharing. I also experienced the 400 => Net::HTTPBadRequest problem. Instead of limiting ACTIVITIES_SEARCH to 100, I limited it to 1 and changed the activities function to look like the following:
def activities(agent)
$activityIdx = 0
$numActivities = 624
while $activityIdx <= $numActivities do
puts("Downloading activity = #$activityIdx")
as = ACTIVITIES_SEARCH % $activityIdx
print "\tas: ", as, "\n"
j = agent.get(as)
search = JSON.parse(j.content)
runs = search['results']['activities'].map {|r|
# Get each activity id to insert into the download URL
r['activity']['activityId']
}.map {|id|
# Download an activity.
download_run(agent, id)
}
$activityIdx += 1
end
end
In my case I wanted all of my history, 624.
Thanks a lot for sharing @tmcw!
Garmin Connect UI has changed and this script no longer works for me.
If you're interested in a Python solution, I have one that does pretty much the same thing (in fact, this script was inspiration for it) and also contains a fix for the new Garmin Connect UI (basically just needs a redirect after login):
yeah, thanks for this.