Skip to content

Instantly share code, notes, and snippets.

@drew-wks
Forked from elifkus/Code.gs
Last active February 6, 2019 22:02
Show Gist options
  • Save drew-wks/e329c9c08735467c1ad12fa5d9c43b93 to your computer and use it in GitHub Desktop.
Save drew-wks/e329c9c08735467c1ad12fa5d9c43b93 to your computer and use it in GitHub Desktop.
Google Apps Script to retrieve data from Strava into a Spreadsheet.
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;
}
@dalltron
Copy link

I'm not getting calories but everything else works great!

Any ideas why calories isn't showing up? Thanks for your help!

@dalltron
Copy link

dalltron commented Feb 6, 2019

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