Excluding a label from a Google Apps Script - google-apps-script

This is my first post :)
I am using a Google Apps Script that tracks e-mail from the last 7 days that have not had a response (basically tracks emails where my message is the last one).
This is the code here:
// This script searches Gmail for conversations where I never received a response
// and puts them in a NoResponse label
var DAYS_TO_SEARCH = 7; // look only in sent messages from last 7 days, otherwise script takes a while
var SINGLE_MESSAGE_ONLY = false; // exclude multi-message conversations where I sent the last message?
function label_messages_without_response() {
var emailAddress = Session.getEffectiveUser().getEmail();
Logger.log(emailAddress);
var EMAIL_REGEX = /[a-zA-Z0-9\._\-]+#[a-zA-Z0-9\.\-]+\.[a-z\.A-Z]+/g;
var label = GmailApp.createLabel("AwaitingResponse");
var d = new Date();
d.setDate(d.getDate() - DAYS_TO_SEARCH);
var dateString = d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
threads = GmailApp.search("in:Sent after:" + dateString);
for (var i = 0; i < threads.length; i++)
{
var thread = threads[i];
if (!SINGLE_MESSAGE_ONLY || thread.getMessageCount() == 1)
{
var lastMessage = thread.getMessages()[thread.getMessageCount()-1];
lastMessageSender = lastMessage.getFrom().match(EMAIL_REGEX)[0];
if (lastMessageSender == emailAddress)
{
thread.addLabel(label);
Logger.log(lastMessageSender);
}
}
}
}
The problem is at the moment, when the script runs the un-replied messages go into the "NoResponse" label which is great. However, when I delete the label from the emails that I don't need to follow up on, they come back up again when the script runs again.
My question is:
Would there would be a way to apply a label to messages that don't need to be followed up on, and then work that into the script, so that the script knows to exclude that label?
Any help would be fantastic :)
Thanks
Aidan

May be the script can apply two labels - NoResponse and Processed. You can remove the NoResponse label manually and yet the Processed label would stay.
The filter can be modified like:
threads = GmailApp.search("in:Sent -in:Processed after:" + dateString);

Related

How to filter out all emails that came from a mailing list in Gmail

Is there a way to filter out all emails that came from a mailing list within Gmail or Google Apps Script using a search query. I know you can filter out a specific email address using list:info#example.com. But I want a catch-all type of query or even a query to catch-all from a specific domain such as list:#example.com. However, this does not work. Any ideas? Any help is greatly appreciated, thank you!
This function will trash all messages from all inbox thread that are not in the list.
function emailFilter() {
var list=['a#company.com','b#company.com','c#company.com','d#company.com','e#company.com'];
var threads=GmailApp.getInboxThreads();
var token=null;
for(var i=0;i<threads.length;i++) {
if(threads[i].getMessageCount()) {
var messages=threads[i].getMessages();
for(var j=0;j<messages.length;j++) {
if(list.indexOf(messages[j].getFrom()==-1)) {
messages[j].moveToTrash();
}
}
}
}
}
I haven't tested it because I keep my inbox empty all of the time. You might want to replace 'moveToTrash()' to 'star()' for testing
What I could understand from your question and your comments, you need to filter the emails in a user's inbox that he has received, which don't only contain a certain label, but also a certain domain. If I understood well this code can help you:
function checkLabels() {
// Get the threads from the label you want
var label = GmailApp.getUserLabelByName("Label Test List");
var threadArr = label.getThreads();
// Init variable for later use
var emailDomain = '';
// Iterate over all the threads
for (var i = 0; i < threadArr.length; i++) {
// for each message in a thread, do something
threadArr[i].getMessages().forEach(function(message){
// Let's get the domains from the the users the messages were from
// example: list:#example.com -> Result: example.com
emailDomain = message.getFrom().split('<').pop().split('>')[0].split('#')[1];
// if emailDomain is equal to example.com, then do something
if(emailDomain === 'example.com'){
Logger.log(message.getFrom());
}
});
}
}
Using the Class GmailApp I got a certain label with the .getUserLabels() method and iterate through the threads thanks to the .getInboxThreads method. With a second loop and the .getMessages() you can get all the messages in a thread and for knowing the one who sent them, just use the .getFrom() method.
Docs
For more info check:
Gmail Service.
Class GmailMessage.
Class GmailThread.
So I was able to avoid replying to emails that come from a mailing list address by using the getRawContent() method and then searching that string for "Mailing-list:". So far the script is working like a charm.
function autoReply() {
var interval = 5; // if the script runs every 5 minutes; change otherwise
var date = new Date();
var day = date.getDay();
var hour = date.getHours();
var noReply = ["email1#example.com",
"email2#example.com"];
var replyMessage = "Hello!\n\nYou have reached me during non-business hours. I will respond by 9 AM next business day.\n\nIf you have any Compass.com related questions, check out Compass Academy! Learn about Compass' tools and get your questions answered at academy.compass.com.\n\nBest,\n\nShamir Wehbe";
var noReplyId = [];
if ([6,0].indexOf(day) > -1 || (hour < 9) || (hour >= 17)) {
var timeFrom = Math.floor(date.valueOf()/1000) - 60 * interval;
var threads = GmailApp.search('from:#example.com is:inbox after:' + timeFrom);
var label = GmailApp.getUserLabelByName("autoReplied");
var repliedThreads = GmailApp.search('label:autoReplied newer_than:4d');
// loop through emails from the last 4 days that have already been replied to
for (var i = 0; i < repliedThreads.length; i++) {
var repliedThreadsId = repliedThreads[i].getMessages()[0].getId();
noReplyId.push(repliedThreadsId);
}
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
var messagesFrom = message.getFrom();
var email = messagesFrom.substring(messagesFrom.lastIndexOf("<") + 1, messagesFrom.lastIndexOf(">"));
var threadsId = message.getId();
var rawMessage = message.getRawContent();
var searchForList = rawMessage.search("Mailing-list:");
var searchForUnsubscribe = rawMessage.search("Unsubscribe now");
// if the message is unread, not on the no reply list, hasn't already been replied to, doesn't come from a mailing list, and not a marketing email then auto reply
if (message.isUnread() && noReply.indexOf(email) == -1 && noReplyId.indexOf(threadsId) == -1 && searchForList === -1 && searchForUnsubscribe === -1){
message.reply(replyMessage);
threads[i].addLabel(label);
}
}
}
}

Inconsistencies between app scripts GmailApp.search and the search in gmail interface

I'm trying to build a google app script to import mail received from an online form to a spreadsheet.
I am using two labels: One "to_process" is added by a gmail filter, the other one "processed" is added by this script after the email was added to the sheet.
I am searching for all emails that have "to_process" but not "processed" using the search query 'label:to_process !label:processed in:all'
I got it working partially (see the core of the script below)
I'm running the script using the script editor run function.
The problem is that using the same query in gmail interface i get more than 100 emails, but in the log of the script I get 6, and they are all processed.
Am I missing something?
function extractInfo() {
var step = 30;
var max = 500;
var currentStep = 0;
while(max--) {
var threads = GmailApp.search('label:to_process !label:processed in:all', currentStep++ * step, step);
if(threads.length == 0) break;
Logger.log("-------- found threads: " + threads.length);
var threadId = threads.length;
while(threadId--) {
var thread = threads[threadId];
thread.refresh();
if(hasLabel(thread, "processed")) {
Logger.log("was processed: " + thread.getPermalink())
continue;
}
if(!hasLabel(thread, "to_process")) {
Logger.log("isn't mark to process: " + thread.getPermalink())
continue;
}
var messages = thread.getMessages();
var messageId = messages.length;
while(messageId--) {
var message = messages[messageId];
var row = extractMessageData(message);
sheet.appendRow(row);
GmailApp.markMessageRead(message);
}
threads[threadId].addLabel(processedLabel);
}
}
}
function hasLabel(thread, name) {
var labels = thread.getLabels();
var l = labels.length;
while(l--) {
if(labels[l].getName() == name) {
return true;
}
}
return false;
}
After much trail and error I partially figured this out.
What I see in gmail ui is in fact message, and not threads. But handling labels is a per thread thing.
I simply pulled all message of all threads from a given label, and then processed theses.
If a thread has a label "processed" it doesn't mean all it's messages were processed, which is a problem.
There are still inconsistencies regarding the number of messages i see in the UI and what I get using the API though.

Retuning a GMail script to prevent "Exceeded maximum execution time"

I use a gmail account to receive status updates from devices which I have listed in a Google spreadsheet by its user and serial number. The status update email will have the serial number of the device in it's email subject and will usually be sent about once a week per device. Due to the number of devices (600+), I get a pretty constant influx of status updates from various devices every day. What I'm trying to do is setup a script that will search my Inbox and create a list of devices that I haven't received a status update email from which would prompt me to figure out why that device isn't reporting in.
I've modified a script I found online at: http://forums.mozillazine.org/viewtopic.php?f=46&t=2740775. It works by obtaining a device serial number listed in a spreadsheet and searching the Inbox for an email subject with that serial number sent within the past 15 days. The script works fine when the spreadsheet has about 200 items, but beyond that I start getting an "Exceeded maximum execution time" error and I need it to do 600+ items now and more as time goes on. I also had to add the Utilities.sleep(1000) line to avoid a "Service invoked too many times in a short time: gmail rateMax" error. Is there a better way to write this script to avoid these errors?
function SearchEmail() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var waittime = 15;
var emailTo = "temp#email.com";
var emailSubject = "NO EMAIL IN " + waittime + " DAYS FROM FOLLOWING DEVICES";
var emailText = "";
for (var i = 1; i < data.length; i++) {
Utilities.sleep(1000);
var j = i + 1;
var gsearch = "in:inbox subject:(" + data[i][1] + ") newer_than:" + waittime + "d";
var threads = GmailApp.search(gsearch, 0, 1);
if (threads.length == 0) {
var emailText = emailText + j + " Device: " + data[i][1] + " User: " + data[i][0] + "\n";
}
}
MailApp.sendEmail(emailTo, emailSubject, emailText);
}
I'd suggest you see the problem from another point of view.
Could you find a common string to all the emails that your report is sending ? Something like "device status" in the subject or in the body...
This way you could search your inbox for that string, put a label on it so that you avoid rechecking them and use a simple function to check in your spreadsheet if that number is in the list.
function checkList(device){
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for(var n=0;n<data.length;n++){
if(data[n][0]==device){ return true};// assuming number are in column A
}
return false;
}
then send a mail only if(checkList(device)!=true){
This would be far more efficient I think.

google apps script very very slow

I am using a google spreadsheet as a simple database of members and I want to create a user interface for searching through them (The primary users for this are not very technically adept and there is quite allot of data associated with each member so viewing it as a spreadsheet can be a bit tedius)
I have written the folowing script which worked last night but appears to run so slowly it times out today and I have no idea why.
function findMember() {
// set spreadsheet variable
var SS = SpreadsheetApp.getActiveSpreadsheet();
// set sheet variables
var memberSearchSheet = SS.getActiveSheet();
var memberDataSheet = SS.getSheetByName("Member Data");
// get the search variables
var searchFirstName = memberSearchSheet.getRange('C2').getValue();
var searchLastName = memberSearchSheet.getRange('C3').getValue();
// get last row of data
var lastRow = memberDataSheet.getLastRow();
for (var i = 2;lastRow;i=i+1){
if (searchFirstName == memberDataSheet.getRange('R'+i+'C2').getValue() && searchLastName == memberDataSheet.getRange('R'+i+'C3').getValue()){
memberSearchSheet.getRange('C5').setValue(memberDataSheet.getRange('R'+i+'C5').getValue());
//throw new Error("ouch")
}
}
// small pop up notification in bottom right corner .toast(message, title, display time)
//var message = "Your search for " + searchFirstName + " " + searchLastName + " is complete.";
//SS.toast(message,"Search Complete",15);
};
You can probably trying getting all the data inside an array in one step and then quickly compare your value with those in the array. Something like this:
var sheet = SpreadsheetApp.getActive().getSheetByName("Member Data");
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][0] == firstName && data[i][1] == secondName) {
throw("Found");
}
}

Google Apps Script: Submit data into specified column

I am trying to find a script, or begin writing one, that takes a simple Google Form with a drop-down list of names (i.e. Tom, Jane) and a text area, and inputs both the date and the text into columns based on the selected name (i.e. Tom Date, Tom Comment). This is so I can make a quick entry feedback form for leaving individualized, date-based feedback for students, which they can then access later.
I looked through the GAS documentation and looked for examples, but as I am a novice, I really didn't know where to begin.
Any ideas on how to do this?
I think I did something similar but mine is for admins to observe teachers. I'm just learning as well, so I'm sure there are better ways to do this but it works. I definitely should have broken it up into more functions.
My form has a trigger to fire the onClose() when submitted. onClose() produces a Google Doc by reading the spreadsheet containing the form data in a nice format that the observer can then edit and share with the teacher. I wanted the Google Doc produced to have the name of the teacher being observed in the file name and I wanted it shared with the admin who did the observing.
The fact that some of the fields are dropdowns doesn't matter, it is just an itemResponse from the list of all responses.
function onClose()
{
var form = FormApp.openById(' whatever your form id is');
//the spreadsheet of the form responses
var formResponses = form.getResponses();
var d = new Date();
var currentTime = d.toLocaleTimeString();
var date = d.toLocaleDateString();
//Need to get the name of the teacher being observed
var formResponse = formResponses[formResponses.length-1];
var itemResponses = formResponse.getItemResponses();
var itemResponse = itemResponses[0]; //the teacher name dropdown box
var teacherName = itemResponse.getResponse() + '-' + itemResponses[1].getResponse();
//create the new document
var fileName = 'Observation-'+ teacherName + '-' + date + '-' + currentTime;
var doc = DocumentApp.create(fileName);
var activeDoc = DocumentApp.getActiveDocument();
var files = DriveApp.getFilesByName(fileName);
while (files.hasNext()) {
var file = files.next();
if (file.getName().equals(fileName))
{
//this is the last item on my form the name of the observer
var itemResponse21 = itemResponses[21];
var observer = itemResponse21.getResponse();
// Logger.log('Person to share with is ' + observer);
// share this google doc with the observer
file.addEditor(observer);
}
}
//ommittd a bunch of styles
//This would get all forms submitted, but I only need the last one
// so I just set the loop to get the last form submitted.
//leaving for loop just so I remember I can go through all forms again
//if I want to.
for (var i = formResponses.length-1; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
//get the individual responses within the form.addCheckboxItem()
for (var j = 0; j < itemResponses.length; j++) {
//pull the first item out again (even though I did for file name)
var itemResponse = itemResponses[j]; //teacher name from a dropbox
var itemResponse2 = itemResponses[j+1]; //date
var itemResponse3 = itemResponses[j+2]; //time
if (j == 0) //the first field on the form
{
//put the headings in
par3 = doc.appendParagraph(' SCHOOL NAME');
par3 = doc.appendParagraph('WALK IN OBSERVATION');
par3 = doc.appendParagraph('2013-2014');
//THIS is the teacher being observed and the date and time --- all on same line
var headingLine = itemResponse.getItem().getTitle() + '\t\t' + itemResponse2.getItem().getTitle() + ' / ' + itemResponse3.getItem().getTitle();
par1 = doc.appendParagraph(headingLine);
var answerLine = itemResponse.getResponse() + '\t\t\t\t\t' + itemResponse2.getResponse() + ' / ' + itemResponse3.getResponse();
par2 = doc.appendParagraph(answerLine);
j++; //do this to skip over date and time
j++;
} //end of j = 0;
else
// then I have a bunch of if statements for some of the
// specific fields I need to do something special with.
// After the last if, I just have an else to handle all other
// form responses that I don't do anything special for other than display.
//my last else is:
else
//THIS ELSE IS HANDLING ALL NON CHECK BOXES AND JUST DISPLAYING THE TITLE IN BOLD FONT, THE COMMENTS IN REGULAR FONT
{
par1 = doc.appendParagraph(itemResponse.getItem().getTitle());
par1.setAttributes(style);
par2 = doc.appendParagraph( itemResponse.getResponse());
par2.setAttributes(style);
} //END OF ELSE
} //end of for going through each cell in a row of the repsonses
} //end of for going through each row -- only had it set to do the very last row