Skip to content

Instantly share code, notes, and snippets.

@jsimpson
Forked from gljeremy/disconnect.rb
Last active April 21, 2018 16:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsimpson/174beffe4e32222cf4da to your computer and use it in GitHub Desktop.
Save jsimpson/174beffe4e32222cf4da to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
## disconnect
# ./disconnect.rb -u yourusername [-o /your/path] [-p yourhttpproxyserver]
#
# 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 November 24, 2016. 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://sso.garmin.com/sso/login?service=http://connect.garmin.com/post-auth/login&clientId=GarminConnect&consumeServiceTicket=false"
LOGIN_REDIRECT = "http://connect.garmin.com/post-auth/login"
ACTIVITIES_SEARCH = "http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?_dc=1220170621856&start=%d"
TCX_EXPORT = "https://connect.garmin.com/modern/proxy/download-service/export/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
option :proxy do
short '-p'
long '--http-proxy'
desc 'Use HTTP proxy for remote operations'
end
option :new do
short '-n'
long '--new'
desc 'Only download new files (based on files in the output directory)'
end
end
password = ask("Enter your password: " ) { |q| q.echo = "*" }
def login(agent, user, password)
agent.get(LOGIN_PAGE) do |page|
puts "Loaded login page."
login_form = page.form
login_form['username'] = user
login_form['password'] = password
puts "Sent login information."
agent.submit(login_form, login_form.buttons.first)
page = agent.get(LOGIN_REDIRECT)
raise "Login incorrect!" if page.title().match('Sign In')
puts "Login successful!"
return page
end
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.
file = File.join(Choice[:dir], "%d.tcx" % id)
agent.get(TCX_EXPORT % (id).to_i).save_as(file) unless File.exists?(file) && Choice[:new]
end
def activities(agent)
start = 0
while true
j = agent.get(ACTIVITIES_SEARCH % start)
search = JSON.parse(j.content)
# Got some JSON content, let's see if there are any activities left to process
if search['results']['activities']==nil then break end
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)
start=start+1
}
puts "|"
end
end
agent = Mechanize.new
if Choice[:proxy] != nil then agent.set_proxy(Choice[:proxy], 80, nil, nil) end
# One needs to log in to get access to private runs. Mechanize will store
# the session data for the API call that comes next.
login(agent, Choice[:user], password)
FileUtils.mkdir_p(Choice[:dir]) if not File.directory?(Choice[:dir])
puts "Downloading runs..."
activities(agent)
@klikr
Copy link

klikr commented Apr 21, 2018

Hi,
none of the existing forks seems to work, yours is maybe the most recently changed one. Can you give me a hint what to do about the following error:

/var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize/http/agent.rb:323:in 'fetch': 404 => Net::HTTPNotFound for https://connect.garmin.com/post-auth/login -- unhandled response (Mechanize::ResponseCodeError)
        from /var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize/http/agent.rb:993:in 'response_redirect'
        from /var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize/http/agent.rb:315:in 'fetch'
        from /var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize/http/agent.rb:993:in 'response_redirect'
        from /var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize/http/agent.rb:315:in 'fetch'
        from /var/lib/gems/2.3.0/gems/mechanize-2.7.5/lib/mechanize.rb:464:in 'get'
        from disconnect.rb:60:in 'login'
        from disconnect.rb:110:in '<main>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment