Gmail Snooze V1.6 - 2015-06-04
// ---------------------------------------------------------------------------------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2015 Iain Brown - http://www.littlebluemonkey.com/gmail-snooze-without-mailboxapp/
//
// Inspired by http://gmailblog.blogspot.co.nz/2011/07/gmail-snooze-with-apps-script.html
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ======================================================================
// Instructions
// ======================================================================
//
// 1. I would recommend making a copy of this script so that you can edit your settings, and maintain ownership of the code.
// This is done using the menu above: File->Make A Copy
//
// 2. Edit these user configurable settings - or leave them if you think they're ok.
var MARK_UNREAD = true; // When true, marks thread as unread when moving it back to the inbox
var MARK_MESSAGE_UNREAD_ONLY = true; // When true, only marks the most recent message as unread
var ADD_UNSNOOZED_LABEL = true; // Adds the unsnoozed label when moving back to the inbox
var ADD_SNOOZE_ERROR_LABEL = true; // If an invalid snooze label is used, labels thread as invalid when returning to inbox.
var SNOOZE_LABEL = "* Snooze"; // Prefixing it with * puts it at the top of the list.
var UNSNOOZE_LABEL = "* Unsnoozed"; // Prefixing it with * puts it at the top of the list.
var SNOOZE_ERROR_LABEL = "Snooze Error!";
var TIME_DAY_STARTS = "0700"; // If you want emails to be unsnoozed at say 8am, change to "0800"
var REMOVE_UNSNOOZE_LABEL = false; // If you want unsnooze label to be removed once message is no longer in Inbox
// 3. Select Run->Setup from the menu above.
//
// 4. If prompted to authorise this script to access Gmail and Calendar (which is used to determine your preferred timezone)
// then authorise the script until you see the text "Now you can run the script." - THEN REPEAT STEP 3 to complete the setup.
//
// 5. You're now good to go - when you go to Gmail, you should now have Snooze labels that you can use to snooze in various ways.
//
//
// ------------------------------------------------------------------------------------------------------------------------
//
// For more detailed instructions, and examples, visit: http://www.littlebluemonkey.com/gmail-snooze-without-mailboxapp/
//
// ------------------------------------------------------------------------------------------------------------------------
//
// Release Notes: 03/07/2013 1.2 - Fixed buy where certain mmddyy dates were resulting in 'Snooze Error!' labels being applied
// 18/08/2013 1.3 - Added Next Week/Next Month functionality that unsnoozes on following Monday/1st of Month
// 11/03/2014 1.4 - Added time for new days to start
// 14/01/2015 1.5 - Replacing deprecated API's
// 04/06/2015 1.6 - Optionally remove Unsnoozed label when no longer in Inbox
// ------------------------------------------------------------------------------------------------------------------------
// ======================================================================
// Code - Don't proceed any further unless you're a trained professional.
// ======================================================================
// --------------------------------------------------------------------------
// Stuff that you really shouldn't be touching unless you want to break stuff.
// --------------------------------------------------------------------------
var KEY_TOMORROW_DATE = "tomorrowDate";
var KEY_CURRENT_DAY = "currentDay";
var MONDAY_LABEL = "1 - Monday";
var TUESDAY_LABEL = "2 - Tuesday";
var WEDNESDAY_LABEL = "3 - Wednesday";
var THURSDAY_LABEL = "4 - Thursday";
var FRIDAY_LABEL = "5 - Friday";
var SATURDAY_LABEL = "6 - Saturday";
var SUNDAY_LABEL = "7 - Sunday";
var snoozeErrorLabel = null;
var unsnoozeLabel = null;
// --------------------------------------------------------------------------
// Setup function to add labels, initialise properties and set script trigger.
// --------------------------------------------------------------------------
function setup() {
GmailApp.createLabel(SNOOZE_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/0 - Tomorrow");
GmailApp.createLabel(SNOOZE_LABEL + "/" + MONDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + TUESDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + WEDNESDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + THURSDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + FRIDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + SATURDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/" + SUNDAY_LABEL);
GmailApp.createLabel(SNOOZE_LABEL + "/8 - next_week");
GmailApp.createLabel(SNOOZE_LABEL + "/8 - next_month");
GmailApp.createLabel(SNOOZE_LABEL + "/8 - time_hhmm");
GmailApp.createLabel(SNOOZE_LABEL + "/8 - time_hhmm/1200");
GmailApp.createLabel(SNOOZE_LABEL + "/8 - time_hhmm/1700");
GmailApp.createLabel(SNOOZE_LABEL + "/8 - time_hhmm/2000");
GmailApp.createLabel(SNOOZE_LABEL + "/9 - date_ddmmyy");
GmailApp.createLabel(SNOOZE_LABEL + "/9 - date_mmddyy");
GmailApp.createLabel(SNOOZE_LABEL + "/9 - date_ddmmyy/280219");
GmailApp.createLabel(SNOOZE_LABEL + "/9 - date_mmddyy/022819");
if (ADD_UNSNOOZED_LABEL) {
GmailApp.createLabel(UNSNOOZE_LABEL);
}
initialiseProperty(KEY_TOMORROW_DATE, getCurrentIsoDate());
initialiseProperty(KEY_CURRENT_DAY, getCurrentDay());
ScriptApp.newTrigger("moveSnoozes").timeBased().everyMinutes(1).create();
}
// ------------------------------------------------------------------------
// The main script that does all the good stuff - triggered every 1 minute:
// ------------------------------------------------------------------------
function moveSnoozes() {
var oldLabel, newLabel, page;
var today = getCurrentDay();
var todayIso = getCurrentIsoDate();
var now = getCurrentIsoDateAndTime();
var newDay = isNewDay(today,TIME_DAY_STARTS);
var snoozeLength = SNOOZE_LABEL.length;
var labelName = "";
var labels = GmailApp.getUserLabels();
for (var i = 0; i < labels.length; i++) {
labelName = labels[i].getName();
if (labelName.length >= snoozeLength) {
if (labelName.substr(0,snoozeLength) == SNOOZE_LABEL) {
if (isDayLabel(labelName)) {
if (newDay && labelName.substr(snoozeLength + 5) === today) {
processLabel(labels[i]);
}
}
else if(labelName.substr(snoozeLength) == "/0 - Tomorrow") {
if (newDay && tomorrowCanBeProcessed(todayIso)) {
processLabel(labels[i]);
PropertiesService.getUserProperties().setProperty(KEY_TOMORROW_DATE, todayIso);
}
}
else if(labelName.substr(snoozeLength) == "/8 - next_week") {
if (newDay && nextWeekCanBeProcessed(today)) {
processLabel(labels[i]);
}
}
else if(labelName.substr(snoozeLength) == "/8 - next_month") {
if (newDay && nextMonthCanBeProcessed(todayIso)) {
processLabel(labels[i]);
}
}
else if(isValidTimeLabel(labelName,snoozeLength)) {
var time = getTimeFromLabel(labelName,snoozeLength);
if (timeCanBeProcessed(time,now)) {
processLabel(labels[i]);
PropertiesService.getUserProperties().setProperty("Time:"+time,now.substr(0,10));
}
}
else if(isValidDDMMYYLabel(labelName,snoozeLength)) {
if (newDay && dateCanBeProcessed(getDateFromDDMMYYLabel(labelName,snoozeLength),todayIso)) {
processLabel(labels[i]);
GmailApp.deleteLabel(labels[i]);
}
}
else if(isValidMMDDYYLabel(labelName,snoozeLength)) {
if (newDay && dateCanBeProcessed(getDateFromMMDDYYLabel(labelName,snoozeLength),todayIso)) {
processLabel(labels[i]);
GmailApp.deleteLabel(labels[i]);
}
}
// If there are any "Snooze" type labels that aren't what we're expecting
// then flag them in error and move back to the inbox:
else {
processInvalidLabel(labels[i]);
}
}
}
}
if (newDay) {
PropertiesService.getUserProperties().setProperty(KEY_CURRENT_DAY, today);
}
if (REMOVE_UNSNOOZE_LABEL == true) {
removeUnsnoozedLabels();
}
return;
}
function removeUnsnoozedLabels() {
var query = "(!label:inbox AND label:"+UNSNOOZE_LABEL.replace(/ /g, "-")+") "
var threads = GmailApp.search(query);
if (threads.length == 0) {
return;
}
var label = GmailApp.getUserLabelByName(UNSNOOZE_LABEL)
for (var i = 0; i<threads.length;i++) {
threads[i].removeLabel(label);
}
}
function processLabel(label) {
var page = null;
// Get threads in "pages" of 100 at a time
while(!page || page.length == 100) {
page = label.getThreads(0, 100);
if (page.length > 0) {
for (var i =0;i<page.length;i++) {
if (MARK_UNREAD) {
if (MARK_MESSAGE_UNREAD_ONLY) {
var msgs = page[i].getMessages();
msgs[msgs.length-1].markUnread();
}
else {
page[i].markUnread();
}
}
page[i].moveToInbox();
page[i].removeLabel(label);
if (ADD_UNSNOOZED_LABEL) {
var unsnooze = getUnsnoozeLabel();
if (unsnooze != null) {
page[i].addLabel(unsnooze);
}
}
}
}
}
}
function processInvalidLabel(label) {
var page = null;
// Get threads in "pages" of 100 at a time
while(!page || page.length == 100) {
page = label.getThreads(0, 100);
if (page.length > 0) {
for (var i =0;i<page.length;i++) {
page[i].markUnread();
page[i].moveToInbox();
page[i].removeLabel(label);
if (ADD_SNOOZE_ERROR_LABEL) {
var snoozeError = getSnoozeErrorLabel();
if (snoozeError != null) {
page[i].addLabel(snoozeError);
}
}
}
}
}
return;
}
function tomorrowCanBeProcessed(newIsoDate) {
var oldDate = PropertiesService.getUserProperties().getProperty(KEY_TOMORROW_DATE);
if (oldDate !== newIsoDate) {
return true;
}
else {
return false;
}
}
function nextWeekCanBeProcessed(newDay) {
if (newDay === "Monday") {
return true;
}
else {
return false;
}
}
function nextMonthCanBeProcessed(newIsoDate) {
if (newIsoDate.substring(8) === "01") {
return true;
}
else {
return false;
}
}
function timeCanBeProcessed(time,now) {
if (time !== "") {
var triggerDateAndTime = now.substr(0,10) + "-" + time;
var processedDate = PropertiesService.getUserProperties().getProperty("Time:"+time);
if (processedDate === null) {
if (now > triggerDateAndTime) {
processedDate = now.substr(0,10);
}
else {
processedDate = "0001-01-01";
}
PropertiesService.getUserProperties().setProperty("Time:"+time, processedDate);
}
if (now >= triggerDateAndTime) {
if (processedDate === null || processedDate === "" || processedDate < getCurrentIsoDate()) {
return true;
}
}
}
return false;
}
function dateCanBeProcessed(theDate,today) {
if (today >= theDate) {
return true;
}
return false;
}
function getTimeFromLabel(labelName,snoozeLength) {
var labelLength = labelName.length;
var timeLength = labelLength - (snoozeLength+15);
var time = "";
if ( labelLength == snoozeLength + 18 || labelLength == snoozeLength + 19) {
if (labelName.substr(snoozeLength,15) == "/8 - time_hhmm/") {
time = labelName.substr(snoozeLength + 15,timeLength);
if (timeLength == 3) {
time = "0" + time;
}
var isValid = true;
for (var i = 0; i<4; i++) {
if (isNaN(time.substr(i,1))) {
isValid = false;
}
}
if (isValid) {
var hours = parseInt(time.substr(0,2),10);
var minutes = parseInt(time.substr(2,2),10);
if (hours < 0 || hours > 23) {
isValid = false;
}
else if (minutes < 0 || minutes > 59) {
isValid = false;
}
}
if (!isValid) {
time = "";
}
}
}
return time;
}
function isValidTimeLabel(labelName,snoozeLength) {
var time = getTimeFromLabel(labelName,snoozeLength);
if (time !== "") {
return true;
}
return false;
}
function getDateFromDDMMYYLabel(labelName,snoozeLength) {
var labelLength = labelName.length;
var ddmmyyLength = labelLength - (snoozeLength+17);
var ddmmyy = "";
if ( labelLength == snoozeLength + 17 + 6) {
if (labelName.substr(snoozeLength,17) == "/9 - date_ddmmyy/") {
ddmmyy = labelName.substr(snoozeLength + 17,6);
var isValid = true;
for (var i = 0; i<6; i++) {
if (isNaN(ddmmyy.substr(i,1))) {
isValid = false;
}
}
if (isValid) {
var dd = parseInt(ddmmyy.substr(0,2),10);
var mm = parseInt(ddmmyy.substr(2,2),10);
var yyyy = 2000 + parseInt(ddmmyy.substr(4,2),10);
isValid = isValidDateValues(dd,mm,yyyy);
}
}
}
if (!isValid) {
ddmmyy = "";
}
else {
ddmmyy = '20' + ddmmyy.substr(4,2) + "-" + ddmmyy.substr(2,2) + '-' + ddmmyy.substr(0,2);
}
return ddmmyy;
}
function getDateFromMMDDYYLabel(labelName,snoozeLength) {
var labelLength = labelName.length;
var mmddyyLength = labelLength - (snoozeLength+17);
var mmddyy = "";
if ( labelLength == snoozeLength + 17 + 6) {
if (labelName.substr(snoozeLength,17) == "/9 - date_mmddyy/") {
mmddyy = labelName.substr(snoozeLength + 17,6);
var isValid = true;
for (var i = 0; i<6; i++) {
if (isNaN(mmddyy.substr(i,1))) {
isValid = false;
}
}
if (isValid) {
var dd = parseInt(mmddyy.substr(2,2),10);
var mm = parseInt(mmddyy.substr(0,2),10);
var yyyy = 2000 + parseInt(mmddyy.substr(4,2),10);
isValid = isValidDateValues(dd,mm,yyyy);
}
}
}
if (!isValid) {
mmddyy = "";
}
else {
mmddyy = '20' + mmddyy.substr(4,2) + "-" + mmddyy.substr(0,2) + '-' + mmddyy.substr(2,2);
}
return mmddyy;
}
function isValidDateValues(dd,mm,yyyy) {
if (mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31 && (yyyy >= 2000 && yyyy <= 9999)) {
if (((mm == 4 || mm == 6 || mm == 9 || mm == 11) && dd <= 30 )
|| (mm == 2 && dd <= 28 || dd <= 29 && isLeapYear(yyyy))
|| (mm != 4 && mm != 6 && mm != 9 && mm != 11 && mm != 2)) {
return true;
}
}
}
function isLeapYear(years) {
if (years % 400 === 0 ) {return true;}
else if (years % 100 === 0 ) {return false;}
else if (years % 4 === 0 ) {return true;}
else {return false;}
}
function isValidDDMMYYLabel(labelName,snoozeLength) {
var ddmmyy = getDateFromDDMMYYLabel(labelName,snoozeLength);
if (ddmmyy !== "") {
return true;
}
return false;
}
function isValidMMDDYYLabel(labelName,snoozeLength) {
var mmddyy = getDateFromMMDDYYLabel(labelName,snoozeLength);
if (mmddyy !== "") {
return true;
}
return false;
}
function getUsersTimeZone() {
return CalendarApp.getTimeZone();
}
function getDayOfWeek(aDate) {
return Utilities.formatDate(aDate, getUsersTimeZone(), "EEEE");
}
function getCurrentDay() {
return getDayOfWeek(new Date());
}
function getCurrentIsoDateAndTime() {
return Utilities.formatDate(new Date(), getUsersTimeZone(), "yyyy-MM-dd-HHmm");
}
function getCurrentIsoDate() {
return Utilities.formatDate(new Date(), getUsersTimeZone(), "yyyy-MM-dd");
}
function isDayLabel(labelName) {
if (labelName === SNOOZE_LABEL + "/" + MONDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + TUESDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + WEDNESDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + THURSDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + FRIDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + SATURDAY_LABEL
|| labelName === SNOOZE_LABEL + "/" + SUNDAY_LABEL) {
return true;
}
return false;
}
function getUnsnoozeLabel() {
if (unsnoozeLabel === null) {
Logger.log("Load unsnooze label from global");
unsnoozeLabel = GmailApp.getUserLabelByName(UNSNOOZE_LABEL);
}
if (unsnoozeLabel === null) {
unsnoozeLabel = GmailApp.createLabel(UNSNOOZE_LABEL);
}
else {
Logger.log("unsnooze found");
}
return unsnoozeLabel;
}
function getSnoozeErrorLabel() {
if (snoozeErrorLabel === null) {
Logger.log("Load snoozeError label from global");
snoozeErrorLabel = GmailApp.getUserLabelByName(SNOOZE_ERROR_LABEL);
}
if (snoozeErrorLabel === null) {
snoozeErrorLabel = GmailApp.createLabel(SNOOZE_ERROR_LABEL);
}
else {
Logger.log("SnoozeError found");
}
return snoozeErrorLabel;
}
function initialiseProperty(key,initialValue) {
var value = PropertiesService.getUserProperties().getProperty(key);
if (value === null || value === "") {
PropertiesService.getUserProperties().setProperty(key, initialValue);
}
}
function getCurrentHHMM() {
var now = getCurrentIsoDateAndTime();
return now.substring(11,15);
}
function isNewDay(today,timeDayStarts) {
var lastDateSaved = PropertiesService.getUserProperties().getProperty(KEY_CURRENT_DAY);
if (lastDateSaved != today && getCurrentHHMM() >= timeDayStarts) {
return true;
}
return false;
}