Sunday, July 7, 2013

Cyclemeter Upload to Strava with Google Apps Script

As a long time Cyclemeter user I have logged about 6000 off-road miles and love the app and it's feature set.  When Strava made it way onto the scene, I was impressed with the community and analytic features that were offered but since Strava has their own mobile apps I have waited long enough for Cyclemeter to add a direct auto-share to their app so I wrote a Google Apps Script to do this for me.

If you use GMail, you are in luck since Google give you Apps Script to add some major automation to a number of their services.  Here is how I did it...

Step 1: Setup Cyclemeter to email you the GPX link to your GMail account.  Go to More -> Settings -> Email Updates (turn on) ->Auto Send Settings -> Done ->Just add the GPX link to the email.


Step 2: Then in GMail setup a filter to label these emails "StravaUpload" by selecting specific information in the Subject.  In my case I used " Cyclemeter Cycle (Mountain)" so it only uploads my MTB rides and not my other activities like runs.  I am going to assume you know how to add filters and labels in GMail.


Step 3: Now the Script.  This part might be a stretch for some but I will share a simple way to do this.  Google Apps Scripts can exist on their own in Google Drive but to create them you need to connect the Google Apps Script to Drive by clicking on the "Connect More Apps" link at the bottom and searching for "Google Apps Script".  You can also create a Doc or Sheet and go to the "Tools" menu and select "Script Editor".  I write a lot of code for GAS so I use the direct method.  Either way, you can get into the script editor and add this function:

 function uploadToStrava() {  
  //Call GMail to look for StraveUpload labeled emails (should only be one)  
  var ulemail = GmailApp.getUserLabelByName('StravaUpload').getThreads();  
  //Quit if email is missing  
  if (ulemail.length != 0){  
   //Get only the first email in the matching array  
   var ulmessage = ulemail[0].getMessages();  
   //Get the entire body of the email  
   var ulbody = ulmessage[0].getBody();  
   //Calculate start position of gpx url  
   var ulbstart = ulbody.indexOf('http://share.abvio.com/');  
   //Calculate start position of extension of gpx  
   var ulbend = ulbody.indexOf('.gpx');  
   //Get only substring of link  
   var ullink = ulbody.substr(ulbstart, (ulbend - ulbstart+4));  
   //Fetch gpx file from url  
   var gpxfile = UrlFetchApp.fetch(ullink).getBlob();  
   gpxfile.getAs('application/gpx+xml');  
   var gpxname = gpxfile.getName();  
   gpxfile.setContentType('application/gpx+xml');  
   gpxfile.setName(gpxname);  
   //Send email to Strava Upload email with gpx attachment  
   MailApp.sendEmail( {  
    to: 'upload@strava.com',  
    subject: 'Upload to Strava',   
    htmlBody: "Upload attached.",  
    attachments: gpxfile  
   })  
   //Move Cyclemeter email to trash  
   ulmessage[0].moveToTrash();  
  }  
 }  

Since Cyclemeter does not create an attachment in the Auto Send options, I had to get the URL and get the attachment from the link.  Now we need this script to trigger daily... I also didn't care for it to parse a number of rides at once so I left out the looping for uploads for now. 

Step 4: Trigger the script to run at whatever interval you like.  I usually ride in the am so I had the script trigger between 9 - 10am but you can pick the interval you like.


Now I can complete my ride, hit Done in Cyclemeter and my email will send and once the trigger for the GAS runs, the email is evaluated, an email is sent to Strava's upload email with a GPX attachment and my ride shows up in Strava without me having to manually upload it.  Works for me but I hope Cyclemeter adds this into the app or simply adds email GPX attachments in their auto share settings.  Hope this helps you. ~Lou

12 comments:

  1. I followed your advice and it worked great.
    It save me a lot of hassles.
    Kudos!

    ReplyDelete
  2. I've been trying to do something like this using IFTTT, and just couldn't get it to work (Strava didn't like the attachment). However, this works a treat. Thanks for sharing.

    ReplyDelete
    Replies
    1. I'll also note I change the script to upload TCX files (as I don't believe GPX files have cadence information in them), which I achieved by doing a global search/replace of "gpx" to "tcx".

      Delete
  3. I might have gotten a little obsessed today and expanded your version so that it processes whole threads, archives instead of deletes, and tries to compensate for messages with multiple URLs.

    /**
    * Process messages meant for Strava
    */
    function uploadToStrava() {
    //Call GMail to look for StravaUpload labeled emails
    var ulthread = GmailApp.getUserLabelByName('StravaUpload').getThreads();
    //Process all threads with label
    for (var k = 0; k < ulthread.length; k++) {
    //Only operate on threads in Inbox
    if (ulthread[k].isInInbox() == 1) {
    //Get array of messages in thread
    var ulmsg = ulthread[k].getMessages();
    //Process all messages in thread
    for (var l = 0; l < ulthread[k].getMessageCount(); l++) {
    //Get the entire body of the email
    var ulbody = ulmsg[l].getBody();
    //Calculate start position of extension of tcx
    var ulbend = ulbody.indexOf('.tcx');
    //Process only if extension (and presumably URL) is present
    if (ulbend != -1) {
    //Calculate start position of tcx url
    var ulbstart = ulbody.indexOf('TCX Link: ');
    //Get only substring of link
    var ullink = ulbody.substr((ulbstart+10), (ulbend - ulbstart - 6));
    //Fetch tcx file from url
    var tcxfile = UrlFetchApp.fetch(ullink).getBlob();
    tcxfile.getAs('application/tcx+xml');
    var tcxname = tcxfile.getName();
    tcxfile.setContentType('application/tcx+xml');
    tcxfile.setName(tcxname);
    //Send email to Strava Upload email with tcx attachment
    MailApp.sendEmail( {
    to: 'upload@strava.com',
    subject: 'Upload to Strava',
    htmlBody: "Upload attached.",
    attachments: tcxfile
    })
    }
    //Delete Cyclemeter message
    //ulmsg[l].moveToTrash();
    }
    }
    //Move Cyclemeter thread to archive
    ulthread[k].markRead()
    ulthread[k].moveToArchive()
    }
    };

    ReplyDelete
    Replies
    1. Nice! I have since built a web app that I can trigger in the event I ride after the trigger so it show up after. So much fun with gapps scripts!

      Delete
  4. Does this still work okay for you guys?

    I am having real trouble getting the sendEmail function to actually send the mail. It seems to get as far as copying the email to my 'Sent' mail folder but doesn't actually send it. I can view it in the Sent folder and everything looks good. It has all the right information and attachments but the recipient never receives it (and I have tried numerous recipient addresses during testing!).

    I have tried using MailApp.sendEmail and GmailApp.sendEmail as I thought the 'replyTo' field might be causing issues but neither seem to work. Same result - mail in Sent folder but never received.

    Any help would be appreciated.

    ReplyDelete
    Replies
    1. I have discovered that the sendEmail functions only appear to work from standard Gmail accounts - not from Google Apps (for Business) accounts. No idea why.

      Anyway I have got it working now through my non-Apps account :-)

      Delete
  5. Should not matter. I have used this method in both vannilla and GApps accounts. Wonder if the permissions were never granted or are messed up. Are you still having issues?

    ReplyDelete
  6. Hi,

    Great post! I was wondering how the script needs to be modified to work with TCX files. Strava's not seeing any time data in my GPX files, however is working with the TCX files. Is it just a matter of replacing the GPX link with the TCX link?

    Thanks!

    Chris
    http://cjrcycl.es

    ReplyDelete
    Replies
    1. You would need to change the areas that say GPX in the code to read TCX, make sure Cyclemeter email sends that file instead and change the mime type to match too. There is another comment above that does this as well as a reference.

      Delete