Call a web-app script within a script with UrlFetchApp.fetch - google-apps-script

I have a script deployed as a webapp. It is asking too many permissions to the user, so I want to divide the script in two parts. One will be run as the user so that I can get his/her email, the other one will be run as me to edit a spreadsheet.
I have tried to use the UrlFetchApp.fetch. I do not get an error, but the second script does not write in a cell as it is supposed to do. I am not sure what is wrong.
Webapp1 - accessed by user (showing only one relevant function. There is more not relevant code which I excluded to show a simple example):
EDITED CODE:
function ServerSideFunc() {
var ss = SpreadsheetApp.openById('1kLZ3CHPkODHRc_judkUSJ3ocWVPh6nIIjJS7TLLejIg');
var sh = ss.getSheetByName('Database');
var response =UrlFetchApp.fetch("https://script....../macros/s/.....s/exec");
}
Webapp2: (the one published in the URL above and run as me)
function test() {
var i = 408 //I would like to pass this as parameter eventually from webapp1
var ss = SpreadsheetApp.openById('1kLZ3CHPkODHRc_judkUSJ3ocWVPh6nIIjJS7TLLejIg');
var sh = ss.getSheetByName('Database');
var str = "OK";
sh.getRange(i+1, 2).setValue(str);
}
function doGet() {
var output = HtmlService.createHtmlOutputFromFile('list');
return output;
}
Few more notes:
The two apps are in two different projects in Google Apps Script.
App2 run OK if executed independently
When running App1, I noticed App2 is not executed from my executions logs in Google Apps Script.
I added try-catch code to see if there is an error when executing. It does not look like there is an error.
The reponse has ~70000 characters. I am wondering if that is normal.

The if condition:
if(vA[i][3]+" REQ ID: "+vA[i][0] == value) {
will never be true as anything + "REQ ID: " will never be equal to "TEST"(value). Thus the urlfetch is never executed.

Related

Google Apps Script withSuccessHandler is running before function fully executes

I have two files in an Apps Script project. One is a .gs file acting as a "server" and one is a .html file containing JavaScript as per Google's Apps Script guidelines.
Everything has been going swimmingly for the first 40 hours of development on this project. I have the following line of code outside of any function, in between two tags in the .html file:
google.script.run.withSuccessHandler(setSheetData).getSheetData();
Documentation: https://developers.google.com/apps-script/guides/html/reference/run#withSuccessHandler(Function)
According to the documentation, getSheetData() should first execute in the .gs file, and return a value that is then passed into setSheetData which exists in the .html file.
Server file:
function getSheetData() {
var ss = SpreadsheetApp.getActive();
var activeSheet = ss.getActiveSheet();
var sheetName = activeSheet.getName();
var sheetVals = activeSheet.getDataRange().getValues();
return [sheetName, sheetVals];
}
Html file:
function setSheetData(data) {
alert(data);
sheetName = data[0];
sheetData = data[1];
headers = sheetData[0];
document.getElementById('sheetLook').innerHTML = 'Looking at sheet: ' + sheetName;
}
How I know it is a matter of execution speed:
Currently the alert() call just prints out null. The sheet it is drawing from contains 4 rows of data. However, all other things remaining the same, if I simply am looking at a sheet with 0-1 rows of data, it correctly alerts the entire data vals.
Inside of getSheetData() if I add Logger.log(sheetVals) it correctly logs the entire sheet's data regardless of size. The issue is that the successhandler is executing before it has time to evaluate.
Possible cause(s):
Illegal return values in rows 3-4 of data
No active sheet is present.
Solution:
Avoid returning illegal values like Date objects. Or JSON.stringify() them before returning to client.
getSheetByName or number instead of getting it by activeness.
References:
google.script.run § myFunction § return

Inserting sleep inside a particular function in Google Sheets Script

I'm trying to pull data out of an API from a third party and inserting into Google Sheets. However this third party only allows 3 requests per minute, so I'm trying to use a Utilities.sleep feature inside the function I'm building for this request.
My sheet looks like this:
It has the two inputs necessary for the function I'm using (this below):
function GET_DETAILS_RECEITA(CNPJ,sleep_seconds) {
Utilities.sleep(sleep_seconds*1000);
var fields = 'nome,fantasia,email,telefone';
var baseUrl = 'https://www.receitaws.com.br/v1/cnpj/';
var queryUrl = baseUrl + CNPJ;
if (CNPJ == '') {
return 'Give me CNPJ...';
}
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var place = JSON.parse(json);
return [[ place.nome,
place.fantasia,
place.telefone,
place.email,
]];
}
Technically it should work but for some reason I'm getting a return only in the first one.
The error I'm getting is very generic "Erro: Erro interno ao executar a função personalizada." (something like "Error: Internal error in the execution of personalized function").
Any ideas?
From https://developers.google.com/apps-script/guides/sheets/functions
A custom function call must return within 30 seconds. If it does not, the cell will display an error: Internal error executing the custom function.
Considering the above, it's not a good idea to use sleep on a custom function that will be used as intended by the OP. Instead use a custom menu or the Script Editor to execute a script.
In order to minimize changes to your function, you could use a function that read/write the values to the spreadsheet and pass the required arguments to GET_DETAILS_RECEITA
I would consider using something like this in a dialog. You can pass an extra parameter in the set interval as long as your using Chrome.
<script>
var CNPJ='what ever';
window.onload=function(){setInterval(getDetails,25000,CNPJ);}
function getDetails(CNPJ){
google.script.run.GET_DETAILS_RECEITA(CNPJ)
}
</script>
And if you want a callback then use with withSuccessHandler();

How to resolve "Too much calls for this service today : gmail" error in Google Apps script?

My question is about an error in Google's spreadsheet using gmail service in a function.
Since a while, an error occur when I run a function (On Google Spreadsheet) for retrieve mails on a label founded in the MailBox (Gmail).
The error message is : "Too much calls for this service today : gmail".
I want to specify that function worked fine before and it hasn't been modified.
The function is launched one time per month (Except in exceptional case)
I did some research on the error message, and the answers found confirmed what I thought,
daily quotas for Google's gmail services are exceed and can not be used until 24 hours.
However, it's the only one that has not worked, while others are working properly with these services without any errors.
Following this, I created a copy of the spreadsheet with the function to test if it isn't the sheet that does not work, but it has not changed.
And I launched it with another Google account, and it worked.
Does anyone know why this message appears please ?
Should we do a special manipulation to make it work again ?
Here is the row that sends an error :
var threads = GmailApp.getUserLabelByName("Label").getThreads();
And the function :
function readMail(){
var threads = GmailApp.getUserLabelByName("Label").getThreads();
var messages = GmailApp.getMessagesForThreads(threads);
for(var i in messages){
var message = messages[i];
for(var j in message){
var mess = message[j];
var sub = mess.getSubject();
if(mess.getTo().indexOf("email#gmail.com") > -1)
continue;
var attach = mess.getAttachments()[0];
var file = {
title: attach.getName()
};
var fileDoc = Drive.Files.insert(file, attach, {convert: false}); // Use Drive API
mess.markRead();
}
}
}

How to trigger Google Apps script function based on insert row via api

I have a Google Sheet with 5 columns (First Name, Address, SKU, Quote, Status).
I have an apps script function (createQuote) which looks at the above variable's values from google sheet row and create a google document quote replacing the variables to values.
I use Zapier to insert row into my above google sheet.
What am struggling with-:
I need a way to trigger my createQuote function right when a new row is inserted via zapier (Google Sheet API call).
I tried playing with triggers but couldn't make it, any help is appreciated.
thank you
here is the code for my function-
function quoteCreator(){
docTemplate = "googledocidgoeshere"
docName = "Proposal"
var sheet = SpreadsheetApp.getActive().getSheetByName("Main")
var values = sheet.getDataRange().getValues()
var full_name = values[1][0]
var copyId = DriveApp.getFileById(docTemplate).makeCopy(docName+" for "+full_name).getId()
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys/tags,
copyBody.replaceText("keyFullName", full_name);
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DriveApp.getFileById(copyId).getAs("application/pdf");
// put the link of created quote in the quote column
var url = DocumentApp.openById(copyId).getUrl()
var last = sheet.getRange(2, 7, 1, 1).setValue(url)
}
Note-: I haven't put the loop yet in above, i'll do that once it starts working as per my requirements.
Changes made via Sheets API or Apps Script do not fire onEdit triggers. I give two workarounds for this.
Web app
Have whatever process updates the sheet also send a GET or POST request to your script, deployed as a web application. As an example, a GET version might access https://script.google.com/.../exec?run=quoteCreator
function doGet(e) {
if (e.parameter.run == "quoteCreator") {
quoteCreator();
return ContentService.createTextOutput("Quote updated");
}
else {
return ContentService.createTextOutput("Unrecognized command");
}
}
The web application should be published in a way that makes it possible for your other process to do the above; usually this means "everyone, even anonymous". If security is an issue, adding a token parameter may help, e.g., the URL would have &token=myToken where myToken is a string that the webapp will check using e.parameter.token.
GET method is used for illustration here, you may find that POST makes more sense for this operation.
Important: when execution is triggered by a GET or POST request, the methods getActive... are not available. You'll need to open any spreadsheets you need using their Id or URL (see openById, openByUrl).
Timed trigger
Have a function running on time intervals (say, every 5 minutes) that checks the number of rows in the sheet and fires quoteCreator if needed. The function checkNewRows stores the number of nonempty rows in Script Properties, so changes can be detected.
function checkNewRows() {
var sp = PropertiesService.getScriptProperties();
var oldRows = sp.getProperty("rows") || 0;
var newRows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Main").getLastRow();
if (newRows > oldRows) {
sp.setProperty("rows", newRows);
quoteCreator();
}
}

GAS - Authentication w/ UrlFetchApp - Form to Spreadsheet

I am testing the functionality of UrlFetchApp and passing data from a Form and its Spreadsheet. I know it's possible to do this another way, however I am testing the functionality of UrlFetchApp (first time using it) within google scripts themselves, and want to get it to work with this method.
Here's the scenario I got, add a bound script to a Form App as so:
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/WebAppID/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
Add a bound script to the attached spreadsheet:
function doPost()
{
var ss = SpreadsheetApp.openById('ssID');
var name = ss.getName();
return ContentService.createTextOutput(name);
}
And then publish this second script attached to the sheet as a web app with only myself to have access.
Currently the above code does not work. The following error appears on the Form side of the script:
Request failed for
https://script.google.com/macros/s/WebAppID/exec
returned code 401. Truncated server response:
Unauthorized Unauthorized Error 401
(use muteHttpExceptions option to examine full response) (line
12, file "Code")
Fails on the UrlFetchApp line of code.
However, if I remove the header option, then publish the web app for public use, this works just fine. Obviously this is not wanted.
What am I missing regarding authentication between scripts that I own?
Side Notes:
Anyone know why SpreadsheetApp.getActiveSheet() doesn't work when run in this fashion? That script is directly bound to a google sheet, so kind of odd.
Ok, found the answer to my own question. It was quite simple really. Needed to add the following scope to my project for accessing a spreadsheet:
https://www.googleapis.com/auth/drive
The easiest way I found to do this is to add a simple function like this and call it:
function authorizeDrive()
{
var forScope = DriveApp.getRootFolder();
}
Doesn't need to return or do anything, just call any method from the DriveApp. Once run, it'll then popup with a dialogue for authorization. Don't even need to call this every time you do your main method calls. Don't even need to leave it coded in the script either. I wonder if there is way to just simple add the scope you need to a project from a properties window (I didn't find any). Or perhaps a way to pass a parameter along with UrlFetchApp regarding what scope need authorized.
Buy anyhow this still wasn't too bad.
Regarding my side note, I still haven't found a reason as to why SpeadsheetApp.getActiveSheet() returns null or undefined. I have to open by ID or URL, which is a pain. Especially since this is a container bound script. Also noticed that Logger.log() doesn't actually add anything to the Logger when run in this manner. If anyone could still shed some light on either of these, that would be great.
You need to get the 'Spreadsheet' object first.
SpeadsheetApp.getActive().getActiveSheet()
However, if you are creating an add-on menu you can use 'SpreadsheetApp.getActiveSheet()'
function myFunction() {
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
var range = SpreadsheetApp.getActiveSheet().getRange(lastRow, 1, 1, 26);
SpreadsheetApp.setActiveRange(range);
}
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('showLastRow', 'myFunction')
.addToUi();
}