├── .gitignore ├── PostgreSQL_cheatsheet.pdf ├── README.md ├── nbproject ├── customs.json ├── project.properties └── project.xml ├── postDrafts └── Android Pay.md └── public_html ├── AppsScript ├── Apps │ ├── EventManager.js │ ├── EventsDateManager.js │ ├── Insta2Drive.js │ └── SiteMonitor.js ├── Breezometer-API │ └── FetchDataToSheet.js ├── Gcal │ └── EarningCalls.js ├── Gmail │ └── FetchMailsToSheet.js ├── Search │ └── lego.js ├── UI │ ├── DatePicker.js │ └── htmlDialog.js ├── Utils │ ├── CheckSheetAndMail.js │ ├── cleanSheetRange.js │ ├── dateUtils.js │ ├── moneyUtils.js │ ├── parseHTML.js │ └── runMeAgain.js └── YouTube │ └── ytStats.js ├── ListOfScripts.md ├── css ├── bootstrap-responsive.css ├── bootstrap-responsive.min.css ├── bootstrap.css └── bootstrap.min.css ├── filesList.php ├── img ├── glyphicons-halflings-white.png └── glyphicons-halflings.png ├── index.html └── js ├── bootstrap.js └── bootstrap.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | nbproject/private/private.xml 3 | 4 | nbproject/private/private.properties 5 | -------------------------------------------------------------------------------- /PostgreSQL_cheatsheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenido/AppsScriptBests/9470b0ef54139f4f13b419c140ff889ff0d9aa80/PostgreSQL_cheatsheet.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AppsScriptBests 2 | =============== 3 | 4 | A little repo to keep track on Google Apps scripts. 5 | Mainly, to see what are a good ways to solve common challenges. 6 | 7 | See: http://greenido.wordpress.com/?s=apps+script to read more. 8 | ### Few Examples 9 | 10 | * [Youtube Analytics Dashboard With Apps Script](https://greenido.wordpress.com/2014/08/04/youtube-analytics-dashboard-with-apps-script) 11 | 12 | * https://greenido.wordpress.com/2014/04/29/monitor-your-site-with-apps-script/ 13 | 14 | * https://greenido.wordpress.com/2013/07/24/big-query-power-with-javascript/ 15 | 16 | ## Current components 17 | 18 | * [Apps](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps) 19 | * [Apps/EventManager.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/EventManager.js) 20 | * [Apps/EventsDateManager.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/EventsDateManager.js) 21 | * [Apps/Insta2Drive.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/Insta2Drive.js) 22 | * [Apps/SiteMonitor.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/SiteMonitor.js) 23 | * [Gcal](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Gcal) 24 | * [Gcal/EarningCalls.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Gcal/EarningCalls.js) 25 | * [Search](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Search) 26 | * [Search/lego.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Search/lego.js) 27 | * [UI](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI) 28 | * [UI/DatePicker.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI/DatePicker.js) 29 | * [UI/htmlDialog.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI/htmlDialog.js) 30 | * [Utils](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils) 31 | * [Utils/cleanSheetRange.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/cleanSheetRange.js) 32 | * [Utils/dateUtils.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/dateUtils.js) 33 | * [Utils/moneyUtils.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/moneyUtils.js) 34 | * [Utils/parseHTML.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/parseHTML.js) 35 | * [Utils/runMeAgain.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/runMeAgain.js) 36 | * [YouTube](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/YouTube) 37 | * [YouTube/ytStats.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/YouTube/ytStats.js) 38 | 39 | ### Other Sources 40 | 50 | 51 | ### Todo 52 | 53 | 1. Use a side bar to hold the menu for all the content 54 | 2. Allow a short html/markup explanation page per module 55 | 3. Add an example to track a form and send an email when new ppl filled it. 56 | 4. Add an example to track PSI. 57 | 5. Build a wish list with ideas for libs. 58 | 59 | 60 | 61 | [![Analytics](https://ga-beacon.appspot.com/UA-65622529-1/AppsScriptBests/main)](https://github.com/igrigorik/ga-beacon) 62 | 63 | -------------------------------------------------------------------------------- /nbproject/customs.json: -------------------------------------------------------------------------------- 1 | { 2 | "attributes": { 3 | "author": { 4 | "context": "meta" 5 | } 6 | }, 7 | "elements": {} 8 | } -------------------------------------------------------------------------------- /nbproject/project.properties: -------------------------------------------------------------------------------- 1 | auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=js 2 | config.folder=${file.reference.AppsScriptBests-config} 3 | file.reference.AppsScriptBests-config=config 4 | file.reference.AppsScriptBests-public_html=public_html 5 | file.reference.AppsScriptBests-test=test 6 | files.encoding=UTF-8 7 | site.root.folder=${file.reference.AppsScriptBests-public_html} 8 | test.folder=${file.reference.AppsScriptBests-test} 9 | -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.web.clientproject 4 | 5 | 6 | AppsScriptBests 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /postDrafts/Android Pay.md: -------------------------------------------------------------------------------- 1 | Hey! There is a new digital wallet it calls Android Pay. Google’s new service provides a way to instantly pay for stuff not only in real world stores, but also from mobile apps. 2 | 3 | Android Pay will power in-app and tap-to-pay purchases on mobile devices. 4 | 5 | (img of the pay in action)[] 6 | 7 | In terms of adoption, seven out of ten Android devices are ready for Pay and around 700,000 merchants can accept it in their stores. 8 | 9 | 10 | See it in action 11 | [youtube=https://www.youtube.com/watch?v=OueObu2aA_M] 12 | 13 | 14 | -------------------------------------------------------------------------------- /public_html/AppsScript/Apps/EventManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Google Apps Script that supports an event registration site with embeded form. 3 | * for more details: https://github.com/greenido/events-site-template 4 | */ 5 | 6 | // These variables are used throughout the script 7 | // 8 | // This is the string you use to indicate a "yes" RSVP 9 | var YES = "Yes"; 10 | 11 | // This is the string you use to indicate a "no" RSVP 12 | var NO = "No"; 13 | 14 | // This string indicates someones on the waitlist 15 | var WAITLIST = "Waitlist"; 16 | 17 | // The number of questions on the form. 18 | var NUM_FORM_QUESTIONS = 12; 19 | 20 | // This function is called by a trigger when the form is submitted. 21 | // It sends a confirmation email to the person who just submitted the form, 22 | // using the template in cell A1 of the Email Templates sheet. 23 | function sendInitialConfirmationEmail() { 24 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 25 | var dataSheet = ss.getSheetByName("Registration"); 26 | var dataRange = dataSheet.getRange(2, 2, dataSheet.getMaxRows() - 1, NUM_FORM_QUESTIONS + 1); 27 | var startRow = 2; // First row of data to process 28 | var templateSheet = ss.getSheetByName("Email Templates"); 29 | var emailTemplate = ""; 30 | 31 | // Create one JavaScript object per row of data. 32 | objects = getRowsData(dataSheet, dataRange); 33 | 34 | // For every row object, create a personalized email from a template and send 35 | // it to the appropriate person. 36 | for (var i = 0; i < objects.length; ++i) { 37 | // Get a row object 38 | var rowData = objects[i]; 39 | if (rowData.status != YES && 40 | rowData.status != NO && 41 | rowData.status != WAITLIST) { // Prevents sending duplicate confirmations 42 | 43 | var numYes = 0; 44 | 45 | // Go through each existing row and count the number of "YES" responses 46 | for (var j = 0; j < objects.length; ++j) { 47 | var registrations = objects[j]; 48 | if (registrations.status == YES) { 49 | ++numYes; 50 | } 51 | } 52 | 53 | // 54 | // GI: Important - this is the new location of our MAX number of users! 55 | var max = dataSheet.getRange("O2").getValue(); 56 | if ((numYes < max)) { 57 | //set the right template (registration confirmed) 58 | emailTemplate = templateSheet.getRange("A1").getValue(); 59 | dataSheet.getRange(startRow + i, NUM_FORM_QUESTIONS + 2).setValue(YES); 60 | // Make sure the cell is updated right away in case the script is interrupted 61 | SpreadsheetApp.flush(); 62 | } else { 63 | dataSheet.getRange(startRow + i, NUM_FORM_QUESTIONS + 2).setValue(WAITLIST); 64 | //set the right template (waitlist) 65 | emailTemplate = templateSheet.getRange("A2").getValue(); 66 | 67 | // Make sure the cell is updated right away in case the script is interrupted 68 | SpreadsheetApp.flush(); 69 | } 70 | // Generate a personalized email. 71 | // Given a template string, replace markers (for instance ${"First Name"}) with 72 | // the corresponding value in a row object (for instance rowData.firstName). 73 | var emailText = fillInTemplateFromObject(emailTemplate, rowData); 74 | var emailSubject = dataSheet.getRange("P2").getValue() + " Registration"; 75 | 76 | MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText); 77 | objects = getRowsData(dataSheet, dataRange); 78 | } 79 | } 80 | } 81 | 82 | //this is what gets called when we want to update the waitlist. 83 | function updateWaitlist() { 84 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 85 | var dataSheet = ss.getSheetByName("Registration"); 86 | var dataRange = dataSheet.getRange(2, 2, dataSheet.getMaxRows() - 1, NUM_FORM_QUESTIONS + 1); 87 | var startRow = 2; // First row of data to process 88 | var templateSheet = ss.getSheetByName("Email Templates"); 89 | var emailTemplate = templateSheet.getRange("A3").getValue(); 90 | 91 | // Create one JavaScript object per row of data. 92 | objects = getRowsData(dataSheet, dataRange); 93 | // For every row object, create a personalized email from a template and send 94 | // it to the appropriate person. 95 | for (var i = 0; i < objects.length; ++i) { 96 | // Get a row object 97 | var rowData = objects[i]; 98 | if (rowData.status == WAITLIST) { //Only contact people who are waitlisted 99 | 100 | var numYes = 0; 101 | //go through each existing row and count the number of "YES" 102 | for (var j = 0; j < objects.length; ++j) { 103 | var registrations = objects[j]; 104 | Logger.log(registrations.status); 105 | if (registrations.status == YES) { 106 | ++numYes; 107 | } 108 | } 109 | var max = dataSheet.getRange("O2").getValue(); 110 | if ((numYes < max)) { 111 | //set the right template (registration confirmed) 112 | dataSheet.getRange(startRow + i, NUM_FORM_QUESTIONS + 2).setValue(YES); 113 | // Generate a personalized email. 114 | // Given a template string, replace markers (for instance ${"First Name"}) with 115 | // the corresponding value in a row object (for instance rowData.firstName). 116 | var emailText = fillInTemplateFromObject(emailTemplate, rowData); 117 | var emailSubject = dataSheet.getRange("K2").getValue() + " Registration"; 118 | 119 | // GI: If you wish to send the email by using alias email add this: 120 | // MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText, {name: "Ido", replyTo: "example@gmail.com"}); 121 | MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText); 122 | objects = getRowsData(dataSheet, dataRange); 123 | 124 | // Make sure the cell is updated right away in case the script is interrupted 125 | SpreadsheetApp.flush(); 126 | } 127 | 128 | } 129 | } 130 | } 131 | 132 | // 133 | //Call this when you want to send the call for Feedback email AFTER the event is done 134 | // 135 | function sendFeedbackEmail() { 136 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 137 | var dataSheet = ss.getSheetByName("Registration"); 138 | var dataRange = dataSheet.getRange(2, 2, dataSheet.getMaxRows() - 1, NUM_FORM_QUESTIONS + 1); 139 | var startRow = 2; // First row of data to process 140 | var templateSheet = ss.getSheetByName("Email Templates"); 141 | var emailTemplate = templateSheet.getRange("A5").getValue(); 142 | 143 | // Create one JavaScript object per row of data. 144 | objects = getRowsData(dataSheet, dataRange); 145 | // For every row object, create a personalized email from a template and send 146 | // it to the appropriate person. 147 | for (var i = 0; i < objects.length; ++i) { 148 | // Get a row object 149 | var rowData = objects[i]; 150 | if (rowData.status == YES) { //Only contact people who are 'yes' status 151 | // Generate a personalized email. 152 | // Given a template string, replace markers (for instance ${"First Name"}) with 153 | // the corresponding value in a row object (for instance rowData.firstName). 154 | var emailText = fillInTemplateFromObject(emailTemplate, rowData); 155 | var emailSubject = dataSheet.getRange("P2").getValue() + " Reminder"; 156 | 157 | MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText); 158 | 159 | // Make sure the cell is updated right away in case the script is interrupted 160 | SpreadsheetApp.flush(); 161 | } 162 | } 163 | } 164 | 165 | 166 | 167 | //Call this when you want to send the reminder email to confirmed attendees 168 | //about a week before the event 169 | function sendReminderEmail() { 170 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 171 | var dataSheet = ss.getSheetByName("Registration"); 172 | var dataRange = dataSheet.getRange(2, 2, dataSheet.getMaxRows() - 1, NUM_FORM_QUESTIONS + 1); 173 | var startRow = 2; // First row of data to process 174 | var templateSheet = ss.getSheetByName("Email Templates"); 175 | var emailTemplate = templateSheet.getRange("A4").getValue(); 176 | 177 | // Create one JavaScript object per row of data. 178 | objects = getRowsData(dataSheet, dataRange); 179 | // For every row object, create a personalized email from a template and send 180 | // it to the appropriate person. 181 | for (var i = 0; i < objects.length; ++i) { 182 | // Get a row object 183 | var rowData = objects[i]; 184 | if (rowData.status == YES) { //Only contact people who are 'yes' status 185 | // Generate a personalized email. 186 | // Given a template string, replace markers (for instance ${"First Name"}) with 187 | // the corresponding value in a row object (for instance rowData.firstName). 188 | var emailText = fillInTemplateFromObject(emailTemplate, rowData); 189 | var emailSubject = dataSheet.getRange("P2").getValue() + " Reminder"; 190 | 191 | MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText); 192 | 193 | 194 | // Make sure the cell is updated right away in case the script is interrupted 195 | SpreadsheetApp.flush(); 196 | } 197 | } 198 | } 199 | 200 | // Replaces markers in a template string with values define in a JavaScript data object. 201 | // Arguments: 202 | // - template: string containing markers, for instance ${"Column name"} 203 | // - data: JavaScript object with values to that will replace markers. For instance 204 | // data.columnName will replace marker ${"Column name"} 205 | // Returns a string without markers. If no data is found to replace a marker, it is 206 | // simply removed. 207 | function fillInTemplateFromObject(template, data) { 208 | var email = template; 209 | // Search for all the variables to be replaced, for instance ${"Column name"} 210 | var templateVars = template.match(/\$\{\"[^\"]+\"\}/g); 211 | 212 | // Replace variables from the template with the actual values from the data object. 213 | // If no value is available, replace with the empty string. 214 | for (var i = 0; i < templateVars.length; ++i) { 215 | // normalizeHeader ignores ${"} so we can call it directly here. 216 | var variableData = data[normalizeHeader(templateVars[i])]; 217 | email = email.replace(templateVars[i], variableData || ""); 218 | } 219 | 220 | return email; 221 | } 222 | 223 | 224 | 225 | 226 | 227 | ////////////////////////////////////////////////////////////////////////////////////////// 228 | // 229 | // The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects' 230 | // tutorial. 231 | // 232 | ////////////////////////////////////////////////////////////////////////////////////////// 233 | 234 | // getRowsData iterates row by row in the input range and returns an array of objects. 235 | // Each object contains all the data for a given row, indexed by its normalized column name. 236 | // Arguments: 237 | // - sheet: the sheet object that contains the data to be processed 238 | // - range: the exact range of cells where the data is stored 239 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 240 | // This argument is optional and it defaults to the row immediately above range; 241 | // Returns an Array of objects. 242 | function getRowsData(sheet, range, columnHeadersRowIndex) { 243 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 244 | var numColumns = range.getEndColumn() - range.getColumn() + 1; 245 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 246 | var headers = headersRange.getValues()[0]; 247 | return getObjects(range.getValues(), normalizeHeaders(headers)); 248 | } 249 | 250 | // For every row of data in data, generates an object that contains the data. Names of 251 | // object fields are defined in keys. 252 | // Arguments: 253 | // - data: JavaScript 2d array 254 | // - keys: Array of Strings that define the property names for the objects to create 255 | function getObjects(data, keys) { 256 | var objects = []; 257 | for (var i = 0; i < data.length; ++i) { 258 | var object = {}; 259 | var hasData = false; 260 | for (var j = 0; j < data[i].length; ++j) { 261 | var cellData = data[i][j]; 262 | if (isCellEmpty(cellData)) { 263 | continue; 264 | } 265 | object[keys[j]] = cellData; 266 | hasData = true; 267 | } 268 | if (hasData) { 269 | objects.push(object); 270 | } 271 | } 272 | return objects; 273 | } 274 | 275 | // Returns an Array of normalized Strings. 276 | // Arguments: 277 | // - headers: Array of Strings to normalize 278 | function normalizeHeaders(headers) { 279 | var keys = []; 280 | for (var i = 0; i < headers.length; ++i) { 281 | var key = normalizeHeader(headers[i]); 282 | if (key.length > 0) { 283 | keys.push(key); 284 | } 285 | } 286 | return keys; 287 | } 288 | 289 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 290 | // to separate words. The output will always start with a lower case letter. 291 | // This function is designed to produce JavaScript object property names. 292 | // Arguments: 293 | // - header: string to normalize 294 | // Examples: 295 | // "First Name" -> "firstName" 296 | // "Market Cap (millions) -> "marketCapMillions 297 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 298 | function normalizeHeader(header) { 299 | var key = ""; 300 | var upperCase = false; 301 | for (var i = 0; i < header.length; ++i) { 302 | var letter = header[i]; 303 | if (letter == " " && key.length > 0) { 304 | upperCase = true; 305 | continue; 306 | } 307 | if (!isAlnum(letter)) { 308 | continue; 309 | } 310 | if (key.length == 0 && isDigit(letter)) { 311 | continue; // first character must be a letter 312 | } 313 | if (upperCase) { 314 | upperCase = false; 315 | key += letter.toUpperCase(); 316 | } else { 317 | key += letter.toLowerCase(); 318 | } 319 | } 320 | return key; 321 | } 322 | 323 | // Returns true if the cell where cellData was read from is empty. 324 | // Arguments: 325 | // - cellData: string 326 | function isCellEmpty(cellData) { 327 | return typeof(cellData) == "string" && cellData == ""; 328 | } 329 | 330 | // Returns true if the character char is alphabetical, false otherwise. 331 | function isAlnum(char) { 332 | return char >= 'A' && char <= 'Z' || 333 | char >= 'a' && char <= 'z' || 334 | isDigit(char); 335 | } 336 | 337 | // Returns true if the character char is a digit, false otherwise. 338 | function isDigit(char) { 339 | return char >= '0' && char <= '9'; 340 | } 341 | 342 | function setMaxAttendees() { 343 | var max = Browser.inputBox("Please enter the maximum number of attendees:"); 344 | var matchDigits = /^\d+$/; 345 | if (max.search(matchDigits) == -1) { 346 | Browser.msgBox("Invalid input."); 347 | } else { 348 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Registration").getRange("J2").setValue(max); 349 | } 350 | } 351 | 352 | function onOpen() { 353 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 354 | var menuEntries = [ {name: "Set Max Attendees", functionName: "setMaxAttendees"}, 355 | {name: "Update Waitlist", functionName: "updateWaitlist"}, 356 | {name: "Send Initial Emails", functionName: "sendInitialConfirmationEmail"}, 357 | {name: "Send Reminder Emails", functionName: "sendReminderEmail"}, 358 | {name: "Export Range as CSV", functionName: "saveAsCSV"}]; 359 | ss.addMenu("Hackathon", menuEntries); 360 | } 361 | 362 | function saveAsCSV() { 363 | // Prompts the user for the file name 364 | var fileName = Browser.inputBox("Save CSV file as (e.g. myCSVFile):"); 365 | 366 | // Check that the file name entered wasn't empty 367 | if (fileName.length !== 0) { 368 | // Add the ".csv" extension to the file name 369 | fileName = fileName + ".csv"; 370 | // Convert the range data to CSV format 371 | var csvFile = convertRangeToCsvFile_(fileName); 372 | // Create a file in the Docs List with the given name and the CSV data 373 | DocsList.createFile(fileName, csvFile); 374 | } else { 375 | Browser.msgBox("Error: Please enter a CSV file name."); 376 | } 377 | } 378 | 379 | function convertRangeToCsvFile_(csvFileName) { 380 | // Get the selected range in the spreadsheet 381 | var ws = SpreadsheetApp.getActiveSpreadsheet().getActiveSelection(); 382 | try { 383 | var data = ws.getValues(); 384 | var csvFile = undefined; 385 | 386 | // Loop through the data in the range and build a string with the CSV data 387 | if (data.length > 1) { 388 | var csv = ""; 389 | for (var row = 0; row < data.length; row++) { 390 | for (var col = 0; col < data[row].length; col++) { 391 | if (data[row][col].toString().indexOf(",") != -1) { 392 | data[row][col] = "\"" + data[row][col] + "\""; 393 | } 394 | } 395 | 396 | // Join each row's columns 397 | // Add a carriage return to end of each row, except for the last one 398 | if (row < data.length-1) { 399 | csv += data[row].join(",") + "\r\n"; 400 | } else { 401 | csv += data[row]; 402 | } 403 | } 404 | csvFile = csv; 405 | } 406 | return csvFile; 407 | } 408 | catch(err) { 409 | Logger.log(err); 410 | Browser.msgBox(err); 411 | } 412 | } -------------------------------------------------------------------------------- /public_html/AppsScript/Apps/EventsDateManager.js: -------------------------------------------------------------------------------- 1 | // EventManagerV3 glued together by mhawksey http://www.google.com/profiles/m.hawksey 2 | // Related blog post http://mashe.hawksey.info/eventmanagerv3/ 3 | // With some code (settings, importIntoCalendar, sendEmails) from 4 | // Romain Vialard's http://www.google.com/profiles/romain.vialard 5 | // Manage your events: Calendar Importer and Registration Form 6 | // https://spreadsheets.google.com/ccc?key=tCHuQkkKh_r69bQGt4yJmNQ 7 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 8 | var BOOKING_ACTION_COL = 10; 9 | function onOpen() { 10 | var conf = ss.getSheetByName("Templates").getRange("B3").getValue(); 11 | if (conf == "") { 12 | ss.toast("Click on setup to start", "Welcome", 10); 13 | } 14 | var menuEntries = [{name: "Process Events", functionName: "importIntoCalendar"}, {name: "Process Bookings", functionName: "sendBookingConf"}, {name: "Email joining instructions", functionName: "sendJoinEmails"}]; 15 | ss.addMenu("Event Manager", menuEntries); 16 | } 17 | function settings() { 18 | var calendarName = Browser.inputBox("First create a calendar in Google Calendar and enter its name here:"); 19 | if (calendarName != "cancel" && calendarName != "") { 20 | var templateSheet = ss.getSheetByName("Templates"); 21 | templateSheet.getRange("E1").setValue(calendarName); 22 | var formURL = ss.getFormUrl(); 23 | templateSheet.getRange("B3").setValue(formURL); 24 | var calTimeZone = CalendarApp.openByName(calendarName).getTimeZone(); 25 | ss.setSpreadsheetTimeZone(calTimeZone); 26 | var timeZone = ss.getSpreadsheetTimeZone(); 27 | var siteUrl = Browser.inputBox("If you would like to update events to a Sites page enter your announcement page url"); 28 | if (siteUrl != "cancel" && siteUrl != "") { 29 | templateSheet.getRange("E2").setValue(siteUrl); 30 | } 31 | var adminEmail = Browser.inputBox("Please enter your administrator email address"); 32 | if (adminEmail != "cancel" && adminEmail != "") { 33 | templateSheet.getRange("B4").setValue(adminEmail); 34 | } 35 | ss.toast("You can now import events in your calendar. Time Zone is currently set to: " + timeZone + ".", "Set up completed!", -1); 36 | SpreadsheetApp.flush(); 37 | } 38 | } 39 | function importIntoCalendar() { 40 | var dataSheet = ss.getSheetByName("Put your events here"); 41 | var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows(), dataSheet.getMaxColumns()); 42 | var templateSheet = ss.getSheetByName("Templates"); 43 | var calendarName = templateSheet.getRange("E1").getValue(); 44 | var siteUrl = templateSheet.getRange("E2").getValue(); 45 | if (calendarName != "") { 46 | var cal = CalendarApp.getCalendarsByName(calendarName)[0]; 47 | var eventTitleTemplate = templateSheet.getRange("E3").getValue(); 48 | var descriptionTemplate = templateSheet.getRange("E4").getValue(); 49 | // Create one JavaScript object per row of data. 50 | objects = getRowsData(dataSheet, dataRange); 51 | // For every row object, create a personalized email from a template and send 52 | // it to the appropriate person. 53 | for (var i = 0; i < objects.length; ++i) { 54 | // Get a row object 55 | var rowData = objects[i]; 56 | if (rowData.eventId && rowData.eventTitle && rowData.action == "Y") { 57 | var eventTitle = fillInTemplateFromObject(eventTitleTemplate, rowData); 58 | var description = fillInTemplateFromObject(descriptionTemplate, rowData); 59 | // add to calendar bit 60 | if (rowData.endDate == "All-day") { 61 | cal.createAllDayEvent(eventTitle, rowData.startDate, rowData.endDate, {location: rowData.location, description: description}).addEmailReminder(15).setTag("Event ID", rowData.eventId); 62 | } 63 | else { 64 | cal.createEvent(eventTitle, rowData.startDate, rowData.endDate, {location: rowData.location, description: description}).addEmailReminder(15).setTag("Event ID", rowData.eventId); 65 | } 66 | // add to site bit 67 | if (siteUrl != "") { 68 | var page = SitesApp.getPageByUrl(siteUrl); 69 | var announcement = page.createAnnouncement(rowData.eventTitle, description); 70 | } 71 | // create event sheet 72 | var temp = ss.getSheetByName("EventTMP"); 73 | var eventSheet = ss.insertSheet("Event " + rowData.eventId, {template: temp}); 74 | eventSheet.getRange(1, 2, 1, 1).setValue(rowData.numberOfPlaces); 75 | eventSheet.getRange(1, 3, 1, 1).setValue(rowData.eventTitle); 76 | eventSheet.getRange(2, 3, 1, 1).setValue(rowData.location); 77 | eventSheet.getRange(3, 6, 1, 1).setValue(rowData.startDate); 78 | eventSheet.getRange(3, 8, 1, 1).setValue(rowData.endDate); 79 | dataSheet.getRange(i + 2, 1, 1, 1).setValue(""); 80 | dataSheet.getRange(i + 2, 2, 1, 1).setValue("Added " + new Date()).setBackgroundRGB(221, 221, 221); 81 | dataSheet.getRange(i + 2, 1, 1, dataSheet.getMaxColumns()).setBackgroundRGB(221, 221, 221); 82 | // Make sure the cell is updated right away in case the script is interrupted 83 | SpreadsheetApp.flush(); 84 | } 85 | } 86 | ss.toast("People can now register to those events", "Events imported"); 87 | } 88 | } 89 | function onFormSubmit() { 90 | var dataSheet = ss.getSheetByName("Bookings"); 91 | var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows(), dataSheet.getMaxColumns()); 92 | var templateSheet = ss.getSheetByName("Templates"); 93 | var emailTemplate = templateSheet.getRange("E6").getValue(); 94 | var adminEmail = templateSheet.getRange("B4").getValue() 95 | // Create one JavaScript object per row of data. 96 | data = getRowsData(dataSheet, dataRange); 97 | for (var i = 0; i < data.length; ++i) { 98 | // Get a row object 99 | var row = data[i]; 100 | row.rowNumber = i + 2; 101 | if (!row.action) { // if no state notify admin of booking 102 | var emailText = fillInTemplateFromObject(emailTemplate, row); 103 | var emailSubject = "Booking Approval Request ID: " + row.rowNumber; 104 | MailApp.sendEmail(adminEmail, emailSubject, emailText); 105 | dataSheet.getRange(row.rowNumber, BOOKING_ACTION_COL).setValue("TBC"); //9 is the column number for 'Action' 106 | } 107 | } 108 | } 109 | function sendBookingConf() { 110 | var dataSheet = ss.getSheetByName("Bookings"); 111 | var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows(), dataSheet.getMaxColumns()); 112 | var templateSheet = ss.getSheetByName("Templates"); 113 | var emailSubjectTemplate = templateSheet.getRange("B1").getValue(); 114 | var emailTemplate = templateSheet.getRange("B2").getValue(); 115 | var emailSentColumn = BOOKING_ACTION_COL; 116 | // To add guests into Calendar 117 | var calendarDataSheet = ss.getSheetByName("Put your events here"); 118 | var calendarDataRange = calendarDataSheet.getRange(2, 1, calendarDataSheet.getMaxRows(), calendarDataSheet.getMaxColumns()); 119 | var calendarName = templateSheet.getRange("E1").getValue(); 120 | // Create one JavaScript object per row of data. 121 | calendarObjects = getRowsData(calendarDataSheet, calendarDataRange); 122 | // Create one JavaScript object per row of data. 123 | objects = getRowsData(dataSheet, dataRange); 124 | // For every row object, create a personalized email from a template and send 125 | // it to the appropriate person. 126 | for (var i = 0; i < objects.length; ++i) { 127 | // Get a row object 128 | var rowData = objects[i]; 129 | if (rowData.action == "Y") { // Prevents sending duplicates 130 | // add guest in calendar 131 | for (var j = 0; j < calendarObjects.length; ++j) { 132 | // Get a row object 133 | var calendarRowData = calendarObjects[j]; 134 | if (calendarRowData.eventId == rowData.eventId) { 135 | var cal = CalendarApp.openByName(calendarName); 136 | if (calendarRowData.endDate == "All-day") { 137 | var getDate = new Date(calendarRowData.startDate).getTime(); 138 | var endDate = new Date().setTime(getDate + 86400000); 139 | var events = cal.getEvents(new Date(calendarRowData.startDate), new Date(endDate)); 140 | } 141 | else { 142 | var events = cal.getEvents(new Date(calendarRowData.startDate), new Date(calendarRowData.endDate)); 143 | } 144 | for (var k in events) { 145 | if (events[k].getTag("Event ID") == rowData.eventId) { 146 | events[k].addGuest(rowData.email); 147 | j = calendarObjects.length; 148 | } 149 | } 150 | } 151 | } 152 | // Generate a personalized email. 153 | // Given a template string, replace markers (for instance ${"First Name"}) with 154 | // the corresponding value in a row object (for instance rowData.firstName). 155 | calendarRowData.bookingId = rowData.eventId + "-B" + (i + 2); 156 | calendarRowData.firstName = rowData.firstName; 157 | var emailSubject = fillInTemplateFromObject(emailSubjectTemplate, calendarRowData); 158 | var emailText = fillInTemplateFromObject(emailTemplate, calendarRowData); 159 | var emailAddress = rowData.email; 160 | MailApp.sendEmail(emailAddress, emailSubject, emailText, {htmlBody: emailText}); 161 | // add booking to right event sheet 162 | dataSheet.getRange(i + 2, emailSentColumn).setValue(calendarRowData.bookingId).setBackgroundRGB(221, 221, 221); 163 | var eventSheet = ss.getSheetByName("Event " + rowData.eventId); 164 | var rowNum = eventSheet.getLastRow() + 1; 165 | eventSheet.getRange(rowNum, 3, 1, 1).setValue(calendarRowData.bookingId); 166 | eventSheet.getRange(rowNum, 4, 1, 1).setValue(rowData.timestamp); 167 | eventSheet.getRange(rowNum, 5, 1, 1).setValue(rowData.firstName); 168 | eventSheet.getRange(rowNum, 6, 1, 1).setValue(rowData.lastName); 169 | eventSheet.getRange(rowNum, 7, 1, 1).setValue(rowData.email); 170 | eventSheet.getRange(rowNum, 8, 1, 1).setValue(rowData.organisationName); 171 | eventSheet.getRange(rowNum, 9, 1, 1).setValue(rowData.startPostcode); 172 | eventSheet.getRange(rowNum, 10, 1, 1).setValue(rowData.preferredMode); 173 | eventSheet.getRange(rowNum, 11, 1, 1).setValue(rowData.otherInfo); 174 | eventSheet.getRange(rowNum, 12, 1, 1).setValue(rowData.comments); 175 | // Make sure the cell is updated right away in case the script is interrupted 176 | // Add delegate to Contacts 177 | var curDate = Utilities.formatDate(new Date(), "GMT", "dd/MM/yy HH:mm"); 178 | var c = ContactsApp.findByEmailAddress(rowData.email); 179 | if (!c) { 180 | var c = ContactsApp.createContact(rowData.firstName, rowData.lastName, rowData.email); 181 | var prop = {}; 182 | prop.Organisation = rowData.organisationName; 183 | prop.Added = curDate; 184 | c.setUserDefinedFields(prop); 185 | var group = ContactsApp.findContactGroup(rowData.organisationName); 186 | if (!group) { 187 | var group = ContactsApp.createContactGroup(rowData.organisationName); 188 | } 189 | c.addToGroup(group); 190 | } else { 191 | c.setUserDefinedField("Last activity", curDate); 192 | } 193 | SpreadsheetApp.flush(); 194 | } 195 | } 196 | ss.toast("", "Emails sent", -1); 197 | } 198 | // Code to send joining instructions - based on simple mail merge code from 199 | // Tutorial: Simple Mail Merge 200 | // Hugo Fierro, Google Spreadsheet Scripts Team 201 | // March 2009 202 | function sendJoinEmails() { 203 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 204 | var dataSheet = ss.getActiveSheet(); 205 | var eventName = ss.getRange("C1").getValue();// pull event name from sheet 206 | var location = ss.getRange("C2").getValue();// pull event location 207 | var emailCount = 0; 208 | var dataRange = dataSheet.getRange(5, 3, dataSheet.getMaxRows(), dataSheet.getMaxColumns()); 209 | var templateSheet = ss.getSheetByName("Templates"); 210 | var emailTemplate = templateSheet.getRange("B6").getValue(); 211 | var emailSubject = templateSheet.getRange("B5").getValue(); 212 | emailSubject = emailSubject.replace('${"Event Name"}', eventName); 213 | // Create one JavaScript object per row of data. 214 | objects = getRowsData(dataSheet, dataRange, 4); 215 | // For every row object, create a personalized email from a template and send 216 | // it to the appropriate person. 217 | for (var i = 0; i < objects.length; ++i) { 218 | // Get a row object 219 | var rowData = objects[i]; 220 | rowData.eventName = eventName; 221 | rowData.rowNumber = i + 5; 222 | // Generate a personalized email. 223 | if (!rowData.emailed) { 224 | if (rowData.startPostcode && (location != "Online" || location)) { 225 | rowData.directions = getMapDirections_(rowData.startPostcode, location, rowData.mode); 226 | } 227 | var emailText = fillInTemplateFromObject(emailTemplate, rowData); 228 | try { 229 | MailApp.sendEmail(rowData.emailAddress, emailSubject, 'Please view in HTML capable email client.', {htmlBody: emailText}); 230 | emailCount++; 231 | dataSheet.getRange(rowData.rowNumber, 2).setValue(Utilities.formatDate(new Date(), "GMT", "dd/MM/yy HH:mm")); 232 | } catch (e) { 233 | Browser.msgBox("There was a problem sending to " + rowData.emailAddress); 234 | } 235 | } 236 | } 237 | ss.toast("Joining instructions have been sent to " + emailCount + " delegates", "Joining instructions sent", 5); 238 | } 239 | 240 | 241 | // Modified from Send customized driving directions in a mail merge template 242 | // http://code.google.com/googleapps/appsscript/templates.html 243 | function getMapDirections_(start, end, mode) { 244 | // Generate personalized static map with directions. 245 | switch (mode) 246 | { 247 | case "Cycling": 248 | var directions = Maps.newDirectionFinder() 249 | .setOrigin(start) 250 | .setDestination(end) 251 | .setMode(Maps.DirectionFinder.Mode.BICYCLING) 252 | .getDirections(); 253 | break; 254 | case "Walking": 255 | var directions = Maps.newDirectionFinder() 256 | .setOrigin(start) 257 | .setDestination(end) 258 | .setMode(Maps.DirectionFinder.Mode.WALKING) 259 | .getDirections(); 260 | break; 261 | default: 262 | var directions = Maps.newDirectionFinder() 263 | .setOrigin(start) 264 | .setDestination(end) 265 | .setMode(Maps.DirectionFinder.Mode.DRIVING) 266 | .getDirections(); 267 | } 268 | var currentLabel = 0; 269 | var directionsHtml = ""; 270 | var map = Maps.newStaticMap().setSize(500, 350); 271 | map.setMarkerStyle(Maps.StaticMap.MarkerSize.SMALL, "red", null); 272 | map.addMarker(start); 273 | map.addMarker(end); 274 | var r1 = new RegExp('
', 'g'); 275 | var r2 = new RegExp('
', 'g'); 276 | var points = []; 277 | var distance = 0; 278 | var time = 0; 279 | for (var i in directions.routes) { 280 | for (var j in directions.routes[i].legs) { 281 | for (var k in directions.routes[i].legs[j].steps) { 282 | var step = directions.routes[i].legs[j].steps[k]; 283 | distance += directions.routes[i].legs[j].steps[k].distance.value; 284 | time += directions.routes[i].legs[j].steps[k].duration.value; 285 | var path = Maps.decodePolyline(step.polyline.points); 286 | points = points.concat(path); 287 | var text = step.html_instructions; 288 | text = text.replace(r1, ''); 289 | text = text.replace(r2, ''); 290 | directionsHtml += "" + (++currentLabel) + " - " + text; 291 | } 292 | } 293 | } 294 | // be conservative, and only sample 100 times... 295 | var lpoints = []; 296 | if (points.length < 200) 297 | lpoints = points; 298 | else { 299 | var pCount = (points.length / 2); 300 | var step = parseInt(pCount / 100); 301 | for (var i = 0; i < 100; ++i) { 302 | lpoints.push(points[i * step * 2]); 303 | lpoints.push(points[(i * step * 2) + 1]); 304 | } 305 | } 306 | // make the polyline if (lpoints.length>0) { 307 | var pline = Maps.encodePolyline(lpoints); 308 | map.addPath(pline); 309 | 310 | 311 | var mapLink = "Click here for complete map and directions"; 312 | var dir = 'Travel Directions' + mapLink + 313 | "Summary of Route" + "Distance: " + 314 | Math.round(0.00621371192 * distance) / 10 + " miles" + 315 | "Time: " + 316 | Math.floor(time / 60) + " minutes" + directionsHtml; 317 | return dir; 318 | } -------------------------------------------------------------------------------- /public_html/AppsScript/Apps/Insta2Drive.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Search, Fetch and save images from Instagram by tags. 4 | // 5 | function fetchInstagram() { 6 | // Feel free to change this one 7 | var TAG_NAME = 'snowboarding'; 8 | 9 | // You need to replace todo with your client-id 10 | // Get it at: http://instagram.com/developer/clients/manage/ 11 | var CLIENT_ID = 'todo'; 12 | var url = 'https://api.instagram.com/v1/tags/' + TAG_NAME + '/media/recent?client_id=' + CLIENT_ID; 13 | 14 | // fetch the top 10 results 15 | while (i < 10) { 16 | //let us fetch the details from API. This will give you the details of photos and URL 17 | var response = UrlFetchApp.fetch(url).getContentText(); 18 | var responseObj = JSON.parse(response); 19 | var photoData = responseObj.data; 20 | 21 | //iterate over this data 22 | for (var i in photoData) { 23 | var imageUrl = photoData[i].images.standard_resolution.url; 24 | Logger.log("image url: "+imageUrl); 25 | //Todo: open this comment if you wish to save the file: fetchImageToDrive_(imageUrl); 26 | } 27 | 28 | //Get the url of the next page 29 | url = responseObj.pagination.next_url; 30 | } 31 | } 32 | 33 | // 34 | // fetch and save the file into drive 35 | // 36 | function fetchImageToDrive_(imageUrl) { 37 | var imageBlob = UrlFetchApp.fetch(imageUrl).getBlob(); 38 | // Create image file in drive 39 | var image = DriveApp.createFile(imageBlob); 40 | // return the URL of the newly created image in drive so we could log it. 41 | return image.getUrl(); 42 | } -------------------------------------------------------------------------------- /public_html/AppsScript/Apps/SiteMonitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Monitor your sites and track their up time with response codes. 4 | * We can control the flow of the emails' alerts from a google form that will give us the option 5 | * to mute. 6 | * 7 | * @author Ido Green | @greenido 8 | * 9 | * @see: http://greenido.wordpress.com/2014/04/29/monitor-your-site-with-apps-script/ 10 | * 11 | */ 12 | 13 | // 14 | // Let the user control the monitor: start and stop function. 15 | // 16 | function onOpen() { 17 | var sheet = SpreadsheetApp.getActiveSpreadsheet(); 18 | var menu = [ 19 | {name: "Start ", functionName: "init"}, 20 | {name: "Stop", functionName: "removeJobs"} 21 | ]; 22 | 23 | sheet.addMenu("Monitor", menu); 24 | } 25 | 26 | // 27 | // 28 | // 29 | function init() { 30 | removeJobs(true); 31 | var sheet = SpreadsheetApp.getActiveSpreadsheet(); 32 | var urls = sheet.getSheets()[0].getRange("B2").getValue(); 33 | urls = urls.replace(/\s/g, "").split(","); 34 | //var time = (new Date()).getTime(); 35 | var db = ScriptDb.getMyDb(); 36 | 37 | for (i=0; i 1) { 61 | // Out process is down! 62 | code = 503; // = Our service is not available 63 | } 64 | else { 65 | code = response.getResponseCode(); 66 | } 67 | } catch(error) {} 68 | 69 | item.status = checkAndNotify(item, code); 70 | db.save(item); 71 | } 72 | } 73 | 74 | // 75 | // 76 | // 77 | function checkAndNotify(item, code) { 78 | var status = item.status; 79 | // We are cool 80 | if (code === 200) { 81 | return code; 82 | } 83 | 84 | // Site was up previously but is now down 85 | if (code === 503 && (status === 200)) { 86 | // Run another check after 1 minutes to prevent false positives 87 | quickCheck(); 88 | return code; 89 | } 90 | 91 | // Site was down previously but up on second check 92 | if (code === 200 && status === 503) { 93 | logToSheet(item.url, "Tweet Process is Up"); 94 | return code; 95 | } 96 | 97 | // Site was down previously and is down again 98 | if ((code === -1 || code === 503) && status === 503 ) { 99 | quickCheck(); 100 | logToSheet(item.url, "Tweet Process is Down"); 101 | return code; 102 | } 103 | 104 | return code; 105 | } 106 | 107 | // 108 | // /use our sheet as the logger to make it easy to debreif cases. 109 | // 110 | function logToSheet(url, message) { 111 | 112 | var sheets = SpreadsheetApp.getActiveSpreadsheet(); 113 | 114 | var emailRow = sheets.getSheets()[1].getLastRow(); 115 | var toStopEmails = sheets.getSheets()[1].getRange(emailRow, 2).getValue(); 116 | 117 | var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]; 118 | var row = logSheet.getLastRow() + 1; 119 | var time = new Date(); 120 | 121 | logSheet.getRange(row,1).setValue(time); 122 | logSheet.getRange(row,2).setValue(message + " : " + url); 123 | 124 | var alert = "Site: " + url + " is " + message.toLowerCase() + " To stop emails: http://goo.gl/todo-your-own-sheet"; 125 | 126 | if (toStopEmails === "Yes") { 127 | Logger.log("We got an alert: " + alert + " But we are not sending emails!"); 128 | } 129 | else { 130 | Logger.log("Send an alert: " + alert); 131 | MailApp.sendEmail(logSheet.getRange("B3").getValue(), "AGF Site " + message, alert); 132 | } 133 | 134 | // If you have a sheet with a form this is the location to see if you 'stoped' the notifications 135 | // 136 | // if (sheet.getRange("B4").getValue().toLowerCase() == "yes") { 137 | // time = new Date(time.getTime() + 15000); 138 | // CalendarApp.createEvent(alert, time, time).addSmsReminder(0); 139 | // } 140 | 141 | return; 142 | } 143 | 144 | // 145 | // 146 | // 147 | function secondCheck() { 148 | var triggers = ScriptApp.getProjectTriggers(); 149 | for (var i=0; i -1) { 50 | cell.offset(index, 2).setValue(row); 51 | Logger.log("key: " + i + " val: "+ row); 52 | } 53 | else if (i.indexOf("breezometer_aqi") > -1) { 54 | cell.offset(index, 3).setValue(row); 55 | Logger.log("key: " + i + " val: "+ row); 56 | } 57 | else if (i.indexOf("dominant_pollutant_canonical_name") > -1) { 58 | cell.offset(index, 4).setValue(row); 59 | Logger.log("key: " + i + " val: "+ row); 60 | } 61 | else if (i.indexOf("breezometer_color") > -1) { 62 | cell.offset(index, 5).setValue(row); 63 | Logger.log("key: " + i + " val: "+ row); 64 | } 65 | else if (i.indexOf("dominant_pollutant_description") > -1) { 66 | cell.offset(index, 6).setValue(row); 67 | Logger.log("key: " + i + " val: "+ row); 68 | } 69 | else if (i.indexOf("dominant_pollutant_text") > -1) { 70 | cell.offset(index, 7).setValue(row); 71 | Logger.log("key: " + i + " val: "+ row); 72 | } 73 | else if (i.indexOf("random_recommendations") > -1) { 74 | cell.offset(index, 8).setValue(row); 75 | Logger.log("key: " + i + " val: "+ row); 76 | } 77 | } 78 | Logger.log("done with: "+ url); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /public_html/AppsScript/Gcal/EarningCalls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fetch Earning Calls dates and put them on Google Calander 3 | * @Author: Ido Green | @greenido | +GreenIdo 4 | * @Date: Feb 2015 5 | */ 6 | 7 | 8 | /** 9 | * 0. Delete all the old/current events. 10 | * 1. Run on your tickers 11 | * 2. Fetch the eraning call for each. 12 | * 3. Create a cal event. 13 | */ 14 | function createEraningCallsEvents() { 15 | 16 | // start with a clean cal 17 | deleteAllEarningEvents(); 18 | 19 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 20 | var sheet = ss.getSheetByName("metaData"); 21 | var earningCalendar = CalendarApp.getCalendarById("TODO-email"); 22 | 23 | var tickers = sheet.getRange("A:A"); 24 | var values = tickers.getValues(); 25 | 26 | for (var row in values) { 27 | var ticker = values[row][0]; 28 | Logger.log(row +") Working ticker: " + ticker); 29 | if (ticker === "" || ticker.length < 1) { 30 | break; 31 | } 32 | 33 | // get the earning date 34 | var d = getEarningDate(ticker); 35 | if (d !== "N/A") { 36 | // update a new event for this date 37 | createEvent(earningCalendar, d, ticker); 38 | } 39 | else { 40 | Logger.log("Could not add event for: " + ticker + " because didn't get a date"); 41 | } 42 | } 43 | } 44 | 45 | // 46 | // Delete all the events we currently have in our cal. 47 | // 48 | function deleteAllEarningEvents() { 49 | var earningCalendar = CalendarApp.getCalendarById("TODO-email"); 50 | var now = new Date(); 51 | var aYearFromNow = new Date(now.getTime() + (365 * 24 * 60 * 60 * 1000)); 52 | var events = earningCalendar.getEvents(now, aYearFromNow, {search: 'Earning call'}); 53 | Logger.log('Number of events: ' + events.length); 54 | for (var i=0; i < events.length; i++) { 55 | events[i].deleteEvent(); 56 | } 57 | Logger.log("Removed all the events"); 58 | } 59 | 60 | // 61 | // unit test for getting the date from finviz 62 | // 63 | function testGetEarningDate() { 64 | var d1 = getEarningDate("GOLD"); 65 | Logger.log("GOLD for: " + JSON.stringify(d1)); 66 | 67 | var d2 = getEarningDate("LVS"); 68 | Logger.log("LVS for: " + JSON.stringify(d2)); 69 | } 70 | 71 | // 72 | // Return the eraning date 73 | // year, month, day, hour, minute, second, millisecond 74 | // var d = new Date(99, 5, 24, 11, 33, 30, 0); 75 | // 76 | function getEarningDate(ticker) { 77 | try { 78 | var urlToFetch = "TODO" + ticker; 79 | var urlOptions = { "followRedirects" : true }; 80 | var response = UrlFetchApp.fetch(urlToFetch, urlOptions); 81 | var text = response.getContentText(); 82 | var inx0 = text.indexOf("snapshot-table2"); 83 | var inx1 = text.indexOf(">Earnings", inx0 + 20); 84 | var inx2 = text.indexOf("", inx1 + 10) + 3; 85 | var inx3 = text.indexOf("", inx2); 86 | var dateStr = text.substring(inx2, inx3); 87 | dateStr = dateStr.replace("AMC" ,""); 88 | dateStr = dateStr.replace("BMO" ,""); 89 | Logger.log("Stock: " + ticker + " | Date: " + dateStr); 90 | 91 | var dateMonth = dateStr.substring(0,3); 92 | var dateDay = dateStr.substring(4); 93 | 94 | // convert 3 letters month into a number (1-12) of the month. 95 | var dateMonthNum = "JanFebMarAprMayJunJulAugSepOctNovDec".indexOf(dateMonth) / 3 + 1 ; 96 | 97 | var curMonth = ( (new Date()).getMonth() ) + 1; // 0-11 98 | var curYear = (new Date()).getYear(); 99 | var monthsGap = dateMonthNum - curMonth; 100 | 101 | if (monthsGap >= 0 && monthsGap < 5) { 102 | var d = new Date( curYear, dateMonthNum - 1, dateDay , 22, 30, 00, 0); 103 | return d; 104 | } else { 105 | if (curMonth > 9) { 106 | // let's check if we have a month 10,11,12 or later: 1,2,3 107 | if (dateMonthNum > 9 || dateMonthNum <= 12) { 108 | var d = new Date( curYear, dateMonthNum - 1, dateDay , 22, 30, 00, 0); 109 | return d; 110 | } else { 111 | if (dateMonthNum > 0 || dateMonthNum <= 3) { 112 | var d = new Date( curYear+1, dateMonthNum - 1, dateDay , 22, 30, 00, 0); 113 | return d; 114 | } 115 | } 116 | } 117 | else { 118 | Logger.log("** Warning: The earning month is: " + dateMonth + 119 | " (and we are at: " + curMonth + 120 | ") which does not make sense - so we skip it"); 121 | } 122 | } 123 | 124 | } 125 | catch(err) { 126 | Logger.log("Err: Could not get the date from " + urlToFetch + 127 | " * Err: " + JSON.stringify(err)); 128 | } 129 | 130 | return "N/A"; 131 | } 132 | 133 | 134 | // 135 | // Helper function to get cal id/name/params 136 | // 137 | function findOurCal() { 138 | var calendar = CalendarApp.getCalendarById("TODO-email"); 139 | Logger.log('** The calendar is named "%s".', calendar.getName()); 140 | 141 | var calendars = CalendarApp.getCalendarsByName('Earnings Release'); 142 | Logger.log('Found %s matching calendars.', calendars.length); 143 | Logger.log('Our Cal: ' + JSON.stringify(calendars[0])); 144 | Logger.log("It got Id: " + calendars[0].getId()); 145 | } 146 | 147 | // 148 | // Unit test for creating a new calendar event 149 | // 150 | function testCreateEvent() { 151 | var calendar = CalendarApp.getCalendarById("TODO-email"); 152 | var d = new Date( 2015, 1, 10 , 20, 30, 00, 0); 153 | createEvent(calendar, d, "Testing 123"); 154 | } 155 | 156 | /** 157 | * Creates a calendar event for the Eraning call 158 | * For more information on using Calendar events, see 159 | * https://developers.google.com/apps-script/class_calendarevent. 160 | * @param {type} cal - our cal for this calls 161 | * @param {type} sDate - the start date for the event 162 | * @param {type} ticker - the ticker for the company 163 | * @returns nothing 164 | */ 165 | function createEvent(cal, sDate, ticker) { 166 | var title = 'Earning call: ' + ticker; 167 | var start = sDate; 168 | var end = new Date(sDate.getTime() + (30 * 60 * 1000)); 169 | var desc = 'Earning Call for ' + ticker; 170 | 171 | var event = cal.createEvent(title, start, end, { 172 | description : desc 173 | }); 174 | Logger.log("Created event: " + JSON.stringify(event)); 175 | }; 176 | 177 | /** 178 | * Creates an event, invite guests, book rooms, and sends invitation emails. 179 | * For more information on using Calendar events, see 180 | * https://developers.google.com/apps-script/class_calendarevent. 181 | */ 182 | function createEventInvitePeople() { 183 | var calId = 'your_cal_id'; 184 | var room1CalId = 'a_room_cal_id'; 185 | var room2CalId = 'another_room_cal_id'; 186 | var guest1Email = 'guest1@yourdomain.com'; 187 | var guest2Email = 'guest2@yourdomain.com'; 188 | var invitees = room1CalId + ',' + room2CalId + ',' + guest1Email + ',' + 189 | guest2Email; 190 | 191 | var cal = CalendarApp.getCalendarById(calId); 192 | var title = 'Script Center Demo Event'; 193 | var start = new Date("April 1, 2012 08:00:00 PDT"); 194 | var end = new Date("April 1, 2012 10:00:00 PDT"); 195 | var desc = 'Created using Apps Script'; 196 | var loc = 'Script Center'; 197 | var send = 'true'; 198 | 199 | var event = cal.createEvent(title, start, end, { 200 | description : desc, 201 | location : loc, 202 | guests : invitees, 203 | sendInvites : send 204 | }); 205 | }; 206 | 207 | /** 208 | * Creates an event that recurs weekly for 10 weeks. These settings 209 | * are very simple; recurring events can become quite complex. Search for 210 | * 'google apps script class recurrence' to get more details. 211 | * For more information on using Calendar events, see 212 | * https://developers.google.com/apps-script/class_calendarevent. 213 | */ 214 | function createEventSeries() { 215 | var calId = 'your_cal_id'; 216 | var cal = CalendarApp.getCalendarById(calId); 217 | var title = 'Script Center Demo Recurring Event'; 218 | var start = new Date("April 1, 2012 08:00:00 PDT"); 219 | var end = new Date("April 1, 2012 10:00:00 PDT"); 220 | var desc = 'Created using Apps Script'; 221 | var loc = 'Script Center'; 222 | 223 | var recurrence = CalendarApp.newRecurrence(); 224 | recurrence.addWeeklyRule().times(10); 225 | var series = cal.createEventSeries(title, start, end, recurrence, { 226 | description : desc, 227 | location : loc 228 | }); 229 | }; 230 | -------------------------------------------------------------------------------- /public_html/AppsScript/Gmail/FetchMailsToSheet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fetch Mails from a certain label and put them (after a little parsing) 3 | * in a nice sheet. 4 | * Later, we can work on them in the sheet or export the data (CSV, anyone?) 5 | * to work on it offline with a good/little DB. 6 | * 7 | * @Author: Ido Green | @greenido | +GreenIdo 8 | * @Date: Feb 2015 9 | */ 10 | 11 | 12 | 13 | // 14 | // Run on all the emails that we have under label: investing/News/bloomberg 15 | // Parse the news item from each email and save it into the sheet. 16 | // 17 | function fetchNews() { 18 | var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("raw-news"); 19 | 20 | var label = GmailApp.getUserLabelByName("TODO"); 21 | 22 | // We will use this meta data sheet to keep track on what is going on with the process 23 | var metaDataS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("metaData"); 24 | var curRow = ss.getLastRow() + 1; 25 | if (curRow < 2) { 26 | // Let's make sure we won't erase our headers (never). 27 | curRow = 2; 28 | } 29 | metaDataS.getRange('B3').setValue(curRow); 30 | var amountOfThreads = 10; 31 | 32 | for (var from = metaDataS.getRange('B1').getValue(); from < 1000; from = from + amountOfThreads) { 33 | metaDataS.getRange('B1').setValue(from); 34 | metaDataS.getRange('B2').setValue(from + amountOfThreads); 35 | var threads = label.getThreads(from, amountOfThreads); 36 | 37 | for (var i = 0; i < threads.length; i++) { 38 | metaDataS.getRange('B1').setValue(from + i); 39 | var message = threads[i].getMessages()[0]; 40 | var body = message.getPlainBody(); 41 | var subject = message.getSubject(); 42 | var msgDate = message.getDate(); 43 | var id = message.getId(); 44 | 45 | Logger.log(i + ") msg: " + id + " On: " + msgDate); // + " text: "+body); 46 | var items = breakNewsToItems(body); 47 | Logger.log("We got: " + items); 48 | for (var k = 1; k < items.length; k++) { 49 | ss.getRange('A' + curRow).setValue(id + "-" + k); 50 | ss.getRange('B' + curRow).setValue(msgDate); 51 | ss.getRange('C' + curRow).setValue(items[k]); 52 | curRow++; 53 | metaDataS.getRange('B3').setValue(curRow); 54 | } 55 | } 56 | } 57 | // 58 | SpreadsheetApp.getActiveSpreadsheet().toast("Hey, We are done! All the news are here.", "OK", 10); 59 | } 60 | 61 | // This is how a news Item looks like: 62 | // 63 | //x) bla-bla 64 | // 65 | // 66 | // Return: array of news items 67 | function breakNewsToItems(rawText) { 68 | items = []; 69 | for (var i = 1; i < 20; i++) { 70 | var indexStr = i + ")"; 71 | var inx1 = rawText.indexOf(indexStr); 72 | if (inx1 > 0) { 73 | var inx2 = rawText.indexOf("", inx1 + 15); 74 | var newsItem = rawText.substring(inx1, inx2); 75 | items[i] = newsItem; 76 | } 77 | else { 78 | break; 79 | } 80 | } 81 | // 82 | Logger.log("Found " + i + " Items"); 83 | return items; 84 | } 85 | -------------------------------------------------------------------------------- /public_html/AppsScript/Search/lego.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fetch lego emails from Gmail and add YT videos and images (from google search) per subject. 3 | * 4 | * @Author: Ido Green 5 | * @Date: JAN 2015 6 | * 7 | * Psst... follow the TODOs so it will work. 8 | */ 9 | 10 | // Fetch 'only' the last 300 emails in that specific label. 11 | var MAX_EMAILS = 300; 12 | 13 | /** 14 | * Main menu 15 | */ 16 | function onOpen() { 17 | var ui = SpreadsheetApp.getUi(); 18 | ui.createMenu(' לגו חלל') 19 | .addItem('הבא מיילים', 'getLegoMsg') 20 | .addToUi(); 21 | } 22 | 23 | /** 24 | * Get all the lego emails and put their subject (=url) in a cell 25 | */ 26 | function getLegoMsg() { 27 | var ss = SpreadsheetApp.getActiveSheet(); 28 | 29 | var label = GmailApp.getUserLabelByName("LEGO"); 30 | var threads = label.getThreads(0, MAX_EMAILS); 31 | var j=2; 32 | for (var i = 0; i < threads.length; i++) { 33 | var subject = threads[i].getFirstMessageSubject(); 34 | if (subject.indexOf("amazon") > 1 ) { 35 | ss.getRange('A'+j).setValue(subject); 36 | var inx1 = subject.indexOf('/', 25); 37 | var legoTitle = subject.substring(22, inx1); 38 | fetchImages(legoTitle, j, ss); 39 | fetchVideo(legoTitle, j, ss); 40 | j++; 41 | } 42 | } 43 | SpreadsheetApp.getActiveSpreadsheet().toast("Hey, We are done! All the Legos are here.", "OK :)", 10); 44 | } 45 | 46 | function fetchImages(legoTitle, curRow, ss) { 47 | var url = "https://www.googleapis.com/customsearch/v1?cx=TODO&q=" +legoTitle + 48 | "&imgSize=medium&key=TODO"; 49 | var json = UrlFetchApp.fetch(url).getContentText(); 50 | var data = JSON.parse(json); 51 | 52 | var img1 = data.items[0].pagemap.cse_image[0].src; 53 | Logger.log("Got from search: " + img1); 54 | var imgTag = '=image("' + img1 + '")'; 55 | ss.getRange('B'+curRow).setValue(imgTag); 56 | } 57 | 58 | // 59 | // 60 | // https://gdata.youtube.com/feeds/api/videos?q=lego-star-war&v=2 61 | function fetchVideo(legoTitle, curRow, ss) { 62 | var url = "https://gdata.youtube.com/feeds/api/videos?q=" + legoTitle +"&v=2&alt=json"; 63 | var json = UrlFetchApp.fetch(url).getContentText(); 64 | var data = JSON.parse(json); 65 | 66 | if (data.feed.entry === undefined) { 67 | ss.getRange('C'+curRow).setValue("No video"); 68 | } 69 | else { 70 | var video1 = data.feed.entry[0]; 71 | var video1title = video1.title.$t; 72 | var video1url = video1.link[0].href; 73 | // update the sheet 74 | ss.getRange('C'+curRow).setValue(video1title); 75 | ss.getRange('D'+curRow).setValue(video1url); 76 | ss.getRange('E'+curRow).setValue(data.feed.entry[1].link[0].href); 77 | ss.getRange('F'+curRow).setValue(data.feed.entry[2].link[0].href); 78 | } 79 | } 80 | 81 | 82 | /** 83 | * Retrieves all inbox threads and logs the respective subject lines. 84 | * For more information on using the GMail API, see 85 | * https://developers.google.com/apps-script/class_gmailapp 86 | */ 87 | function processInbox() { 88 | // get all threads in inbox 89 | var threads = GmailApp.getInboxThreads(); 90 | for (var i = 0; i < threads.length; i++) { 91 | // get all messages in a given thread 92 | var messages = threads[i].getMessages(); 93 | // iterate over each message 94 | for (var j = 0; j < messages.length; j++) { 95 | // log message subject 96 | Logger.log(messages[j].getSubject()); 97 | } 98 | } 99 | }; 100 | 101 | /** 102 | * Retrieves a given user label by name and logs the number of unread threads 103 | * associated with that that label. 104 | * For more information on interacting with GMail labels, see 105 | * https://developers.google.com/apps-script/class_gmaillabel 106 | * 107 | * @ labelName - the label we wish to work on. 108 | */ 109 | function processLabel(labelName) { 110 | // get the label for given name 111 | var label = GmailApp.getUserLabelByName(labelName); 112 | // get count of all threads in the given label 113 | var threadCount = label.getUnreadCount(); 114 | Logger.log(threadCount); 115 | }; 116 | 117 | 118 | -------------------------------------------------------------------------------- /public_html/AppsScript/UI/DatePicker.js: -------------------------------------------------------------------------------- 1 | function showDatesDialog() { 2 | var app = UiApp.createApplication(); 3 | var panel = app.createVerticalPanel(); 4 | 5 | var dateLabel1 = app.createLabel('Start Date'); 6 | var dateLabel2 = app.createLabel('End Date'); 7 | 8 | var dateBox1 = app.createDateBox().setId('date1'); 9 | var dateBox2 = app.createDateBox().setId('date2'); 10 | 11 | var button = app.createButton('Submit'); 12 | 13 | var dateInfo1 = app.createLabel().setId('dateInfo1').setVisible(false); 14 | var dateInfo2 = app.createLabel().setId('dateInfo2').setVisible(false); 15 | 16 | var handler = app.createServerClickHandler('showDates'); 17 | handler.addCallbackElement(panel); 18 | 19 | button.addClickHandler(handler); 20 | 21 | // Put all the UI elements on our main panel (like in the good old days of GWT) 22 | panel.add(dateLabel1) 23 | .add(dateBox1) 24 | .add(dateLabel2) 25 | .add(dateBox2) 26 | .add(button) 27 | .add(dateInfo1) 28 | .add(dateInfo2); 29 | 30 | app.add(panel); 31 | 32 | var ss = SpreadsheetApp.getActive(); 33 | // show the UI 34 | ss.show(app); 35 | } 36 | 37 | // 38 | // Print the dates on the UI 39 | // 40 | function showDates(e){ 41 | var app = UiApp.getActiveApplication(); 42 | // Get the elements by Ids and print the dates 43 | app.getElementById('dateInfo1').setVisible(true).setText(e.parameter.date1.toString()); 44 | app.getElementById('dateInfo2').setVisible(true).setText(e.parameter.date2.toString()); 45 | 46 | } -------------------------------------------------------------------------------- /public_html/AppsScript/UI/htmlDialog.js: -------------------------------------------------------------------------------- 1 | var start = new Date().getTime(); 2 | Logger.log("Going to fetch something"); 3 | // 4 | // do some work 5 | // 6 | var end = new Date().getTime(); 7 | var execTime = (end - start) / 1000; 8 | 9 | // Our amazing HTML5 dialog :) 10 | var htmlApp = HtmlService 11 | .createHtmlOutput('

It took us: ' + execTime + 12 | 'sec to run like crazy on the beach

') 13 | .setTitle('Tracker') 14 | .setWidth(250) 15 | .setHeight(300); 16 | 17 | SpreadsheetApp.getActiveSpreadsheet().show(htmlApp); 18 | 19 | -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/CheckSheetAndMail.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Alert when there are new memebers that sign to the group 3 | * 4 | * @author: Ido Green | @greenido 5 | * Update: 13/5/2015 6 | * 7 | */ 8 | 9 | // 10 | // Check if new member signed on our registration form and email this info on a daily basis. 11 | // 12 | function checkForNewMembers() { 13 | var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1"); 14 | var lastRow = getFirstEmptyRow(ss); 15 | 16 | var sMsgs = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("msgs"); 17 | var lastTotal = sMsgs.getRange("B25").getValue(); 18 | 19 | if (lastRow > lastTotal) { 20 | sMsgs.getRange("B25").setValue(lastRow); 21 | MailApp.sendEmail("TODO-fill-your-email", "New Members sign up", 22 | "Yo! We got " + (lastRow - lastTotal) + " new members. Please add them."); 23 | } 24 | else { 25 | Logger.log("It's all good. We still standing on " + lastRow + " members"); 26 | } 27 | } 28 | 29 | // 30 | // Get the last empty row (quickly) 31 | // 32 | function getFirstEmptyRow(spr) { 33 | var column = spr.getRange('A:A'); 34 | var values = column.getValues(); // get all data in one call 35 | var ct = 0; 36 | while ( values[ct][0] != "" ) { 37 | ct++; 38 | } 39 | return (ct); 40 | } 41 | -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/cleanSheetRange.js: -------------------------------------------------------------------------------- 1 | // 2 | // Clean the values from a sheet 3 | // 4 | function cleanSheet(sheetName, fRow, fCol, tRow, tCol) { 5 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 6 | var aSheet = ss.getSheetByName(sheetName); 7 | aSheet.getRange(fRow, fCol, tRow, tCol).clear({contentsOnly: true}); 8 | Logger.log("Done cleaning " + sheetName + " on range: (" + 9 | fRow + ", " + fCol + ", " + tRow + ", " + tCol + ")"); 10 | } -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/dateUtils.js: -------------------------------------------------------------------------------- 1 | // 2 | // return the current data in this format: mm/dd/yyyy 3 | // 4 | function getDateOfToday() { 5 | var today = new Date(); 6 | var dd = today.getDate(); 7 | var mm = today.getMonth()+1; //January is 0! 8 | 9 | var yyyy = today.getFullYear(); 10 | if(dd<10){ 11 | dd='0'+dd; 12 | } 13 | if(mm<10){ 14 | mm='0'+mm; 15 | } 16 | var todayStr = mm+'/'+dd+'/'+yyyy; 17 | return todayStr; 18 | } 19 | 20 | 21 | // 22 | // 0 = sunday , 1 = monday.... 6=SAT 23 | // 24 | function getDayOfWeek() { 25 | var d=new Date(); 26 | Logger.log(d.getDay()); 27 | return d.getDay(); 28 | } 29 | 30 | // 31 | // Return true if the current day is SUN or SAT 32 | // As we are not working on weekends (=no new data). 33 | // 34 | function isWeekend() { 35 | var curDay = getDayOfWeek(); 36 | if ( curDay === 0 || curDay === 6) { 37 | return true; 38 | } 39 | else { 40 | return false; 41 | } 42 | } -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/moneyUtils.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Format a number with -+,. 4 | // 5 | Number.prototype.formatMoney = function(c, d, t){ 6 | var n = this, 7 | c = isNaN(c = Math.abs(c)) ? 2 : c, 8 | d = (d === undefined) ? "." : d, 9 | t = (t === undefined) ? "," : t, 10 | s = n < 0 ? "-" : "", 11 | i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", 12 | j = (j = i.length) > 3 ? j % 3 : 0; 13 | return s + (j ? i.substr(0, j) + t : "") + 14 | i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + 15 | (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); 16 | }; 17 | 18 | // 19 | // clean prices from things like: $ or , 20 | // 21 | function cleanSymbolFromDigits(sym) { 22 | var re = /^[A-Za-z]+$/; 23 | var newSym = sym; 24 | if (!re.test(sym)) { 25 | newSym = sym.substring(0, sym.length-1); 26 | } 27 | return newSym; 28 | } 29 | 30 | // 31 | // Do we have a valid number 32 | // 33 | function isNumber(n) { 34 | return !isNaN(parseFloat(n)) && isFinite(n); 35 | } 36 | 37 | // 38 | // Check if we have a float 39 | // 40 | function isFloat(n) { 41 | return n === +n && n !== (n|0); 42 | } 43 | -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/parseHTML.js: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | 7 | function doGet() { 8 | var html = UrlFetchApp.fetch('http://en.wikipedia.org/wiki/Document_Object_Model').getContentText(); 9 | var doc = XmlService.parse(html); 10 | var html = doc.getRootElement(); 11 | var menu = getElementsByClassName(html, 'vertical-navbox nowraplinks')[0]; 12 | var output = XmlService.getRawFormat().format(menu); 13 | return HtmlService.createHtmlOutput(output); 14 | } 15 | 16 | //We fetch the HTML through UrlFetch 17 | //We use the XMLService to parse this HTML 18 | //Then we can use a specific function to grab the element we want in the DOM tree (like getElementsByClassName) 19 | //And we convert back this element to HTML 20 | //Or we could get all the links / anchors available in this menu and display them 21 | function doGet() { 22 | var html = UrlFetchApp.fetch('http://en.wikipedia.org/wiki/Document_Object_Model').getContentText(); 23 | var doc = XmlService.parse(html); 24 | var html = doc.getRootElement(); 25 | var menu = getElementsByClassName(html, 'vertical-navbox nowraplinks')[0]; 26 | var output = ''; 27 | var linksInMenu = getElementsByTagName(menu, 'a'); 28 | for (i in linksInMenu) 29 | output += XmlService.getRawFormat().format(linksInMenu[i]) + '
'; 30 | return HtmlService.createHtmlOutput(output); 31 | } 32 | 33 | //Available functions 34 | //Contents 35 | //1 getElementById 36 | //2 getElementsByClassName 37 | //3 getElementsByTagName 38 | 39 | //getElementById 40 | function getElementById(element, idToFind) { 41 | var descendants = element.getDescendants(); 42 | for (i in descendants) { 43 | var elt = descendants[i].asElement(); 44 | if (elt != null) { 45 | var id = elt.getAttribute('id'); 46 | if (id != null && id.getValue() == idToFind) 47 | return elt; 48 | } 49 | } 50 | } 51 | 52 | //getElementsByClassName 53 | function getElementsByClassName(element, classToFind) { 54 | var data = []; 55 | var descendants = element.getDescendants(); 56 | descendants.push(element); 57 | for (i in descendants) { 58 | var elt = descendants[i].asElement(); 59 | if (elt !== null) { 60 | var classes = elt.getAttribute('class'); 61 | if (classes !== null) { 62 | classes = classes.getValue(); 63 | if (classes === classToFind) 64 | data.push(elt); 65 | else { 66 | classes = classes.split(' '); 67 | for (j in classes) { 68 | if (classes[j] === classToFind) { 69 | data.push(elt); 70 | break; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | return data; 78 | } 79 | //getElementsByTagName 80 | function getElementsByTagName(element, tagName) { 81 | var data = []; 82 | var descendants = element.getDescendants(); 83 | for (i in descendants) { 84 | var elt = descendants[i].asElement(); 85 | if (elt !== null && elt.getName() === tagName) 86 | data.push(elt); 87 | } 88 | return data; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /public_html/AppsScript/Utils/runMeAgain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * When you wish to run a task more then once. 3 | * These 2 helper function will help you set another task (or more) to 'finish' the job. 4 | * @author Ido Green | @greenido 5 | * 6 | */ 7 | 8 | // 9 | // We will call this function to update 10 | // (!) Make sure to replace the TODO 11 | // 12 | function updateXX() { 13 | // Do some work here 14 | Logger.log("** Going to work hard & long **"); 15 | 16 | // run me again in 7min = 7*60*1000 17 | var tmpTrigger = runMeAgainInXmin(420000, "TODO-your-function-name-here"); 18 | Logger.log("our trigger for updateXX: " + tmpTrigger.getUniqueId()); 19 | 20 | // Save our triggerId so we can remove it after the execution. 21 | // It's also helpful to have all our meta data in a sheet to debug issues. 22 | SpreadsheetApp.getActiveSpreadsheet().getSheetByName("metaData").getRange(1,1).setValue(tmpTrigger.getUniqueId()); 23 | } 24 | 25 | 26 | // 27 | // Run me in X milliseconds into the future 28 | // 29 | function runMeAgainInXmin(waitForMin, functionToRun) { 30 | Logger.log("** Going to set a new trigger for: " + functionToRun + 31 | " to run in: " + waitForMin); 32 | var oneTimeOnly = ScriptApp.newTrigger(functionToRun).timeBased() 33 | .after(waitForMin).create(); 34 | return oneTimeOnly; 35 | } -------------------------------------------------------------------------------- /public_html/AppsScript/YouTube/ytStats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ido's YouTube Dashboard Stats example 3 | * Fetch stats on videos and channel by using YT APIs. 4 | 1. It using the ATOM feeds - v1. 5 | 2. For the channel we are using the v3 version of the API. 6 | 7 | * @Author: Ido Green 8 | * @Date: Aug 2014 9 | * 10 | */ 11 | 12 | 13 | /** 14 | * This function uses the YouTube Analytics API to fetch data about the 15 | * authenticated user's channel, creating a new Google Sheet in the user's Drive 16 | * with the data. 17 | * 18 | * The first part of this sample demonstrates a simple YouTube Analytics API 19 | * call. This function first fetches the active user's channel ID. Using that 20 | * ID, the function makes a YouTube Analytics API call to retrieve views, 21 | * likes, dislikes and shares for the last 30 days. The API returns the data 22 | * in a response object that contains a 2D array. 23 | * 24 | * The second part of the sample constructs a Spreadsheet. This spreadsheet 25 | * is placed in the authenticated user's Google Drive with the name 26 | * 'YouTube Report' and date range in the title. The function populates the 27 | * spreadsheet with the API response, then locks columns and rows that will 28 | * define a chart axes. A stacked column chart is added for the spreadsheet. 29 | 30 | @see: 31 | * https://developers.google.com/youtube/analytics/v1/code_samples/apps-script 32 | * https://developers.google.com/youtube/analytics/sample-requests#channel-time-based-reports 33 | * https://developers.google.com/apis-explorer/#p/youtubeAnalytics/v1/youtubeAnalytics.reports.query 34 | */ 35 | function spreadsheetAnalytics() { 36 | // Get the channel ID 37 | var myChannels = YouTube.Channels.list('id', {mine: true}); 38 | var channel = myChannels.items[0]; 39 | var channelId = channel.id; 40 | // Set the dates for our report 41 | var today = new Date(); 42 | var monthAgo12 = new Date(); 43 | monthAgo12.setMonth(today.getMonth() - 11); 44 | var todayFormatted = Utilities.formatDate(today, 'UTC', 'yyyy-MM-dd') 45 | var oneMonthAgoFormatted = Utilities.formatDate(monthAgo12, 'UTC', 'yyyy-MM-dd'); 46 | 47 | // The YouTubeAnalytics.Reports.query() function has four required parameters and one optional 48 | // parameter. The first parameter identifies the channel or content owner for which you are 49 | // retrieving data. The second and third parameters specify the start and end dates for the 50 | // report, respectively. The fourth parameter identifies the metrics that you are retrieving. 51 | // The fifth parameter is an object that contains any additional optional parameters 52 | // (dimensions, filters, sort, etc.) that you want to set. 53 | var analyticsResponse = YouTubeAnalytics.Reports.query( 54 | 'channel==' + channelId, 55 | oneMonthAgoFormatted, 56 | todayFormatted, 57 | // dimensions=day metrics=views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage,subscribersGained 58 | 'views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage,likes,dislikes,shares', 59 | { 60 | dimensions: 'day', 61 | sort: '-day' 62 | }); 63 | 64 | // Create a new Spreadsheet with rows and columns corresponding to our dates 65 | var ssName = 'YouTube channel report ' + oneMonthAgoFormatted + ' - ' + todayFormatted; 66 | var numRows = analyticsResponse.rows.length; 67 | var numCols = analyticsResponse.columnHeaders.length; 68 | 69 | // Add an extra row for column headers 70 | var ssNew = SpreadsheetApp.create(ssName, numRows + 1, numCols); 71 | 72 | // Get the first sheet 73 | var sheet = ssNew.getSheets()[0]; 74 | 75 | // Get the range for the title columns 76 | // Remember, spreadsheets are 1-indexed, whereas arrays are 0-indexed 77 | var headersRange = sheet.getRange(1, 1, 1, numCols); 78 | var headers = []; 79 | 80 | // These column headers will correspond with the metrics requested 81 | // in the initial call: views, likes, dislikes, shares 82 | for(var i in analyticsResponse.columnHeaders) { 83 | var columnHeader = analyticsResponse.columnHeaders[i]; 84 | var columnName = columnHeader.name; 85 | headers[i] = columnName; 86 | } 87 | // This takes a 2 dimensional array 88 | headersRange.setValues([headers]); 89 | 90 | // Bold and freeze the column names 91 | headersRange.setFontWeight('bold'); 92 | sheet.setFrozenRows(1); 93 | 94 | // Get the data range and set the values 95 | var dataRange = sheet.getRange(2, 1, numRows, numCols); 96 | dataRange.setValues(analyticsResponse.rows); 97 | 98 | 99 | // Bold and freeze the dates 100 | var dateHeaders = sheet.getRange(1, 1, numRows, 1); 101 | dateHeaders.setFontWeight('bold'); 102 | sheet.setFrozenColumns(1); 103 | 104 | // Include the headers in our range. The headers are used 105 | // to label the axes 106 | var range = sheet.getRange(1, 1, numRows, numCols); 107 | var chart = sheet.newChart() 108 | .asColumnChart() 109 | .setStacked() 110 | .addRange(range) 111 | .setPosition(4, 2, 10, 10) 112 | .build(); 113 | sheet.insertChart(chart); 114 | } 115 | 116 | // 117 | // A Helper function to extract the ID of our video 118 | // It works both on version of links: 119 | // 1. https://www.youtube.com/watch?v=BuHEhmp47VE 120 | // 2. http://youtu.be/75EuHl6CSTo 121 | // 122 | function extractVideoID() { 123 | var curSheet = SpreadsheetApp.getActiveSheet(); 124 | var ytLinks = curSheet.getRange("D:D"); 125 | var totalRows = ytLinks.getNumRows(); 126 | var ytVal = ytLinks.getValues(); 127 | // let's run on the rows 128 | for (var i = 1; i <= totalRows - 1; i++) { 129 | var curLink = ytVal[i][0]; 130 | if (curLink == "") { 131 | break; 132 | } 133 | 134 | var videoID = ""; 135 | var inx1 = curLink.indexOf('watch?v=') + 8; 136 | if (inx1 == 7) { 137 | // check if it's the short format: http://youtu.be/75EuHl6CSTo 138 | if (curLink != "" && curLink.indexOf("youtu.be") > 0) { 139 | videoID = curLink.substr(16, curLink.length); 140 | } 141 | } 142 | else { 143 | // we have the link in this format: https://www.youtube.com/watch?v=YIgSucMNFAo 144 | var inx2 = curLink.indexOf("&", inx1); 145 | 146 | if (inx2 > inx1) { 147 | videoID = curLink.substr(inx1, inx2-inx1); 148 | } else { 149 | videoID = curLink.substr(inx1, curLink.length); 150 | } 151 | } 152 | 153 | curSheet.getRange("E" + (i+1)).setValue(videoID); 154 | } 155 | var htmlMsg = HtmlService 156 | .createHtmlOutput('

Done - Please check the IDs on Column D:D

').setTitle('YT Dashboard Example').setWidth(450).setHeight(300); 157 | SpreadsheetApp.getActiveSpreadsheet().show(htmlMsg); 158 | } 159 | 160 | // 161 | // Run on all the rows and according to the video ID fetch the feed 162 | // 163 | function fetchAllData() { 164 | var start = new Date().getTime(); 165 | 166 | var curSheet = SpreadsheetApp.getActiveSheet(); 167 | var ytIds = curSheet.getRange("E:E"); 168 | var totalRows = ytIds.getNumRows(); 169 | var ytVal = ytIds.getValues(); 170 | var errMsg = "

Errors:

    "; 171 | // let's run on the rows after the header row 172 | for (var i = 1; i <= totalRows - 1; i++) { 173 | // e.g. for a call: https://gdata.youtube.com/feeds/api/videos/YIgSucMNFAo?v=2&prettyprint=true 174 | if (ytVal[i] === "") { 175 | Logger.log("We stopped at row: " + (i+1)); 176 | break; 177 | } 178 | var link = "https://gdata.youtube.com/feeds/api/videos/" + ytVal[i] + "?v=2&prettyprint=true"; 179 | try { 180 | fetchYTdata(link, i+1); 181 | } 182 | catch (err) { 183 | errMsg += "
  • Line: " + i + " we could not fetch data for ID: " + ytVal[i] + "
  • "; 184 | Logger.log("*** ERR: We have issue with " + ytVal[i] + " On line: " + i); 185 | } 186 | } 187 | 188 | var end = new Date().getTime(); 189 | var execTime = (end - start) / 1000; 190 | var htmlApp = HtmlService 191 | .createHtmlOutput('

    Done updating!

    It took us: '+ execTime + 'sec. to update: ' + 192 | (i+1) + ' videos

    ' + errMsg).setTitle('GStudio Rock').setWidth(450).setHeight(450); 193 | SpreadsheetApp.getActiveSpreadsheet().show(htmlApp); 194 | } 195 | 196 | 197 | // 198 | // Read YT stats data on our videos and fill the sheet with the data 199 | // 200 | function fetchYTdata(url, curRow) { 201 | //var url = 'https://gdata.youtube.com/feeds/api/videos/Eb7rzMxHyOk?v=2&prettyprint=true'; 202 | var rawData = UrlFetchApp.fetch(url).getContentText(); 203 | //Logger.log(rawData); 204 | 205 | // published 2014-05-09T06:22:52.000Z 206 | var inx1 = rawData.indexOf('published>') + 10; 207 | var inx2 = rawData.indexOf("T", inx1); 208 | var publishedDate = rawData.substr(inx1, inx2-inx1); 209 | 210 | // viewCount='16592' 211 | var inx1 = rawData.indexOf('viewCount') + 11; 212 | var inx2 = rawData.indexOf("'/>", inx1); 213 | var totalViews = rawData.substr(inx1, inx2-inx1); 214 | 215 | // 216 | var inx1 = rawData.indexOf('duration seconds') + 18; 217 | var inx2 = rawData.indexOf("'/>", inx1); 218 | var durationSec = rawData.substr(inx1, inx2-inx1); 219 | 220 | Logger.log(curRow + ") TotalViews: " + totalViews + " durationSec: " + durationSec); 221 | 222 | // update the sheet 223 | var ss = SpreadsheetApp.getActiveSheet(); 224 | ss.getRange("C" + curRow).setValue(publishedDate); 225 | ss.getRange("G" + curRow).setValue(totalViews); 226 | ss.getRange("H" + curRow).setValue(durationSec); 227 | 228 | } 229 | 230 | // 231 | // Our custom menu 232 | // 233 | function onOpen() { 234 | var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); 235 | var entries = [{ name : "Update Stats", functionName : "fetchAllData"}, 236 | { name : "Extract Video IDs", functionName : "extractVideoID"} 237 | ]; 238 | spreadsheet.addMenu("YT Dashboard", entries); 239 | }; 240 | 241 | 242 | ////////////////////////////////////////////////////////////////////////////////////////// 243 | ///////// Version 2.0 with YT Analytics --> should move it to another file /////////////// 244 | ////////////////////////////////////////////////////////////////////////////////////////// 245 | 246 | /** 247 | * GStudio Stats 248 | * Fetch stats on our videos by using YT ATOM feeds. 249 | * 250 | * @Author: Ido Green 251 | * @Date: July 2014 252 | * 253 | * @see: https://github.com/greenido/AppsScriptBests 254 | * https://developers.google.com/youtube/analytics/v1/code_samples/apps-script 255 | * https://developers.google.com/youtube/analytics/sample-requests#channel-time-based-reports 256 | * https://developers.google.com/apis-explorer/#p/youtubeAnalytics/v1/youtubeAnalytics.reports.query 257 | 258 | * Older doc on working with YT API: 259 | * https://docs.google.com/a/google.com/document/d/1Htgm9LieOWe-DHxAWzpKxc5qfjNZm79jGSyy9BXl1Xc/edit 260 | */ 261 | 262 | 263 | 264 | function testGetVideoEstimatedMinutesWatched() { 265 | var coltPerf = getVideoEstimatedMinutesWatched('zVK6TKSx1lU'); 266 | Logger.log("colt perf video: \n" + coltPerf); 267 | } 268 | 269 | // 270 | // Fetch the estimated Minutes Watched on specific video 271 | // ToDo: 272 | // gain more metrics for example: views,estimatedMinutesWatched,averageViewDuration,likes,dislikes,shares 273 | function getVideoEstimatedMinutesWatched(videoId) { 274 | var myChannels = YouTube.Channels.list('id', {mine: true}); 275 | var channel = myChannels.items[0]; 276 | var channelId = channel.id; 277 | 278 | if (channelId) { 279 | var today = new Date(); 280 | var monthAgo12 = new Date(); 281 | monthAgo12.setMonth(today.getMonth() - 11); 282 | var todayFormatted = Utilities.formatDate(today, 'UTC', 'yyyy-MM-dd') 283 | var MonthAgo12Formatted = Utilities.formatDate(monthAgo12, 'UTC', 'yyyy-MM-dd'); 284 | 285 | var analyticsResponse = YouTubeAnalytics.Reports.query( 286 | 'channel==' + channelId, 287 | MonthAgo12Formatted, 288 | todayFormatted, 289 | 'views,estimatedMinutesWatched', 290 | { 291 | dimensions: 'video', 292 | filters: 'video==' + videoId 293 | }); 294 | 295 | //Logger.log("analytics for " + videoId + ": \n " +analyticsResponse.rows); 296 | return analyticsResponse.rows[0]; 297 | } 298 | else { 299 | return "N/A"; 300 | } 301 | } 302 | 303 | // 304 | // We will use this function inorder to extract the IDs of the video out of YT links. 305 | // It's working with both long/short version of youtube. 306 | // 307 | // The function works on column J as the source and column K as the output. 308 | // 309 | function extractVideoID() { 310 | var curSheet = SpreadsheetApp.getActiveSheet(); 311 | var ytLinks = curSheet.getRange("J:J"); 312 | var totalRows = ytLinks.getNumRows(); 313 | var ytVal = ytLinks.getValues(); 314 | // let's run on the rows 315 | for (var i = 1; i <= totalRows - 1; i++) { 316 | var curLink = ytVal[i][0]; 317 | var videoID = ""; 318 | var inx1 = curLink.indexOf('watch?v=') + 8; 319 | if (inx1 == 7) { 320 | // check if it's the short format: http://youtu.be/75EuHl6CSTo 321 | if (curLink != "" && curLink.indexOf("youtu.be") > 0) { 322 | videoID = curLink.substr(16, curLink.length); 323 | } 324 | } 325 | else { 326 | // we have the link in this format: https://www.youtube.com/watch?v=BuHEhmp47VE 327 | var inx2 = curLink.indexOf("&", inx1); 328 | 329 | if (inx2 > inx1) { 330 | videoID = curLink.substr(inx1, inx2-inx1); 331 | } else { 332 | videoID = curLink.substr(inx1, curLink.length); 333 | } 334 | } 335 | 336 | curSheet.getRange("K" + (i+1)).setValue(videoID); 337 | } 338 | var htmlMsg = HtmlService 339 | .createHtmlOutput('

    Done - Please check the IDs on Column K:K

    ').setTitle('GStudio').setWidth(450).setHeight(300); 340 | SpreadsheetApp.getActiveSpreadsheet().show(htmlMsg); 341 | } 342 | 343 | // 344 | // A function we will use to run on a daily bases to refresh the stats 345 | // 346 | function fetchDataQ1Q2Q3() { 347 | 348 | var q3 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q3 - 2014"); 349 | Logger.log("== Working on " + q3.getName()); 350 | fetchAllData(q3); 351 | 352 | var q2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q2 - 2014"); 353 | Logger.log("== Working on " + q2.getName()); 354 | fetchAllData(q2); 355 | 356 | var q1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q1 - 2014"); 357 | Logger.log("== Working on " + q1.getName()); 358 | fetchAllData(q1); 359 | } 360 | 361 | // 362 | // CronJob helper function to by pass the 5min limit that: fetchDataQ1Q2Q3() got us into. 363 | // 364 | function fetchDataCronJobQ1_2014() { 365 | var q = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q1 - 2014"); 366 | Logger.log("== Working on " + q.getName()); 367 | fetchAllData(q); 368 | } 369 | function fetchDataCronJobQ2_2014() { 370 | var q = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q2 - 2014"); 371 | Logger.log("== Working on " + q.getName()); 372 | fetchAllData(q); 373 | } 374 | function fetchDataCronJobQ3_2014() { 375 | var q = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Q3 - 2014"); 376 | Logger.log("== Working on " + q.getName()); 377 | fetchAllData(q); 378 | } 379 | // 380 | // Run on all the rows and according to the video IDs fetch the feed of 381 | // YT API v1 and extract from it: 382 | // 1. Date published. 383 | // 2. Views. 384 | // 3. Duration in seconds. 385 | // 386 | function fetchAllData(cSheet) { 387 | var start = new Date().getTime(); 388 | 389 | var curSheet = cSheet; 390 | var doWeHaveUser = false; 391 | if (curSheet == null || curSheet == undefined) { 392 | curSheet = SpreadsheetApp.getActiveSheet(); 393 | doWeHaveUser = true; 394 | } 395 | var ytIds = curSheet.getRange("K:K"); 396 | var totalRows = ytIds.getNumRows(); 397 | var ytVal = ytIds.getValues(); 398 | var errMsg = "

    Errors on " + curSheet.getName() + " :

      "; 399 | // let's run on the rows after the header row 400 | for (var i = 1; i <= totalRows - 1; i++) { 401 | // e.g. for a call: https://gdata.youtube.com/feeds/api/videos/Eb7rzMxHyOk?v=2&prettyprint=true 402 | if (ytVal[i] == "") { 403 | Logger.log("We stopped at row: " + (i+1)); 404 | break; 405 | } 406 | var link = "https://gdata.youtube.com/feeds/api/videos/" + ytVal[i] + "?v=2&prettyprint=true"; 407 | try { 408 | fetchYTdata(link, i+1, curSheet); 409 | } 410 | catch (err) { 411 | errMsg += "
    • Line: " + i + " we could not fetch data for ID: " + ytVal[i] + "
    • "; 412 | Logger.log("*** ERR: We have issue with " + ytVal[i] + " On line: " + i + " with err: " + JSON.stringify(err)); 413 | } 414 | } 415 | 416 | var end = new Date().getTime(); 417 | var execTime = (end - start) / 1000; 418 | if (doWeHaveUser) { 419 | var htmlApp = HtmlService.createHtmlOutput('

      Done updating!

      It took us: '+ execTime + 'sec. to update: ' + 420 | (i+1) + ' videos

      ' + errMsg).setTitle('GStudio Rock').setWidth(450).setHeight(450); 421 | SpreadsheetApp.getActiveSpreadsheet().show(htmlApp); 422 | } 423 | 424 | // Keeping us updated 425 | MailApp.sendEmail("btollefson@google.com", "GDStudio Tracking Daily Updater Status", 426 | "We've just updated all the stats on GDStudio Tracking (go/gdstudio-tracking). " + errMsg ); 427 | } 428 | 429 | 430 | // 431 | // Read YT stats data on our videos and fill the sheet with the data: 432 | // 1. Date published - Column I 433 | // 2. Views - Column Q 434 | // 3. Duration in seconds - Column R 435 | // 436 | function fetchYTdata(url, curRow, ss) { 437 | // fetch the estimated min watched 438 | var videoId = ss.getRange("K" + curRow).getValue(); 439 | var stats = getVideoEstimatedMinutesWatched(videoId); 440 | var totalViews = stats[1]; 441 | var estimatedMin = stats[2]; 442 | ss.getRange("Q" + curRow).setValue(totalViews); 443 | ss.getRange("V" + curRow).setValue(estimatedMin); 444 | 445 | Logger.log(curRow + ") TotalViews: " + totalViews + " estimatedMin: " + estimatedMin); 446 | 447 | // OLD CODE just for length of video and publish date: 448 | //var url = 'https://gdata.youtube.com/feeds/api/videos/Eb7rzMxHyOk?v=2&prettyprint=true'; 449 | var rawData = UrlFetchApp.fetch(url).getContentText(); 450 | //Logger.log(rawData); 451 | 452 | // published 2014-05-09T06:22:52.000Z 453 | var inx1 = rawData.indexOf('published>') + 10; 454 | var inx2 = rawData.indexOf("T", inx1); 455 | var publishedDate = rawData.substr(inx1, inx2-inx1); 456 | 457 | // viewCount='16592' 458 | // var inx1 = rawData.indexOf('viewCount') + 11; 459 | // var inx2 = rawData.indexOf("'/>", inx1); 460 | // var totalViews = rawData.substr(inx1, inx2-inx1); 461 | 462 | // 463 | var inx1 = rawData.indexOf('duration seconds') + 18; 464 | var inx2 = rawData.indexOf("'/>", inx1); 465 | var durationSec = rawData.substr(inx1, inx2-inx1); 466 | 467 | Logger.log(curRow + ") durationSec: " + durationSec); 468 | 469 | // update the sheet 470 | ss.getRange("I" + curRow).setValue(publishedDate); 471 | //ss.getRange("Q" + curRow).setValue(totalViews); 472 | ss.getRange("R" + curRow).setValue(durationSec); 473 | 474 | 475 | 476 | } 477 | 478 | 479 | -------------------------------------------------------------------------------- /public_html/ListOfScripts.md: -------------------------------------------------------------------------------- 1 | * [Apps](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps) 2 | * [Apps/EventManager.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/EventManager.js) 3 | * [Apps/EventsDateManager.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/EventsDateManager.js) 4 | * [Apps/Insta2Drive.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/Insta2Drive.js) 5 | * [Apps/SiteMonitor.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Apps/SiteMonitor.js) 6 | * [Gcal](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Gcal) 7 | * [Gcal/EarningCalls.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Gcal/EarningCalls.js) 8 | * [Search](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Search) 9 | * [Search/lego.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Search/lego.js) 10 | * [UI](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI) 11 | * [UI/DatePicker.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI/DatePicker.js) 12 | * [UI/htmlDialog.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/UI/htmlDialog.js) 13 | * [Utils](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils) 14 | * [Utils/cleanSheetRange.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/cleanSheetRange.js) 15 | * [Utils/dateUtils.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/dateUtils.js) 16 | * [Utils/moneyUtils.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/moneyUtils.js) 17 | * [Utils/parseHTML.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/parseHTML.js) 18 | * [Utils/runMeAgain.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/Utils/runMeAgain.js) 19 | * [YouTube](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/YouTube) 20 | * [YouTube/ytStats.js](https://github.com/greenido/AppsScriptBests/tree/master/public_html/AppsScript/YouTube/ytStats.js) 21 | -------------------------------------------------------------------------------- /public_html/css/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2013 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat. 9 | */ 10 | 11 | .clearfix { 12 | *zoom: 1; 13 | } 14 | 15 | .clearfix:before, 16 | .clearfix:after { 17 | display: table; 18 | line-height: 0; 19 | content: ""; 20 | } 21 | 22 | .clearfix:after { 23 | clear: both; 24 | } 25 | 26 | .hide-text { 27 | font: 0/0 a; 28 | color: transparent; 29 | text-shadow: none; 30 | background-color: transparent; 31 | border: 0; 32 | } 33 | 34 | .input-block-level { 35 | display: block; 36 | width: 100%; 37 | min-height: 30px; 38 | -webkit-box-sizing: border-box; 39 | -moz-box-sizing: border-box; 40 | box-sizing: border-box; 41 | } 42 | 43 | @-ms-viewport { 44 | width: device-width; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | .visible-print { 99 | display: none !important; 100 | } 101 | 102 | @media print { 103 | .visible-print { 104 | display: inherit !important; 105 | } 106 | .hidden-print { 107 | display: none !important; 108 | } 109 | } 110 | 111 | @media (min-width: 1200px) { 112 | .row { 113 | margin-left: -30px; 114 | *zoom: 1; 115 | } 116 | .row:before, 117 | .row:after { 118 | display: table; 119 | line-height: 0; 120 | content: ""; 121 | } 122 | .row:after { 123 | clear: both; 124 | } 125 | [class*="span"] { 126 | float: left; 127 | min-height: 1px; 128 | margin-left: 30px; 129 | } 130 | .container, 131 | .navbar-static-top .container, 132 | .navbar-fixed-top .container, 133 | .navbar-fixed-bottom .container { 134 | width: 1170px; 135 | } 136 | .span12 { 137 | width: 1170px; 138 | } 139 | .span11 { 140 | width: 1070px; 141 | } 142 | .span10 { 143 | width: 970px; 144 | } 145 | .span9 { 146 | width: 870px; 147 | } 148 | .span8 { 149 | width: 770px; 150 | } 151 | .span7 { 152 | width: 670px; 153 | } 154 | .span6 { 155 | width: 570px; 156 | } 157 | .span5 { 158 | width: 470px; 159 | } 160 | .span4 { 161 | width: 370px; 162 | } 163 | .span3 { 164 | width: 270px; 165 | } 166 | .span2 { 167 | width: 170px; 168 | } 169 | .span1 { 170 | width: 70px; 171 | } 172 | .offset12 { 173 | margin-left: 1230px; 174 | } 175 | .offset11 { 176 | margin-left: 1130px; 177 | } 178 | .offset10 { 179 | margin-left: 1030px; 180 | } 181 | .offset9 { 182 | margin-left: 930px; 183 | } 184 | .offset8 { 185 | margin-left: 830px; 186 | } 187 | .offset7 { 188 | margin-left: 730px; 189 | } 190 | .offset6 { 191 | margin-left: 630px; 192 | } 193 | .offset5 { 194 | margin-left: 530px; 195 | } 196 | .offset4 { 197 | margin-left: 430px; 198 | } 199 | .offset3 { 200 | margin-left: 330px; 201 | } 202 | .offset2 { 203 | margin-left: 230px; 204 | } 205 | .offset1 { 206 | margin-left: 130px; 207 | } 208 | .row-fluid { 209 | width: 100%; 210 | *zoom: 1; 211 | } 212 | .row-fluid:before, 213 | .row-fluid:after { 214 | display: table; 215 | line-height: 0; 216 | content: ""; 217 | } 218 | .row-fluid:after { 219 | clear: both; 220 | } 221 | .row-fluid [class*="span"] { 222 | display: block; 223 | float: left; 224 | width: 100%; 225 | min-height: 30px; 226 | margin-left: 2.564102564102564%; 227 | *margin-left: 2.5109110747408616%; 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | box-sizing: border-box; 231 | } 232 | .row-fluid [class*="span"]:first-child { 233 | margin-left: 0; 234 | } 235 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 236 | margin-left: 2.564102564102564%; 237 | } 238 | .row-fluid .span12 { 239 | width: 100%; 240 | *width: 99.94680851063829%; 241 | } 242 | .row-fluid .span11 { 243 | width: 91.45299145299145%; 244 | *width: 91.39979996362975%; 245 | } 246 | .row-fluid .span10 { 247 | width: 82.90598290598291%; 248 | *width: 82.8527914166212%; 249 | } 250 | .row-fluid .span9 { 251 | width: 74.35897435897436%; 252 | *width: 74.30578286961266%; 253 | } 254 | .row-fluid .span8 { 255 | width: 65.81196581196582%; 256 | *width: 65.75877432260411%; 257 | } 258 | .row-fluid .span7 { 259 | width: 57.26495726495726%; 260 | *width: 57.21176577559556%; 261 | } 262 | .row-fluid .span6 { 263 | width: 48.717948717948715%; 264 | *width: 48.664757228587014%; 265 | } 266 | .row-fluid .span5 { 267 | width: 40.17094017094017%; 268 | *width: 40.11774868157847%; 269 | } 270 | .row-fluid .span4 { 271 | width: 31.623931623931625%; 272 | *width: 31.570740134569924%; 273 | } 274 | .row-fluid .span3 { 275 | width: 23.076923076923077%; 276 | *width: 23.023731587561375%; 277 | } 278 | .row-fluid .span2 { 279 | width: 14.52991452991453%; 280 | *width: 14.476723040552828%; 281 | } 282 | .row-fluid .span1 { 283 | width: 5.982905982905983%; 284 | *width: 5.929714493544281%; 285 | } 286 | .row-fluid .offset12 { 287 | margin-left: 105.12820512820512%; 288 | *margin-left: 105.02182214948171%; 289 | } 290 | .row-fluid .offset12:first-child { 291 | margin-left: 102.56410256410257%; 292 | *margin-left: 102.45771958537915%; 293 | } 294 | .row-fluid .offset11 { 295 | margin-left: 96.58119658119658%; 296 | *margin-left: 96.47481360247316%; 297 | } 298 | .row-fluid .offset11:first-child { 299 | margin-left: 94.01709401709402%; 300 | *margin-left: 93.91071103837061%; 301 | } 302 | .row-fluid .offset10 { 303 | margin-left: 88.03418803418803%; 304 | *margin-left: 87.92780505546462%; 305 | } 306 | .row-fluid .offset10:first-child { 307 | margin-left: 85.47008547008548%; 308 | *margin-left: 85.36370249136206%; 309 | } 310 | .row-fluid .offset9 { 311 | margin-left: 79.48717948717949%; 312 | *margin-left: 79.38079650845607%; 313 | } 314 | .row-fluid .offset9:first-child { 315 | margin-left: 76.92307692307693%; 316 | *margin-left: 76.81669394435352%; 317 | } 318 | .row-fluid .offset8 { 319 | margin-left: 70.94017094017094%; 320 | *margin-left: 70.83378796144753%; 321 | } 322 | .row-fluid .offset8:first-child { 323 | margin-left: 68.37606837606839%; 324 | *margin-left: 68.26968539734497%; 325 | } 326 | .row-fluid .offset7 { 327 | margin-left: 62.393162393162385%; 328 | *margin-left: 62.28677941443899%; 329 | } 330 | .row-fluid .offset7:first-child { 331 | margin-left: 59.82905982905982%; 332 | *margin-left: 59.72267685033642%; 333 | } 334 | .row-fluid .offset6 { 335 | margin-left: 53.84615384615384%; 336 | *margin-left: 53.739770867430444%; 337 | } 338 | .row-fluid .offset6:first-child { 339 | margin-left: 51.28205128205128%; 340 | *margin-left: 51.175668303327875%; 341 | } 342 | .row-fluid .offset5 { 343 | margin-left: 45.299145299145295%; 344 | *margin-left: 45.1927623204219%; 345 | } 346 | .row-fluid .offset5:first-child { 347 | margin-left: 42.73504273504273%; 348 | *margin-left: 42.62865975631933%; 349 | } 350 | .row-fluid .offset4 { 351 | margin-left: 36.75213675213675%; 352 | *margin-left: 36.645753773413354%; 353 | } 354 | .row-fluid .offset4:first-child { 355 | margin-left: 34.18803418803419%; 356 | *margin-left: 34.081651209310785%; 357 | } 358 | .row-fluid .offset3 { 359 | margin-left: 28.205128205128204%; 360 | *margin-left: 28.0987452264048%; 361 | } 362 | .row-fluid .offset3:first-child { 363 | margin-left: 25.641025641025642%; 364 | *margin-left: 25.53464266230224%; 365 | } 366 | .row-fluid .offset2 { 367 | margin-left: 19.65811965811966%; 368 | *margin-left: 19.551736679396257%; 369 | } 370 | .row-fluid .offset2:first-child { 371 | margin-left: 17.094017094017094%; 372 | *margin-left: 16.98763411529369%; 373 | } 374 | .row-fluid .offset1 { 375 | margin-left: 11.11111111111111%; 376 | *margin-left: 11.004728132387708%; 377 | } 378 | .row-fluid .offset1:first-child { 379 | margin-left: 8.547008547008547%; 380 | *margin-left: 8.440625568285142%; 381 | } 382 | input, 383 | textarea, 384 | .uneditable-input { 385 | margin-left: 0; 386 | } 387 | .controls-row [class*="span"] + [class*="span"] { 388 | margin-left: 30px; 389 | } 390 | input.span12, 391 | textarea.span12, 392 | .uneditable-input.span12 { 393 | width: 1156px; 394 | } 395 | input.span11, 396 | textarea.span11, 397 | .uneditable-input.span11 { 398 | width: 1056px; 399 | } 400 | input.span10, 401 | textarea.span10, 402 | .uneditable-input.span10 { 403 | width: 956px; 404 | } 405 | input.span9, 406 | textarea.span9, 407 | .uneditable-input.span9 { 408 | width: 856px; 409 | } 410 | input.span8, 411 | textarea.span8, 412 | .uneditable-input.span8 { 413 | width: 756px; 414 | } 415 | input.span7, 416 | textarea.span7, 417 | .uneditable-input.span7 { 418 | width: 656px; 419 | } 420 | input.span6, 421 | textarea.span6, 422 | .uneditable-input.span6 { 423 | width: 556px; 424 | } 425 | input.span5, 426 | textarea.span5, 427 | .uneditable-input.span5 { 428 | width: 456px; 429 | } 430 | input.span4, 431 | textarea.span4, 432 | .uneditable-input.span4 { 433 | width: 356px; 434 | } 435 | input.span3, 436 | textarea.span3, 437 | .uneditable-input.span3 { 438 | width: 256px; 439 | } 440 | input.span2, 441 | textarea.span2, 442 | .uneditable-input.span2 { 443 | width: 156px; 444 | } 445 | input.span1, 446 | textarea.span1, 447 | .uneditable-input.span1 { 448 | width: 56px; 449 | } 450 | .thumbnails { 451 | margin-left: -30px; 452 | } 453 | .thumbnails > li { 454 | margin-left: 30px; 455 | } 456 | .row-fluid .thumbnails { 457 | margin-left: 0; 458 | } 459 | } 460 | 461 | @media (min-width: 768px) and (max-width: 979px) { 462 | .row { 463 | margin-left: -20px; 464 | *zoom: 1; 465 | } 466 | .row:before, 467 | .row:after { 468 | display: table; 469 | line-height: 0; 470 | content: ""; 471 | } 472 | .row:after { 473 | clear: both; 474 | } 475 | [class*="span"] { 476 | float: left; 477 | min-height: 1px; 478 | margin-left: 20px; 479 | } 480 | .container, 481 | .navbar-static-top .container, 482 | .navbar-fixed-top .container, 483 | .navbar-fixed-bottom .container { 484 | width: 724px; 485 | } 486 | .span12 { 487 | width: 724px; 488 | } 489 | .span11 { 490 | width: 662px; 491 | } 492 | .span10 { 493 | width: 600px; 494 | } 495 | .span9 { 496 | width: 538px; 497 | } 498 | .span8 { 499 | width: 476px; 500 | } 501 | .span7 { 502 | width: 414px; 503 | } 504 | .span6 { 505 | width: 352px; 506 | } 507 | .span5 { 508 | width: 290px; 509 | } 510 | .span4 { 511 | width: 228px; 512 | } 513 | .span3 { 514 | width: 166px; 515 | } 516 | .span2 { 517 | width: 104px; 518 | } 519 | .span1 { 520 | width: 42px; 521 | } 522 | .offset12 { 523 | margin-left: 764px; 524 | } 525 | .offset11 { 526 | margin-left: 702px; 527 | } 528 | .offset10 { 529 | margin-left: 640px; 530 | } 531 | .offset9 { 532 | margin-left: 578px; 533 | } 534 | .offset8 { 535 | margin-left: 516px; 536 | } 537 | .offset7 { 538 | margin-left: 454px; 539 | } 540 | .offset6 { 541 | margin-left: 392px; 542 | } 543 | .offset5 { 544 | margin-left: 330px; 545 | } 546 | .offset4 { 547 | margin-left: 268px; 548 | } 549 | .offset3 { 550 | margin-left: 206px; 551 | } 552 | .offset2 { 553 | margin-left: 144px; 554 | } 555 | .offset1 { 556 | margin-left: 82px; 557 | } 558 | .row-fluid { 559 | width: 100%; 560 | *zoom: 1; 561 | } 562 | .row-fluid:before, 563 | .row-fluid:after { 564 | display: table; 565 | line-height: 0; 566 | content: ""; 567 | } 568 | .row-fluid:after { 569 | clear: both; 570 | } 571 | .row-fluid [class*="span"] { 572 | display: block; 573 | float: left; 574 | width: 100%; 575 | min-height: 30px; 576 | margin-left: 2.7624309392265194%; 577 | *margin-left: 2.709239449864817%; 578 | -webkit-box-sizing: border-box; 579 | -moz-box-sizing: border-box; 580 | box-sizing: border-box; 581 | } 582 | .row-fluid [class*="span"]:first-child { 583 | margin-left: 0; 584 | } 585 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 586 | margin-left: 2.7624309392265194%; 587 | } 588 | .row-fluid .span12 { 589 | width: 100%; 590 | *width: 99.94680851063829%; 591 | } 592 | .row-fluid .span11 { 593 | width: 91.43646408839778%; 594 | *width: 91.38327259903608%; 595 | } 596 | .row-fluid .span10 { 597 | width: 82.87292817679558%; 598 | *width: 82.81973668743387%; 599 | } 600 | .row-fluid .span9 { 601 | width: 74.30939226519337%; 602 | *width: 74.25620077583166%; 603 | } 604 | .row-fluid .span8 { 605 | width: 65.74585635359117%; 606 | *width: 65.69266486422946%; 607 | } 608 | .row-fluid .span7 { 609 | width: 57.18232044198895%; 610 | *width: 57.12912895262725%; 611 | } 612 | .row-fluid .span6 { 613 | width: 48.61878453038674%; 614 | *width: 48.56559304102504%; 615 | } 616 | .row-fluid .span5 { 617 | width: 40.05524861878453%; 618 | *width: 40.00205712942283%; 619 | } 620 | .row-fluid .span4 { 621 | width: 31.491712707182323%; 622 | *width: 31.43852121782062%; 623 | } 624 | .row-fluid .span3 { 625 | width: 22.92817679558011%; 626 | *width: 22.87498530621841%; 627 | } 628 | .row-fluid .span2 { 629 | width: 14.3646408839779%; 630 | *width: 14.311449394616199%; 631 | } 632 | .row-fluid .span1 { 633 | width: 5.801104972375691%; 634 | *width: 5.747913483013988%; 635 | } 636 | .row-fluid .offset12 { 637 | margin-left: 105.52486187845304%; 638 | *margin-left: 105.41847889972962%; 639 | } 640 | .row-fluid .offset12:first-child { 641 | margin-left: 102.76243093922652%; 642 | *margin-left: 102.6560479605031%; 643 | } 644 | .row-fluid .offset11 { 645 | margin-left: 96.96132596685082%; 646 | *margin-left: 96.8549429881274%; 647 | } 648 | .row-fluid .offset11:first-child { 649 | margin-left: 94.1988950276243%; 650 | *margin-left: 94.09251204890089%; 651 | } 652 | .row-fluid .offset10 { 653 | margin-left: 88.39779005524862%; 654 | *margin-left: 88.2914070765252%; 655 | } 656 | .row-fluid .offset10:first-child { 657 | margin-left: 85.6353591160221%; 658 | *margin-left: 85.52897613729868%; 659 | } 660 | .row-fluid .offset9 { 661 | margin-left: 79.8342541436464%; 662 | *margin-left: 79.72787116492299%; 663 | } 664 | .row-fluid .offset9:first-child { 665 | margin-left: 77.07182320441989%; 666 | *margin-left: 76.96544022569647%; 667 | } 668 | .row-fluid .offset8 { 669 | margin-left: 71.2707182320442%; 670 | *margin-left: 71.16433525332079%; 671 | } 672 | .row-fluid .offset8:first-child { 673 | margin-left: 68.50828729281768%; 674 | *margin-left: 68.40190431409427%; 675 | } 676 | .row-fluid .offset7 { 677 | margin-left: 62.70718232044199%; 678 | *margin-left: 62.600799341718584%; 679 | } 680 | .row-fluid .offset7:first-child { 681 | margin-left: 59.94475138121547%; 682 | *margin-left: 59.838368402492065%; 683 | } 684 | .row-fluid .offset6 { 685 | margin-left: 54.14364640883978%; 686 | *margin-left: 54.037263430116376%; 687 | } 688 | .row-fluid .offset6:first-child { 689 | margin-left: 51.38121546961326%; 690 | *margin-left: 51.27483249088986%; 691 | } 692 | .row-fluid .offset5 { 693 | margin-left: 45.58011049723757%; 694 | *margin-left: 45.47372751851417%; 695 | } 696 | .row-fluid .offset5:first-child { 697 | margin-left: 42.81767955801105%; 698 | *margin-left: 42.71129657928765%; 699 | } 700 | .row-fluid .offset4 { 701 | margin-left: 37.01657458563536%; 702 | *margin-left: 36.91019160691196%; 703 | } 704 | .row-fluid .offset4:first-child { 705 | margin-left: 34.25414364640884%; 706 | *margin-left: 34.14776066768544%; 707 | } 708 | .row-fluid .offset3 { 709 | margin-left: 28.45303867403315%; 710 | *margin-left: 28.346655695309746%; 711 | } 712 | .row-fluid .offset3:first-child { 713 | margin-left: 25.69060773480663%; 714 | *margin-left: 25.584224756083227%; 715 | } 716 | .row-fluid .offset2 { 717 | margin-left: 19.88950276243094%; 718 | *margin-left: 19.783119783707537%; 719 | } 720 | .row-fluid .offset2:first-child { 721 | margin-left: 17.12707182320442%; 722 | *margin-left: 17.02068884448102%; 723 | } 724 | .row-fluid .offset1 { 725 | margin-left: 11.32596685082873%; 726 | *margin-left: 11.219583872105325%; 727 | } 728 | .row-fluid .offset1:first-child { 729 | margin-left: 8.56353591160221%; 730 | *margin-left: 8.457152932878806%; 731 | } 732 | input, 733 | textarea, 734 | .uneditable-input { 735 | margin-left: 0; 736 | } 737 | .controls-row [class*="span"] + [class*="span"] { 738 | margin-left: 20px; 739 | } 740 | input.span12, 741 | textarea.span12, 742 | .uneditable-input.span12 { 743 | width: 710px; 744 | } 745 | input.span11, 746 | textarea.span11, 747 | .uneditable-input.span11 { 748 | width: 648px; 749 | } 750 | input.span10, 751 | textarea.span10, 752 | .uneditable-input.span10 { 753 | width: 586px; 754 | } 755 | input.span9, 756 | textarea.span9, 757 | .uneditable-input.span9 { 758 | width: 524px; 759 | } 760 | input.span8, 761 | textarea.span8, 762 | .uneditable-input.span8 { 763 | width: 462px; 764 | } 765 | input.span7, 766 | textarea.span7, 767 | .uneditable-input.span7 { 768 | width: 400px; 769 | } 770 | input.span6, 771 | textarea.span6, 772 | .uneditable-input.span6 { 773 | width: 338px; 774 | } 775 | input.span5, 776 | textarea.span5, 777 | .uneditable-input.span5 { 778 | width: 276px; 779 | } 780 | input.span4, 781 | textarea.span4, 782 | .uneditable-input.span4 { 783 | width: 214px; 784 | } 785 | input.span3, 786 | textarea.span3, 787 | .uneditable-input.span3 { 788 | width: 152px; 789 | } 790 | input.span2, 791 | textarea.span2, 792 | .uneditable-input.span2 { 793 | width: 90px; 794 | } 795 | input.span1, 796 | textarea.span1, 797 | .uneditable-input.span1 { 798 | width: 28px; 799 | } 800 | } 801 | 802 | @media (max-width: 767px) { 803 | body { 804 | padding-right: 20px; 805 | padding-left: 20px; 806 | } 807 | .navbar-fixed-top, 808 | .navbar-fixed-bottom, 809 | .navbar-static-top { 810 | margin-right: -20px; 811 | margin-left: -20px; 812 | } 813 | .container-fluid { 814 | padding: 0; 815 | } 816 | .dl-horizontal dt { 817 | float: none; 818 | width: auto; 819 | clear: none; 820 | text-align: left; 821 | } 822 | .dl-horizontal dd { 823 | margin-left: 0; 824 | } 825 | .container { 826 | width: auto; 827 | } 828 | .row-fluid { 829 | width: 100%; 830 | } 831 | .row, 832 | .thumbnails { 833 | margin-left: 0; 834 | } 835 | .thumbnails > li { 836 | float: none; 837 | margin-left: 0; 838 | } 839 | [class*="span"], 840 | .uneditable-input[class*="span"], 841 | .row-fluid [class*="span"] { 842 | display: block; 843 | float: none; 844 | width: 100%; 845 | margin-left: 0; 846 | -webkit-box-sizing: border-box; 847 | -moz-box-sizing: border-box; 848 | box-sizing: border-box; 849 | } 850 | .span12, 851 | .row-fluid .span12 { 852 | width: 100%; 853 | -webkit-box-sizing: border-box; 854 | -moz-box-sizing: border-box; 855 | box-sizing: border-box; 856 | } 857 | .row-fluid [class*="offset"]:first-child { 858 | margin-left: 0; 859 | } 860 | .input-large, 861 | .input-xlarge, 862 | .input-xxlarge, 863 | input[class*="span"], 864 | select[class*="span"], 865 | textarea[class*="span"], 866 | .uneditable-input { 867 | display: block; 868 | width: 100%; 869 | min-height: 30px; 870 | -webkit-box-sizing: border-box; 871 | -moz-box-sizing: border-box; 872 | box-sizing: border-box; 873 | } 874 | .input-prepend input, 875 | .input-append input, 876 | .input-prepend input[class*="span"], 877 | .input-append input[class*="span"] { 878 | display: inline-block; 879 | width: auto; 880 | } 881 | .controls-row [class*="span"] + [class*="span"] { 882 | margin-left: 0; 883 | } 884 | .modal { 885 | position: fixed; 886 | top: 20px; 887 | right: 20px; 888 | left: 20px; 889 | width: auto; 890 | margin: 0; 891 | } 892 | .modal.fade { 893 | top: -100px; 894 | } 895 | .modal.fade.in { 896 | top: 20px; 897 | } 898 | } 899 | 900 | @media (max-width: 480px) { 901 | .nav-collapse { 902 | -webkit-transform: translate3d(0, 0, 0); 903 | } 904 | .page-header h1 small { 905 | display: block; 906 | line-height: 20px; 907 | } 908 | input[type="checkbox"], 909 | input[type="radio"] { 910 | border: 1px solid #ccc; 911 | } 912 | .form-horizontal .control-label { 913 | float: none; 914 | width: auto; 915 | padding-top: 0; 916 | text-align: left; 917 | } 918 | .form-horizontal .controls { 919 | margin-left: 0; 920 | } 921 | .form-horizontal .control-list { 922 | padding-top: 0; 923 | } 924 | .form-horizontal .form-actions { 925 | padding-right: 10px; 926 | padding-left: 10px; 927 | } 928 | .media .pull-left, 929 | .media .pull-right { 930 | display: block; 931 | float: none; 932 | margin-bottom: 10px; 933 | } 934 | .media-object { 935 | margin-right: 0; 936 | margin-left: 0; 937 | } 938 | .modal { 939 | top: 10px; 940 | right: 10px; 941 | left: 10px; 942 | } 943 | .modal-header .close { 944 | padding: 10px; 945 | margin: -10px; 946 | } 947 | .carousel-caption { 948 | position: static; 949 | } 950 | } 951 | 952 | @media (max-width: 979px) { 953 | body { 954 | padding-top: 0; 955 | } 956 | .navbar-fixed-top, 957 | .navbar-fixed-bottom { 958 | position: static; 959 | } 960 | .navbar-fixed-top { 961 | margin-bottom: 20px; 962 | } 963 | .navbar-fixed-bottom { 964 | margin-top: 20px; 965 | } 966 | .navbar-fixed-top .navbar-inner, 967 | .navbar-fixed-bottom .navbar-inner { 968 | padding: 5px; 969 | } 970 | .navbar .container { 971 | width: auto; 972 | padding: 0; 973 | } 974 | .navbar .brand { 975 | padding-right: 10px; 976 | padding-left: 10px; 977 | margin: 0 0 0 -5px; 978 | } 979 | .nav-collapse { 980 | clear: both; 981 | } 982 | .nav-collapse .nav { 983 | float: none; 984 | margin: 0 0 10px; 985 | } 986 | .nav-collapse .nav > li { 987 | float: none; 988 | } 989 | .nav-collapse .nav > li > a { 990 | margin-bottom: 2px; 991 | } 992 | .nav-collapse .nav > .divider-vertical { 993 | display: none; 994 | } 995 | .nav-collapse .nav .nav-header { 996 | color: #777777; 997 | text-shadow: none; 998 | } 999 | .nav-collapse .nav > li > a, 1000 | .nav-collapse .dropdown-menu a { 1001 | padding: 9px 15px; 1002 | font-weight: bold; 1003 | color: #777777; 1004 | -webkit-border-radius: 3px; 1005 | -moz-border-radius: 3px; 1006 | border-radius: 3px; 1007 | } 1008 | .nav-collapse .btn { 1009 | padding: 4px 10px 4px; 1010 | font-weight: normal; 1011 | -webkit-border-radius: 4px; 1012 | -moz-border-radius: 4px; 1013 | border-radius: 4px; 1014 | } 1015 | .nav-collapse .dropdown-menu li + li a { 1016 | margin-bottom: 2px; 1017 | } 1018 | .nav-collapse .nav > li > a:hover, 1019 | .nav-collapse .nav > li > a:focus, 1020 | .nav-collapse .dropdown-menu a:hover, 1021 | .nav-collapse .dropdown-menu a:focus { 1022 | background-color: #f2f2f2; 1023 | } 1024 | .navbar-inverse .nav-collapse .nav > li > a, 1025 | .navbar-inverse .nav-collapse .dropdown-menu a { 1026 | color: #999999; 1027 | } 1028 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1029 | .navbar-inverse .nav-collapse .nav > li > a:focus, 1030 | .navbar-inverse .nav-collapse .dropdown-menu a:hover, 1031 | .navbar-inverse .nav-collapse .dropdown-menu a:focus { 1032 | background-color: #111111; 1033 | } 1034 | .nav-collapse.in .btn-group { 1035 | padding: 0; 1036 | margin-top: 5px; 1037 | } 1038 | .nav-collapse .dropdown-menu { 1039 | position: static; 1040 | top: auto; 1041 | left: auto; 1042 | display: none; 1043 | float: none; 1044 | max-width: none; 1045 | padding: 0; 1046 | margin: 0 15px; 1047 | background-color: transparent; 1048 | border: none; 1049 | -webkit-border-radius: 0; 1050 | -moz-border-radius: 0; 1051 | border-radius: 0; 1052 | -webkit-box-shadow: none; 1053 | -moz-box-shadow: none; 1054 | box-shadow: none; 1055 | } 1056 | .nav-collapse .open > .dropdown-menu { 1057 | display: block; 1058 | } 1059 | .nav-collapse .dropdown-menu:before, 1060 | .nav-collapse .dropdown-menu:after { 1061 | display: none; 1062 | } 1063 | .nav-collapse .dropdown-menu .divider { 1064 | display: none; 1065 | } 1066 | .nav-collapse .nav > li > .dropdown-menu:before, 1067 | .nav-collapse .nav > li > .dropdown-menu:after { 1068 | display: none; 1069 | } 1070 | .nav-collapse .navbar-form, 1071 | .nav-collapse .navbar-search { 1072 | float: none; 1073 | padding: 10px 15px; 1074 | margin: 10px 0; 1075 | border-top: 1px solid #f2f2f2; 1076 | border-bottom: 1px solid #f2f2f2; 1077 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1078 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1079 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1080 | } 1081 | .navbar-inverse .nav-collapse .navbar-form, 1082 | .navbar-inverse .nav-collapse .navbar-search { 1083 | border-top-color: #111111; 1084 | border-bottom-color: #111111; 1085 | } 1086 | .navbar .nav-collapse .nav.pull-right { 1087 | float: none; 1088 | margin-left: 0; 1089 | } 1090 | .nav-collapse, 1091 | .nav-collapse.collapse { 1092 | height: 0; 1093 | overflow: hidden; 1094 | } 1095 | .navbar .btn-navbar { 1096 | display: block; 1097 | } 1098 | .navbar-static .navbar-inner { 1099 | padding-right: 10px; 1100 | padding-left: 10px; 1101 | } 1102 | } 1103 | 1104 | @media (min-width: 980px) { 1105 | .nav-collapse.collapse { 1106 | height: auto !important; 1107 | overflow: visible !important; 1108 | } 1109 | } 1110 | -------------------------------------------------------------------------------- /public_html/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2013 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /public_html/filesList.php: -------------------------------------------------------------------------------- 1 | $value) { 17 | if (!in_array($value, array("." , ".." , ".DS_Store"))) { 18 | if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) { 19 | $result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value); 20 | } 21 | else { 22 | $result[] = $value; 23 | } 24 | } 25 | } 26 | return $result; 27 | } 28 | 29 | // 30 | // Start the party 31 | // 32 | $path = 'AppsScript'; 33 | //$files = dirToArray($path); 34 | exec("find . -follow", $files); 35 | print_r($files); 36 | 37 | foreach ($files as $name) { 38 | if (strpos($name, "AppsScript/" ) > 0) { 39 | if ( !strpos($name, ".DS_Store") ) { 40 | $name = str_replace("./AppsScript/", "", $name); 41 | $fName = "* [$name](" . $gitPathPre . "/" . $name . ")\n"; 42 | array_push($fullList, $fName ); 43 | file_put_contents($mdFileName, $fName, FILE_APPEND); 44 | } 45 | } 46 | } 47 | echo "\n\n=== list of files: \n"; 48 | print_r($fullList); 49 | 50 | -------------------------------------------------------------------------------- /public_html/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenido/AppsScriptBests/9470b0ef54139f4f13b419c140ff889ff0d9aa80/public_html/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public_html/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greenido/AppsScriptBests/9470b0ef54139f4f13b419c140ff889ff0d9aa80/public_html/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | A little Repo to hold all the great libraries, utils and class around Apps Script 9 | 10 | 11 | 12 | 13 | 14 | 15 |

      Todo - Create a list of the current components

      16 | 17 |

      Todo - build a wish list with ideas for libs/components

      18 | 19 |

      Other Sources

      20 | 33 | 34 |
      TODO: 35 | 1. Use a side bar to hold the menu for all the content 36 | 2. Allow a short html/markup explanation page per module 37 |
      38 | 39 | 40 | -------------------------------------------------------------------------------- /public_html/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('