Gmail Add-on pass parameters to function triggered by card action - google-apps-script

I'm building a Gmail add-on that creates a card with just one form button. Once clicked, I want the button to trigger a function that will send the open email's content to an external API.
So far, I have something like this:
function createCard(event) {
var currentMessage = getCurrentMessage(event).getBody();
var section = CardService.newCardSection();
var submitForm = CardService.newAction()
.setFunctionName('callAPI');
var submitButton = CardService.newTextButton()
.setText('Submit')
.setOnClickAction(submitForm);
section.addWidget(CardService.newButtonSet()
.addButton(submitButton));
var card = CardService.newCardBuilder()
.setHeader(CardService.newCardHeader()
.setTitle('Click this button'))
.addSection(section)
.build();
return [card];
}
function callAPI(event) {
var payload = { "msg": msg }; // msg is the parameter I need to get from the function call
var options = {
"method" : "POST",
"contentType": "application/json",
"payload" : JSON.stringify(payload),
"followRedirects" : true,
"muteHttpExceptions": true
};
return UrlFetchApp.fetch('https://www.someAPI.com/api/endpoint', options);
}
How can I pass the currentMessage variable into the callAPI function? According to the documentation, the only parameter that we can get from an action function seems to be event that only has the form fields data. If there isn't a way to pass other parameters, is there a way for that function to get the context data of the message directly inside the function??
Thanks!

I believe the proper way to pass the currentMessage content to the callAPI function would be to use the setParameters as described in this documentation
Your code would look like this:
var submitForm = CardService.newAction()
.setFunctionName('callAPI')
.setParameters({message: currentMessage});
In the callback, your would fetch the message by using something like:
function callAPI(event) {
var payload = event.parameters.message;
...
As a reminder, both the key (message) as the value (currentMessage) must be Strings.

You can use Properties Service, which allows you to store strings as key-value pairs. The idea would be to store the value of currentMessage in a property in the function createCard, and then use it in callAPI.
First, to store the variable, you can add the following lines to createCard after declaring currentMessage:
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty("currentMessage", currentMessage);
Second, to retrieve this variable, add the following lines to callAPI:
var userProperties = PropertiesService.getUserProperties();
var msg = userProperties.getProperty("currentMessage");
Update:
In order to avoid the limit of characters for a single property, you could also split your message into as many properties as were necessary.
You would first have to split the message into an array via substring, and store each element of the array in a different property. You would have to add this to createCard, after declaring currentMessage:
var messageArray = [];
var i = 0;
var k = 5000; // Character limit for one property (change accordingly)
while (i < currentMessage.length) {
var part = currentMessage.substring(i, i += k);
messageArray.push(part); // Splitting string into an array of strings
}
var userProperties = PropertiesService.getUserProperties();
userProperties.deleteAllProperties(); // Delete old properties
for (var j = 0; j < messageArray.length; j++) { // Set a different property for each element in the array
userProperties.setProperty('messagePart' + j, messageArray[j]);
}
Then, to retrieve the message body, you could first retrieve all the properties corresponding to the message, and then join all the values in a single string with concat. Add the following lines to callAPI:
var userProperties = PropertiesService.getUserProperties();
var keys = userProperties.getKeys(); // Get all property keys
var j = 0;
var currentMessage = "";
do {
var part = userProperties.getProperty('messagePart' + j);
currentMessage = currentMessage.concat(part); // Concat each property value to a single string.
j++;
} while (keys.indexOf('messagePart' + j) !== -1); // Check if property key exists
Reference:
Properties Service
String.prototype.substring()
String.prototype.concat()
I hope this is of any help.

Related

How to catch execution error in XMLService getAttribute method?

I'm using this script in a Google Sheets spreadsheet to parse a table in a web page and to store results:
var doc = XmlService.parse(result);
var html = doc.getRootElement();
var resulttable = getElementsByClassName(html, 'resulttable')[0];
var descendants = html.getDescendants();
descendants.push(html);
for(var i in descendants) {
var elt = descendants[i].asElement(); <== it crashes
if(elt != null) {
var test = elt.getAttributes();
var test_bis = elt.getAttribute('http-equiv'); <== it does not crashes: 'http-equiv' exists
var classes = elt.getAttribute('class'); <== it crashes:'class' does not exists
}
}
As it's shown, I have some errors (simply raised as "server errors") in the marked lines of this code. I also put try...catch block, but they don't catch the errors: the script terminates abruptly.
How can I catch the errors so as to let the script continuing despite some of these XML errors?
My expectations were to have undefined elements when the asElement or the getAttribute methods fail.
To parse the URL I was using this approach
var url = "https://albopretorio.comune.gravina.ba.it/fo/?ente=GravinaInPuglia";
var today = Utilities.formatDate(new Date(), "GMT+1", "dd/MM/yyyy");
var payload =
{
"tipoSubmit":"ricerca",
"enti":"GravinaInPuglia",
"uo":"",
"tipoatto":"",
"anno":"",
"numda":"",
"numa":"",
"annoatto":"",
"numatto":"",
"datada":"",
"dataa":"",
"pubblicatoda":today,
"pubblicatoa":"",
"presenteal":"",
"chiave":"",
"provenienza":"",
};
var options =
{
"method" : "POST",
"payload" : payload,
"followRedirects" : true,
"muteHttpExceptions": true
};
var result = UrlFetchApp.fetch(url, options);
Issue:
You don't know which ContentType each descendant is, so you don't know which method you should use to cast the node.
Solution:
For each descendant, check for the corresponding ContentType with the method getType(). You can use the returned value (the ContentType) and a switch statement to use one method or another.
Code snippet:
for (var i in descendants) {
var contentType = descendants[i].getType();
var elt;
switch (contentType.toString()) {
case 'TEXT':
elt = descendants[i].asText();
break;
case 'ELEMENT':
elt = descedants[i].asElement();
break;
// Add other possible ContentTypes, if necessary
Update: Issue with getAttribute:
In order to avoid the script trying retrieve an attribute that does not exist, you can retrieve an array of the attribute names for this element, and then check if your attribute is included in that array:
var attributes = elt.getAttributes().map(attribute => attribute.getName());
if (attributes.includes('class')) {
var classes = elt.getAttribute('class');
}
Reference:
Enum ContentType
Interface Content: getType()
switch

How can I write in Google Sheets my Firebase data?

I have a database in Firebase, and I want to get the data from there and put them in a Google SpreadSheet.
function getData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Database");
var data = getFirebaseData('contacts');
var [rows, columns] = [sheet.getLastRow(), sheet.getLastColumn()];
var range = sheet.getRange(1,1,1,1);
Logger.log(data)
range.setValue(data)
}
function getFirebaseData(data){
var firebaseUrl = "https://XXXXX.firebaseio.com/";
var secret = 'XXXXXXXX';
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
var result = base.getData('contacts');
for(var i in data) {
Logger.log(data[i].eMail + ' ' + data[i].title);
return result;
}
}
and here the image:
No data is shown, and I cannot understand why
Your problem should be solved by completing several steps:
In your getFirebaseData() function, move the return statement outside of the loop;
Instead of looping over data, loop over result (currently, you iterate over each property of the "contacts" String);
Optionally, add checks for getData() returning null or invalid firebaseUrl (in the last case, getData() will cause an error, use try...catch to account for that);
Change base.getData('contacts') to base.getData(data) (isn't it
the reason you pass data to the function?);

Pulling PubMed data into Google Sheets

I'm looking for some help. I am trying to grab an author's publications from PubMed and populate the data into Google Sheets using Apps Script. I've gotten as far as the code below and am now stuck.
Basically, what I have done was first pull all the Pubmed IDs from a particular author whose name comes from the name of the sheet. Then I have tried creating a loop to go through each Pubmed ID JSON summary and pull each field I want. I have been able to pull the pub date. I had set it up with the idea that I would do a loop for each field of that PMID I want, store it in an array, and then return it to my sheet. However, I'm now stuck trying to get the second field - title - and all the subsequent fields (e.g. authors, last author, first author, etc.)
Any help would be greatly appreciated.
function IMPORTPMID(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var author = sheet.getSheetName();
var url = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=" + author + "[author]&retmode=json&retmax=1000");
var response = UrlFetchApp.fetch(url);
var AllAuthorPMID = JSON.parse(response.getContentText());
var xpath = "esearchresult/idlist";
var patharray = xpath.split("/");
for (var i = 0; i < patharray.length; i++) {
AllAuthorPMID = AllAuthorPMID[patharray[i]];
}
var PMID = AllAuthorPMID;
var PDparsearray = [PMID.length];
var titleparsearray = [PMID.length];
for (var x = 0; x < PMID.length; x++) {
var urlsum = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=" + PMID[x]);
var ressum = UrlFetchApp.fetch(urlsum);
var contentsum = ressum.getContentText();
var jsonsum = JSON.parse(contentsum);
var PDpath = "result/" + PMID[x] + "/pubdate";
var titlepath = "result/" + PMID[x] + "/title";
var PDpatharray = PDpath.split("/");
var titlepatharray = titlepath.split("/");
for (var j = 0; j < PDpatharray.length; j++) {
var jsonsum = jsonsum[PDpatharray[j]];
}
PDparsearray[x] = jsonsum;
}
var tempArr = [];
for (var obj in AllAuthorPMID) {
tempArr.push([obj, AllAuthorPMID[obj], PDparsearray[obj]]);
}
return tempArr;
}
From a PubMed JSON response for a given PubMed ID, you should be able to determine the fieldnames (and paths to them) that you want to include in your summary report. Reading them all is simpler to implement if they are all at the same level, but if some are properties of a sub-field, you can still access them if you give the right path in your setup.
Consider the "source JSON":
[
{ "pubMedId": "1234",
"name": "Jay Sahn",
"publications": [
{ "pubId": "abcd",
"issn": "A1B2C3",
"title": "Dynamic JSON Parsing: A Journey into Madness",
"authors": [
{ "pubMedId": "1234" },
{ "pubMedId": "2345" }
]
},
{ "pubId": "efgh",
...
},
...
],
...
},
...
]
The pubId and issn fields would be at the same level, while the publications and authors would not.
You can retrieve both the pubMedId and publications fields (and others you desire) in the same loop by either 1) hard-coding the field access, or 2) writing code that parses a field path and supplying field paths.
Option 1 is likely to be faster, but much less flexible if you suddenly want to get a new field, since you have to remember how to write the code to access that field, along with where to insert it, etc. God save you if the API changes.
Option 2 is harder to get right, but once right, will (should) work for any field you (properly) specify. Getting a new field is as easy as writing the path to it in the relevant config variable. There are possibly libraries that will do this for you.
To convert the above into spreadsheet rows (one per pubMedId in the outer array, e.g. the IDs you queried their API for), consider this example code:
function foo() {
const sheet = /* get a sheet reference somehow */;
const resp = UrlFetchApp.fetch(...).getContentText();
const data = JSON.parse(resp);
// paths relative to the outermost field, which for the imaginary source is an array of "author" objects
const fields = ['pubMedId', 'name', 'publications/pubId', 'publications/title', 'publications/authors/pubMedId'];
const output = data.map(function (author) {
var row = fields.map(function (f) {
var desiredField = f.split('/').reduce(delve_, author);
return JSON.stringify(desiredField);
});
return row;
});
sheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
function delve_(parentObj, property, i, fullPath) {
// Dive into the given object to get the path. If the parent is an array, access its elements.
if (parentObj === undefined)
return;
// Simple case: parentObj is an Object, and property exists.
const child = parentObj[property];
if (child)
return child;
// Not a direct property / index, so perhaps a property on an object in an Array.
if (parentObj.constructor === Array)
return collate_(parentObj, fullPath.splice(i));
console.warn({message: "Unhandled case / missing property",
args: {parent: parentObj, prop: property, index: i, pathArray: fullPath}});
return; // property didn't exist, user error.
}
function collate_(arr, fields) {
// Obtain the given property from all elements of the array.
const results = arr.map(function (element) {
return fields.slice().reduce(delve_, element);
});
return results;
}
Executing this yields the following output in Stackdriver:
Obviously you probably want some different (aka real) fields, and probably have other ideas for how to report them, so I leave that portion up to the reader.
Anyone with improvements to the above is welcome to submit a PR.
Recommended Reading:
Array#reduce
Array#map
Array#splice
Array#slice
Internet references on parsing nested JSON. There are a lot.

What is the best way to split a variable containing a json from an API and then to put it into a specific cell?

Here is my code:
var pUrl = "http://api.perk.com/api/user/id/";
var tk = "/token/";
var options = {
"method" : "GET",
"contentType": 'application/text'
}
function perkTvApi1() {
var response = UrlFetchApp.fetch("http://api.perk.com/api/user/id/515098/token/01133528575d15554742e5bb9bc1fc484fd95ac2/", options);
Logger.log(response.getContentText());
}
I'm trying to figure out how to split the response variable so I can then put it into a spreadsheet row which corresponds to a time stamp.
I haven't been able to find any kind of split function, like I would use in javascript, in Script Services. I'm running out of ideas and approaches.
The easiest way is to use the JSON library: the two most useful functions are JSON.parse() and JSON.stringify().
Both are native to Google Apps Script, so you'd call something like
var object = JSON.parse(response)
in perkTvApi1. Now, the object contains an actual object, which is exactly what you want.
Then, it's just a matter of setting the right cell, so if you wanted cell A1 to be the first name:
SpreadsheetApp.getActiveSheet().getRange('A1').setValue(object["firstName"]);
JSON.parse() is the method you want. Check out this thread
function myFunction() {
var options = {
"method" : "GET",
"contentType": 'application/text'
}
var response = UrlFetchApp.fetch("http://api.perk.com/api/user/id/515098/token/01133528575d15554742e5bb9bc1fc484fd95ac2/", options);
var jsonResponse = JSON.parse(response)
Logger.log(jsonResponse.firstName);
Logger.log("Key length: " + Object.keys(jsonResponse).length);
var keys = Object.keys(jsonResponse);
//Loop through the keys array to push the json object into an array
//The array can then be used to set values in spreadsheet range
//May need to create 2d array depending on how you want your rows/columns arranged.
var ssData = [];
for (var i in keys){
ssData.push(jsonResponse[keys[i]]);
}
Logger.log(ssData);
}

Need to edit gdoc using a googleappscript

Is it possible that I could add the body of a gdoc into an email? I kinda have an idea of how to do it but I am not completely sure. I have written this code below to kinda help me. I am new to this and I have managed to have a few scripts running, but I am completely lost on this one. I have watched several videos and this is what I was able to do. The code is below.
Basically what I want to do is to be able to have a user input his name and another variable and then go to the google doc file and change it to the value that was input and then put it back in an email and send it to an address... Any ideas of what I am doing wrong or where should I start?? Thanks in advance.
function gsnot() {
var emailaddress="albdominguez25#gmail.net";
var sub="Subject1";
var pattern = Browser.inputBox("Enter your name");
var pattern2 = Browser.inputBox("Enter the minutes:");
var templateDocID= ScriptProperties.getProperty("EmailTemplateDocId");
var doc = DocumentApp.openById(templateDocID);
var body = doc.getActiveSection()
var html = "";
var keys = {
name: pattern,
min: pattern2,
};
for ( var k in keys ) {
body.replaceText("%" + k + "%", keys[k]);
doc.saveAndClose();
html = getDocAsHtml(docId);
DocsList.getFileById(docId).setTrashed(true);
return html;
var emailaddress="albdominguez25#gmail.net";
var sub="Subject1";
MailApp.sendEmail(emailaddress,sub, {htmlBody: body});}}
You might want to change your code by reading the body from the document into a variable, doing the replace on the variable and inserting that into your email. For example:
function gsnot() {
var emailaddress = "albdominguez25#gmail.net";
var sub = "New Subject";
var pattern = Browser.inputBox("Enter your name:");
var pattern2 = Browser.inputBox("Enter the minutes:");
var templateDocID = ScriptProperties.getProperty("EmailTemplateDocId");
var doc = DocumentApp.openById(templateDocID);
var body = doc.getText();
var replacement;
var k;
var keys = {
name: pattern,
min: pattern2
};
for (k in keys) {
if (keys.hasOwnProperty(k)) {
replacement = new RegExp("%" + k + "%",'g');
body = body.replace(replacement, keys[k]);
}
}
MailApp.sendEmail(emailaddress,sub, '', {htmlBody: body});
}
A few notes:
It is good form to have all your var statements at the beginning of
the function
When you use for-in (eg. for (k in keys) ), it returns all properties of the object. You only want the ones you assigned. This is the reason for: for (k in keys)
You had the mail sending for each property, I believe you wanted it outside the for-in loop so it only sent after all the replacements were completed.
Using replace(), you need to create a regular expression object that is set to global
or it will only replace the first instance of the pattern (you have name twice).
In your parameters for sendEmail(), even if you are using the htmlBody option, you need to specify a plain text body. I used empty quotes ''.