I'm creating a GAS for Gmail.
The first part of this script is collecting all recipients and senders from a thread object from the GmailApp. No problem there, except I'm just left with an array of strings with the "full name <email>" syntax.
Secondly, I want to create a card for each contact email, by first looking up the contact object inside the ContactsApp. This however causes the script to time out, even though logs show it is able to fetch the contacts.
I've tested 2 alternatives:
get all contacts with ContactsApp.getContacts() and there's no timeout.
look up a single contact with CotnactsApp.getContact(email)an no timeout either.
// TEST 1: Get All Contacts
var allContacts = ContactsApp.getContacts();
// TEST 2: Get one contact
var testContact = ContactsApp.getContact("test#email.com");
// TEST 3: Loop over and get contacts
var threadContacts = [];
for (var i = 0; i < participants.length; i++) {
var email = participants[i].split("<")[1].split(">")[0];
var contact = ContactsApp.getContact(email);
threadContacts.push(contact)
}
So I'm a bit unclear here as to the intended usage. Should I go for option 1 and load all the contacts on the client side and iterate through these? Or is it intended to look for a contact each time I need to get the data? Doesn't loading all contact risks loading a gazillion contacts (should someone have so many?). It seems cumbersome.
I couldn't see any guidelines on the documentation.
You want to retrieve the contact object by searching the emails.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
In this answer, the contact objects are retrieved by searching the emails from ContactsApp.getContacts(). This method is also mentioned in your question.
Flow:
The flow of this sample script is as follows.
Create an array by retrieving email from "full name <email>".
Retrieve all contacts.
Retrieve the contact object by searching the emails from all contacts.
Sample script:
In this case, it supposes that participants is an array like ["full name <email1>", "full name <email2>",,,].
// Retrieve email address from "full name <tanaike#hotmail.com>" in participants and put them to an array.
var convertedParticipants = participants.map(function(e) {return e.split("<")[1].split(">")[0]});
var allContacts = ContactsApp.getContacts();
// Here, the contact objects are retrieved by searching the emails.
var results = allContacts.reduce(function(ar, c) {
var emails = c.getEmails();
if (emails.length > 0) {
// Retrieve email addresses from a contact object and put them to an array.
var addresses = emails.map(function(e) {return e.getAddress()});
// When the emails of "convertedParticipants" are included in the contact object, the object is put to an array.
if (convertedParticipants.some(function(f) {return addresses.indexOf(f) != -1})) {
ar.push(c);
}
}
return ar;
}, []);
When above script is run, you can see the searched contact objects in results as an array.
References:
getEmails()
getAddress()
reduce()
some()
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
When var results = allContacts.reduce(function(ar, c) {###}, []); is modified using the for loop, it becomes as follows.
Script:
var results = [];
for (var i = 0; i < allContacts.length; i++) {
var emails = allContacts[i].getEmails();
if (emails.length > 0) {
var addresses = [];
for (var j = 0; j < emails.length; j++) {
addresses.push(emails[j].getAddress());
}
var f = false;
for (var j = 0; j < convertedParticipants.length; j++) {
if (addresses.indexOf(convertedParticipants[j]) != -1) {
f = true;
break;
}
}
if (f) {
results.push(allContacts[i]);
}
}
}
Related
CONTEXT
I'm using a script to fetch emails that I found here (from Niclas, I think): fetching emails script
I've adapted it to my needs and it works very well!
WHAT I'VE TESTED
I saw getDescription() from the Google script Class Attachment but couldn't get it to work and I'm not even sure if that's the way to go
WHAT I WOULD WANT
In addition, I would like to fetch the attachments filenames since It's a distinctive mark on each email
Any help would me very appreciated. Thanks in advance
Modifications:
Do the following modifications to the answer of the post you mentioned in your question:
Add these lines:
var attachments = messages[maxIndex].getAttachments();
var attNames = attachments.map(att=>att.getName());
and modify this one:
ss.appendRow([from, cc, time, sub,...attNames ,'https://mail.google.com/mail/u/0/#inbox/'+mId])
Solution:
function myFunction() {
// Use sheet
var ss = SpreadsheetApp.getActiveSheet();
// Gmail query
var query = "label:support -label:trash -label:support-done -from:me";
// Search in Gmail, bind to array
var threads = GmailApp.search(query);
// Loop through query results
for (var i = 0; i < threads.length; i++)
{
// Get messages in thread, add to array
var messages = threads[i].getMessages();
// Used to find max index in array
var max = messages[0];
var maxIndex = 0;
// Loop through array to find maxIndexD = most recent mail
for (var j = 0; j < messages.length; j++) {
if (messages[j] > max) {
maxIndex = j;
max = messages[j];
}
}
// Find data
var mId = messages[maxIndex].getId() // ID used to create mail link
var from = messages[maxIndex].getFrom();
var cc = messages[maxIndex].getCc();
var time = threads[i].getLastMessageDate()
var sub = messages[maxIndex].getSubject();
var attachments = messages[maxIndex].getAttachments();
var attNames = attachments.map(att=>att.getName());
// Write data to sheet
ss.appendRow([from, cc, time, sub,...attNames ,'https://mail.google.com/mail/u/0/#inbox/'+mId])
}
}
Don't forget to change the value of query to your needs.
References:
Class GmailMessage
map()
Rest parameters
You have to enable V8 runtime to be able to use the snippet.
I am looking to create a simple script for a Google sheet in which array 1 will already be populated with a list of names. As a new name gets added to array 2, array 1 is checked for the name. If the name which was entered into array 2 is present in array 1 an action will be performed. This search function must take place each time a new name is added to array 2 to determine if it exists in array 1.
function findPlayer() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var picksRange = ss.getRange("B2:M15");
var poolRange = ss.getRange("B21:G96");
var picksPlayerName = picksRange.getValues();
var poolPlayerName = poolRange.getValues();
for (var i = 0; i < picksRange.length; i++)
for (var j = 0; j < poolRange.lenth; j++)
if (picksPlayerName[i] == poolPlayerName[j]) {
poolPlayerName[i].setBackground("red")}
else {
poolPlayerName[j].setBackground("blue");}
}
This is not a complete answer, nor does it perfectly fit your use-case, but you should be able to take it from here, or perhaps come back with another question when you have a question about a specific part of your code.
const existingNames = ["Carl", "Emma", "Sarah", "Ahmad"];
const newNames = ["Emma", "Sarah", "Isa", "Igor", "Kent"];
// go through the new names and check against existing ones
newNames.forEach(newName => {
if(existingNames.includes(newName)) {
// handle duplicates: do nothing?
} else {
// handle new names: maybe add them to the existing names?
existingNames.push(newName);
}
});
console.log('After going through all new names, the complete list of known names are: ' + existingNames);
Demo where you can play with the code and learn: https://jsfiddle.net/jonahe/11uom4cu/
I'm new to Javascript/Coding in general and am trying to solve a problem.
I have a program that pulls all my contact emails into an array then sorts through them and adds new emails. Pulling all the contacts takes too long (2000 contacts!) and I want to have Google Apps Script run this part automatically every 5 minutes to I have an updated list if I want to run the sort function.
Is it possible to make the contact pulling part of my function it's own function then use logger.log to save the values for use later? How can a seperate function access the logged info from this new function?
function getEmailAddressList(){
var addrA = [];
var contact = ContactsApp.getContacts();
//////////
for(var i = 0; i < contact.length; i++){
var addresses = contact[i].getEmails();
for(var j = 0;j < addresses.length; j++){
var address = addresses[j].getAddress();
addrA.push(address);
logger.log(addrA);
};
};
Depending upon the size of your data, you can store the data in "Properties Service" or "Cache." Properties Service can hold 9k of data per property name and a total of 500k. If the data will ever be larger than that, then you will need a different data storage option. For example, you could store the contact information in a text file in your Google Drive. You could obviously save your data to a database also. The point is, you need to store the contact information somewhere. You can not store it in a global variable, because a global variable will loose it's data as soon as the code stops running. Logger.log() won't help you in this situation.
You can run code on a time interval with a time based trigger. You will need to set that up from the "Edit" menu and "Current project's triggers."
Here is an example of how you might store the data in a text file:
function getEmailAddressList(){
var addrA,addresses,addressFile,allContacts,
contactsAsJSON,i,j,thisAddress,thisContact;
allContacts = ContactsApp.getContacts();
//////////
addrA = [];
for (i = 0; i < allContacts.length; i++) {
addresses = allContacts[i].getEmails();
for (j = 0;j < addresses.length; j++) {
thisAddress = addresses[j].getAddress();
thisContact = addresses[j].getDisplayName();
addrA.push(thisAddress);
Logger.log(thisContact);
}
}
contactsAsJSON = JSON.stringify(addrA);
try{
addressFile = DriveApp.getFileById('id');
}catch(e){}
if (!addressFile) {
addressFile = DriveApp.createFile('My Contacts', contactsAsJSON);
Logger.log('New file ID: ' + addressFile.getId());//Get the new file ID and hard code into
//the code above
}
addressFile.setContent(contactsAsJSON);
};
Here is an example of how you might store all contacts in Cache Service with each contact being one property name:
function getEmailAddressList(){
var addresses,allContacts,Cache,i,j,thisAddress,thisContact;
Cache = CacheService.getDocumentCache();
allContacts = ContactsApp.getContacts();
//////////
for (i = 0; i < allContacts.length; i++) {
addresses = contact[i].getEmails();
for (j = 0;j < addresses.length; j++) {
thisAddress = addresses[j].getAddress();
thisContact = addresses[j].getDisplayName();
Cache.put(thisContact, thisAddress)
logger.log(thisContact);
}
}
};
I have built a script that pulls from multiple spreadsheets and calculates total values. Basically what happens is multiple employees will input hours performed on a particular task for a particular customer. I then want to calculate how much work was done for a particular customer on a master spreadsheet and pull some analytics.
So on the master spreadsheet, there is a tab for each customer. This function takes the sheet names from the master spreadsheet and creates an array of all of the customer names. The reason why I do this is so that if a new tab is created, that customer name will automatically be included in the customer array.
function getCurrentCustomers() {
var sheets = servicesSpreadsheet.getSheets();
for (var i=0 ; i < sheets.length ; i++) {
if (sheets[i].getName() != "Services" && sheets[i].getName() != "Employee Files") {
currentCustomers.push(sheets[i].getName());
};
};
};
The next function takes a look at all of the files in a particular Google Drive folder and returns the IDs in an array. This allows me to create a copy of an employee spreadsheet stored in this particular folder, and that spreadsheet's values will automatically be calculated as they change.
function listFilesInFolder() {
var folder = DriveApp.getFolderById("0B7zrWIHovJrKVXlsaGx0d2NFT2c");
var contents = folder.getFiles();
var cnt = 0;
var file;
while (contents.hasNext()) {
//Finds the file in the specified folder
var file = contents.next();
//Increases the count
cnt++;
//Gets the Id of the file
data = [
file.getName(),
file.getId()
];
//Appends it to the employeeId list
employeeIds.push(data);
};
return employeeIds;
};
The last function is the one that is slowing it down a great deal.
First I create an array for all of the possible services. Unfortunately there are 137 individual services.
Then I loop through all of the customers. For each customer, I loop through every service to see if it appears on any of the employees spreadsheets. I am thinking there is a more efficient way to do this. The Google Script times out before I even get through one full customer. Also, I haven't even included the spreadsheets for all of the employees yet. I am just testing using dummy data.
function calculateNumbers(){
var allServices = servicesSpreadsheet.getSheetByName("Services").getRange("Z2:Z137").getValues();
Logger.log(allServices);
Logger.log(allServices[0][0]);
employeeList = listFilesInFolder();
//Gets services spreadsheet range
/*Loops through all of the current customers (currentCustomers comes from function getCurrentCustomers)*/
for (var c = 0; c < currentCustomers.length; c++) {
var currentCustomer = currentCustomers[c];
var lastColumn = servicesSpreadsheet.getSheetByName(currentCustomer).getLastColumn();
var servicesRange = SpreadsheetApp.openById("1X3RRR3UVeot-DYCyXOsfVo0DoKjHezltwBPwUm8ZYig").getSheetByName(currentCustomer).getRange("A4:BC227").getValues();
//Loops through all of the services
var serviceTotal = 0;
for (var service = 0; service < allServices.length; service++){
//Loops through employee spreadsheet Ids
for (var i = 0; i < employeeList.length; i++) {
//Get employee spreadsheet ID
var spreadsheetId = employeeList[i][1];
//Open the employee spreadsheet by ID
var employeeSpreadsheet = SpreadsheetApp.openById(spreadsheetId);
//Get the sheets from the particular employee spreadsheet
var sheets = employeeSpreadsheet.getSheets();
//Gets the name of each sheet in the employee spreadsheet
var sheetsName = [];
for (var j = 0; j < sheets.length; j++) {
sheetsName.push(sheets[j].getName());
};
//Loops through all of the sheets in an employee spreadsheet ignoring the Services spreadsheet
for (var q = 0; q < sheetsName.length; q++) {
if (sheetsName[q] != "Services") {
//Gets the range of the spreadsheet
var range = employeeSpreadsheet.getSheetByName(sheetsName[q]).getRange("A5:E1000").getValues();
//Loops through the range to see if range matches service and customer
for (var r = 0; r < range.length; r++) {
if (range[r][3] == allServices[service][0] && range[r][1] == currentCustomer) {
serviceTotal += range[r][4];
};
};
};
};
};
//Adds the service total to the correct customer's spreadsheet
for (var serviceServices = 4; serviceServices <= servicesRange.length; serviceServices++){
var end = 0;
if (end > 0) {break}
else if (allServices[service][0] == servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,1).getValues()) {
servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,6).setValue(serviceTotal);
end += 1;
};
};
};
};
};
The first image shows what an employee spreadsheet looks like. The second shows what an individual customer sheet looks like. There are many customer sheets on the master spreadsheet.
One of the things you will notice, is that each service has a category. I was thinking maybe to check the category and then check the particular service. There are 23 categories.
I was also hoping there was a way to only look at services that have actually had work done on them. If no one has ever done a Campaign Setup, maybe that could be ignored.
Any help would be greatly appreciated. I apologize for the lengthy post!
You are calling services inside of loops multiple times. These can take up to a couple seconds each, and are generally considered to be very slow. This is against Apps Script best practice as it greatly increases your execution time.
TL;DR: You are calling Apps Script services potentially thousands of times. Each call can take up to a couple seconds to execute. You need to cache your service calls outside of your loops, otherwise your performance is going to suffer horribly.
Examples:
1:
if (sheets[i].getName() != "Services" && sheets[i].getName() != "Employee Files")
Create and set a variable with the sheet name once, and check that instead of calling the getName() method twice. This isn't a huge deal, but will increase execution time.
2:
This is a biggie, as it's one level deep in your loop in calculateNumbers
var lastColumn = servicesSpreadsheet.getSheetByName(currentCustomer).getLastColumn();
var servicesRange = SpreadsheetApp.openById("1X3RRR3UVeot-DYCyXOsfVo0DoKjHezltwBPwUm8ZYig").getSheetByName(currentCustomer).getRange("A4:BC227").getValues();
2a:
You are opening a new spreadsheet, opening a new worksheet, and then getting a range for the same sheet, and getting the values of that range once per loop for your servicesRange. These service calls will stack up quick, and bloat your execution time.
2b:
I see you are getting the lastColumn, but I don't see it used anywhere? Maybe I missed something, but it going unused while making a service call for each loop will add even more to your execution time.
3:
This is massive, you are potentially calling Apps Script services thousands or tens of thousands of times here. This snippet is already two loop levels deep.
//Loops through all of the sheets in an employee spreadsheet ignoring the Services spreadsheet
for (var q = 0; q < sheetsName.length; q++) {
if (sheetsName[q] != "Services") {
//Gets the range of the spreadsheet
var range = employeeSpreadsheet.getSheetByName(sheetsName[q]).getRange("A5:E1000").getValues();
//Loops through the range to see if range matches service and customer
for (var r = 0; r < range.length; r++) {
if (range[r][3] == allServices[service][0] && range[r][1] == currentCustomer) {
serviceTotal += range[r][4];
};
};
};
};
};
//Adds the service total to the correct customer's spreadsheet
for (var serviceServices = 4; serviceServices <= servicesRange.length; serviceServices++){
var end = 0;
if (end > 0) {break}
else if (allServices[service][0] == servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,1).getValues()) {
servicesSpreadsheet.getSheetByName(currentCustomer).getRange(serviceServices,6).setValue(serviceTotal);
end += 1;
};
};
};
You have multi-level nested loops in nested loops calling the same services. If you had a thrice nested loop with each level being iterated 10 times, and the bottom level calling a service once per loop. You would be calling an Apps Script service 1,000 times. Being conservative, at let's say 0.5 seconds per service call, you would already have 8.3 minutes of execution time.
Cache your service calls, and perform bulk operations.
I have set up my google analytics API to generate a report within google sheets and feed it data from the given analytics view (profile) ID. I know that you can only run a report from one ID, so I would like to use JS to iterate through all 35 of them. I know enough JS to loop through a grouping, but how would I call the ID's? I will be using the google app script since it is already integrated. any ideas? thanks in advance!
*EDIT
so here is a little more detail. i have a google analytics account that monitors about 35 sites (all unique profile ID's that i have listed and have permissions to). those stats have been manually added to a google sheets doc up until this point. i want to grab the data from each of those Profile ID's and populate an existing spreadsheet without having to run 35 individual reports, which will create 35 individual sheets. i am already able to pull the reports one by one; writing a script to iterate through all the profiles and populating an existing spreadsheet (all my efforts thus far have just created new tabs on the existing sheet) is what i am wrestling with. thank you for your patience and time! happy holidays!
Here are copy/pasted from a Google Spreadsheet I use a few functions, one that lists all accounts for which the user is authenticated, one that returns all properties in an account (pass in account id as parameter) and one that lists all views in a property of a given account (pass in property and account id as parameter). The functions are written to return JSON objects where each element containts key/value pairs for name and (internal) id. Simply use the output of previous functions as parameters for the subsequent functions.
Note that for historical reasons the function that return the view info is named Analytics.Management.Profiles.list, "profiles" being what views used to be called before Universal Analytics.
/* Generates a list of accounts that are accessible to the currently logged in user */
function listAccounts() {
var accounts = Analytics.Management.Accounts.list();
var items = accounts.getItems();
var item;
var accountList = [];
if (items) {
for (i = 0, cnt = items.length; i < cnt; i++) {
item = items[i];
accountList.push({
"name": item.name,
"id": item.id
});
}
}
return accountList;
}
/* .... and all the properties in those accounts .. */
function listProperties(accountId) {
var webProperties = Analytics.Management.Webproperties.list(accountId);
var items = webProperties.getItems();
var item;
var propertyList = [];
if (items) {
for (i = 0, cnt = items.length; i < cnt; i++) {
item = items[i];
propertyList.push({
"name": item.name,
"id": item.id
});
}
}
return propertyList;
}
/* And all data view in the selected property */
function listViews(accountId, propertyId) {
var views = Analytics.Management.Profiles.list(accountId, propertyId);
var items = views.getItems();
var item;
var viewList = [];
if (items) {
for (i = 0, cnt = items.length; i < cnt; i++) {
item = items[i];
viewList.push({
"name": item.name,
"id": item.id
});
}
}
return viewList;
}