|
/** |
|
* Copyright (c) 2015, Isiah Meadows |
|
* All rights reserved. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* |
|
* 1. Redistributions of source code must retain the above copyright notice, |
|
* this list of conditions and the following disclaimer. |
|
* |
|
* 2. Redistributions in binary form must reproduce the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
/** |
|
* Original source: https://gist.github.com/isiahmeadows/63716b78c58b116c8eb7 |
|
*/ |
|
|
|
/* jshint maxlen: 80, indent: 2, eqnull: true, freeze: true, eqeqeq: true, |
|
es3: true, unused: true */ |
|
/* global Session, LockService, Logger, PropertiesService, ScriptApp, |
|
GmailApp, __i, __r, __e */ |
|
/* exported initialize, runQueries, emailResults */ |
|
|
|
function initialize() { |
|
__i(); |
|
} |
|
|
|
function runQueries() { |
|
__r(); |
|
} |
|
|
|
function emailResults() { |
|
__e(); |
|
} |
|
|
|
(function (global) { |
|
"use strict"; |
|
|
|
var DEBUG = !!global.__DEBUG; |
|
|
|
function d(value, default_) { |
|
return value != null ? value : default_; |
|
} |
|
|
|
function get(name) { |
|
return d(global[name], defaults[name]); |
|
} |
|
|
|
var assert, assertType, writeLog, log, error, section; |
|
|
|
if (DEBUG) { |
|
assert = function (condition, message) { |
|
if (!condition) throw new TypeError(message); |
|
}; |
|
|
|
assertType = function (obj, type, message) { |
|
assert(typeof obj === type, message); |
|
}; |
|
|
|
var lastBlank = false; |
|
|
|
writeLog = function (str) { |
|
lastBlank = str === void 0; |
|
Logger.log('== Timed Filters == ' + d(str, '')); |
|
}; |
|
|
|
log = function (str) { writeLog('LOG: ' + str); }; |
|
error = function (str) { writeLog('ERROR: ' + str); }; |
|
|
|
section = function section(str) { |
|
if (!lastBlank) writeLog(); |
|
writeLog(str); |
|
writeLog(); |
|
}; |
|
} else { |
|
assert = assertType = writeLog = log = error = section = function () {}; |
|
} |
|
|
|
var defaults = { |
|
'queries': null, |
|
'queryInterval': 10, |
|
'notifyEmail': true, |
|
'subject': 'Weekly Deletion Totals', |
|
'header': 'Number of emails successfully deleted this past week: {}', |
|
'emailWeekday': 'Monday', |
|
'emailHour': 1, |
|
'email': null |
|
}; |
|
|
|
var properties = PropertiesService.getUserProperties(); |
|
|
|
function requiredParameter(name) { |
|
return '`' + name + '` is required for this script to function ' + |
|
'properly. Please fix this as soon as possible.\n' + |
|
'\n' + |
|
'Documentation for this script can be found at ' + |
|
'https://gist.github.com/isiahmeadows/63716b78c58b116c8eb7' + |
|
'.'; |
|
} |
|
|
|
var QUERIES = get('queries'); |
|
|
|
assert(QUERIES !== null, requiredParameter('queries')); |
|
assert(Array.isArray(QUERIES), 'queries must be an array'); |
|
|
|
each(QUERIES, function (pair) { |
|
assertType(pair[0], 'string', 'query must be a string'); |
|
assertType(pair[1], 'function', 'callback must be a function'); |
|
}); |
|
|
|
// If we don't get a lock within an hour, then there's something seriously |
|
// wrong. This is for the less frequent runs. |
|
var MAX_TIMEOUT = 1000 /*ms*/ * 60 /*s*/ * 60 /*min*/; |
|
|
|
var USER = d(get('email'), Session.getEffectiveUser().getEmail()); |
|
|
|
assert(USER !== '' || global.email != null, requiredParameter('email')); |
|
|
|
var SUBJECT = get('subject'); |
|
var HEADER = get('header'); |
|
|
|
assert(USER, 'there should exist a user email somewhere'); |
|
assertType(USER, 'string', 'email must be a string'); |
|
assertType(SUBJECT, 'string', 'subject must be a string'); |
|
assertType(HEADER, 'string', 'header must be a string'); |
|
|
|
var NOTIFY_EMAIL = get('notifyEmail'); |
|
|
|
assertType(NOTIFY_EMAIL, 'boolean', |
|
'notifyEmail must be a boolean if given.'); |
|
|
|
// Shorthand, primarily to better assist in name mangling. |
|
function exportName(name, fn) { |
|
assertType(name, 'string', 'name must be a string'); |
|
assertType(fn, 'function', 'object must be a function'); |
|
fn.name = fn.displayName = name; |
|
global['__' + name] = fn; |
|
} |
|
|
|
function doSync(block) { |
|
var lock = LockService.getUserLock(); |
|
lock.waitLock(MAX_TIMEOUT); |
|
var ret = block(); |
|
lock.releaseLock(); |
|
return ret; |
|
} |
|
|
|
function each(xs, f) { |
|
for (var i = 0, len = xs.length; i !== len; i++) f(xs[i]); |
|
} |
|
|
|
function parseHour(hour) { |
|
if (typeof hour === 'number') { |
|
assert(hour % 1 === 0, 'emailHour must be an integer'); |
|
assert(hour >= 0 && hour < 24, |
|
'emailHour is out of range (not within 0-23)'); |
|
return hour; |
|
} |
|
|
|
assertType(hour, 'string', 'emailHour must be a string or number'); |
|
|
|
hour = hour.trim(); |
|
|
|
var suffix = hour.slice(-2); |
|
var last = suffix[1]; |
|
|
|
if (last === 'm') { |
|
hour = hour.slice(0, -2); |
|
suffix = suffix[0]; |
|
} else { |
|
hour = hour.slice(0, -1); |
|
suffix = suffix[1]; |
|
} |
|
|
|
var res = parseInt(hour, 10); |
|
|
|
if (res === 12) { |
|
if (suffix === 'a') res = 0; |
|
} else if (suffix === 'p') { |
|
res += 12; |
|
} |
|
|
|
assert(res >= 0 && res < 24, |
|
'emailHour is out of range (not within 12am-11pm)'); |
|
return res; |
|
} |
|
|
|
exportName('i', function initialize() { |
|
section('Initializing triggers'); |
|
|
|
var day = get('emailWeekday'); |
|
assertType(day, 'string', 'emailWeekday should be a string.'); |
|
day = day.toUpperCase(); |
|
|
|
var hour = parseHour(get('emailHour')); |
|
|
|
// The lock is needed to make sure the callbacks aren't executed while we |
|
// are setting them up. |
|
doSync(function () { |
|
log('Removing duplicate triggers...'); |
|
|
|
each(ScriptApp.getProjectTriggers(), function (trigger) { |
|
var handler = trigger.getHandlerFunction(); |
|
|
|
if (handler === 'runQueries' || handler === 'emailResults') { |
|
log('Removing trigger for function: ' + trigger); |
|
ScriptApp.deleteTrigger(handler); |
|
} |
|
}); |
|
|
|
log('Resetting total...'); |
|
properties.setProperty('total', '0'); |
|
|
|
log('Installing triggers...'); |
|
|
|
var queryInterval = get('queryInterval'); |
|
assertType(queryInterval, 'number', 'queryInterval must be a number'); |
|
|
|
ScriptApp.newTrigger('runQueries') |
|
.timeBased() |
|
.everyMinutes(queryInterval) |
|
.create(); |
|
|
|
ScriptApp.newTrigger('emailResults') |
|
.timeBased() |
|
.everyWeeks(1) |
|
.onWeekDay(ScriptApp.WeekDay[day]) |
|
.atHour(hour) |
|
.create(); |
|
}); |
|
|
|
section('Triggers initialized successfully.'); |
|
}); |
|
|
|
exportName('r', function runQueries() { |
|
section('Running queries'); |
|
|
|
var lock = LockService.getUserLock(); |
|
|
|
log('Scheduled run now commencing...'); |
|
|
|
if (!lock.tryLock(500)) { |
|
// A single lock failure isn't the end of the world here. It's only |
|
// going to try again a minute later. |
|
error('Run failed. Gracefully exiting...'); |
|
return; |
|
} |
|
|
|
var total = +properties.getProperty('total'); |
|
|
|
each(QUERIES, function (pair) { |
|
var query = pair[0]; |
|
var callback = pair[1]; |
|
|
|
log('Executing query: ' + query); |
|
each(GmailApp.search(query), function (thread) { |
|
log('Addressing Gmail thread: ' + thread.getFirstMessageSubject()); |
|
callback(thread); |
|
total++; |
|
}); |
|
}); |
|
|
|
properties.setProperty('total', total + ''); |
|
|
|
lock.releaseLock(); |
|
|
|
section('Queries sucessfully executed.'); |
|
}); |
|
|
|
exportName('e', function emailResults() { |
|
section('Emailing results'); |
|
|
|
if (!NOTIFY_EMAIL) return; |
|
|
|
log('Generating email...'); |
|
|
|
var total = doSync(function () { |
|
log('Retrieving total...'); |
|
return properties.getProperty('total'); |
|
}); |
|
|
|
log('Total: ' + total); |
|
log('Resetting total...'); |
|
properties.setProperty('total', '0'); |
|
|
|
log('Sending weekly email...'); |
|
|
|
var body = HEADER.replace(/\{\}/g, total); |
|
|
|
log('Email: ' + USER); |
|
log('Subject: ' + SUBJECT); |
|
log('Body: ' + body); |
|
|
|
GmailApp.sendEmail(USER, SUBJECT, body); |
|
|
|
section('Email sent successfully'); |
|
}); |
|
|
|
section('Script initialized successfully.'); |
|
})(this); |