├── .gitignore
├── PostgreSQL_cheatsheet.pdf
├── nbproject
├── customs.json
├── project.xml
└── project.properties
├── public_html
├── img
│ ├── glyphicons-halflings.png
│ └── glyphicons-halflings-white.png
├── AppsScript
│ ├── Utils
│ │ ├── cleanSheetRange.js
│ │ ├── dateUtils.js
│ │ ├── moneyUtils.js
│ │ ├── CheckSheetAndMail.js
│ │ ├── runMeAgain.js
│ │ └── parseHTML.js
│ ├── UI
│ │ ├── htmlDialog.js
│ │ └── DatePicker.js
│ ├── Apps
│ │ ├── Insta2Drive.js
│ │ ├── SiteMonitor.js
│ │ ├── EventsDateManager.js
│ │ └── EventManager.js
│ ├── Gmail
│ │ └── FetchMailsToSheet.js
│ ├── Breezometer-API
│ │ └── FetchDataToSheet.js
│ ├── Search
│ │ └── lego.js
│ ├── Gcal
│ │ └── EarningCalls.js
│ └── YouTube
│ │ └── ytStats.js
├── filesList.php
├── index.html
├── ListOfScripts.md
├── css
│ ├── bootstrap-responsive.min.css
│ └── bootstrap-responsive.css
└── js
│ └── bootstrap.min.js
├── postDrafts
└── Android Pay.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | nbproject/private/private.xml
3 |
4 | nbproject/private/private.properties
5 |
--------------------------------------------------------------------------------
/PostgreSQL_cheatsheet.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/greenido/AppsScriptBests/HEAD/PostgreSQL_cheatsheet.pdf
--------------------------------------------------------------------------------
/nbproject/customs.json:
--------------------------------------------------------------------------------
1 | {
2 | "attributes": {
3 | "author": {
4 | "context": "meta"
5 | }
6 | },
7 | "elements": {}
8 | }
--------------------------------------------------------------------------------
/public_html/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/greenido/AppsScriptBests/HEAD/public_html/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/public_html/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/greenido/AppsScriptBests/HEAD/public_html/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/nbproject/project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.netbeans.modules.web.clientproject
4 |
5 |
6 | AppsScriptBests
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 | [](https://github.com/igrigorik/ga-beacon)
62 |
63 |
--------------------------------------------------------------------------------
/public_html/AppsScript/Breezometer-API/FetchDataToSheet.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Fetch Data from http://api-beta.breezometer.com/baqi/?location=new+york&key=YOURKEY
3 | * @Author: Ido Green | @greenido | +GreenIdo
4 | * @Date: July 2015
5 |
6 | A sample return object:
7 | -----------------------
8 | key_valid TRUE
9 | breezometer_description Fair Air Quality
10 | breezometer_aqi 65
11 | data_valid TRUE
12 | dominant_pollutant_canonical_name pm2.5
13 | country_description Good Air Quality
14 | country_color #92F300
15 | breezometer_color #A2DB26
16 | country_aqi 43
17 | dominant_pollutant_description Fine particulate matter (<2.5µm)
18 | dominant_pollutant_text {effects=Particles enter the lungs and cause local and systemic inflammation in the respiratory system & heart, thus cause cardiovascular and respiratory diseases such as asthma and bronchitis., causes=Main sources are fuel burning processes in industry, transportation and indoor heating., main=At the moment, fine particulate matter (PM2.5) is the main pollutant in the air:}
19 | country_name USA
20 | random_recommendations {inside=The air quality is still good - we'll keep you updated if things get worse, children=No reason to panic, but pay attention to changes in air quality and any signals of breathing problems in your children, sport=You can go on a run - just keep your nose open for any changes!, health=Exposure to air hazards is dangerous for people with health sensitivities, so it is important to monitor air quality at this time, outside=It's still OK out there. Just stay alert for notifications about change in air quality}
21 | */
22 |
23 | //
24 | // Run on all the locations you wish to save for analysis
25 | //
26 | function collectLocations() {
27 | fetchData("http://api-beta.breezometer.com/baqi/?location=new+york&key=9de9e08086af4c6ea6d2ac60b3d38acb", "New York");
28 | fetchData("http://api-beta.breezometer.com/baqi/?location=new+york&key=9de9e08086af4c6ea6d2ac60b3d38acb", "San Francisco");
29 | fetchData("http://api-beta.breezometer.com/baqi/?location=menlo+park,+ca,+united+states&key=9de9e08086af4c6ea6d2ac60b3d38acb", "Palo Alto");
30 | }
31 |
32 | //
33 | // Fetch a json object and push the values to a certain sheet that will collect them.
34 | // This will allow us in the future to create charts from the data per location.
35 | //
36 | function fetchData(url, location) {
37 | // Fetch the url with the data
38 | var result = UrlFetchApp.fetch(url);
39 | var o = JSON.parse(result.getContentText());
40 |
41 | // Locate the sheet the is dedicate for this location
42 | var doc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(location);
43 | var index = doc.getLastRow() ;
44 | var cell = doc.getRange('a1');
45 | cell.offset(index, 0).setValue(location);
46 | cell.offset(index, 1).setValue(new Date());
47 | for (var i in o) {
48 | var row = o[i];
49 | if (i.indexOf("breezometer_description") > -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/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/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; iEarnings", 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/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/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/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/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() + " :