Google Apps Script - Contacts - Query on multiple strings - google-apps-script

I'm working on a script to remove and re-add about 100 contacts. I have 12 different search criteria for ContactsApp.getContactsByEmailAddress, which the initiated know takes 30+ seconds to run. Is there some way I can only run it once and search all of my criteria? I've looked for others trying to do this same thing and was unsuccessful.
Below is one of the searches from my function (repeats 12 times with various search terms being passed to ContactsApp.getContactsByEmailAddress). I added the try-catch block because the script kept throwing errors out for seemingly no reason during various delete loops.
Would appreciate any and all advice.
var apa = ContactsApp.getContactsByEmailAddress('apa.')
try{
for (var i in apa) {
apa[i].deleteContact()
}
} catch(e){
Logger.log(e)
}

With the Contacts Service, you are limited to a single search criteria. Thus, the only way to search multiple patterns is to call the method once with each search parameter. You can, thankfully, use standard programming practices to minimize the amount of repeated code:
function getContactsWithEmails(emailSearchCriteria) {
if (!emailSearchCriteria || !emailSearchCriteria.length)
throw new Error("No search inputs given");
// Collect the results of each search into a single Array.
const matches = [];
emailSearchCriteria.forEach(function (email) {
var results = ContactsApp.getContactsByEmailAddress(email);
if (results.length)
Array.prototype.push.apply(matches, results);
else
console.log({message: "No results for search query '" + email + "'", query: email, resultsSoFar: matches});
});
return matches;
}
function deleteContacts(arrayOfContacts) {
if (!arrayOfContacts || !arrayOfContacts.length)
throw new Error("No contacts to delete");
arrayOfContacts.forEach(function (contact) {
ContactsApp.deleteContact(contact);
});
}
// Our function that uses the above helper methods to do what we want.
function doSomething() {
// Define all email searches to be performed.
const emailFragmentsToSearchWith = [
"email1",
...
"emailN"
];
const matchingContacts = getContactsWithEmails(emailFragmentsToSearchWith);
if (matchingContacts.length) {
/** do something with the contacts that matched the search.
* someMethodThatSavesContacts(matchingContacts);
* someMethodThatModifiesContacts(matchingContacts);
* deleteContacts(matchingContacts);
* ...
*/
}
/** do other stuff that doesn't need those contacts. */
}
The Google Calendar v3 GData API, as mentioned in this SO question, does support multiple query parameters. However, there is no simple integration with this API - you will need to write the appropriate URL requests and execute them with UrlFetchApp.
In addition to the Google Contacts API, you could use the Google People REST API, specifically the people.connections#list endpoint.
Both of these APIs require you to associate your Apps Script project with a Google Cloud Project that has the respective API enabled, and will likely require you to manually set the scopes your Apps Script project requires in its manifest file, in addition to providing OAuth2 authorizations of the associated HTTP requests you make to the API endpoints.
References
Accessing External APIs
Enabling Google APIs (steps 3-5)
Google Contacts API v3
Google People API
Array#forEach

Related

Google Apps Script - call function deployed as API executable

I have created a Google apps script attached to a google sheet (where I have various methods manipulating the spreadsheet), and I have deployed it as API executable (enabling OAuth etc). Target is to call those methods via REST from an external location not part of Google cloud (like an independent React client, or a standalone server, or my local machine)
Question is: How can I call this from a standalone javascript (like a node.js script executed on my local machine? I do have the script URL (script id) , the secret and the key, but don;t know how to use them all.
Could you help with some sample code, pointers, etc. It looks like my google searches hit only unrelated topics...
You can check this example on how to call the script as an API executable. You will see that the way to call the script from different languages is similar for example using JavaScript, you need to also take note on some important information like:
The basic types in Apps Script are similar to the basic types in JavaScript: strings, arrays, objects, numbers and booleans. The Execution API can only take and return values corresponding to these basic types -- more complex Apps Script objects (like a Document or Sheet) cannot be passed by the API.
An example to make a call the way that you currently want using Apps script would be like:
Target Script
/** This is the Apps Script method these API examples will be calling.
*
* It requires the following scope list, which must be used when authorizing
* the API:
* https://www.googleapis.com/auth/spreadsheets
*/
/**
* Return a list of sheet names in the Spreadsheet with the given ID.
* #param {String} a Spreadsheet ID.
* #return {Array} A list of sheet names.
*/
function getSheetNames(sheetId) {
var ss = SpreadsheetApp.openById(sheetId);
var sheets = ss.getSheets();
return sheets.map(function(sheet) {
return sheet.getName();
});
}
This is the script that you have setup as an API executable and you can call this script using JavaScript like this:
// ID of the script to call. Acquire this from the Apps Script editor,
// under Publish > Deploy as API executable.
var scriptId = "<ENTER_YOUR_SCRIPT_ID_HERE>";
// Initialize parameters for function call.
var sheetId = "<ENTER_ID_OF_SPREADSHEET_TO_EXAMINE_HERE>";
// Create execution request.
var request = {
'function': 'getSheetNames',
'parameters': [sheetId],
'devMode': true // Optional.
};
// Make the request.
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + scriptId + ':run',
'method': 'POST',
'body': request
});
// Log the results of the request.
op.execute(function(resp) {
if (resp.error && resp.error.status) {
// The API encountered a problem before the script started executing.
console.log('Error calling API: ' + JSON.stringify(resp, null, 2));
} else if (resp.error) {
// The API executed, but the script returned an error.
var error = resp.error.details[0];
console.log('Script error! Message: ' + error.errorMessage);
} else {
// Here, the function returns an array of strings.
var sheetNames = resp.response.result;
console.log('Sheet names in spreadsheet:');
sheetNames.forEach(function(name){
console.log(name);
});
}
});
Please note as well that there are some limitations that you may want to check before further perform tests.

Google Apps Scripts - Optimal way to replace text

I have tons of strings that I need to replace in a Google document. My performance to run my script is taking a huge hit and takes forever to run now. I am looking at how to optimize.
body.replaceText("oldvalue1",newValue1)
body.replaceText("oldvalue2",newValue2)
body.replaceText("oldvalue3",newValue3)
..
..
..
Is there a more optimal way to replace text in a Google Doc using google scripts?
As #Kos refers in the comments, the best approximation is using Google Docs API batchUpdates as Advanced Service inside Google Apps Script.
I leave you an example on how Docs API works as an Advanced Service. For the example I suppose you have an array of objects that contains the oldValue and newValue:
function batchUpdating(DocID = DocID) {
const replaceRules = [
{
toReplace: "oldValue1",
newValue: "newValue1"
},
...
]
const requestBuild = replaceRules.map(rule => {
var replaceAllTextRequest = Docs.newReplaceAllTextRequest()
replaceAllTextRequest.replaceText = rule.newValue
replaceAllTextRequest.containsText = Docs.newSubstringMatchCriteria()
replaceAllTextRequest.containsText.text = rule.toReplace
replaceAllTextRequest.containsText.matchCase = false
Logger.log(replaceAllTextRequest)
var request = Docs.newRequest()
request.replaceAllText = replaceAllTextRequest
return request
})
var batchUpdateRequest = Docs.newBatchUpdateDocumentRequest()
batchUpdateRequest.requests = requestBuild
var result = Docs.Documents.batchUpdate(batchUpdateRequest, DocID)
Logger.log(result)
}
Google Apps Script helps us handle the authorization flow, and gives us hints on how to construct our request. For example, Docs.newReplaceAllTextRequest(), helps us build the request for that service, giving us hints that it contains replaceText and containText. In any case, we could also supply the object directly to the request:
const requestBuild = replaceRules.map(rule => {
return {
replaceAllText:
{
replaceText: rule.newValue,
containsText: { text: rule.oldValue, matchCase: false }
}
}
})
To take in account
Each request is validated before being applied. If any request is not valid, then the entire request will fail and nothing will be applied.
If your script project uses a default GCP project created on or after April 8, 2019, the API is enabled automatically after you enable the advanced service and save the script project. If you have not done so already, you may also be asked to agree to the Google Cloud Platform and Google APIs Terms of Service as well.
Documentation:
ReplaceAllTextRequest
SubstringMatchCriteria
Array.forEach(obj => { body.replaceText(obj.old,obj.new)})
#kos way is probably a better way to go.

Using Google Apps Script, how do you remove built in Gmail category labels from an email?

I'd like to completely undo any of Gmails built in category labels. This was my attempt.
function removeBuiltInLabels() {
var updatesLabel = GmailApp.getUserLabelByName("updates");
var socialLabel = GmailApp.getUserLabelByName("social");
var forumsLabel = GmailApp.getUserLabelByName("forums");
var promotionsLabel = GmailApp.getUserLabelByName("promotions");
var inboxThreads = GmailApp.search('in:inbox');
for (var i = 0; i < inboxThreads.length; i++) {
updatesLabel.removeFromThreads(inboxThreads[i]);
socialLabel.removeFromThreads(inboxThreads[i]);
forumsLabel.removeFromThreads(inboxThreads[i]);
promotionsLabel.removeFromThreads(inboxThreads[i]);
}
}
However, this throws....
TypeError: Cannot call method "removeFromThreads" of null.
It seems you can't access the built in labels in this way even though you can successfully search for label:updates in the Gmail search box and get the correct results.
The question...
How do you access the built in Gmail Category labels in Google Apps Script and remove them from an email/thread/threads?
Thanks.
'INBOX' and other system labels like 'CATEGORY_SOCIAL' can be removed using Advanced Gmail Service. In the Script Editor, go to Resources -> Advanced Google services and enable the Gmail service.
More details about naming conventions for system labels in Gmail can be found here Gmail API - Managing Labels
Retrieve the threads labeled with 'CATEGORY_SOCIAL' by calling the list() method of the threads collection:
var threads = Gmail.Users.Threads.list("me", {labels: ["CATEGORY_SOCIAL"]});
var threads = threads.threads;
var nextPageToken = threads.nextPageToken;
Note that you are going to need to store the 'nextPageToken' to iterate over the entire collection of threads. See this answer.
When you get all thread ids, you can call the 'modify()' method of the Threads collection on them:
threads.forEach(function(thread){
var resource = {
"addLabelIds": [],
"removeLabelIds":["CATEGORY_SOCIAL"]
};
Gmail.Users.Threads.modify(resource, "me", threadId);
});
If you have lots of threads in your inbox, you may still need to call the 'modify()' method several times and save state between calls.
Anton's answer is great. I marked it as accepted because it lead directly to the version I'm using.
This function lets you define any valid gmail search to isolate messages and enables batch removal labels.
function removeLabelsFromMessages(query, labelsToRemove) {
var foundThreads = Gmail.Users.Threads.list('me', {'q': query}).threads
if (foundThreads) {
foundThreads.forEach(function (thread) {
Gmail.Users.Threads.modify({removeLabelIds: labelsToRemove}, 'me', thread.id);
});
}
}
I call it via the one minute script trigger like this.
function ProcessInbox() {
removeLabelsFromMessages(
'label:updates OR label:social OR label:forums OR label:promotions',
['CATEGORY_UPDATES', 'CATEGORY_SOCIAL', 'CATEGORY_FORUMS', 'CATEGORY_PROMOTIONS']
)
<...other_stuff_to_process...>
}

Set Maps API key in Script Editor

As far as I understand, in order to track our quota usage, we need to provide our API key to the Google App Service on the service we are planning to use.
In my case I have a spreadsheet with Origin and Destination and a Custom function to calculate the distance between.
I ran into the problem of meeting the quota from invoking .getDirections():
Error: Service invoked too many times for one day: route. (line **).
Sample of the code:
function getDirections_(origin, destination) {
var directionFinder = Maps.newDirectionFinder();
directionFinder.setOrigin(origin);
directionFinder.setDestination(destination);
var directions = directionFinder.getDirections();
return directions;
}
So I read that if I assign the API Key to my project I should be able to see the usage and how close to the free quota I am.
In the script editor, I did enable all of the APIs under Resources menu/ Advanced Google Services. Then I went to the Google Developers Console and there
I did not see any record of how many times my custom function called the Google Maps API or any API usage.
Logically I think that in my script I need to set my google API Key so my scripts start to call the API under my user name and count the number of time I used certain API. I guess right now I am using the Google Maps API as anonymous and since the whole company is assigned with the same IP, so we exhaust the permitted numbers to call this function.
Bottom line please reply if you know a way to connect my simple Spreadsheet function to the Public API access Key I have.
Thank you,
Paul
I also have been eager to find this answer for a long time and am happy to say that I've found it. It looks like Google might have just made this available around Oct 14, 2015 based on the date this page was updated.
You can leverage the UrlFetchApp to add your API key. The link I posted above should help with obtaining that key.
function directionsAPI(origin, destination) {
var Your_API_KEY = "Put Your API Key Here";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+
"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&alternatives="+Boolean(1)+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
return false;
}
So walking through the code... first put in your API key. Then choose your parameters in the var serviceUrl. I've thrown in additional parameters (mode and alternatives) to show how you can add them. If you don't want them, remove them.
With UrlFetch you can add options. I've used muteHttpExceptions so that the fetch will not throw an exception if the response code indicates failure. That way we can choose a return type for the function instead of it throwing an exception. I'm using JSON for the content type so we can use the same format to send and retrieve the request. A response code of 200 means success, so directions will then parse and act like the object that getDirections() would return. The function will return false if the UrlFetch was not successful (a different response code) or if the object is null.
You will be able to see the queries in real time in your developer console when you look in the Google Maps Directions API. Be sure that billing is enabled, and you will be charged once you exceed the quotas.
1.) I added an API key from my console dashboard. Remember to select the correct project you are working on. https://console.developers.google.com/apis/credentials?project=
2.) In my Project (Scripts Editor) I setAuthentication to Maps using the API key and the Client ID from the console. I have included the script below:
function getDrivingDirections(startLoc, wayPoint, endLoc){
var key = "Your_API_Key";
var clientID = "Your_Client_ID";
Maps.setAuthentication(clientID, key);
var directions = Maps.newDirectionFinder()
.setOrigin(startLoc)
.addWaypoint(wayPoint)
.setDestination(endLoc)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
} return directions;
https://developers.google.com/apps-script/reference/maps/maps#setAuthentication(String,String)
As of 7/13/2017, I was able to get the API to function by enabling the Sheets API in both the "Advanced Google Services" menu (images 1 and 2), and in the Google Developer Console. If you're logged into Google Sheets with the same email address, no fetch function should be necessary.
[In the Resources menu, select Advanced Google Services.][1]
[In Advanced Google Services, make sure the Google Sheets API is turned on.][2]
[1]:
[2]:
Thank goodness for JP Carlin! Thank you for your answer above. JP's answer also explains his code. Just to share, without a code explanation (just go look above for JP Carlin's explanation), below is my version. You will see that I also have the departure_time parameter so that I will get distance and driving-minutes for a specific date-time. I also added a call to Log errors (to view under "View/Logs"):
Note: Google support told me that using your API-key for Google Maps (e.g. with "Directions API") with Google Sheets is not supported. The code below works, but is an unsupported work-around. As of 11/4/2018, Google has an internal ticket request to add support for Google Maps APIs within Google Sheets, but no timeline for adding that feature.
/********************************************************************************
* directionsAPI: get distance and time taking traffic into account, from Google Maps API
********************************************************************************/
function directionsAPI(origin, destination, customDate) {
var Your_API_KEY = "<put your APK key here between the quotes>";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+"&departure_time="+customDate.getTime()+"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
Logger.log("Error: " + response.getResponseCode() + " From: " + origin + ", To: " + destination + ", customDate: " + customDate + ", customDate.getTime(): " + customDate.getTime() );
return false;
}

How to update Google Spreadsheet cell using PHP API

I would like to know how I can interact with a Google spreadsheet using PHP.
I've looked through many pages of Google's documentation, however, none of that is doing what I'm looking for.
My goal is to be able to change the content of the cells using oAuth (not the email/pass).
Please, forgive me if this is a RTFM issues, but I did spend more than 2 weeks with this with no result. :/
You can do this with asimlqt/php-google-spreadsheet-client library.
Install the library via composer:
"require": {
"asimlqt/php-google-spreadsheet-client": "dev-master"
}
Bootstrap composer in your PHP file:
require('vendor/autoload.php');
Follow the steps to get a Google API client ID, client email, and P12 access key, as explained here:
https://github.com/asimlqt/php-google-spreadsheet-client/wiki/How-to-use-%22Service-account%22-authorization-(rather-than-user-based-access-refresh-tokens)
Use the following code:
$accessToken = getGoogleTokenFromKeyFile(CLIENT_ID_HERE, CLIENT_EMAIL_HERE, P12_FILE_PATH_HERE);
use Google\Spreadsheet\DefaultServiceRequest;
use Google\Spreadsheet\ServiceRequestFactory;
ServiceRequestFactory::setInstance(new DefaultServiceRequest($accessToken));
// Load spreadsheet and worksheet
$worksheet = (new Google\Spreadsheet\SpreadsheetService())
->getSpreadsheets()
->getByTitle('xxxxxxxxx') // Spreadsheet name
->getWorksheets()
->getByTitle('xxxxxxxxx'); // Worksheet name
$listFeed = $worksheet->getListFeed();
// Uncomment this to find out what Google calls your column names
// print_r($listFeed->getEntries()[0]->getValues());
// Add a new blank row to the spreadsheet, using the column headings
$listFeed->insert(['name' => 'Simon', 'age' => 25, 'gender' => 'male']);
/**
* Retrieves a Google API access token by using a P12 key file,
* client ID and email address
*
* These three things may be obtained from
* https://console.developers.google.com/
* by creating a new "Service account"
*/
function getGoogleTokenFromKeyFile($clientId, $clientEmail, $pathToP12File) {
$client = new Google_Client();
$client->setClientId($clientId);
$cred = new Google_Auth_AssertionCredentials(
$clientEmail,
array('https://spreadsheets.google.com/feeds'),
file_get_contents($pathToP12File)
);
$client->setAssertionCredentials($cred);
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
$service_token = json_decode($client->getAccessToken());
return $service_token->access_token;
}
Drive API doesn't provide any means to edit a spreadsheet, Spreadsheets API contains low level cell modification methods. As a note, you can't use Google APIs PHP Client Library to consume the Spreadsheets API.
API v4, you can use curl. Get Access Token then use curl. You can update single row or multiple rows Or you can update single column or multiple columns. If you are updating Single Column or Single row then use https://sheets.googleapis.com/v4/spreadsheets/sheetID/values/RowORColumnRange/?valueInputOption=RAW&includeValuesInResponse=true
sheetID = FileID
RowORColumnRange = Sheet!A1 or Sheet!B2
Pass value data with PUT method like
$update['values'] = [[1234]];
for the Batch Update instead of use method values in the URL you can change to values:batchUpdate. and then pass other data in array and use method POST;
You can ref this https://developers.google.com/sheets/api/guides/migration
This is the solutions i got for my issue :
Zend GData Library
Zend Gdata for Spreadsheet with the examples code