Identifying Form destination (Spreadsheet AND SHEET) - google-apps-script

I'm working on a script that interacts with Google Form' response sheet.
FormApp.getActiveForm().getDestinationId()
give me the spreadsheet id, but I don't find a way to get the sheet itself. User can change its name and position, so I need to get its id, like in
Sheet.getSheetId()
I also have to determine the number of columns the responses uses. It's not equal to the number of questions in the form. I can count the number of items in the form:
Form.getItems().length
and then search for gridItems, add the number of rows in each and add them minus one:
+ gridItem.getRows().length - 1
Finally, I think there's no way to relate each question with each column in the sheet, but by comparing somehow columns names with items title.
Thank you

#tehhowch came very close to the correct answer, but there is a problem with the code: there is no guarantee that form.getPublishedUrl() and sheet.getFormUrl() will return exactly the same string. In my case, form.getPublishedUrl() returned a URL formed as https://docs.google.com/forms/d/e/{id}/viewform and sheet.getFormUrl() returned https://docs.google.com/forms/d/{id}/viewform. Since the form id is part of the URL, a more robust implementation would be:
function get_form_destination_sheet(form) {
const form_id = form.getId();
const destination_id = form.getDestinationId();
if (destination_id) {
const spreadsheet = SpreadsheetApp.openById(destination_id);
const matches = spreadsheet.getSheets().filter(function (sheet) {
const url = sheet.getFormUrl();
return url && url.indexOf(form_id) > -1;
});
return matches.length > 0 ? matches[0] : null;
}
return null;
}

There is now a way to verify which sheet in a Google Sheets file with multiple linked forms corresponds to the current Form - through the use of Sheet#getFormUrl(), which was added to the Sheet class in 2017.
function getFormResponseSheet_(wkbkId, formUrl) {
const matches = SpreadsheetApp.openById(wkbkId).getSheets().filter(
function (sheet) {
return sheet.getFormUrl() === formUrl;
});
return matches[0]; // a `Sheet` or `undefined`
}
function foo() {
const form = FormApp.getActiveForm();
const destSheet = getFormResponseSheet_(form.getDestinationId(), form.getPublishedUrl());
if (!destSheet)
throw new Error("No sheets in destination with form url '" + form.getPublishedUrl() + "'");
// do stuff with the linked response destination sheet.
}
If you have unlinked the Form and the destination spreadsheet, then obviously you won't be able to use getDestinationId or getFormUrl.

I needed this also, and remarkably there is still no apps script method that facilitates it. In the end I set about finding a reliable way to determine the sheet id, and this is what I ended up with by way of programmatic workaround:
Add a temporary form item with a title that's a random string (or something similarly suitable)
Wait for the new corresponding column to be added to the destination sheet (typically takes a few seconds)
Look though each sheet in the destination until you find this new form item title string in a header row
Delete the temporary form item that was added
Wait for the corresponding column in the sheet to unlink from the form and become deletable (typically takes a few seconds)
Delete the column corresponding to the temporary form item
Return the sheet ID
I'm sure some won't like this approach because it modifies the form and spreadsheet, but it does work well.
With the necessary wait times included it takes about 12 seconds to perform all the look up / clean up operations.
Here's my code for this method in case anyone else might like to use it.
// Takes Apps Script 'Form' object as single paramater
// The second parameter 'obj', is for recursion (do not pass a second parameter)
// Return value is either:
// - null (if the form is not linked to any spreadsheet)
// - sheetId [int]
// An error is thrown if the operations are taking too long
function getFormDestinationSheetId(form, obj) {
var obj = obj || {}; // Initialise object to be passed between recursions of this function
obj.attempts = (obj.attempts || 1);
Logger.log('Attempt #' + obj.attempts);
if (obj.attempts > 14) {
throw 'Unable to determine destination sheet id, too many failed attempts, taking too long. Sorry!';
}
obj.spreadsheetId = obj.spreadsheetId || form.getDestinationId();
if (!obj.spreadsheetId) {
return null; // This means there actually is no spreadsheet destination set at all.
} else {
var tempFormItemTitle = '### IF YOU SEE THIS, PLEASE IGNORE! ###';
if (!obj.tempFormItemId && !obj.sheetId) { // If the sheet id exists from a previous recusion, we're just in a clean up phase
// Check that temp item does not already exist in form
form.getItems(FormApp.ItemType.TEXT).map(function(textItem) {
var textItemTitle = textItem.getTitle();
Logger.log('Checking against form text item: ' + textItemTitle);
if (textItemTitle === tempFormItemTitle) {
obj.tempFormItemId = textItem.getId();
Logger.log('Found matching form text item reusing item id: ' + obj.tempFormItemId);
}
return 0;
}); // Note: Just using map as handy iterator, don't need to assign the output to anything
if (!obj.tempFormItemId) {
Logger.log('Adding temporary item to form');
obj.tempFormItemId = form.addTextItem().setTitle(tempFormItemTitle).getId();
}
}
obj.spreadsheet = obj.spreadsheet || SpreadsheetApp.openById(obj.spreadsheetId);
obj.sheets = obj.sheets || obj.spreadsheet.getSheets();
obj.sheetId = obj.sheetId || null;
var sheetHeaderRow = null;
for (var i = 0, x = obj.sheets.length; i < x; i++) {
sheetHeaderRow = obj.sheets[i].getSheetValues(1, 1, 1, -1)[0];
for (var j = 0, y = sheetHeaderRow.length; j < y; j++) {
if (sheetHeaderRow[j] === tempFormItemTitle) {
obj.sheetId = obj.sheets[i].getSheetId();
Logger.log('Temporary item title found in header row of sheet id: ' + obj.sheetId);
break;
}
}
if (obj.sheetId) break;
}
// Time to start cleaning things up a bit!
if (obj.sheetId) {
if (obj.tempFormItemId) {
try {
form.deleteItem(form.getItemById(obj.tempFormItemId));
obj.tempFormItemId = null;
Logger.log('Successfully deleted temporary form item');
} catch (e) {
Logger.log('Tried to delete temporary form item, but it seems it was already deleted');
}
}
if (obj.sheetId && !obj.tempFormItemId && !obj.tempColumnDeleted) {
try {
obj.sheets[i].deleteColumn(j + 1);
obj.tempColumnDeleted = true;
Logger.log('Successfully deleted temporary column');
} catch (e) {
Logger.log('Could not delete temporary column as it was still attached to the form');
}
}
if (!obj.tempFormItemId && obj.tempColumnDeleted) {
Logger.log('Completed!');
return obj.sheetId;
}
}
SpreadsheetApp.flush(); // Just in case this helps!
// Normally this process takes three passes, and a delay of 4.5 secs seems to make it work in only 3 passes most of the time
// Perhaps if many people are submitting forms/editing the spreadsheet, this delay would not be long enough, I don't know.
obj.delay = ((obj.delay || 4500));
// If this point is reached then we're not quite finished, so try again after a little delay
Logger.log('Delay before trying again: ' + obj.delay / 1000 + ' secs');
Utilities.sleep(obj.delay);
obj.attempts++;
return getFormDestinationSheetId(form, obj);
}
}

To get the spreadsheet, once you have the DestinationID, use SpreadsheetApp.openById(). Once you have that, you can retrieve an array of sheets, and get the response sheet by index, regardless of its name.
var destId = FormApp.getActiveForm().getDestinationId();
var ss = SpreadsheetApp.openById(destId);
var respSheet = ss.getSheets()[0]; // Forms typically go into sheet 0.
...
From this point, you can manipulate the data in the spreadsheet using other Spreadsheet Service methods.
I also have to determine the number of columns the responses uses. It's not equal to the number of questions in the form. I can count the number of items in the form... (but that doesn't match the spreadsheet)
You're right - the number of current items does not equal the number of columns in the spreadsheet. The number of columns each response takes up in the destination sheet includes any questions that have been deleted from the form, and excludes items that are not questions. Also, the order of the columns in the spreadsheet is the order that questions were created in - as you re-arrange your form or insert new questions, the spreadsheet column order does not reflect the new order.
Assuming that the only columns in the spreadsheet are from forms, here's how you could make use of them:
...
var data = respSheet.getDataRange().getValues(); // 2d array of form responses
var headers = data[0]; // timestamp and all questions
var numColumns = headers.length; // count headers
var numResponses = data.length - 1; // count responses
And your last point is correct, you need to correlate names.
Finally, I think there's no way to relate each question with each column in the sheet, but by comparing somehow columns names with items title.

Related

How do I get the row number of a Google Sheets cell that contains a specific value in Google Apps Script?

I'm trying to create a roll marking system that reads from a Google Sheets spreadsheet, takes input via a Telegam bot, and fills in checkboxes on the Google Sheet. The flow is as follows:
User picks a class (e.g. Year 7-8 Girls) via Telegram bot: https://imgur.com/v9PAgXR
Look through Google Sheet for cells containing the class name (7-8 G): https://imgur.com/yuCckA1
Extract the students' names from rows containing the class name, then presents them in a Telegram poll: https://imgur.com/q20I4iw
Check the boxes corresponding to the student names.
Everything is going pretty well until step 4, where I can't seem to be able to get the actual row number that the student data came from, so I can't tell the script which checkbox to manipulate.
Here are some code snippets to give an idea of what I'm doing (I'm very new to all of this so open to suggestions/improvements):
if (contents.message) { // If the bot receives a regular message
var id_message = contents.message.from.id;
var firstName = contents.message.from.first_name;
sendText(id_message, "Hi " + firstName + "! Please choose a year group.", keyBoard)
}
else if (contents.callback_query) { // If the bot gets an inline keyboard input
var names = [];
var id_callback = contents.callback_query.from.id;
var data = contents.callback_query.data;
var filteredRows = rows.filter(function(row){
if (row[4] == data) { // If the row contains the class code
return row
}
});
filteredRows.forEach(function(row) {
names.push(row[0] + " " + row[1]) // Compile a list of the students in the class
});
if (filteredRows.length <= 10) { //
sendPoll(id_callback,names)
}
else if (filteredRows.length >= 11) { //To get around the 10 option maximum for polls
sendPoll(id_callback,chunkArray(names,9)[0])
}
}
And the code for sending the poll (completely hacky solution just as a proof of concept that I can manipulate the checkboxes). Someone please show me a better way of doing the string manipulation at the start, because it shouldn't take this many steps to go from "[1,2,3,4,5]" to "12345"!
else if (contents.poll_answer) { // If the bot receives a poll response
var pollOptions = JSON.stringify(contents.poll_answer.option_ids);
var sliced = pollOptions.replace(/,/g,"");
sliced = sliced.slice(1,-1);
var i = sliced.length;
if (pollOptions != '[]') {
while (i--) {
ss.getRange("H"+(parseInt(sliced.charAt(i),10)+7)).setValue("TRUE")
}
}
else if (pollOptions == '[]') {
ss.getRange("H7:H13" ).setValue("FALSE")
}
}

Having trouble running custom search function on specific cell data for HTML sidebar display

I'm setting up a sidebar in Google Sheets to display information from rows to give my team a better view of certain data. Through a few of Mogsdad's answers (huge props) I was able to set up the sidebar HTML and display functions that update the DOM with info from whichever row is selected. I'm trying to expand this by running a custom search on a specific cell (string) within the row range and adding an element to the DOM that displays the first 5 Google search results; however, I'm having a tough time pointing to the string value in that specific cell (really a column in the array), running it in my custom search function, and getting the function that adds elements to the HTML to append the results.
The inspiration for this use case comes from Grant Timmerman's Apps Script demo at Angular Connect 2018, where he updates the sidebar with info about event speakers and pulls videos from Youtube using a search string from a specific cell. In the below code, you'll see that I pulled a lot from Mogsdad's sheet polling technique and their walkthrough on setting up a custom search engine using Google's API Key protocol.
Here's my .gs setup:
/**
*This function creates the sidebar in Sheets' UI based on HTML I set up *separately.
*/
function checkUpdates() {
var ui = HtmlService.createTemplateFromFile('CheckSidebar')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle('Recent Sheet Updates');
SpreadsheetApp.getUi().showSidebar(ui);
};
/**
* Returns the active row.
* All based on Mogsdad's sheet polling function from 2015.
*/
function getRecord() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var headers = data[0];
var rowNum = sheet.getActiveCell().getRow();
if (rowNum > data.length) return [];
var record = [];
for (var col=0;col<headers.length;col++) {
var cellval = data[rowNum-1][col];
// Here I tried to set another variable to data[rowNum-1][2] to get some //specific data from column #3
Logger.log(cellval);
record.push({ heading: headers[col],cellval: cellval });
}
return record;
}
In a separate editor tab, I have the search function set up, also largely just following Mogsdad's instructions:
function SearchFetch(query) {
var urlTemplate = "https://www.googleapis.com/customsearch/v1?key=%KEY%&cx=%CX%&q=%Q%";
var ApiKey = "custom API Key";
var searchEngineID = "ID for the search engine";
var url = urlTemplate
.replace("%KEY%", encodeURIComponent(ApiKey))
.replace("%CX%", encodeURIComponent(searchEngineID))
.replace("%Q%", encodeURIComponent(query));
var params = {
muteExceptions: true
};
Logger.log(UrlFetchApp.getRequest(url, params));
var response = UrlFetchApp.fetch(url, params);
var respCode = response.getResponseCode();
if (respCode !== 200) {
throw new Error ("Error " +respCode+ " " + response.getContentText());
}
else {
var result = JSON.parse(response.getContentText());
Logger.log("Obtained %s search results in %s seconds.",
result.searchInformation.formattedTotalResults,
result.searchInformation.formattedSearchTime);
return result;
Finally, I have a function between tags in HTML that grabs the values from the array generated by getRecord() and dumps them into a DOM element with the class ID "floatypar" (for floaty-looking paragraphs, naturally). Lots of Mogsdad's markup in here:
function showRecord(record) {
if (record.length) {
for (var i = 0; i < record.length; i++) {
// build field name on the fly, formatted field-1234
var str = '' + i;
var fieldId = 'field-' + ('0000' + str).substring(str.length)
// If this field # doesn't already exist on the page, create it
if (!$('#'+fieldId).length) {
var newField = $($.parseHTML('<div id="'+fieldId+'"></div>'));
$('.floatypar').append(newField);
}
// Replace content of the field div with new record
$('#'+fieldId).replaceWith('<div id="'+fieldId+'" class="floatypar">' +record[i].cellval + '</div>');
}
}
//Setup the next poll
poll();
}
When I run this, the sidebar displays values from each cell in the row in their own div in the sidebar. However, I'm at a loss for passing the values from column #3 to the search engine and displaying results in their own sidebar div. Can anyone steer me in the right direction? Please do give props to Mogsdad in any case!

Detect formula errors in Google Sheets using Script

My ultimate goal is here, but because I've gotten no replies, I'm starting to learn things from scratch (probably for the best anyway). Basically, I want a script that will identify errors and fix them
Well, the first part of that is being able to ID the errors. Is there a way using Google Script to identify if a cell has an error in it, and return a particular message as a result? Or do I just have to do an if/else that says "if the cell value is '#N/A', do this", plus "if the cell value is '#ERROR', do this", continuing for various errors?. Basically I want ISERROR(), but in the script
Use a helper function to abstract away the nastiness:
function isError_(cell) {
// Cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
const errorValues = ["#N/A", "#REF", .... ];
for (var i = 0; i < errorValues.length; ++i)
if (cell == errorValues[i])
return true;
return false;
}
function foo() {
const vals = SpreadsheetApp.getActive().getSheets()[0].getDataRange().getValues();
for (var row = 0; row < vals.length; ++row) {
for (var col = 0; col < vals[0].length; ++col) {
if (isError_(vals[row][col])) {
Logger.log("Array element (" + row + ", " + col + ") is an error value.");
}
}
}
}
Using Array#indexOf in the helper function:
function isError_(cell) {
// Cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
// Note: indexOf uses strict equality when comparing cell to elements of errorValues, so make sure everything's a primitive...
const errorValues = ["#N/A", "#REF", .... ];
return (errorValues.indexOf(cell) !== -1);
}
If/when Google Apps Script is upgraded with Array#includes, that would be a better option than Array#indexOf:
function isError_(cell) {
// cell is a value, e.g. came from `range.getValue()` or is an element of an array from `range.getValues()`
const errorValues = ["#N/A", "#REF", .... ];
return errorValues.includes(cell);
}
Now that the v8 runtime is available, there are a number of other changes one could make to the above code snippets (arrow functions, etc) but note that changing things in this manner is not required.
Update: 25 March 2020
#tehhowch remarked "If/when Google Apps Script is upgraded with Array#includes, that would be a better option than Array#indexOf".
Array.includes does now run in Apps Script and, as anticipated provides a far more simple approach when compared to indexOf.
This example varies from the previous answers by using a specific range to show that looping through each cell is not required. In fact, this answer will apply to any range length.
The two key aspects of the answer are:
map: to create an array for each column
includes: used in an IF statement to test for a true or false value.
function foo() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcename = "source_sheet";
var source = ss.getSheetByName(sourcename);
var sourcerange = source.getRange("A2:E500");
var sourcevalues = sourcerange.getValues();
// define the error values
var errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
// loop though the columns
for (var c = 0;c<5;c++){
// create an array for the column
var columnoutput = sourcevalues.map(function(e){return e[c];});
// loop through errors
for (var errorNum=0; errorNum<errorValues.length;errorNum++){
// get the error
var errorvalue = errorValues[errorNum]
// Logger.log("DEBUG: column#:"+c+", error#:"+e+", error value = "+errorvalue+", does col include error = "+columnoutput.includes(errorvalue));
// if the error exists in this column then resposnse = true, if the error doesn't exist then response = false
if (columnoutput.includes(errorvalue) != true){
Logger.log("DEBUG: Column#:"+c+", error#:"+errorNum+"-"+errorvalue+" - No ERROR");
} else {
Logger.log("DEBUG: column#:"+c+", error#:"+errorNum+"-"+errorvalue+"- ERROR EXISTS");
}
}
}
return;
}
Shorter yet, use a nested forEach() on the [vals] array, then check to see if the cell value matches a value of the [errorValues] array with indexOf. This was faster than a for loop...
function foo() {
const errorValues = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A","#ERROR!"];
const vals = SpreadsheetApp.getActive().getSheets()[0].getDataRange().getValues();
vals.forEach((val,row) => { val.forEach((item, col) => {
(errorValues.indexOf(item) !== -1) ? Logger.log("Array element (" + row + ", " + col + ") is an error value.") : false ;
});
});
}
I had a similar question and resolved using getDisplayValue() instead of getValue()
Try something like:
function checkCells(inputRange) {
var inputRangeCells = SpreadsheetApp.getActiveSheet().getRange(inputRange);
var cellValue;
for(var i=0; i < inputRangeCells.length; i++) {
cellValue = inputRangeCells[i].getDisplayValue();
if (cellValue=error1.....) { ... }
}
}
Display value should give you what's displayed to the user rather than #ERROR!

Fill multiple rows in google spreadsheet via google script

I am currently working on a semester project for my university in which we want to log data from an Arduino to a Google Sheet.
I was following the numerous tutorials and examples that I could find on Google and it worked so far really, really well. My Arduino is able to upload data to said spreadsheet.
Unfortunately all those examples always only deal with one row to be filled. For our project we would like to fill 2 or 3 lines simultaneously.
I will shortly show what I have done so far and maybe you can help me solve my (probably easy) problem.
I created a google spreadsheet in which I want to log my data
I used the script from a tutorial that should fill one row.
By typing the following line in my browserhttps://script.google.com/macros/s/<gscript id>/exec?tempData=datahereI am now able to fill row one with my data in enter in the end of the url.
But how do I progress now, when I want to fill two or three rows of the table? I say that the author of the code already implemented an option to fill the third row, yet I can't find out what to input in my url then to fill it with data.
All my attempts to write something like
https://script.google.com/macros/s/<gscript id>/exec?tempData=datahere&tempData1=value2
just ended in writing
datahere&tempData1=value2
in my first row, not filling datahere into the first and value2 in to the second row.
How can I provide and write multiple rows of data?
The code in this script is:
/*
GET request query:
https://script.google.com/macros/s/<gscript id>/exec?tempData=data_here
*/
/* Using spreadsheet API */
function doGet(e) {
Logger.log( JSON.stringify(e) ); // view parameters
var result = 'Ok'; // assume success
if (e.parameter == undefined) {
result = 'No Parameters';
}
else {
var id = '<ssheet id>'; // Spreadsheet ID
var sheet = SpreadsheetApp.openById(id).getActiveSheet();
var newRow = sheet.getLastRow() + 1;
var rowData = [];
//var waktu = new Date();
rowData[0] = new Date(); // Timestamp in column A
for (var param in e.parameter) {
Logger.log('In for loop, param='+param);
var value = stripQuotes(e.parameter[param]);
//Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'tempData': //Parameter
rowData[1] = value; //Value in column B
break;
case 'tempData1':
rowData[2] = value; //Value in column C
break;
default:
result = "unsupported parameter";
}
}
Logger.log(JSON.stringify(rowData));
// Write new row below
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
// Return result of operation
return ContentService.createTextOutput(result);
}
/**
* Remove leading and trailing single or double quotes
*/
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
I would suggest the following:
Create a 2d array of your data you wish to write to the spreadsheet. If your client on Arduino were using JavaScript this might look like :
var data = [
["row1value1", "row1value2"],
["row2value1", "row2value2"]
];
Convert this to JSON, again in JavaScript this might look like:
var json = JSON.stringify(data);
This gives you a string representation of your array.
Now make your request using this data. I would suggest you should look at using doPost instead of doGet, as you are sending data to the spreadsheet that updates state. However, for the purposes of getting something working, your URL would look like:
https://script.google.com/<.....>/exec?myarray=<stringified JSON>
In Apps Script, in your doGet (again, consider using doPost instead), you could then use:
// Get the JSON representation of the array:
var json = e.parameter.myarray;
// Convert back to 2d array
var data = JSON.parse(json);
Now you can write this to a Range in Sheets using setValues, e.g. assuming a rectangular 2d array:
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
Hope this helps

getMessageById() slows down

I am working on a script that works with e-mails and it needs to fetch the timestamp, sender, receiver and subject for an e-mail. The Google script project has several functions in separate script files so I won't be listing everything here, but essentially the main function performs a query and passes it on to a function that fetches data:
queriedMessages = Gmail.Users.Messages.list(authUsr.mail, {'q':query, 'pageToken':pageToken});
dataOutput_double(sSheet, queriedMessages.messages, queriedMessages.messages.length);
So this will send an object to the function dataOutput_double and the size of the array (if I try to get the size of the array inside the function that outputs data I get an error so that is why this is passed here). The function that outputs the data looks like this:
function dataOutput_double(sSheet, messageInfo, aLenght) {
var sheet = sSheet.getSheets()[0],
message,
dataArray = new Array(),
row = 2;
var i, dateCheck = new Date;
dateCheck.setDate(dateCheck.getDate()-1);
for (i=aLenght-1; i>=0; i--) {
message = GmailApp.getMessageById(messageInfo[i].id);
if (message.getDate().getDate() == dateCheck.getDate()) {
sheet.insertRowBefore(2);
sheet.getRange(row, 1).setValue(message.getDate());
sheet.getRange(row, 2).setValue(message.getFrom());
sheet.getRange(row, 3).setValue(message.getTo());
sheet.getRange(row, 4).setValue(message.getSubject());
}
}
return;
};
Some of this code will get removed as there are leftovers from other types of handling this.
The problem as I noticed is that some messages take a long time to get with the getMessageById() method (~ 4 seconds to be exact) and when the script is intended to work with ~1500 mails every day this makes it drag on for quite a while forcing google to stop the script as it takes too long.
Any ideas of how to go around this issue or is this just something that I have to live with?
Here is something I whipped up:
function processEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var messages = Gmail.Users.Messages.list('me', {maxResults:200, q:"newer_than:1d AND label:INBOX NOT label:PROCESSED"}).messages,
headers,
headersFields = ["Date","From","To","Subject"],
outputValue=[],thisRowValue = [],
message
if(messages.length > 0){
for(var i in messages){
message = Gmail.Users.Messages.get('me', messages[i].id);
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
headers = message.payload.headers
for(var ii in headers){
if(headersFields.indexOf(headers[ii].name) != -1){
thisRowValue.push(headers[ii].value);
}
}
outputValue.push(thisRowValue)
thisRowValue = [];
}
var range = ss.getRange(ss.getLastRow()+1, ss.getLastColumn()+1, outputValue.length, outputValue[0].length);
range.setValues(outputValue);
}
}
NOTE: This is intended to run as a trigger. This will batch the trigger call in 200 messages. You will need to add the label PROCESSED to gmail. Also on the line:
Gmail.Users.Messages.modify( {addLabelIds:["Label_4"]},'me',messages[i].id);
it shows Label_4. In my gmail account "PROCESSED" is my 4th custom label.