ST3ALTHPSYCH0

Fitbit Idle Alert.gs

Jun 16th, 2016
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // intraday.gs -- Supports downloading intraday (minute-by-minute) step data
  2. // Will not work without special permission from Fitbit, with the exception that you can use it for your own personal data if your app type is set to personal.
  3.  
  4. /*
  5. Script modified June 2016 to act as an SMS gateway idle alert.
  6. All previous comments and acknowledgments left intact so that I myself properly acknowledge sources!!
  7. Original script found here: https://github.com/simonbromberg/googlefitbit/blob/master/intraday.gs
  8.  
  9. */
  10.  
  11. // Simon Bromberg (http://sbromberg.com)
  12. // You are free to use, modify, copy any of the code in this script for your own purposes, as long as it's not for evil
  13. // If you do anything cool with it, let me know!
  14. // Note: there are minor improvements/cleanups still to be made in this file, but it should work as is if everything is setup properly
  15. // See readme on github repo for more information
  16.  
  17. // Script based on post here http://quantifiedself.com/2014/09/download-minute-fitbit-data/ by Ernesto Ramirez
  18. /*
  19. * Do not change these key names. These are just keys to access these properties once you set them up by running the Setup function from the Fitbit menu
  20. */
  21.  
  22.  
  23.  
  24.  
  25. // Key of ScriptProperty for Fitbit consumer key.
  26. var CONSUMER_KEY_PROPERTY_NAME = "fitbitConsumerKey";
  27. // Key of ScriptProperty for Fitbit consumer secret.
  28. var CONSUMER_SECRET_PROPERTY_NAME = "fitbitConsumerSecret";
  29.  
  30. var SERVICE_IDENTIFIER = 'fitbit';
  31.  
  32. // This variable assumes that you are as lazy as I am and left your sheet generically named 'Sheet1'. If not, change as appropriate.
  33. var DOC = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
  34.  
  35. /* These 2 variables tell the script how many minutes in the past you want to use for your count, and how many steps to look for.
  36. I set my script to run every 15 minutes and look for 125 steps within the past 30. In theory, this will always ensure that I
  37. make my 250 steps per hour goal. */
  38. var MINUTES_TO_SUM = 30;
  39.  
  40. var STEPS_TARGET = 125;
  41.  
  42. /*These 2 variables set the active timeframe in which the script is allowed to send 'nag' messages, based on 24 hour time (0-23). I have my fitbit set to look
  43. for 250 steps each hour from 7:00 AM until 8:00 PM. Thus 7 and 20 below. Adjust as appropriate for your application. */
  44. var FIRST_HOUR_TO_NAG = 7;
  45.  
  46. var LAST_HOUR_TO_NAG = 20;
  47.  
  48. function onOpen() {
  49.     var ss = SpreadsheetApp.getActiveSpreadsheet();
  50.   var menuEntries = [
  51.     {
  52.       name: "Setup",
  53.       functionName: "setup"
  54.     },
  55.   {
  56.         name: "Authorize",
  57.         functionName: "showSidebar"
  58.   },
  59.   {
  60.     name: "Reset",
  61.     functionName: "clearService"
  62.   },
  63.   {
  64.         name: "Download data",
  65.     functionName: "refreshTimeSeries"
  66.   }];
  67.     ss.addMenu("Fitbit", menuEntries);
  68. }
  69.  
  70.  
  71. function isConfigured() {
  72.     return getConsumerKey() != "" && getConsumerSecret() != "";
  73. }
  74.  
  75. function setConsumerKey(key) {
  76.     ScriptProperties.setProperty(CONSUMER_KEY_PROPERTY_NAME, key);
  77. }
  78.  
  79. function getConsumerKey() {
  80.     var key = ScriptProperties.getProperty(CONSUMER_KEY_PROPERTY_NAME);
  81.     if (key == null) {
  82.         key = "";
  83.     }
  84.     return key;
  85. }
  86.  
  87. function setLoggables(loggable) {
  88.     ScriptProperties.setProperty("loggables", loggable);
  89. }
  90.  
  91. function getLoggables() {
  92.     var loggable = ScriptProperties.getProperty("loggables");
  93.     if (loggable == null) {
  94.         loggable = LOGGABLES;
  95.     } else {
  96.         loggable = loggable.split(',');
  97.     }
  98.     return loggable;
  99. }
  100.  
  101. function setConsumerSecret(secret) {
  102.     ScriptProperties.setProperty(CONSUMER_SECRET_PROPERTY_NAME, secret);
  103. }
  104.  
  105. function getConsumerSecret() {
  106.     var secret = ScriptProperties.getProperty(CONSUMER_SECRET_PROPERTY_NAME);
  107.     if (secret == null) {
  108.         secret = "";
  109.     }
  110.     return secret;
  111. }
  112.  
  113. // function saveSetup saves the setup params from the UI
  114. function saveSetup(e) {
  115.     setConsumerKey(e.parameter.consumerKey);
  116.     setConsumerSecret(e.parameter.consumerSecret);
  117.     setLoggables(e.parameter.loggables);
  118.     setFirstDate(e.parameter.firstDate);
  119.     setLastDate(e.parameter.lastDate);
  120.     var app = UiApp.getActiveApplication();
  121.     app.close();
  122.     return app;
  123. }
  124.  
  125. /* Since I have repurposed this script solely as an idle alert, I now have it set to clear the sheet every time it's run. */
  126. function clearSheet() {
  127.     DOC.getRange('A1:D' + DOC.getLastRow()).clearContent();
  128. }
  129.  
  130. /* The inbuilt date formatting utility seemed to have taken an unscheduled leave of absence when I attempted to used this script as originally written.
  131. As such, I copied this function from Stackoverflow to accomplish my goal.*/
  132. function getToday() {
  133.     var today = new Date();
  134.     var dd = today.getDate();
  135.     var mm = today.getMonth() + 1; //January is 0!
  136.     var yyyy = today.getFullYear();
  137.  
  138.     if(dd<10) {
  139.         dd='0'+dd
  140.     }
  141.  
  142.     if(mm<10) {
  143.         mm='0'+mm
  144.     }
  145.  
  146.     today = yyyy + '-' + mm + '-' + dd;
  147.     return today;
  148. }
  149.  
  150. function setFirstDate(firstDate) {
  151.     ScriptProperties.setProperty("firstDate", firstDate);
  152. }
  153.  
  154. function getFirstDate() {
  155.     var firstDate = ScriptProperties.getProperty("firstDate");
  156.     if (firstDate == null) {
  157.         firstDate = getToday();
  158.     }
  159.    
  160. }
  161.  
  162. function setLastDate(lastDate) {
  163.     ScriptProperties.setProperty("lastDate", lastDate);
  164. }
  165.  
  166. function getLastDate() {
  167.     var lastDate = ScriptProperties.getProperty("lastDate");
  168.     if (lastDate == null) {
  169.       var lastDate = getToday();
  170.     }
  171.     return lastDate;
  172. }
  173.  
  174. // function setup accepts and stores the Consumer Key, Consumer Secret, Project Key, firstDate, and list of Data Elements
  175. function setup() {
  176.     var doc = SpreadsheetApp.getActiveSpreadsheet();
  177.     var app = UiApp.createApplication().setTitle("Setup Fitbit Download");
  178.     app.setStyleAttribute("padding", "10px");
  179.  
  180.     var consumerKeyLabel = app.createLabel("Fitbit OAuth 2.0 Client ID:*");
  181.     var consumerKey = app.createTextBox();
  182.     consumerKey.setName("consumerKey");
  183.     consumerKey.setWidth("100%");
  184.     consumerKey.setText(getConsumerKey());
  185.  
  186.     var consumerSecretLabel = app.createLabel("Fitbit OAuth Consumer Secret:*");
  187.     var consumerSecret = app.createTextBox();
  188.     consumerSecret.setName("consumerSecret");
  189.     consumerSecret.setWidth("100%");
  190.     consumerSecret.setText(getConsumerSecret());
  191.  
  192.     var projectKeyTitleLabel = app.createLabel("Project key: ");
  193.     var projectKeyLabel = app.createLabel(ScriptApp.getProjectKey());
  194.  
  195.     var firstDate = app.createTextBox().setId("firstDate").setName("firstDate");
  196.     firstDate.setName("firstDate");
  197.     firstDate.setWidth("100%");
  198.     firstDate.setText(getFirstDate());
  199.  
  200.     var lastDate = app.createTextBox().setId("lastDate").setName("lastDate");
  201.     lastDate.setName("lastDate");
  202.     lastDate.setWidth("100%");
  203.     lastDate.setText(getLastDate());
  204.     // create the save handler and button
  205.     var saveHandler = app.createServerClickHandler("saveSetup");
  206.     var saveButton = app.createButton("Save Setup", saveHandler);
  207.  
  208.     // put the controls in a grid
  209.     var listPanel = app.createGrid(8, 3);
  210.     listPanel.setWidget(1, 0, consumerKeyLabel);
  211.     listPanel.setWidget(1, 1, consumerKey);
  212.     listPanel.setWidget(2, 0, consumerSecretLabel);
  213.     listPanel.setWidget(2, 1, consumerSecret);
  214.     listPanel.setWidget(3, 0, app.createLabel(" * (obtain these at dev.fitbit.com, use OAuth2.0)"));
  215.     listPanel.setWidget(4, 0, projectKeyTitleLabel);
  216.     listPanel.setWidget(4, 1, projectKeyLabel);
  217.     listPanel.setWidget(5, 0, app.createLabel("Start Date for download (yyyy-mm-dd)"));
  218.     listPanel.setWidget(5, 1, firstDate);
  219.     listPanel.setWidget(6, 0, app.createLabel("End date for download (yyyy-mm-dd)"));
  220.     listPanel.setWidget(6, 1, lastDate);
  221.     listPanel.setWidget(7, 0, app.createLabel("Very long intervals will not work; exceed Fitbit rate limit and/or function will timeout"));
  222.    
  223.     // Ensure that all controls in the grid are handled
  224.     saveHandler.addCallbackElement(listPanel);
  225.     // Build a FlowPanel, adding the grid and the save button
  226.     var dialogPanel = app.createFlowPanel();
  227.     dialogPanel.add(listPanel);
  228.     dialogPanel.add(saveButton);
  229.     app.add(dialogPanel);
  230.     doc.show(app);
  231. }
  232.  
  233. function getFitbitService() {
  234.   // Create a new service with the given name. The name will be used when
  235.   // persisting the authorized token, so ensure it is unique within the
  236.   // scope of the property store
  237.   Logger.log(PropertiesService.getUserProperties());
  238.   return OAuth2.createService(SERVICE_IDENTIFIER)
  239.  
  240.       // Set the endpoint URLs, which are the same for all Google services.
  241.       .setAuthorizationBaseUrl('https://www.fitbit.com/oauth2/authorize')
  242.       .setTokenUrl('https://api.fitbit.com/oauth2/token')
  243.  
  244.       // Set the client ID and secret, from the Google Developers Console.
  245.       .setClientId(getConsumerKey())
  246.       .setClientSecret(getConsumerSecret())
  247.  
  248.       // Set the name of the callback function in the script referenced
  249.       // above that should be invoked to complete the OAuth flow.
  250.       .setCallbackFunction('authCallback')
  251.  
  252.       // Set the property store where authorized tokens should be persisted.
  253.       .setPropertyStore(PropertiesService.getUserProperties())
  254.  
  255.       .setScope('activity profile')
  256.      
  257.       .setTokenHeaders({
  258.         'Authorization': 'Basic ' + Utilities.base64Encode(getConsumerKey() + ':' + getConsumerSecret())
  259.       });
  260.  
  261. }
  262.  
  263. function clearService(){
  264.   OAuth2.createService(SERVICE_IDENTIFIER)
  265.   .setPropertyStore(PropertiesService.getUserProperties())
  266.   .reset();
  267. }
  268.  
  269. function showSidebar() {
  270.   var service = getFitbitService();
  271.   if (!service.hasAccess()) {
  272.     var authorizationUrl = service.getAuthorizationUrl();
  273.     var template = HtmlService.createTemplate(
  274.         '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
  275.         'Reopen the sidebar when the authorization is complete.');
  276.     template.authorizationUrl = authorizationUrl;
  277.     var page = template.evaluate();
  278.     SpreadsheetApp.getUi().showSidebar(page);
  279.   } else {
  280.     Logger.log("Has access!!!!");
  281.   }
  282. }
  283.  
  284. function authCallback(request) {
  285.   Logger.log("authcallback");
  286.   var service = getFitbitService();
  287.   var isAuthorized = service.handleCallback(request);
  288.   if (isAuthorized) {
  289.     Logger.log("success");
  290.     return HtmlService.createHtmlOutput('Success! You can close this tab.');
  291.   } else {
  292.     Logger.log("denied");
  293.     return HtmlService.createHtmlOutput('Denied. You can close this tab');
  294.   }
  295. }
  296.  
  297. function getUser() {
  298.   var service = getFitbitService();
  299.  
  300.   var options = {
  301.           headers: {
  302.       "Authorization": "Bearer " + service.getAccessToken(),
  303.         "method": "GET"
  304.           }};
  305.   var response = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/profile.json",options);
  306.   var o = JSON.parse(response.getContentText());
  307.   return o.user;
  308. }
  309.  
  310. /* Here we are using a formula within the spreadsheet to calculate our steps during the time frame we set above.
  311. If the number of steps does not match or exceed out set target the 'nag function is called. */
  312. function calculateSteps() {
  313.     var cell = DOC.getRange('C' + DOC.getLastRow());
  314.     var startofrange = DOC.getLastRow() - MINUTES_TO_SUM;
  315.     cell.setFormula("=SUM(B" + startofrange + ":B" + DOC.getLastRow() + ")");
  316.     var numberofsteps = cell.getValue();
  317.     if (numberofsteps < STEPS_TARGET) {
  318.       nag(numberofsteps);
  319.     }
  320. }
  321.  
  322. function refreshTimeSeries() {
  323.   if (!isConfigured()) {
  324.     setup();
  325.     return;
  326. }
  327.     clearSheet();
  328.     var user = getUser();
  329.     var doc = DOC;
  330.     doc.setFrozenRows(2);
  331.     // two header rows
  332.     doc.getRange("a1").setValue(user.fullName);
  333.     doc.getRange("a1").setComment("DOB:" + user.dateOfBirth)
  334.     doc.getRange("b1").setValue(user.country + "/" + user.state + "/" + user.city);
  335.     var options =
  336.         {headers:{
  337.         "Authorization": 'Bearer ' + getFitbitService().getAccessToken(),
  338.         "method": "GET"
  339.         }};
  340.       var activities = ["activities/log/steps"];
  341.       var intradays = ["activities-log-steps-intraday"];
  342.  
  343.   var lastIndex = 0;
  344.     for (var activity in activities) {
  345.       var index = 0;
  346.     var dateString = getToday();
  347.     date = parseDate(dateString);
  348.         var table = new Array();
  349.       while (1) {
  350.           var currentActivity = activities[activity];
  351.           try {
  352.                 var result = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/" + currentActivity + "/date/" + dateString + "/1d" + ".json", options);
  353.           } catch(exception) {
  354.               Logger.log(exception);
  355.           }
  356.           var o = JSON.parse(result.getContentText());
  357.          
  358.  
  359.           var cell = doc.getRange('a3');
  360.           var titleCell = doc.getRange("a2");
  361.           titleCell.setValue("Date");
  362.           var title = currentActivity.split("/");
  363.           title = title[title.length - 1];
  364.           titleCell.offset(0, 1 + activity * 1.0).setValue(title);
  365.             var row = o[intradays[activity]]["dataset"];
  366.           for (var j in row) {
  367.               var val = row[j];
  368.                 var arr = new Array(2);
  369.                 arr[0] = dateString + ' ' + val["time"];
  370.                 arr[1] = val["value"];
  371.                 table.push(arr);
  372.               // set the value index index
  373.                index++;
  374.            }
  375.           date.setDate(date.getDate()+1);
  376.           dateString = Utilities.formatDate(date, "GMT", "yyyy-MM-dd");
  377.           if (dateString > getLastDate()) {
  378.             break;
  379.           }
  380.  
  381.         }
  382.      
  383.       // Batch set values of table, much faster than doing each time per loop run, this wouldn't work as is if there were multiple activities being listed
  384.           doc.getRange("A3:B"+(table.length+2)).setValues(table);
  385.           calculateSteps();
  386.   }
  387. }
  388.  
  389.  
  390. // parse a date in yyyy-mm-dd format
  391. function parseDate(input) {
  392.   var parts = input.match(/(\d+)/g);
  393.   // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  394.   return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
  395. }
  396.  
  397. // parse a date in 2011-10-25T23:57:00.000 format
  398. function parseDate2(input) {
  399.   var parts = input.match(/(\d+)/g);
  400.   return new Date(parts[0], parts[1]-1, parts[2], parts[3], parts[4]);
  401.  
  402. }
  403.  
  404. /* Our nag function (AKA idle alert) starts by making sure it's within the time range that it's allowed to run (I won't get my steps in during the night
  405. unless I'm peeing a lot or sleepwalking!). The outgoing message is then set based on variables from above (modify as you see fit) and sent via gmail.*/
  406. function nag(stepcount) {
  407.     var hour = new Date().getHours();
  408.     if (hour >= FIRST_HOUR_TO_NAG && hour < LAST_HOUR_TO_NAG) {
  409.       message = "MOVE IT!! You've only taken " + stepcount + " of " + STEPS_TARGET + " steps in the last " + MINUTES_TO_SUM + " minutes!!"
  410.         /* I used information from http://stackoverflow.com/a/30053105 in order to send myself SMS that I could receive via Hangouts
  411.         Obviously, you could send the email to any phone carrier's SMS or MMS gateway for a text to any carrier, OR just send yourself
  412.         an email.*/
  413.       GmailApp.sendEmail("[email protected]", "", message);
  414.     }
  415. }
Add Comment
Please, Sign In to add comment