Skip to content

Instantly share code, notes, and snippets.

@sergiopvilar
Created August 14, 2020 00:13
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 sergiopvilar/3e849a4b4c3ddc9c71ba65ee48b96545 to your computer and use it in GitHub Desktop.
Save sergiopvilar/3e849a4b4c3ddc9c71ba65ee48b96545 to your computer and use it in GitHub Desktop.
/**
* Instruções em ingles para rodar este script: https://elifk.us/en/retrieving-your-strava-data-with-google-app-scripts/
*/
var CLIENT_ID = ''; // Client ID do Strava
var CLIENT_SECRET = ''; // Client Secret do Strava
var SPREADSHEET_NAME = "Registro de Exercícios"; // Nome da Planilha
var SPREADSHEET_ID = ""; // ID da Planilha
var SHEET_NAME = "Dados"; // Nome da página de dados
var DEBUG = false;
// Se você quiser detalhes como 'description'
var RETRIEVE_DETAILS = false;
var ONLY_WITH_HEARTRATE = false;
// Se você quiser buscar apenas um tipo de atividade. Ex: var FILTER_BY_TYPE = "Ride";
var FILTER_BY_TYPE = "";
// Lista de campos em http://developers.strava.com/docs/reference/#api-Activities-getActivityById
var DATA_FIELDS = ['start_date_local', 'distance', 'moving_time', 'average_speed', 'type','calories', 'elapsed_time'];
// Cabeçalhos da planilha
var HEADING_FOR_DATA_FIELDS = ['Timestamp', 'Data Formatada', 'Distância', 'Tempo de Movimento', 'Velocidade Média', 'Tipo Atividade', 'Calorias', 'Tempo Total'];
var STRAVA_API_SCOPE = "activity:read_all";
var ALL = false;
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Sincronizar')
.addItem('Obter dados do Strava', 'retrieveData')
.addToUi();
}
/**
* Configura o serviço
*/
function getService() {
return OAuth2.createService('Strava')
// URLs do Strava
.setAuthorizationBaseUrl('https://www.strava.com/oauth/authorize')
.setTokenUrl('https://www.strava.com/oauth/token')
// ClientID e ClientSecret
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Nome da função que vai lidar com o callback
.setCallbackFunction('authCallback')
// Persiste a autorização
.setPropertyStore(PropertiesService.getUserProperties())
// Incluir atividades privadas
.setScope(STRAVA_API_SCOPE)
}
/**
* OAuth callback.
*/
function authCallback(request) {
var service = getService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Success!');
} else {
return HtmlService.createHtmlOutput('Denied');
}
}
function logAccessToken() {
var service = getService();
Logger.log("Access token: "+ service.getAccessToken());
}
/**
* Reseta o estado da autorização
*/
function reset() {
var service = getService();
service.reset();
}
/**
* Autoriza e faz o request à 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() {
// Valida as variáveis
validateConfig()
// Se a planilha tá vazia, busca todos os dados
var service = getService();
if (service.hasAccess()) {
var sheet = getStravaSheet();
var unixTime = retrieveLastDate(sheet);
// Ou se ALL é setado como true
var url = ALL ? 'https://www.strava.com/api/v3/athlete/activities?per_page=100' : 'https://www.strava.com/api/v3/athlete/activities?per_page=100&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;
}
if (RETRIEVE_DETAILS) {
retrieveAndInsertActivityDetailsForActivityList(result);
}
var data = convertData(result);
if (data.length == 0) {
Logger.log("No new data with the used filters (ONLY_WITH_HEARTRATE, FILTER_BY_TYPE)");
return;
}
insertData(sheet, data);
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s',
authorizationUrl);
}
}
function validateConfig() {
if (DATA_FIELDS.length != HEADING_FOR_DATA_FIELDS.length) {
Logger.log("The length of DATA_FIELDS does not match with the length of HEADING_FOR_DATA_FIELDS. ");
Logger.log(DATA_FIELDS.length.toString()+" != "+HEADING_FOR_DATA_FIELDS.length.toString() + " Please fix.");
}
}
function retrieveAndInsertActivityDetailsForActivityList(activityListResult) {
var service = getService();
if (service.hasAccess()) {
var url = 'https://www.strava.com/api/v3/activities/';
var query = '?include_all_efforts=false';
for (var i = 0; i < activityListResult.length; i++) {
var activityURL = url + activityListResult[i]['id'] + query;
var response = UrlFetchApp.fetch(activityURL, {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
extendObj(activityListResult[i], result);
}
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s',
authorizationUrl);
}
}
function extendObj(obj1, obj2){
for (var key in obj2){
if(!obj1.hasOwnProperty(key)){
obj1[key] = obj2[key];
}
}
}
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) {
var data = [];
for (var i = 0; i < result.length; i++) {
if ((!ONLY_WITH_HEARTRATE || result[i]["has_heartrate"]) && (FILTER_BY_TYPE.length == 0 || result[i]["type"] == FILTER_BY_TYPE)) {
var item = [];
for (var j = 0; j < DATA_FIELDS.length; j++) {
var field_name = DATA_FIELDS[j];
var single_data = result[i][field_name];
// Distância é retornada em metros, dividimos por 1000 para converter para kms
if (field_name == 'distance') {
single_data = single_data/1000;
}
// Tempo é dado em segundos, dividimos por 60 para termos minutos
if(field_name == 'moving_time') {
single_data = single_data/60;
}
// Convertemos a data para o formato Brasileiro, assim tempos também um cabeçalho adicional
if(field_name == 'start_date_local') {
item.push(single_data);
single_data = new Date(single_data).toLocaleDateString("en-GB", {timeZone: "GMT"}) + " " + new Date(single_data).toLocaleTimeString("en-GB", {timeZone: "GMT"});
}
// Se o dado não existe, vira "-"
if (single_data == undefined) {
single_data = '-'
}
item.push(single_data);
}
data.push(item);
}
}
return data;
}
function getStravaSheet() {
var spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
var sheet = getOrCreateSheet(spreadsheet, SHEET_NAME);
return sheet;
}
function insertData(sheet, data) {
var header = HEADING_FOR_DATA_FIELDS;
ensureHeader(header, sheet);
Logger.log(header);
var lastRow = sheet.getLastRow();
var range = sheet.getRange(lastRow+1,1,data.length,DATA_FIELDS.length +1);
range.setValues(data);
}
function ensureHeader(header, sheet) {
// Só adiciona o cabeçalho se a planilha estiver vazia
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 exist, adding new one.', sheetName);
sheet = spreadsheet.insertSheet(sheetName)
}
return sheet;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment