-
-
Save runspired/3526501 to your computer and use it in GitHub Desktop.
/* | |
As of 5/13/2013 3:06PM Central Time this script is working. Tweet @runspired to report malfunctions. | |
*/ | |
/* | |
Use the Javascript console in Google Chrome and the tcx2nikeplus converter located here: http://www.awsmithson.com/tcx2nikeplus/ | |
This will take some time to run but will port all of your garmin data to nike+. | |
you will need to set all of your garmin plus workouts to public for this to work properly. The first function converts all of your garmin connect data to a public privacy setting after it gathers the activity urls, if you would like to return your activities to private, tweet me @runspired and I'll show you how to do so. | |
*/ | |
//------------------------- Garmin Connect set privacy to public and gather activity urls PART --------------------------- | |
/* | |
Run this in chrome's javascript console | |
on Garmin Connect's activities page. By opening the console, copy pasting this | |
function, and hitting the return key. | |
*/ | |
(function(){ | |
function garminWalker() { | |
var walkerInstance = this | |
, activities = new Array() | |
, sendRequest = function(index) { | |
jQuery.ajax({ | |
type : 'POST' | |
, url : 'http://connect.garmin.com/proxy/activity-service-1.2/json/privacy/' + activities[index].match(/^\/activity\/(\d+)\/?$/ )[1] | |
, processData : false | |
, data : 'value=public&_=' | |
, success : function(m,s,r) { if( index == 0 ) console.log( '["' + activities.join('","') + '"]' ); else sendRequest( index-1); } | |
}); | |
} | |
, makePublic = function() { | |
console.log( "making all activities public" ); | |
sendRequest( activities.length - 1 ); | |
} | |
, getActivities = function() { | |
console.log( "gathering activities from this frame" ); | |
var wrappers = document.getElementsByClassName('activityName') | |
, index = wrappers.length; | |
while(index--) | |
activities.push(wrappers[index].childNodes[0].getAttribute('href')); | |
var next = document.getElementsByClassName('rich-datascr-button')[3]; | |
if( next.hasAttribute('onclick') && next.getAttribute('onclick') != '') | |
wait( wrappers[0], 0 ); | |
else | |
makePublic(); | |
} | |
, wait = function( compare , count ) { | |
//infinite recursion preventer | |
var count = !!count? count : 0; | |
//ensure the necessary action is taking place | |
if( count == 30 ) | |
document.getElementsByClassName('rich-datascr-button')[3].click(); | |
//hard cap on recursion | |
if( count > 100 ) | |
return; | |
//continue recursion if the val has not changed | |
if( document.getElementsByClassName('activityName')[0] === compare ) | |
setTimeout( (function(){ wait(compare,++count); }), 100); | |
else | |
getActivities(); | |
}; | |
getActivities(); | |
} | |
//start the script running | |
new garminWalker(); | |
}).call(this); | |
/* | |
After the page stops loading new results and sets all activities to public it will wait ~5s and then print | |
a string (probably very long) that looks something like | |
["/activity/216534341","/activity/21554341","/activity/13456341"] | |
Copy this string and paste it somewhere you can copy it from again in a moment. | |
*/ | |
//------------------------- garmin2nike PART --------------------------- | |
/* | |
uploader, run this in Chrome's javascript console on the garmin2nike+ page located | |
here http://www.awsmithson.com/tcx2nikeplus/ | |
by copy pasting it into the console and hitting return. | |
This Script will take a VERY long time to complete, just leave the window open and let it run. | |
Periodically some of the activities will fail to upload, the most common reason is the lack | |
of a GPS track or not enout (at least 3) data points on the GPS track. | |
The script will continue executing, you can manually go look at the activities that failed if you | |
would like to verify the reason they didn't convert. There is (unfortunately) nothing I can do about | |
Garmin data being bad. | |
*/ | |
(function() { | |
function tcxConverter(u,p,a) { | |
var requestURL = 'http://www.awsmithson.com/tcx2nikeplus/tcx2nikeplus/convert' | |
, baseUrl = "http://connect.garmin.com" | |
, activities = a || false | |
, username = u || '' | |
, password = p || '' | |
, inputElement = document.getElementById('garminActivityId') | |
, submitButton = document.getElementById('ext-gen38') | |
, buildFormString = function(id){ | |
var text = '' | |
, EOL = "\r\n" | |
, boundary = "------WebKitFormBoundarywZcuQVboZzlXS7Yv"; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="fsGarminId-checkbox"' + EOL + EOL + 'on' + EOL; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="garminActivityId"' + EOL + EOL + id + EOL; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="nikeEmail"' + EOL + EOL + username + EOL; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="nikePassword"' + EOL + EOL + password + EOL; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="chkSaveCookies"' + EOL + EOL + 'on' + EOL; | |
text += boundary + EOL + 'Content-Disposition: form-data; name="clientTimeZoneOffset"' + EOL + EOL +(-1*(new Date()).getTimezoneOffset())+ EOL; | |
text += boundary + '--' + EOL; | |
return text; | |
} | |
, ajax = function(o) { | |
var xhr; | |
if( window.XMLHttpRequest ) | |
xhr = new XMLHttpRequest(); | |
else { | |
console.log("error establishing server connection"); | |
return false; | |
} | |
//ensure readiness | |
xhr.onreadystatechange = function() { | |
if(xhr.readyState < 4) { | |
return; | |
} | |
if(xhr.status !== 200) { | |
o.error( xhr ); | |
return; | |
} | |
// all is well | |
if(xhr.readyState === 4) | |
o.success(xhr); | |
}; | |
xhr.open( o.type , o.url , true ); | |
xhr.setRequestHeader("Content-Type",o.contentType); | |
xhr.setRequestHeader("Accept",'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'); | |
xhr.send( o.data ); | |
return false; | |
} | |
, sendRequest = function(index) { | |
console.log("transferring activity: "+activities[index]); | |
ajax({ | |
type : 'POST' | |
, url : requestURL | |
, contentType : 'multipart/form-data; boundary=----WebKitFormBoundarywZcuQVboZzlXS7Yv' | |
, data : buildFormString(activities[index]) | |
, success : function(xhr ) { | |
xhr.data = JSON.parse( xhr.responseText ); | |
if( xhr.data.success == false ) { | |
console.log("Transfer failed for "+activities[index], xhr.data.data.errorMessage); | |
} | |
if( index > 0 ) sendRequest( index-1 ); | |
else console.log("all activities transferred"); | |
} | |
, error : function(xhr) { console.log("aborting process, unable to reach server");} | |
}); | |
}; | |
sendRequest( activities.length - 1 ); | |
} | |
this.garmin2nike = function(u,p,a) { new tcxConverter(u,p,a); }; | |
}).call(this); | |
/* | |
Enter your Nike+ email and password. | |
after you get the string from running the script on Garmin Connect, | |
type the line below and copy paste the string from Garmin Connect | |
where it says 'stringFromPartOne'. Remove the outter layer of quotes (ie it should | |
look something like ["/url","/url"] not "["/url","/url"]"), make sure you entered your | |
Nike+ email and password, and hit return. | |
*/ | |
garmin2nike( nikePlusEmailInQuotes , nikePlusPasswordInQuotes , stringFromPartOne ); | |
/* | |
Sit back and let the script do it's thing, note, error messages may occassionally pop up that you will need to dismiss, | |
such as "Error message: 1 is smaller than the minimum (3): number of points (1)" | |
This is something to do with the script located here not being able to convert the data for a particular workout, not | |
something I can change. | |
*/ |
I got the same error message as the previous commenter. I would LOVE for this to work... any help??
@serenamichelle Still need help? (for some reason I didn't get a notification for your comment, and just happened to stumble past it)
Im having the same issue: TypeError: Object [object global] has no method 'activityUrls'
Hi,
exact same issue here, not sure whats missing or where it went wrong. Would love to import all my garmin activity's to nike+. great idea !
@cohengal @ssamuel68 @serenamichelle I believe this issue has now been fixed, easiest way to reach me if it isn't is on twitter @runspired
I've completely rewritten both scripts to make them more future proof.
Hi I am trying to get run garmin2nike PART of your script I was able to retreive all the garmin activities from my garmin.connect account. when I run the second script from http://www.awsmithson.com/tcx2nikeplus/ chrome console. I get the following error: SyntaxError: Unexpected token ILLEGAL
I not sure what this error means. I try to hard my username and password and enter the the activitiy
garmin2nike( nikePlusEmailInQuotes , nikePlusPasswordInQuotes , stringFromPartOne ) function
I still get the error.
Can you et me know what this is?
Trying to run the second script (on Garmin Connect) I got this error message: "Uncaught TypeError: Object [object Window] has no method 'activityUrls'".
Any hint?