-
-
Save drew-wks/e329c9c08735467c1ad12fa5d9c43b93 to your computer and use it in GitHub Desktop.
Google Apps Script to retrieve data from Strava into a Spreadsheet.
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
var CLIENT_ID = '<ClientId for the Strava App>'; | |
var CLIENT_SECRET = '<Client Secret for the Strava App>'; | |
var SPREADSHEET_NAME = "StravaData"; | |
var SPREADSHEET_ID = "<Spreadsheet id for the Google Spreadsheet>"; | |
var SHEET_NAME = "Sheet1"; | |
var DEBUG = false; | |
/** | |
* Configures the service. | |
*/ | |
function getService() { | |
return OAuth2.createService('Strava') | |
// Set the endpoint URLs. | |
.setAuthorizationBaseUrl('https://www.strava.com/oauth/authorize') | |
.setTokenUrl('https://www.strava.com/oauth/token') | |
// Set the client ID and secret. | |
.setClientId(CLIENT_ID) | |
.setClientSecret(CLIENT_SECRET) | |
// Set the name of the callback function that should be invoked to complete | |
// the OAuth flow. | |
.setCallbackFunction('authCallback') | |
// Set the property store where authorized tokens should be persisted. | |
.setPropertyStore(PropertiesService.getUserProperties()) | |
} | |
/** | |
* Handles the OAuth callback. | |
*/ | |
function authCallback(request) { | |
var service = getService(); | |
var authorized = service.handleCallback(request); | |
if (authorized) { | |
return HtmlService.createHtmlOutput('Success!'); | |
} else { | |
return HtmlService.createHtmlOutput('Denied'); | |
} | |
} | |
/** | |
* Reset the authorization state, so that it can be re-tested. | |
*/ | |
function reset() { | |
var service = getService(); | |
service.reset(); | |
} | |
/** | |
* Authorizes and makes a request to the GitHub API. | |
*/ | |
function run() { | |
var service = getService(); | |
if (service.hasAccess()) { | |
var url = 'https://www.strava.com/api/v3/athlete'; | |
var response = UrlFetchApp.fetch(url, { | |
headers: { | |
Authorization: 'Bearer ' + service.getAccessToken() | |
} | |
}); | |
var result = JSON.parse(response.getContentText()); | |
Logger.log(JSON.stringify(result, null, 2)); | |
} else { | |
var authorizationUrl = service.getAuthorizationUrl(); | |
Logger.log('Open the following URL and re-run the script: %s', | |
authorizationUrl); | |
} | |
} | |
function retrieveData() { | |
//if sheet is empty retrieve all data | |
var service = getService(); | |
if (service.hasAccess()) { | |
var sheet = getStravaSheet(); | |
var unixTime = retrieveLastDate(sheet); | |
//dw upps the number of requested lines to the max (200) | |
var url = 'https://www.strava.com/api/v3/athlete/activities?per_page=200&after=' + unixTime; | |
var response = UrlFetchApp.fetch(url, { | |
headers: { | |
Authorization: 'Bearer ' + service.getAccessToken() | |
} | |
}); | |
var result = JSON.parse(response.getContentText()); | |
if (result.length == 0) { | |
Logger.log("No new data"); | |
return; | |
} | |
var data = convertData(result); | |
if (data.length == 0) { | |
Logger.log("No new data with heart rate"); | |
return; | |
} | |
insertData(sheet, data); | |
} else { | |
var authorizationUrl = service.getAuthorizationUrl(); | |
Logger.log('Open the following URL and re-run the script: %s', | |
authorizationUrl); | |
} | |
} | |
function retrieveLastDate(sheet) { | |
var lastRow = sheet.getLastRow(); | |
var unixTime = 0; | |
if (lastRow > 0) { | |
var dateCell = sheet.getRange(lastRow, 1); | |
var dateString = dateCell.getValue(); | |
var date = new Date((dateString || "").replace(/-/g,"/").replace(/[TZ]/g," ")); | |
unixTime = date/1000; | |
} | |
return unixTime; | |
} | |
function convertData(result) { | |
//dw The list below contains all top level Strava activity variables that are not arrays | |
//dw Be sure any changes to the number or order of Strava data fields here are | |
//dw also reflected in insertData headers and lastRow below | |
var data = []; | |
//dw Square brackets defines the variable 'data' as an array so it can take all these items below | |
//dw the issue with this approach is it returns 'undefined' when there's no result for an item. | |
//dw To eliminate 'undefined' results in the Google sheet, | |
//dw I added the LOGICAL OR expression to the end of several of these | |
//dw || '' | |
//dw which has the syntax expr1 || expr2 | |
//dw It returns expr1 if it can be converted to true; otherwise, returns expr2 | |
//dw I expanded the number of fields returned in the array | |
//dw I also added code to convert results to Imperial units: feet, Fahrenheit, mph | |
for (var i = 0; i < result.length; i++) { | |
var item = [result[i]['start_date_local'], | |
result[i]['name'], | |
(result[i]['distance']*0.000621371192) || '', | |
result[i]['moving_time'] || '', | |
result[i]['elapsed_time'] || '', | |
(result[i]['total_elevation_gain']*3.28084) || '', | |
(result[i]['elev_high']*3.28084) || '', | |
(result[i]['elev_low']*3.28084) || '', | |
(result[i]['average_speed']*2.2369362920544) ||'', | |
(result[i]['max_speed']*2.2369362920544) ||'', | |
result[i]['average_cadence'] ||'', | |
(result[i]['average_temp']*(9/5)+32) || '', | |
result[i]['average_watts'] ||'', | |
result[i]['max_watts'] || '', | |
result[i]['weighted_average_watts'] || '', | |
result[i]['kilojoules'] || '', | |
result[i]['device_watts'] ||'', | |
result[i]['has_heartrate'], | |
result[i]['average_heartrate'] || '', | |
result[i]['max_heartrate'] || '', | |
result[i]['calories'] || '', | |
result[i]['suffer_score'], | |
result[i]['type'], | |
result[i]['start_date'], | |
result[i]['timezone'], | |
result[i]['start_latlng'], | |
result[i]['end_latlng'], | |
result[i]['achievement_count'], | |
result[i]['pr_count'], | |
result[i]['kudos_count'], | |
result[i]['comment_count'], | |
result[i]['athlete_count'], | |
result[i]['photo_count'], | |
result[i]['total_photo_count'], | |
result[i]['trainer'], | |
result[i]['commute'], | |
result[i]['manual'], | |
result[i]['private'], | |
result[i]['device_name'] ||'', | |
result[i]['embed_token'] ||'', | |
result[i]['flagged'], | |
result[i]['workout_type'] ||'', | |
result[i]['has_kudoed'], | |
result[i]['id'] ||'', | |
result[i]['resource_state'], | |
result[i]['external_id'] ||'', | |
result[i]['upload_id'] ||'', | |
result[i]['description'] ||'', | |
result[i]['gear_id'] ||'']; | |
data.push(item); | |
} | |
return data; | |
} | |
function getStravaSheet() { | |
var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID); | |
var sheet = getOrCreateSheet(spreadsheet, SHEET_NAME); | |
return sheet; | |
} | |
//dw added additional fields to the header | |
function insertData(sheet, data) { | |
var header = ["start_date_local", | |
"name", | |
"distance", | |
"moving_time", | |
"elapsed_time", | |
"total_elevation_gain", | |
"elev_high", | |
"elev_low", | |
"average_speed", | |
"max_speed", | |
"average_cadence", | |
"average_temp", | |
"average_watts", | |
"max_watts", | |
"weighted_average_watts", | |
"kilojoules", | |
"device_watts", | |
"has_heartrate", | |
"average_heartrate", | |
"max_heartrate", | |
"calories", | |
"suffer_score", | |
"type", | |
"start_date", | |
"timezone", | |
"start_latlng", | |
"end_latlng", | |
"achievement_count", | |
"pr_count", | |
"kudos_count", | |
"comment_count", | |
"athlete_count", | |
"photo_count", | |
"total_photo_count", | |
"trainer", | |
"commute", | |
"manual", | |
"private", | |
"device_name", | |
"embed_token", | |
"flagged", | |
"workout_type", | |
"has_kudoed", | |
"id", | |
"resource_state", | |
"external_id", | |
"upload_id", | |
"description", | |
"gear"]; | |
ensureHeader(header, sheet); | |
var lastRow = sheet.getLastRow(); | |
var range = sheet.getRange(lastRow+1,1,data.length,49); | |
range.setValues(data); | |
} | |
function ensureHeader(header, sheet) { | |
// Only add the header if sheet is empty | |
if (sheet.getLastRow() == 0) { | |
if (DEBUG) Logger.log('Sheet is empty, adding header.') | |
sheet.appendRow(header); | |
return true; | |
} else { | |
if (DEBUG) Logger.log('Sheet is not empty, not adding header.') | |
return false; | |
} | |
} | |
function getOrCreateSheet(spreadsheet, sheetName) { | |
var sheet = spreadsheet.getSheetByName(sheetName); | |
if (!sheet) { | |
if (DEBUG) Logger.log('Sheet "%s" does not exists, adding new one.', sheetName); | |
sheet = spreadsheet.insertSheet(sheetName) | |
} | |
return sheet; | |
} | |
I added 86400 seconds to unixTime to avoid duplicates.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm not getting calories but everything else works great!
Any ideas why calories isn't showing up? Thanks for your help!