├── .clasp.json ├── habitica-api-screenshot.png ├── logo for habitica connector.jpg ├── README.md ├── appsscript.json └── Code.ts /.clasp.json: -------------------------------------------------------------------------------- 1 | {"scriptId":"1PXHGS2n5GrdHQVvhtJnXOKLhDoW28ukXEz2LN9kpd1EenFdtmIfacU7W"} 2 | -------------------------------------------------------------------------------- /habitica-api-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevpedia/Habitica-Habit-History-Connector/HEAD/habitica-api-screenshot.png -------------------------------------------------------------------------------- /logo for habitica connector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevpedia/Habitica-Habit-History-Connector/HEAD/logo for habitica connector.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Habitica-Habit-History-Connector 2 | 3 | A Data Studio Connector for your Habitica Habits 4 | 5 | ## Get Your API User ID and Key 6 | 7 | Log into https://habitica.com and visit https://habitica.com/user/settings/api 8 | ![screenshot](https://raw.githubusercontent.com/Kevpedia/Habitica-Habit-History-Connector/master/habitica-api-screenshot.png 'screenshot') 9 | 10 | ## Enable the Connector 11 | 12 | ### Testing (QA) Deployment 13 | 14 | https://datastudio.google.com/datasources/create?connectorId=AKfycbz8w8z-ytH1neIZCR_z2yt24bYdM0kxzcUBKJc7j6g 15 | 16 | ### Production Deployment 17 | 18 | https://datastudio.google.com/datasources/create?connectorId=AKfycbwVWBHo7E6LVKxLG1Q9IZYXpEgjc5wFZjtfsSkouUNn_rcl4a73WbwF 19 | 20 | ## Configure the Connector 21 | 22 | Add the API User ID and Key obtained in the first step above and click 'Connect' 23 | 24 | --- 25 | 26 | [privacy policy](https://kevpedia.github.io/Habitica-Habit-History-Connector/privacy-policy) 27 | -------------------------------------------------------------------------------- /appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "Pacific/Pago_Pago", 3 | "dependencies": {}, 4 | "dataStudio": { 5 | "name": "Habitica Habit History by Kevpedia", 6 | "logoUrl": "https://raw.githubusercontent.com/Kevpedia/Habitica-Habit-History-Connector/master/logo%20for%20habitica%20connector.jpg", 7 | "company": "Kevpedia", 8 | "companyUrl": "https://kevpedia.github.io/Habitica-Habit-History-Connector/?utm_source=connector&utm_medium=manifest&utm_content=companyUrl", 9 | "addonUrl": "https://kevpedia.github.io/Habitica-Habit-History-Connector/?utm_source=connector&utm_medium=manifest&utm_content=addonUrl", 10 | "supportUrl": "https://kevpedia.github.io/Habitica-Habit-History-Connector/?utm_source=connector&utm_medium=manifest&utm_content=supportUrl", 11 | "privacyPolicyUrl": "https://kevpedia.github.io/Habitica-Habit-History-Connector/privacy-policy?utm_source=connector&utm_medium=manifest&utm_content=privacyPolicyUrl", 12 | "termsOfServiceUrl": "https://kevpedia.github.io/Habitica-Habit-History-Connector/terms", 13 | "description": "Connect to Habitica and report on your habits! Visit https://habitica.com/ to get started", 14 | "shortDescription": "Connect to Habitica and report on your habits!", 15 | "authType": ["KEY"], 16 | "feeType": ["FREE"], 17 | "templates": { 18 | "default": "0B-Nb3-Iy-1AsT0MxWGhEMG1TU1U" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Code.ts: -------------------------------------------------------------------------------- 1 | function getConfig(request) { 2 | var config = { 3 | configParams: [ 4 | { 5 | name: 'UserId', 6 | displayName: 'Habitica User ID', 7 | helpText: 'Enter the User ID found in https://habitica.com/#/options/settings/api', 8 | placeholder: '00000000-0000-0000-0000-000000000000' 9 | }, 10 | { 11 | name: 'ApiToken', 12 | displayName: 'Habitica API Token', 13 | helpText: 'Enter the API Token found in https://habitica.com/#/options/settings/api', 14 | placeholder: '00000000-0000-0000-0000-000000000000' 15 | } 16 | ] 17 | }; 18 | return config; 19 | } 20 | 21 | /** Data Schema w/ Sample Values 22 | * Task Name Brush teeth 23 | * Task ID 0d50e4e3-feba-40fb-ba51-f987e9233b0f 24 | * Task Type daily 25 | * Date 2017-02-02 11:35:34 26 | * Value 18.210684655562847 27 | */ 28 | var habitsDataSchema = [ 29 | { 30 | name: 'task_name', 31 | label: 'Task Name', 32 | dataType: 'STRING', 33 | semantics: { 34 | conceptType: 'DIMENSION' 35 | } 36 | }, 37 | { 38 | name: 'task_id', 39 | label: 'Task ID', 40 | dataType: 'STRING', 41 | semantics: { 42 | conceptType: 'DIMENSION' 43 | } 44 | }, 45 | { 46 | name: 'task_type', 47 | label: 'Task Type', 48 | dataType: 'STRING', 49 | semantics: { 50 | conceptType: 'DIMENSION' 51 | } 52 | }, 53 | { 54 | name: 'date', 55 | label: 'Date', 56 | dataType: 'STRING', 57 | semantics: { 58 | conceptType: 'DIMENSION' 59 | } 60 | }, 61 | { 62 | name: 'value', 63 | label: 'Value', 64 | dataType: 'NUMBER', 65 | semantics: { 66 | conceptType: 'METRIC', 67 | isReaggregatable: true 68 | } 69 | } 70 | ]; 71 | 72 | function getSchema(request) { 73 | return {schema: habitsDataSchema}; 74 | } 75 | 76 | function getData(request) { 77 | var dataSchema = []; 78 | request.fields.forEach(function(field) { 79 | for (var i = 0; i < habitsDataSchema.length; i++) { 80 | if (habitsDataSchema[i].name === field.name) { 81 | dataSchema.push(habitsDataSchema[i]); 82 | break; 83 | } 84 | } 85 | }); 86 | 87 | // Set URL for Habitica API, history end-point 88 | var url = "https://habitica.com/export/history.csv"; 89 | 90 | // Fetch the data. 91 | // By default URL fetch will throw an exception if the response code indicates failure. 92 | /** Example Data: 93 | Task Name,Task ID,Task Type,Date,Value 94 | Be Awesome,e826ddfa-dc2e-445f-a06c-64d3881982ea,habit,2016-06-02 13:26:05,1 95 | Be Awesome,e826ddfa-dc2e-445f-a06c-64d3881982ea,habit,2016-06-03 05:06:55,1.026657310999762 96 | ... 97 | **/ 98 | var headers = { 99 | "x-api-user": request.configParams.UserId, 100 | "x-api-key": request.configParams.ApiToken 101 | } 102 | var options = { 103 | 'method': "get", 104 | 'headers': headers 105 | } 106 | try { 107 | var response = UrlFetchApp.fetch(url, options); 108 | } 109 | catch(e) { 110 | if (e.message.search("There is no account that uses those credentials.")!=-1) { 111 | logConnectorError(e, 'fetch-error-authorization'); // Log to Stackdriver. 112 | throwConnectorError("There is no Habitica account that uses those credentials." 113 | + "\nPlease visit https://habitica.com/user/settings/api and Enter the API User ID and Key from there into the connector configuraiton", true); 114 | } 115 | else { 116 | logConnectorError(e, 'fetch-error-other'); // Log to Stackdriver. 117 | throwConnectorError("Unable to Reach the Habitica API. Try again later.", true); 118 | } 119 | } 120 | 121 | try { 122 | var history = response.getContentText(); 123 | var rows = history.split("\n"); 124 | rows = rows.filter(function(row, index){ 125 | if (index!=0) { 126 | return row 127 | } 128 | }); 129 | rows = rows.map(function(row){ 130 | var values = row.split(","); 131 | return { 132 | 'task_name': values[0], 133 | 'task_id': values[1], 134 | 'task_type': values[2], 135 | 'date': values[3], 136 | 'value': values[4], 137 | } 138 | }); 139 | } 140 | catch(e) { 141 | logConnectorError(e + "\nHistory Response: "+response, 'response-parsing-error'); // Log to Stackdriver. 142 | throwConnectorError("We're having some trouble parsing the response from Habitica.com", true); 143 | } 144 | 145 | // Prepare the tabular data. 146 | try { 147 | var data = []; 148 | rows.forEach(function(row) { 149 | var values = []; 150 | // Provide values in the order defined by the schema. 151 | dataSchema.forEach(function(field) { 152 | switch(field.name) { 153 | case 'task_name': 154 | values.push(row['task_name']); 155 | break; 156 | case 'task_id': 157 | values.push(row['task_id']); 158 | break; 159 | case 'task_type': 160 | values.push(row['task_type']); 161 | break; 162 | case 'date': 163 | values.push(row['date']); 164 | break; 165 | case 'value': 166 | values.push(row['value']); 167 | break; 168 | default: 169 | values.push(''); 170 | } 171 | }); 172 | data.push({ 173 | values: values 174 | }); 175 | }); 176 | } 177 | catch (e) { 178 | logConnectorError(e, 'response-formatting-error'); // Log to Stackdriver. 179 | throwConnectorError("Unable to process data in required format.", true); 180 | } 181 | 182 | return { 183 | schema: dataSchema, 184 | rows: data 185 | }; 186 | } 187 | 188 | function getAuthType() { 189 | var response = { 190 | "type": "NONE" 191 | }; 192 | return response; 193 | } 194 | 195 | /** 196 | * Throws an error that complies with the community connector spec. 197 | * @param {string} message The error message. 198 | * @param {boolean} userSafe Determines whether this message is safe to show 199 | * to non-admin users of the connector. true to show the message, false 200 | * otherwise. false by default. 201 | */ 202 | function throwConnectorError(message, userSafe) { 203 | userSafe = (typeof userSafe !== 'undefined' && 204 | typeof userSafe === 'boolean') ? userSafe : false; 205 | if (userSafe) { 206 | message = 'DS_USER:' + message; 207 | } 208 | throw new Error(message); 209 | } 210 | 211 | /** 212 | * Log an error that complies with the community connector spec. 213 | * @param {Error} originalError The original error that occurred. 214 | * @param {string} message Additional details about the error to include in 215 | * the log entry. 216 | */ 217 | function logConnectorError(originalError, message) { 218 | var logEntry = [ 219 | 'Original error (Message): ', 220 | originalError, 221 | '(', message, ')' 222 | ]; 223 | console.error(originalError); // Log to Stackdriver. 224 | } --------------------------------------------------------------------------------