Skip to content

Instantly share code, notes, and snippets.

@dbro
Created April 26, 2013 12:50
Show Gist options
  • Save dbro/5467157 to your computer and use it in GitHub Desktop.
Save dbro/5467157 to your computer and use it in GitHub Desktop.
This is a Google Apps Script (https://script.google.com) that replicates the "Snippets" messaging process as used by Google internally. See the notes at the bottom of this page for more info.
/* **************************************
Weekly Update Scripts
by Dan Brown, March 2013
For automatic collection of weekly
update messages from employees.
* sends reminder messages
* posts to public sites pages
* alerts managers, coworkers and
other stakeholders
Known Issues:
* html tags should be stripped from subscription messages (ready to test)
* lines beginning with ">" are not being dropped as expected from both announcements
and subscription messages (might be fixed by previous issue's fix)
* fix link creation in sites list page
Suggestions for improvements:
* include most recent update in reminder (ready to test)
* link to instructions and example
* avoid sending reminder to people who
already sent an update this round
************************************** */
var domainName = 'your-domain-here.com';
var siteName = 'your-site-name-here';
var site = SitesApp.getSite(domainName, siteName);
var debug = 2; // set to zero to disable logging
var dryRun = 1; // set to 1 to disable writing/sending. for testing
function getTextFromHtml(html) {
// getTextFromHtml("hello <div>foo</div>&amp; world <br /><div>bar</div>!");
// will return
// "hello foo& world bar!"
// from http://stackoverflow.com/questions/13288928/trouble-with-html-encoding-in-google-apps-script
// TODO: check to see if <br> tags and others like </li> get converted to newlines
// \n: br p li tr, others? (divs are difficult)
// space: td
return getTextFromNode(Xml.parse(html, true).getElement());
}
function getTextFromNode(x) {
switch(x.toString()) {
case 'XmlText': return x.toXmlString() + '\n';
case 'XmlElement': return x.getNodes().map(getTextFromNode).join('');
default: return '';
}
}
function cleanString(input) {
if (typeof(input) === "undefined") { return ""; }
if (input === null) { return ""; }
return input.trim().toLowerCase();
}
function parseEmailAddress(input) {
// we expect it to return a simple [a-z0-9\.]*@[a-z0-9\.]* text string.
// returns an empty string in case of invalid input
if (typeof(input) === "undefined") { return ""; }
if (input === null) { return ""; }
var email = cleanString(input);
// remove stuff outside angle braces that gmail adds to addresses
var iOpenAngle = email.lastIndexOf("<");
var iCloseAngle = -1; // default
if (iOpenAngle != -1) {
iCloseAngle = email.indexOf(">", iOpenAngle);
if (iCloseAngle != -1) {
var trimmedEmail = email.substr(iOpenAngle + 1, iCloseAngle - iOpenAngle -1);
if (debug > 2) {
Logger.log("Trimming email address: " + email + " --becomes--> " + trimmedEmail);
}
email = trimmedEmail;
}
}
iAt = email.indexOf("@");
iDot = email.indexOf(".", iAt);
if ((email.length > 0) && (iAt != -1) && (iAt > 0) && (iDot != -1) && (iAt < iDot) && (iDot < (email.length - 1))) {
return email;
} else {
return "";
};
}
function removeOldThreadContent(input) {
// attempts to remove lines from a message body that come from older messages in a thread
// these start with ">" or have the pattern "On ... wrote:"
var output = [];
var inputArray = input.split("\n");
while (inputArray.length) {
var line = inputArray.shift();
var linelength = line.length;
if ((line.substr(0,1) == ">") || (line.substr(0,2) == " >") || (line.substr(0,4) == "&gt;") || (line.substr(0,5) == " &gt;")) {
// don't include this line
} else if ((line.substr(0,3) == "On ") && ((line.substr(linelength - 6,6) == "wrote:") || (line.substr(linelength - 12,6) == "wrote:"))) {
// don't include this line
} else {
output.push(line);
}
}
return output.join("\n");
}
var INVALID_DATE = new Date(0); // Jan 1, 1970
var TODAY = new Date();
var THREEDAYSAGO = new Date(TODAY.valueOf() - 259200000);
var TENYEARSAGO = new Date(TODAY.valueOf() - 315569259747);
function parseDateString(input) {
// converts from ISO format (eg. "2013-01-20T23:45:56.000Z")
//if (isNaN(input) || (input == "")) { return INVALID_DATE; }
if (input == "") { return INVALID_DATE; }
try {
var date = new Date(input);
} catch(err) {
return INVALID_DATE;
}
if (date.valueOf() < TENYEARSAGO.valueOf()) {
return INVALID_DATE;
}
return date;
}
function formatDateString(input) {
// converts to ISO format (eg. "2013-01-20T23:45:56.000Z")
return input.toISOString();
}
function maxDate(x, y) {
if ((x == INVALID_DATE) && (y == INVALID_DATE)) { return x; }
if (x == INVALID_DATE) { return y; }
if (y == INVALID_DATE) { return x; }
if (x > y) { return x; }
return y;
}
function getTextBetween(fullstring, start_marker, end_marker) {
var startIndex = fullstring.indexOf(start_marker) + start_marker.length;
if (startIndex == -1) { return ""; }
var endIndex = fullstring.indexOf(end_marker, startIndex);
if (endIndex == -1) { return ""; }
return fullstring.substring(startIndex,endIndex);
}
function createLink(ref, anchor) {
// URLs are stored as strings in Google Sites,
// eg. <a xmlns="http://www.w3.org/1999/xhtml" href="/a/your-domain-here.com/your-site-here/who/someone">here</a>
var trimThis = "https://sites.google.com";
if (ref.slice(0,trimThis.length) === trimThis) {
ref = ref.slice(trimThis.length);
}
// return '<a xmlns="http://www.w3.org/1999/xhtml" href="' + ref + '">' + anchor + '</a>';
return '<a href="' + ref + '">' + anchor + '</a>';
}
function parseUrl(inputstring) {
// URLs are stored as strings in Google Sites,
// eg. <a xmlns="http://www.w3.org/1999/xhtml" href="/a/your-domain-here.com/your-site-here/who/someone">here</a>
var refStart = 'href="';
var refEnd = '"';
var anchorStart = '>';
var anchorEnd = '<';
return [getTextBetween(inputstring, refStart, refEnd), getTextBetween(inputstring, anchorStart, anchorEnd)];
}
function getUrlRef(inputstring) {
return parseUrl(inputstring)[0];
}
function getUrlAnchor(inputstring) {
return parseUrl(inputstring)[1];
}
var DIRECTORY_PAGE_NAME = "who";
var UPDATES_PAGE_NAME = "updates";
var PAR_FIRSTNAME = "First name";
var PAR_LASTNAME = "Last name";
var PAR_EMAIL = "email";
var PAR_DEPARTMENT = "Department";
var PAR_EMAIL = "email";
var PAR_OBJECTIVES = "Objectives";
var PAR_UPDATES = "Updates";
function getParticipants() {
if (debug > 0) { Logger.log("starting getParticipants() function"); }
var directoryPage = site.getChildByName(DIRECTORY_PAGE_NAME);
var participants = directoryPage.getListItems();
var participantSeenFirstAtRow = {}; // to ignore duplicates
var participantsArray = [];
var participantCount = participants.length;
for (var i=0; i < participantCount; i++) { // don't need to skip header row, because it won't be a valid email address
var p = participants[i];
var rawEmail = p.getValueByName(PAR_EMAIL);
var recipientEmail = parseEmailAddress(rawEmail);
// test to see if the row should be skipped
if (recipientEmail.length === 0) {
if (debug > 0) {
Logger.log("Skipping invalid email address " + rawEmail + " found at row " + i);
}
continue;
}
if (participantSeenFirstAtRow.hasOwnProperty(recipientEmail)) {
if (debug > 0) {
Logger.log("Skipping duplicate email address " + recipientEmail + " found at row " + i + ", first seen at row " + participantSeenFirstAtRow[recipientEmail]);
}
continue;
}
// clean original email if necessary
if (rawEmail != recipientEmail) {
p.setValueByName(PAR_EMAIL, recipientEmail);
if (debug > 0) {
Logger.log("Replaced original email value (" + rawEmail + ") at row " + i + " with cleaned version (" + recipientEmail + ")");
}
}
if (p.getValueByName(PAR_DEPARTMENT) != "Product Management") {
continue; // skip this person who is not in PM dept. for testing phase
}
// new participant
participantSeenFirstAtRow[recipientEmail] = i;
var username = recipientEmail.split("@")[0];
var pageURLObjectives = cleanString(p.getValueByName(PAR_OBJECTIVES)); // may be blank
if (pageURLObjectives.length === 0) {
try {
var pageObjectives = directoryPage.getChildByName(username);
} catch (exception) {
var pageObjectives = site.createWebPage(p.getValueByName(PAR_FIRSTNAME) + " " + p.getValueByName(PAR_LASTNAME), username, "");
}
// URLs are strings, eg. "<a xmlns="http://www.w3.org/1999/xhtml" href="/a/getyourguide.com/poca/who/villars">here</a>"
//p.setValueByName(PAR_OBJECTIVES, pageObjectives.getUrl());
// p.setValueByName(PAR_OBJECTIVES, createLink(pageObjectives.getUrl(), "here")); // TODO: re-enable once URLs are working (bug?)
// p.setValueByName(PAR_OBJECTIVES, '<a href="http://who">whourl</a>&nbsp;');
} else {
var pageObjectives = directoryPage.getChildByName(username);
}
var pageURLToAppend = cleanString(p.getValueByName(PAR_UPDATES)); // may be blank
if (pageURLToAppend.length === 0) {
try {
var pageToAppend = pageObjectives.getChildByName(UPDATES_PAGE_NAME);
} catch (exception) {
var pageToAppend = pageObjectives.createAnnouncementsPage("Weekly updates from " + username, UPDATES_PAGE_NAME, "");
}
//p.setValueByName(PAR_UPDATES, pageToAppend.getUrl());
// p.setValueByName(PAR_UPDATES, createLink(pageToAppend.getUrl(), "here")); // TODO: re-enable once URLs are working (bug?)
} else {
var pageToAppend = pageObjectives.getChildByName(UPDATES_PAGE_NAME);
}
participantsArray.push(p);
}
return participantsArray;
}
function getSenderEmail() {
// we attempt to send from the official email alias for the service
// that we created and associated with Dan Brown's gmail account (brown@)
// if that fails, return an empty string
var senderEmail = "[email protected]";
if (GmailApp.getAliases().indexOf(senderEmail) != -1) {
return senderEmail;
}
return "";
}
function getPreviousUpdateContent(updatesURL) {
var previousUpdateContent = ""; // default is an empty string
try {
var pageUpdates = SitesApp.getPageByUrl(updatesURL);
var previousUpdates = pageUpdates.getAnnouncements();
previousUpdateContent = getTextFromHtml(previousUpdates[previousUpdates.length - 1].getHtmlContent());
previousUpdateContent = "\n> Your most recent update:\n> " + previousUpdateContent.replace(/\n/g,"\n> ");
} catch (exception) {
// no operation
if (debug > 0) { Logger.log("no previous update found at page " + updatesURL); }
}
return previousUpdateContent;
}
function emailReminderMessages() {
if (debug > 0) { Logger.log("starting emailReminderMessages() function"); }
// assemble the contents of the message to be sent
var subject = "Weekly Update reminder";
var salutation = "> Hello ";
var body = "\n> Please respond with your weekly update before 15:00 CET today.";
body += "\n> ";
body += "\n> Include 3-5 bullet points describing your accomplishments for the past week";
body += "\n> and 3-5 more describing your plan for next week.";
body += "\n> It should take less than 5 minutes to write.";
body += "\n> ";
body += "\n> Your response will be publicly visible as part of your profile page on our intranet.";
body += "\n> ";
var closing = "\n> Lines beginning with \">\" will be ignored.\n";
// TODO: add reference to instructions and examples
var extraParams = { name: "Weekly Update" };
// we prefer to send from the official email alias for the service
var senderEmail = getSenderEmail();
if (senderEmail.length > 0) {
extraParams.from = senderEmail;
extraParams.replyTo = senderEmail; // TODO: get this to work properly! Or maybe it's because brown@ is also wupdate@, and others will be OK?
}
// send a message to each participant that wants to receive a reminder
var participants = getParticipants();
while (participants.length) {
p = participants.shift();
var email = p.getValueByName(PAR_EMAIL);
var firstname = p.getValueByName(PAR_FIRSTNAME);
var updatesURL = "https://sites.google.com" + getUrlRef(p.getValueByName(PAR_UPDATES));
var previousUpdateContent = getPreviousUpdateContent(updatesURL);
// TODO: check to see if the person has already sent an update recently, and don't send a reminder if so
// if (p[LAST] > THREEDAYSAGO) {
// // TODO: do a Gmail search instead of this P[LAST] check!
// if (debug > 0) { Logger.log("INFO: no message sent to " + email + " (recent update received on " + p[LAST].toISOString() + ")"); }
// } else {
if (dryRun === 0) {
GmailApp.sendEmail(email, subject, salutation + firstname + body + updatesURL + closing + previousUpdateContent, extraParams);
}
if (debug > 0) { Logger.log("sent message to " + email); }
if (debug > 1) { Logger.log("content :\n" + salutation + firstname + body + updatesURL + closing + previousUpdateContent); }
// }
}
}
// we use escape key names to avoid collisions with existing properties of objects, when used as a dictionary
function escape(input) {
return "_" + input;
}
function unescape(input) {
return input.substr(1, input.length-1);
}
function isEscaped(input) {
return (input.charAt(0) === "_");
}
function addDefaultDomain(rawstring, domain) {
// add the defaultDomain if the raw Email doesn't end in it
var atInd = rawstring.indexOf('@');
if (atInd === -1) {
return rawstring + '@' + domain;
} else {
return rawstring;
}
}
var SUBSCRIPTIONS_PAGE_NAME = "--subscriptions";
// schema of "Subscriptions" array
var SUB_ROW = 0;
var SUB_EMAIL = 1;
var SUB_TERM = 2;
var SUB_EMAILFIELD = "Subscriber email (@your-domain-name.com)";
var SUB_TERMFIELD = "receive updates that contain this term";
function getSubscriptions() {
// Create list of subscriptions to receive copies of relevant update messages
// each subscription is a list where the first element is the search term,
// and the second element is the address of a single subscriber
if (debug > 0) { Logger.log("starting getSubscriptions() function"); }
var directoryPage = site.getChildByName(DIRECTORY_PAGE_NAME);
var subscriptionsPage = directoryPage.getChildByName(SUBSCRIPTIONS_PAGE_NAME);
var subscriptions = subscriptionsPage.getListItems();
var subscriptionsArray = [];
var alreadySeenSubscriptions = {};
var i=-1;
while (subscriptions.length) {
i++;
subscription = subscriptions.shift();
var rawEmail = addDefaultDomain(subscription.getValueByName(SUB_EMAILFIELD), "your-domain-name.com");
var recipientEmail = parseEmailAddress(rawEmail);
// test to see if the row should be skipped
if (recipientEmail.length === 0) {
if (debug > 0) {
Logger.log("Skipping invalid email address " + rawEmail + " found at row " + i);
}
continue;
}
// clean original email if necessary
if (rawEmail != recipientEmail) {
subscription.setValueByName(SUB_EMAILFIELD, recipientEmail);
if (debug > 0) {
Logger.log("Replaced original email value (" + rawEmail + ") at row " + i + " with cleaned version (" + recipientEmail + ")");
}
}
var searchTerm = subscription.getValueByName(SUB_TERMFIELD);
var subFingerprint = recipientEmail + "__@@__@@__" + searchTerm;
if (alreadySeenSubscriptions.hasOwnProperty(subFingerprint)) {
if (debug > 0) {
Logger.log("Skipping duplicate subscription on row " + i + " (" + recipientEmail + " : " + searchTerm + ")");
}
} else {
subscriptionsArray.push(subscription);
alreadySeenSubscriptions[subFingerprint] = true;
}
}
return subscriptionsArray;
}
function groupSubscriptionsBySearchTerm(input) {
// input should be an array of list items, with no duplicate subscription information
// returns an array of arrays:
// each subscription is a list where the first element is the search term,
// and the second element is the address of a single subscriber
// and all following elements are email addresses of subscribers
var subscriptions = {};
while (input.length) {
s = input.shift();
var recipientEmail = escape(addDefaultDomain(s.getValueByName(SUB_EMAILFIELD), "getyourguide.com"));
var escapedSearchTerm = escape(s.getValueByName(SUB_TERMFIELD));
if (!(subscriptions.hasOwnProperty(escapedSearchTerm))) {
subscriptions[escapedSearchTerm] = [];
}
subscriptions[escapedSearchTerm].push(recipientEmail);
}
// filter and flatten object data structure into an array of arrays
var unescapedSubscriptions = [];
for (escapedSearchTerm in subscriptions) {
if (isEscaped(escapedSearchTerm)) {
var pushThis = subscriptions[escapedSearchTerm];
pushThis.unshift(unescape(escapedSearchTerm));
unescapedSubscriptions.push(pushThis);
}
}
return unescapedSubscriptions;
}
function checkForResponseMessages() {
if (debug > 0) { Logger.log("starting checkForResponseMessages() function"); }
// remember start time and exclude messages received after that time
var functionStartTime = new Date();
// Look for the time of the last run in the subscriptions page description. It is expected
// to be in a specific text string.
var functionLastRunTime = parseDateString(""); // default to invalid date
var directoryPage = site.getChildByName(DIRECTORY_PAGE_NAME);
var subscriptionsPage = directoryPage.getChildByName(SUBSCRIPTIONS_PAGE_NAME);
var subscriptionPageText = subscriptionsPage.getHtmlContent();
var last_updated_marker_start = "[Last publish to subscribers was on ";
var last_updated_marker_end = "]";
var last_updated_start_index = subscriptionPageText.indexOf(last_updated_marker_start);
var last_updated_end_index = -1; //default
if (last_updated_start_index != -1) {
last_updated_start_index += last_updated_marker_start.length;
last_updated_end_index = subscriptionPageText.indexOf(last_updated_marker_end, last_updated_start_index);
if (last_updated_end_index != -1) {
var functionLastRunTimeString = subscriptionPageText.substring(last_updated_start_index, last_updated_end_index);
var functionLastRunTime = parseDateString(functionLastRunTimeString);
}
}
var directoryPage = site.getChildByName(DIRECTORY_PAGE_NAME);
// Create set of subscriptions to receive copies of relevant update messages
var groupedSubscriptions = groupSubscriptionsBySearchTerm(getSubscriptions());
// Get instructions for where to record information for each participant
var participants = getParticipants();
// assemble the contents of the message to be sent
var subject = "Weekly Update subscription";
var extraParams = { name: "Weekly Update" };
// we prefer to send from the official email alias for the service
var senderEmail = getSenderEmail();
if (senderEmail.length > 0) {
extraParams.from = senderEmail;
extraParams.replyTo = senderEmail; // TODO: get this to work properly!
}
var messagesFor = {}; // store the messages relevant to each subscriber
// data structure schema:
// messagesFor[escapedSubscriberEmail][escapedMessageID] = [array of searchTerms]
while (participants.length) {
p = participants.shift();
// Look for new messages from this participant
var query = "from:" + p.getValueByName(PAR_EMAIL);
if (senderEmail.length > 0) {
query += " to:" + senderEmail;
}
//var lastHeardFrom = parseDateString(""); // Date object (aka epoch milliseconds) // TODO: implement last-heard-from cutoff, maybe using the list page's created/updated times?
var lastHeardFrom = functionLastRunTime;
if (lastHeardFrom != INVALID_DATE) {
var dateString = lastHeardFrom.toISOString().split("T")[0];
query += " after:" + dateString;
}
var latestDate = INVALID_DATE;
if (debug > 0) { Logger.log("\n*** searching for messages with query: " + query); }
var threads = GmailApp.search(query);
if (debug > 1) { Logger.log("found " + threads.length + " threads to examine"); }
while (threads.length) {
t = threads.shift();
messages = t.getMessages();
if (debug > 1) { Logger.log(" found " + messages.length + " in this thread"); }
while (messages.length) {
m = messages.shift();
var mdate = m.getDate();
var mfrom = parseEmailAddress(m.getFrom());
// check for skip conditions
if (m.isInTrash()) {
if (debug > 1) { Logger.log(" skipping trashed message"); }
continue;
}
if (mfrom != p.getValueByName(PAR_EMAIL)) {
if (debug > 1) { Logger.log(" skipping message from " + mfrom + " because it doesnt match " + p.getValueByName(PAR_EMAIL)); }
continue;
}
if ((lastHeardFrom != INVALID_DATE) && (mdate < lastHeardFrom)) {
if (debug > 1) { Logger.log(" skipping message sent on " + mdate.toISOString() + " which is earlier than " + lastHeardFrom.toISOString()); }
continue;
}
if (mdate > functionStartTime) {
if (debug > 1) { Logger.log(" skipping message sent on " + mdate.toISOString() + " which is after this script started at " + functionStartTime.toISOString()); }
continue;
}
var newContent = removeOldThreadContent(getTextFromHtml(m.getBody())); // TODO: check to see if newlines are handled appropriately when input formatted with html tags
if (newContent.length === 0) {
if (debug > 1) { Logger.log(" skipping message sent on " + mdate.toISOString() + " which has no new content"); }
continue;
}
// this message passed all the tests, so we accept it
latestDate = maxDate(latestDate, mdate);
if (debug > 0) { Logger.log(" found new message from " + mfrom + " sent on " + mdate.toISOString()); }
if (debug > 2) { Logger.log(" latestDate is now " + latestDate.toISOString()); }
// append this content to the person's google site page // TODO: make sure this page exists, and create it if necessary
var username = mfrom.split("@")[0];
var appendToPage = directoryPage.getChildByName(username).getChildByName(UPDATES_PAGE_NAME);
if (debug > 0) {
Logger.log("Creating new announcement on " + appendToPage.getUrl());
}
if (dryRun === 0) {
appendToPage.createAnnouncement(mdate.toISOString(), newContent);
}
// check to see if any subscribers should be notified of this message
for (var i=0; i<groupedSubscriptions.length; i++) {
var s = groupedSubscriptions[i];
var searchTerm = s[0];
var searchRegEx = /\bsearchTerm\b/i; // to require word boundaries at start and end of search term
// if ((mfrom == searchTerm) || (newContent.indexOf(searchTerm) != -1)) {
if ((mfrom == searchTerm) || (searchRegEx.test(newContent))) {
for (var j=1; j<s.length; j++) { // start at second term in array
var escapedSubscriber = s[j]; // it's already escaped
if (!(messagesFor.hasOwnProperty(escapedSubscriber))) {
messagesFor[escapedSubscriber] = {};
}
var messagesForSubscriber = messagesFor[escapedSubscriber];
var escapedMessageId = escape(m.getId());
if (!(messagesForSubscriber.hasOwnProperty(escapedMessageId))) {
messagesForSubscriber[escapedMessageId] = [];
}
messagesForSubscriber[escapedMessageId].push(searchTerm);
}
}
}
}
}
// maybe todo: store the time stamp of the most recent message received from this participant
}
// send messages to subscribers
// batch all notifications into as few messages as possible (limit 20kB)
for (var escapedSubscriber in messagesFor) {
if (isEscaped(escapedSubscriber)) {
var subscriber = unescape(escapedSubscriber);
if (debug > 1) { Logger.log("preparing notification messages for " + subscriber); }
var content = "";
for (var escapedMessageId in messagesFor[escapedSubscriber]) {
if (isEscaped(escapedMessageId)) {
var messageId = unescape(escapedMessageId);
var message = GmailApp.getMessageById(messageId);
var searchTerms = messagesFor[escapedSubscriber][escapedMessageId];
content += "\n----- found search term(s): " + searchTerms.join(" , ");
content += "\n----- in message from " + message.getFrom() + " sent " + message.getDate().toISOString();
content += "\n" + removeOldThreadContent(getTextFromHtml(message.getBody())); // TODO: check newlines from html handled properly
content += "\n----------------------------\n";
if (debug > 1) { Logger.log("found message (ID = " + messageId + ") regarding search term(s) " + searchTerms.join(" , ")); }
if (content.length > 15000) { // send this now if the message is approaching the 20kb limit
if (debug > 0) { Logger.log("sending message to " + subscriber + " triggered by large message accumulator size"); }
if (dryRun === 0) {
GmailApp.sendEmail(subscriber, subject, content, extraParams);
}
content = "";
}
}
}
if (content.length !== 0) {
if (debug > 0) { Logger.log("sending message to " + subscriber); }
if (dryRun === 0) {
GmailApp.sendEmail(subscriber, subject, content, extraParams);
}
}
}
}
if (functionStartTime != INVALID_DATE) {
// Store the time of this run in the subscriptions page description for future reference.
if ((last_updated_start_index === -1) || (last_updated_end_index === -1)) {
var newSubscriptionPageText = subscriptionPageText + "\n" + last_updated_marker_start +
formatDateString(functionStartTime) + last_updated_marker_end;
} else {
var newSubscriptionPageText = subscriptionPageText.substring(0,last_updated_start_index) +
formatDateString(functionStartTime) + subscriptionPageText.substring(last_updated_end_index);
}
if (debug > 0) { Logger.log("updating last run time on subscriptions page to " + formatDateString(functionStartTime)); }
if (dryRun === 0) {
subscriptionsPage.setHtmlContent(newSubscriptionPageText);
}
}
}
function onOpen() {
// create some convenient manual trigger menu options
var subMenus = [
{name:"Send Reminder Email Messages Now", functionName: "emailReminderMessages"},
{name:"Check For Response Messages Now", functionName: "checkForResponseMessages"}
];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Weekly Update Automated Functions", subMenus);
}
@dbro
Copy link
Author

dbro commented Apr 26, 2013

This script does 3 things:

  • reminds people to send in a weekly update of tasks done this week and planned for next week.
  • collects responses and stores them in a permanent place
  • notifies subscribers of relevant responses by keyword or author

Notes: The script interacts with Google Sites and Gmail. It expects a list page with a company directory to be stored at /who with certain fields present. It has some testing settings (dryrun=1 disables writing and email sending operations). It expects there to be an alias "wupdate@" used to send a received messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment