Gmail API message.list from trigger - google-apps-script

I am having a problem creating an google script that would run every night. The code runs fine when I run it from the script file and behaves as expected, however when performed by an installed trigger I get the following:
TypeError: Cannot read property "length" from undefined. (line 17, file "Main")
EDIT: To be clear I know that the particular query used has to return results as running the same script from the script editor works fine
Code:
function doGet(query) {
var sSheet = sheetSelect(), //calls spreadsheet selection function and assigns the spreadsheet to variable
queriedMessages, //object to store the queried messages list
pageToken, //string token value that will be pulled from the queredMessages
auth = 'me';
if (!query) query = 'in:all newer_than:1d -is:chats -in:trash';
do {
queriedMessages = Gmail.Users.Messages.list(auth, {'q':query, 'pageToken':pageToken}); //callls the Gmail API to query messages
dataOutput(sSheet, queriedMessages.messages, queriedMessages.messages.length); //calls function to output all data to spreadsheet from the current list
pageToken = queriedMessages.nextPageToken; //gets the next page token from the list
}
while (pageToken); //the loop is executed until there are no more next page tokens left
}
Any ideas why it behaves so differently? I have tried providing userId for a specific e-mail. Seems like this might be some kind of authentication issue but I cannot figure out how to fix it other than forgetting about Gmail API and go a roundabout way of using Gmail App as it seems to be an issue with Gmail API method messages.list()
Thank You for any help in advance!
I managed to fix the issue. The problem was me wanting to leave an option to pass on a query with the function call. The problem then is that the installed trigger actually passed on a variable to the query variable and a new one is then not set.

I think it's much simpler than that. If I list messages in the last day, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1d
Response:
{
"messages": [
{
"id": "150612f9d7f83db9",
"threadId": "150611d4e92b7a5f"
}, ...
]
}
If I list messages in the last second, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1s
Response:
{
"resultSizeEstimate": 0
}
In other words, queriedMessages.messages will be undefined if you get no messages with that particular query, and queriedMessages.messages.length will give rise to your error.

Related

appendRow() adds blank row in google sheets (app script)

I've setup a google app script that would be triggered from an external system. This script would fetch the details from the third party system and add them to google sheet row.
function doPost(request) {
try{
var jsonString = request.postData.getDataAsString(); //get the request from KF as JSON String
setLog("\n postData*********************"+jsonString+"************************************* \n");
setLog("before the row append");
ss.appendRow([jsonString["Name"], jsonString["Age"], jsonString["Contact"]]);
setLog("After the row append");
var returnJson = '{"status": "success"}';
//used to send the return value to the calling function
setLog("/n returnJson****************************"+returnJson+"************************************* /n")
return ContentService.createTextOutput(returnJson).setMimeType(ContentService.MimeType.JSON);
}
There's absolutely no errors or warnings, but somehow it keeps adding the blank rows into the sheet.
Note: setLog() is a function where I print the values into google doc for debugging.
Maybe the reason your script is not working has to do with the value of jsonString.
I could not find any reference to request.postData.getDataAsString() inside GAS Documentation, so maybe you are trying to call a method on an object which does not support it, which would not raise an Error, but would return undefined.
One quick way to debug this would be to LOG the value (using your custom function or Logger.log(jsonString)) BEFORE you call .appendRow(). Then, you can verify if your variable has the value you expect it to have.
On the other hand, my suggestion is to use this method:
var jsonString = JSON.parse(request.postData.contents) //Gets the content of your request, then parses it
This method is present in the Documentation, and has been consistently working on all of my projects.
I think you should sort the coulmns with google app script. Write this code after ss.appendRow. The column will be sorted and all blank rows gets down.
// Sorts the sheet by the first column, ascending
ss.sort(1)
or if errors try this one also
var fl = SpreadsheetApp.getActiveSpreadsheet();
var sheet = fl.getSheets()[0];
fl.sort(1)

List all Gmail History details

function history(){
var newResponse = Gmail.Users.History.list('email ID / 'me'');
console.log(newResponse);
for(var i = 0; i< newResponse.history.length; i++){
var label = newResponse.history[i];
console.log(JSON.stringify(label));
}
}
GOAL I have been trying to list all my history using the above given function but it is not working. I have been trying to search for a way to make this work but I keep receiving the same error over and over: "GoogleJsonResponseException: API call to gmail.users.history.list failed with error: Missing/invalid parameter: startHistoryId (line 13, file "GMailAPI")"
I hope my question is understandable.
I believe your goal as follows.
You want to retrieve the value from the method "Users.history: list" in Gmail API using Google Apps Script.
How about this answer?
Modification points:
In your case, it is required to include startHistoryId in the request of Gmail.Users.History.list. At the official document, startHistoryId is "Required".
I think that the reason of your issue is due to this.
In order to retrieve startHistoryId, please use the method "Users: getProfile" in Gmail API.
When above points are reflected to your script, it becomes as follows.
Usage:
In order to retrieve the values from Gmail.Users.History.list, please run the following flow.
Please run the function getStartHistoryId().
By this, startHistoryId is saved to PropertiesService.
For example, as a sample situation, please send a sample email on Gmail using browser.
Please run the function history().
By this, you can retrieve the values from Gmail.Users.History.list.
When history() is run just after getStartHistoryId() was run, no values are returned from Gmail.Users.History.list. Please be careful this. The official document of startHistoryId is as follows.
startHistoryId : Required. Returns history records after the specified startHistoryId. The supplied startHistoryId should be obtained from the historyId of a message, thread, or previous list response. History IDs increase chronologically but are not contiguous with random gaps in between valid IDs. Supplying an invalid or out of date startHistoryId typically returns an HTTP 404 error code. A historyId is typically valid for at least a week, but in some rare circumstances may be valid for only a few hours. If you receive an HTTP 404 error response, your application should perform a full sync. If you receive no nextPageToken in the response, there are no updates to retrieve and you can store the returned historyId for a future request.
Modified script:
// Added: At first, please run this function and save the startHistoryId to the PropertiesService.
function getStartHistoryId() {
var startHistoryId = Gmail.Users.getProfile('me').historyId;
PropertiesService.getScriptProperties().setProperty("startHistoryId", startHistoryId);
}
// Modified: As the next step, please run this function.
function history() {
var startHistoryId = PropertiesService.getScriptProperties().getProperty("startHistoryId");
var newResponse = Gmail.Users.History.list('me', {startHistoryId: Number(startHistoryId)});
if ("history" in newResponse) {
for(var i = 0; i< newResponse.history.length; i++){
var label = newResponse.history[i];
console.log(JSON.stringify(label));
}
}
}
In my environment, I could confirm that {startHistoryId: startHistoryId} can be also used.
Note:
This modified script supposes that Gmail API has already been enabled at Advanced Google services. Ref
References:
Users.history: list
Users: getProfile

google apps script returns 'undefined' error message but still completes

To set the scene, here is the request I'm making via Google Apps Script...
function getHvdcData() {
// call the API
var response = UrlFetchApp.fetch(dDomain + epHvdc, dParams);
var json = JSON.parse(response.getContentText());
Logger.log(json);
// define array location in response
var dataLevel = json['data']['hvdcFlow'];
for (var i = 0; i <= dataLevel.length; i++) {
var input = [];
input.push(dataLevel[i].date);
input.push(dataLevel[i].period);
input.push(dataLevel[i].flow);
input.push(dataLevel[i].direction);
input.push(now = new Date());
shtHvdc.appendRow(input);
}
}
This is an example of what comes back in the response...
{
resultCode:200,
message:"Success",
generated:"2018-11-07T14:00:46",
expires:"2018-11-07T14:05:46",
data:{
hvdcFlow:[
{
date:"2018-11-07T00:00:00",
period:27,
flow:387.446,
direction:"North"
}
]
}
}
I'm hoping someone can help me understand why I'm getting this message
TypeError: Cannot read property "date" from undefined. at getHvdcData(demo_site:79)
Line 79 in my code singles out an attribute of a JSON response, which I'm then adding to an array which gets pushed to a google sheet (input.push(dataLevel[i].date);). I realise that I don't have lines in my code that specifically defined the attribute(s) but thought that was part of the JSON.parse function. The script is still able to read .date and add the associated value to var input = []; and insert it into the google sheet.
If the error is generated because I don't specifically declare what .date is, is there a way that I can turn off validation for this part of the code? Given that the script still inserts the data as expected, I wouldn't normally be concerned however I have also created a time based trigger for this to run. Currently, my executions are sitting at 100% Failed because of this and I'd prefer the Trigger Execution page only showed where my script failed to run.
Any help or advice is greatly appreciated.

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();
}

Getting a Parameters Error for OAuth2

I'm pretty new to this and am struggling at the moment to get an OAuth 2.0 token for use with Google Apps Script to write to a Fusion Table. I'm using the Google Developers Live code from Arun and I can't seem to get the access token. When I run the doGet function below, it gives me a "Type Error: cannot read property "parameters" from undefined".
function doGet(e) {
var HTMLToOutput;
if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>Finished with oAuth</h1>You can close this window.</html>';
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
function getAndStoreAccessToken(code){
var parameters = {
method : 'post',
payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code
};
var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();
var tokenResponse = JSON.parse(response);
// store the token for later retrieval
UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token);
}
Any help would be greatly appreciated.
In Appsscript there are some triggers, these triggers execute a piece of code in response to certain action or parameters.
In this case you are using the trigger doGet (which is the name of your function). As you can see, that function receives the parameter "e". If you run that function directly in the environment, this parameter will be "undefined" as you are not passing anything to the function.
This trigger is executed when you access your code as a web application. To do this you have to click on the icon next to the "save" button (the one that looks like a cloud with an arrow) here you can find the information.
When you access your code through the url you obtained after deploying your app, the function receives the necessary parameter (inside "e") and then it should work.