├── README.md ├── LICENSE └── FitbitDailyData.gs /README.md: -------------------------------------------------------------------------------- 1 | Fitbit Daily Data Script 2 | ============== 3 | This script will allow you to access and download your daily aggregate Fitbit Data to a Google Spreadsheet. Full instructions for using this script are posted on [QuantifiedSelf.com](http://quantifiedself.com/2013/02/how-to-download-fitbit-data-using-google-spreadsheets/) 4 | 5 | This script has been updated to reflect the new HTTPS connection mandate for accessing the Fitbit API. 6 | 7 | Contact Information 8 | ------------------- 9 | 10 | Questions/Comments? - [labs@quantifiedself.com](mailto:labs@quantifiedself.com) and [@quantifiedself](http://www.twitter.com/quantifiedself) 11 | 12 | Copyright & License 13 | --------- 14 | This code is made public under the MIT License. See [LICENSE]() for details. 15 | Copyright (c) 2014 Quantified Self labs. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Quantified Self Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /FitbitDailyData.gs: -------------------------------------------------------------------------------- 1 | /* FitbitDownload.gs 2 | This script will access your Fitbit data via the Fitbit API and insert it into a Google spreadsheet. 3 | The first row of the spreadsheet will be a header row containing data element names. Subsequent rows will contain data, one day per row. 4 | Note that Fitbit uses metric units (weight, distance) so you may wish to convert them. 5 | 6 | Original script by loghound@gmail.com 7 | Original instructional video by Ernesto Ramirez at http://vimeo.com/26338767 8 | Modifications by Mark Leavitt (PDX Quantified Self organizer) www.markleavitt.com 9 | Here's to your (quantified) health! 10 | */ 11 | 12 | // Key of ScriptProperty for Firtbit consumer key. 13 | var CONSUMER_KEY_PROPERTY_NAME = "fitbitConsumerKey"; 14 | // Key of ScriptProperty for Fitbit consumer secret. 15 | var CONSUMER_SECRET_PROPERTY_NAME = "fitbitConsumerSecret"; 16 | // Default loggable resources (from Fitbit API docs). 17 | var LOGGABLES = ["activities/log/steps", "activities/log/distance", 18 | "activities/log/activeScore", "activities/log/activityCalories", 19 | "activities/log/calories", "foods/log/caloriesIn", 20 | "activities/log/minutesSedentary", 21 | "activities/log/minutesLightlyActive", 22 | "activities/log/minutesFairlyActive", 23 | "activities/log/minutesVeryActive", "sleep/timeInBed", 24 | "sleep/minutesAsleep", "sleep/minutesAwake", "sleep/awakeningsCount", 25 | "body/weight", "body/bmi", "body/fat",]; 26 | 27 | // function authorize() makes a call to the Fitbit API to fetch the user profile 28 | function authorize() { 29 | var oAuthConfig = UrlFetchApp.addOAuthService("fitbit"); 30 | oAuthConfig.setAccessTokenUrl("https://api.fitbit.com/oauth/access_token"); 31 | oAuthConfig.setRequestTokenUrl("https://api.fitbit.com/oauth/request_token"); 32 | oAuthConfig.setAuthorizationUrl("https://api.fitbit.com/oauth/authorize"); 33 | oAuthConfig.setConsumerKey(getConsumerKey()); 34 | oAuthConfig.setConsumerSecret(getConsumerSecret()); 35 | var options = { 36 | "oAuthServiceName": "fitbit", 37 | "oAuthUseToken": "always", 38 | }; 39 | // get the profile to force authentication 40 | Logger.log("Function authorize() is attempting a fetch..."); 41 | try { 42 | var result = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/profile.json", options); 43 | var o = Utilities.jsonParse(result.getContentText()); 44 | return o.user; 45 | } 46 | catch (exception) { 47 | Logger.log(exception); 48 | Browser.msgBox("Error attempting authorization"); 49 | return null; 50 | } 51 | } 52 | 53 | // function setup accepts and stores the Consumer Key, Consumer Secret, firstDate, and list of Data Elements 54 | function setup() { 55 | var doc = SpreadsheetApp.getActiveSpreadsheet(); 56 | var app = UiApp.createApplication().setTitle("Setup Fitbit Download"); 57 | app.setStyleAttribute("padding", "10px"); 58 | 59 | var consumerKeyLabel = app.createLabel("Fitbit OAuth Consumer Key:*"); 60 | var consumerKey = app.createTextBox(); 61 | consumerKey.setName("consumerKey"); 62 | consumerKey.setWidth("100%"); 63 | consumerKey.setText(getConsumerKey()); 64 | var consumerSecretLabel = app.createLabel("Fitbit OAuth Consumer Secret:*"); 65 | var consumerSecret = app.createTextBox(); 66 | consumerSecret.setName("consumerSecret"); 67 | consumerSecret.setWidth("100%"); 68 | consumerSecret.setText(getConsumerSecret()); 69 | var firstDate = app.createTextBox().setId("firstDate").setName("firstDate"); 70 | firstDate.setName("firstDate"); 71 | firstDate.setWidth("100%"); 72 | firstDate.setText(getFirstDate()); 73 | 74 | // add listbox to select data elements 75 | var loggables = app.createListBox(true).setId("loggables").setName( 76 | "loggables"); 77 | loggables.setVisibleItemCount(4); 78 | // add all possible elements (in array LOGGABLES) 79 | var logIndex = 0; 80 | for (var resource in LOGGABLES) { 81 | loggables.addItem(LOGGABLES[resource]); 82 | // check if this resource is in the getLoggables list 83 | if (getLoggables().indexOf(LOGGABLES[resource]) > -1) { 84 | // if so, pre-select it 85 | loggables.setItemSelected(logIndex, true); 86 | } 87 | logIndex++; 88 | } 89 | // create the save handler and button 90 | var saveHandler = app.createServerClickHandler("saveSetup"); 91 | var saveButton = app.createButton("Save Setup", saveHandler); 92 | 93 | // put the controls in a grid 94 | var listPanel = app.createGrid(6, 3); 95 | listPanel.setWidget(1, 0, consumerKeyLabel); 96 | listPanel.setWidget(1, 1, consumerKey); 97 | listPanel.setWidget(2, 0, consumerSecretLabel); 98 | listPanel.setWidget(2, 1, consumerSecret); 99 | listPanel.setWidget(3, 0, app.createLabel(" * (obtain these at dev.fitbit.com)")); 100 | listPanel.setWidget(4, 0, app.createLabel("Start Date for download (yyyy-mm-dd)")); 101 | listPanel.setWidget(4, 1, firstDate); 102 | listPanel.setWidget(5, 0, app.createLabel("Data Elements to download:")); 103 | listPanel.setWidget(5, 1, loggables); 104 | 105 | // Ensure that all controls in the grid are handled 106 | saveHandler.addCallbackElement(listPanel); 107 | // Build a FlowPanel, adding the grid and the save button 108 | var dialogPanel = app.createFlowPanel(); 109 | dialogPanel.add(listPanel); 110 | dialogPanel.add(saveButton); 111 | app.add(dialogPanel); 112 | doc.show(app); 113 | } 114 | 115 | // function sync() is called to download all desired data from Fitbit API to the spreadsheet 116 | function sync() { 117 | // if the user has never performed setup, do it now 118 | if (!isConfigured()) { 119 | setup(); 120 | return; 121 | } 122 | 123 | var user = authorize(); 124 | var doc = SpreadsheetApp.getActiveSpreadsheet(); 125 | doc.setFrozenRows(1); 126 | var options = { 127 | "oAuthServiceName": "fitbit", 128 | "oAuthUseToken": "always", 129 | "method": "GET" 130 | }; 131 | // prepare and format today's date, and a list of desired data elements 132 | var dateString = formatToday(); 133 | var activities = getLoggables(); 134 | // for each data element, fetch a list beginning from the firstDate, ending with today 135 | for (var activity in activities) { 136 | var currentActivity = activities[activity]; 137 | try { 138 | var result = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/" 139 | + currentActivity + "/date/" + getFirstDate() + "/" 140 | + dateString + ".json", options); 141 | } catch (exception) { 142 | Logger.log(exception); 143 | Browser.msgBox("Error downloading " + currentActivity); 144 | } 145 | var o = Utilities.jsonParse(result.getContentText()); 146 | 147 | // set title 148 | var titleCell = doc.getRange("a1"); 149 | titleCell.setValue("date"); 150 | var cell = doc.getRange('a2'); 151 | 152 | // fill the spreadsheet with the data 153 | var index = 0; 154 | for (var i in o) { 155 | // set title for this column 156 | var title = i.substring(i.lastIndexOf('-') + 1); 157 | titleCell.offset(0, 1 + activity * 1.0).setValue(title); 158 | 159 | var row = o[i]; 160 | for (var j in row) { 161 | var val = row[j]; 162 | cell.offset(index, 0).setValue(val["dateTime"]); 163 | // set the date index 164 | cell.offset(index, 1 + activity * 1.0).setValue(val["value"]); 165 | // set the value index index 166 | index++; 167 | } 168 | } 169 | } 170 | } 171 | 172 | function isConfigured() { 173 | return getConsumerKey() != "" && getConsumerSecret() != ""; 174 | } 175 | 176 | function setConsumerKey(key) { 177 | ScriptProperties.setProperty(CONSUMER_KEY_PROPERTY_NAME, key); 178 | } 179 | 180 | function getConsumerKey() { 181 | var key = ScriptProperties.getProperty(CONSUMER_KEY_PROPERTY_NAME); 182 | if (key == null) { 183 | key = ""; 184 | } 185 | return key; 186 | } 187 | 188 | function setLoggables(loggable) { 189 | ScriptProperties.setProperty("loggables", loggable); 190 | } 191 | 192 | function getLoggables() { 193 | var loggable = ScriptProperties.getProperty("loggables"); 194 | if (loggable == null) { 195 | loggable = LOGGABLES; 196 | } else { 197 | loggable = loggable.split(','); 198 | } 199 | return loggable; 200 | } 201 | 202 | function setFirstDate(firstDate) { 203 | ScriptProperties.setProperty("firstDate", firstDate); 204 | } 205 | 206 | function getFirstDate() { 207 | var firstDate = ScriptProperties.getProperty("firstDate"); 208 | if (firstDate == null) { 209 | firstDate = "2012-01-01"; 210 | } 211 | return firstDate; 212 | } 213 | 214 | function formatToday() { 215 | var todayDate = new Date; 216 | return todayDate.getFullYear() 217 | + '-' 218 | + ("00" + (todayDate.getMonth() + 1)).slice(-2) 219 | + '-' 220 | + ("00" + todayDate.getDate()).slice(-2); 221 | } 222 | 223 | function setConsumerSecret(secret) { 224 | ScriptProperties.setProperty(CONSUMER_SECRET_PROPERTY_NAME, secret); 225 | } 226 | 227 | function getConsumerSecret() { 228 | var secret = ScriptProperties.getProperty(CONSUMER_SECRET_PROPERTY_NAME); 229 | if (secret == null) { 230 | secret = ""; 231 | } 232 | return secret; 233 | } 234 | 235 | // function saveSetup saves the setup params from the UI 236 | function saveSetup(e) { 237 | setConsumerKey(e.parameter.consumerKey); 238 | setConsumerSecret(e.parameter.consumerSecret); 239 | setLoggables(e.parameter.loggables); 240 | setFirstDate(e.parameter.firstDate); 241 | var app = UiApp.getActiveApplication(); 242 | app.close(); 243 | return app; 244 | } 245 | 246 | // function onOpen is called when the spreadsheet is opened; adds the Fitbit menu 247 | function onOpen() { 248 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 249 | var menuEntries = [{ 250 | name: "Sync", 251 | functionName: "sync" 252 | }, { 253 | name: "Setup", 254 | functionName: "setup" 255 | }, { 256 | name: "Authorize", 257 | functionName: "authorize" 258 | }]; 259 | ss.addMenu("Fitbit", menuEntries); 260 | } 261 | 262 | // function onInstall is called when the script is installed (obsolete?) 263 | function onInstall() { 264 | onOpen(); 265 | } 266 | --------------------------------------------------------------------------------