I'm trying to pull data from gmail (ie. from, to, date, subject, body, etc.) but I'm getting an error saying the getBody() length is too long:
your input contains more than the maximum of 50000 characters in a single cell
I'm trying to have a cut off of characters or just not pull the email with a Body over the character limit. Also, I'm pretty out of practice on this and it's not working...
//Allow user to select label(s) and date range
function getGmailEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var label = sheet.getRange(1, 3).getValue();
var after = sheet.getRange(2, 3).getDisplayValue();
var before = sheet.getRange(3, 3).getDisplayValue();
var threads = GmailApp.search("label:" + label + " AND " + "after:" + after + " AND " + "before:" + before);
// Export emails into table of values in google sheet
// Character length cutoff is 2000, the cell with get Body would tell you to go to the inbox
var values = [];
for (var i = 0; i < threads.length < 2000; i++) {
var temp = [];
var label = threads[i].getLabels().map(e => e.getName()).join(", ");
let messages = threads[i].getMessages();
for (var j = 0; j < messages.length; j++) {
temp.push([
label,
messages[j].getFrom(),
messages[j].getTo(),
messages[j].getCc(),
messages[j].getDate(),
messages[j].getSubject(),
messages[j].getBody(),
messages[0].getId()
]);
}
values = values.concat(temp);
}
sheet.getRange(6, 1, values.length, values[0].length).setValues(values);
}
One way is by using String.prototype.slice().
Replace
messages[j].getBody(),
by
messages[j].getBody().slice(0,50000),
Related
Substring vs Slice vs Other?
Related
Here is the problem & logic for the find & replace script I am using.
Search Sheet for to_replace string.
If found, replace to_replace with replace_with.
If not found, replace to_replace with to_replace // This is not needed, and causes problems (it replaces all formulas, and replaces it with a string).
My Objective:
I would like the script to only replace cells that match to_replace, and ignore every other cell.
My Rookie Solution:
Exclude specific columns in the foruma by eliminating column C from array using script from here. (only find & replace within Column B & D).
Here is the modified code I added in My Current Script...
const range = sheet.getRange('B2:D'+lastRow).getValues();
range.forEach(a => a.splice(1, 1)); //removes column C.
But I get the error: "TypeError: var data = range.getValues(); is not a function"
Question
Can you help me troubleshoot my rookie solution, or teach me a better way to solve this problem?
My current script
function findAndReplace(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow()
var lastColumn = sheet.getLastColumn()
// var range = sheet.getRange(1, 1, lastRow, lastColumn) //REMOVED - Searches all columns.
const range = sheet.getRange('B2:D'+lastRow).getValues(); //ADDED - Searches only B & D
range.forEach(a => a.splice(1, 1)); //ADDED - Searches only B & D
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
var data = range.getValues();
var oldValue="";
var newValue="";
var cellsChanged = 0;
for (var r=0; r<data.length; r++) {
for (var i=0; i<data[r].length; i++) {
oldValue = data[r][i];
newValue = data[r][i].toString().replace(to_replace, replace_with);
if (oldValue!=newValue)
{
cellsChanged++;
data[r][i] = newValue;
}
}
}
range.setValues(data);
}
From teach me a better way to solve this problem, in your situation, I thought that when TextFinder is used, the process cost might be able to be reduced. When TextFinder is used for achieving your goal, it becomes as follows.
Sample script:
function myFunction() {
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow();
var ranges = ['B2:B' + lastRow, 'D2:D' + lastRow];
sheet.getRangeList(ranges).getRanges().forEach(r =>
r.createTextFinder(to_replace).matchEntireCell(true).replaceAllWith(replace_with)
);
}
Note:
If you want to replace the part of cell value, please modify r.createTextFinder(to_replace).matchEntireCell(true).replaceAllWith(replace_with) to r.createTextFinder(to_replace).replaceAllWith(replace_with).
As an additional modification, if your script is modified, how about the following modification?
function findAndReplace() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastRow = sheet.getLastRow()
var range = sheet.getRange('B2:D' + lastRow);
var data = range.getValues();
var to_replace = "TextToFind";
var replace_with = ""; //Leave blank to delete Text, or enter text in quotes to add string.
for (var r = 0; r < data.length; r++) {
for (var i = 0; i < data[r].length; i++) {
var value = data[r][i].toString();
if (i != 1 && value.includes(to_replace)) {
data[r][i] = data[r][i].replace(to_replace, replace_with);
}
}
}
range.setValues(data);
}
References:
createTextFinder(findText)
Class TextFinder
I am working on a script that allows a user to automatically take data from a spreadsheet and apply it to a Google Docs template.
My createTemplate() function individually takes each row in a sheet and applies it to a doc template. The function will do this for each row in the active sheet.
I have another function, chooseRow(), that allows the user to search for a tagNo. through a prompt.
The tagNo. is a unique identifier for a machine that is stored in the spreadsheet.
Each machine has its own tagNo, and user will always know the tagNo.
This function is logging the row number for me, but I cannot apply the same code as the createTemplate() function to actually create the document.
I have tried applying the code from createTemplate() to chooseRow() to hopefully get the result I am looking for, but it just ended doing the exact same as createTemplate().
I applied the code to the inside of the for loop used in chooseRow().
function chooseRow(){
var ui = SpreadsheetApp.getUi(); // Same variations.
var result = ui.prompt('Please enter the Tag number of the row you wish to print.', ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var response = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK".
ui.alert('Your name is ' + response + '.');
} else if (button == ui.Button.CLOSE) {
// User clicked X in the title bar.
ui.alert('You closed the dialog.');
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
Logger.log(i);
}
}
}
}
function createTemplate(){
var sleepInt = 0;
var templateId = "1uSAcH8F21zEjuprIcE2_d84ojQT24ek85Y1W6L17Xno"; //Template ID (Taken from address bar)
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.toast("starting");
Utilities.sleep(sleepInt);
var sheet = ss.getActiveSheet();
//starting with row 2 and column 1 as our upper-left most column, get values from cells from 1 row down, and 15 columns along - hence (2,1,1,15)
var data = sheet.getRange(2, 2, 11, 18).getValues();
ss.toast("created document and adding data");
Utilities.sleep(sleepInt);
//Get the title and tag number columns ready. Also get todays date
//Keeping this block above the for loop allows the code to run faster as it doesn't have to do a .getRange or recalculate the date every time the loop iterates
var docTitle = sheet.getRange(2, 2, 11, 1).getValues();//this is grabbing the data in field B2
var docTitleTagNumber = sheet.getRange(2, 3, 11, 1).getValues();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
today = dd + '/' + mm + '/' + yyyy;
//apply data to template
for(var i in data){
var row = data[i];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
body.replaceText("%SITEID%", row[0]);
body.replaceText("%TAG%", row[1]);
body.replaceText("%CATEGORY%", row[2]);
body.replaceText("%DESCRIPTION%", row[3]);
body.replaceText("%AREA%", row[4]);
body.replaceText("%SERIALNO%", row[5]);
body.replaceText("%MODEL%", row[6]);
body.replaceText("%MANUAL%", row[7]);
body.replaceText("%HOOKUP%", row[8]);
body.replaceText("%WEB%", row[9]);
body.replaceText("%CONNECTED%", row[11]);
body.replaceText("%CALIBRATED%", row[12]);
body.replaceText("%AUTOMATED%", row[13]);
body.replaceText("%SAT%", row[14]);
body.replaceText("%SIGNED%", row[16]);
doc.saveAndClose();
ss.toast("added data");
Utilities.sleep(sleepInt);
//Copy the modified template into the specific folder, then delete the first copy we made (to modify it)
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("16wRGBVdV0OZ5YfKhqEQSFMsux-ekGCCa");
newFolder.addFile(file);
ss.toast("finished");
Utilities.sleep(sleepInt);
//uses i to iterate through each row in the first column
var newDocTitle = docTitle[i][0];
var newDocTagNumber = docTitleTagNumber[i][0];
//apply document names, tag numbers, and dates
doc.setName(newDocTitle + " " + newDocTagNumber + " " + today);
}
}
The expected output is one document containing all of the data from the row that contains the tagNo. that the user entered into the prompt, but the results I am getting are the same as createTemplate().
The problem is I was taking the entire for loop in createTemplate() and applying it inside the if statement in chooseRow(). This can be solved by taking the following code in createtemplate(),
var row = data[i];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
body.replaceText("%SITEID%", row[0]);
body.replaceText("%TAG%", row[1]);
body.replaceText("%CATEGORY%", row[2]);
body.replaceText("%DESCRIPTION%", row[3]);
...
body.replaceText("%AUTOMATED%", row[13]);
body.replaceText("%SAT%", row[14]);
body.replaceText("%SIGNED%", row[16]);
doc.saveAndClose();
ss.toast("added data");
Utilities.sleep(sleepInt);
//Copy the modified template into the specific folder, then delete the first copy we made (to modify it)
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("16wRGBVdV0OZ5YfKhqEQSFMsux-ekGCCa");
newFolder.addFile(file);
ss.toast("finished");
Utilities.sleep(sleepInt);
//uses i to iterate through each row in the first column
var newDocTitle = docTitle[i][0];
var newDocTagNumber = docTitleTagNumber[i][0];
//apply document names, tag numbers, and dates
doc.setName(newDocTitle + " " + newDocTagNumber + " " + today);
along with the appropriate variables, and pasting it into the if statement here, beneath Logger.log(i);
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
Logger.log(i);
}
}
}
I have the below script which essentially looks at a google sheets tab, which has some data that needs to be uploaded to Google Import API (Google Analytics):
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('UK Only');
var maxRows = ss.getLastRow();
var maxColumns = ss.getLastColumn();
var rows = ss.getRange(1, 1, maxRows, maxColumns).getValues();
var rowsCSV = rows.join("\n")
var blobData = Utilities.newBlob(rowsCSV, "application/octet-stream", "GA import data");
try {
var upload = Analytics.Management.Uploads.uploadData("10131233", "UA-1234576-2", "righfghfghfgT8Dox1nwXDg", blobData);
Logger.log("Test Data Import Successful");
}
catch (e) {
console.error(e);
}
The output is something like the below:
ga:productSku,ga:productName,ga:productBrand,ga:productCategoryHierarchy,ga:dimension25,ga:dimension28,ga:dimension31
456456456,example value wit,h char ,that "breaks" csv,fgjfgjf Tjghjghjg,FP,Women,dasdasd
456456456,example value wit,h char ,that "breaks" csv,123123123,FP,Women,dasdasd
456456456,example value wit,h char ,that "breaks" csv,Rdasdasd,FP,asdasdasd
The above shows some values that will break the CSV format (commas and quotes).
How do I go about properly formatting the CSV so that it will not break on these characters?
Managed to resolve this with the below function, I had to account for quotes and commas within values that Google API doesn't like:
function arrayToCSV (twoDiArray) {
var csvRows = [];
for (var i = 0; i < twoDiArray.length; ++i) {
for (var j = 0; j < twoDiArray[i].length; ++j) {
twoDiArray[i][j] = '\"' + twoDiArray[i][j].replace('"','""') + '\"';
}
csvRows.push(twoDiArray[i].join(','));
}
return csvRows.join('\r\n');
};
Making sure to call it with the below:
var maxRows = ss.getLastRow();
var maxColumns = ss.getLastColumn();
var rows = ss.getRange(1, 1, maxRows, maxColumns).getValues();
var rowsCSV = arrayToCSV(rows);
I'm trying to build a script in google sheets script that will iterate down a sheet, row by row, and if it encounters an ID number in the first cell of that row that is identical to the one above it, it strips out the data from every cell EXCEPT Column A and B, and appends it to the line above. Ideally, this would work with an indeterminate number of duplicate row IDs, might be 2, might be 3, might be 4.
After stripping out the data I want to keep (eg colums C and onward), I then want to delete the entire contents of the processed duplicate ID row, but I just haven't put that in my script until after it copies the data correctly.
In this example, sheet rows 6, 7 and 8 have identical ID numbers (Column A)
Here is the result I'm trying to get:
And here is the result I'm getting:
I've tried a number of different ways, and torn down and rebuilt my script a couple of times without getting the result I want:
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,4,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-1,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
I'm not quite sure where I'm going wrong. Does anyone have any suggestions? Thanks!
(Also I'm still just a beginner with this so if I've overlooked anything obvious or taken the wrong approach to do this, I apologize!)
The results for the questioner's code in the case of copied data arise from a convoluted loop. In essence, though duplicates were identified, there was a mis-counting to assign the copied data to the correct rowID. So far as clearing data, no provision was included.
The following code works to meet the questioner's goals, though it is far from perfect.
At present, the recalculation of the "last column" after copy each duplicate is an absolute rather than than a row-based figure. So, if a duplicate was detected for, say, ID=3, the data would be copied to column 12 rather than column 6. This requires the addition of a simple dupID row counter.
The second factor is the calculation of the last column in the spreadsheet.
var dataRange = mainSheet.getRange(startRow, 1,Rowlast+1,120);
The questioner used 120 columns; and I have retained that number simply for the sake of consistency. The questioner should re-assess whether this is excessive.
function ejb_so_5284922701() {
var ss = SpreadsheetApp.openById("<< insert questioners spreadsheet ID>>");
var mainSheet = ss.getSheetByName("<< insert questioner's sheet name >>");
var startRow = 5;
// calculate the last row containing data
var Rowvals = ss.getRange("A5:A").getValues();
var Rowlast = Rowvals.filter(String).length; //6
Logger.log("last row = " + Rowlast); // DEBUG
// calculate the last column containing data
var cell = mainSheet.getRange("A5"); //or however you determine "cell"
var drCol = mainSheet.getDataRange().getLastColumn();
Logger.log('getLastColumn = ' + drCol); //DEBUG
for (var i = drCol; i >= 1; i--) {
if (mainSheet.getRange(cell.getRow(), i).getValue() != "") {
break;
}
}
var lastColumn = i;
Logger.log("Last column with data = " + lastColumn); //DEBUG
var setrgh = mainSheet
// numColumns neds to be reviewed
var dataRange = mainSheet.getRange(startRow, 1, Rowlast + 1, 120); //rowStart, columnStart, row count, column count, the column count needs to be large enough to encompass all your ancillary data
// start row = 5, 1= column A, 4, rows, 120, columns
Logger.log("startRow = " + startRow + ", and the datarange = " + dataRange.getA1Notation()); //DEBUG
var data = dataRange.getValues();
Logger.log("length of data =" + data.length); //DEBUG
var lastid = 0;
for (i = 0; i < data.length; i++) {
if (i == 0) {
// if this is the first row, then assign anything but zero to last id
lastid = 100;
Logger.log(" this is the first row; set last id to 100");
}
var thisid = data[i][0];
// evaluate whether this is a duplicate ID
if (thisid == lastid) {
// this is a dup
Logger.log("i=" + i + ". This is a dup" + ", name is " + data[i][2]); //DEBUG
var stufftocopyrange = mainSheet.getRange(startRow + i, 3, 1, 3);
var stufftocopy = stufftocopyrange.getValues();
Logger.log("the range to copy is " + stufftocopyrange.getA1Notation()); //DEBUG
var targetrange = mainSheet.getRange(startRow + lastid - 1, lastColumn + 1, 1, 3);
targetrange.setValues(stufftocopy);
lastColumn = lastColumn + 3;
var duprange = mainSheet.getRange(startRow + i, 1, 1, 5);
Logger.log("the range to clear is " + duprange.getA1Notation()); //DEBUG
duprange.clearContent();
} else {
// no dup
//assign lastid value
var lastid = thisid;
Logger.log("i=" + i + ". No dup. Last id set to " + lastid); // DEBUG
} // if
} // end for loop
}
BEFORE
AFTER
The solutions previously posted didn't quite get the result I needed, however I managed to cobble together something that works for my purposes. It expects to see data in the format like:
And turn it into something like this:
Where it uses duplicate ID numbers (with an indeterminite number of duplicates) to pull only certain columns of data from the duplicate lines and append it to the first line.
function stripMiner() {
var ss = SpreadsheetApp.openById("1WDPoTICQvdruxfhAwHLtA51fz05DqyZ-NhNfpAyPO6Y");
var mainSheet = ss.getSheetByName("Main");
var startRow = 5;
var numRows = mainSheet.getLastRow();//obtains the last row in the sheet
var setrgh = mainSheet
var dataRange = mainSheet.getRange(startRow, 1,3,120); //rowStart, columnStart, row count, column count, the columncount needs to be large enough to encompass all your ancillary data
var data = dataRange.getValues();
var iter = 0;
var maxItRow = 4;
var prevIdNum = 0;
var dupCount = 1;
var cc1 = "P5"; //Cells to dump check values into
var cc2 = "P6";
var dumpRow = startRow;
//if (numRows >= maxItRow){var maxIter = maxItRow;}
for (i in data){
if (iter != maxItRow){ //making sure we haven't gone over the iteration limit
var row = data[i];
var idNum = (row[0]);
var jCount = 0; //resets icount if the id number is different icount is used to skip some cells in a row
if (idNum == prevIdNum){//only proceed if we've hit another line with the same ID number
dupCount = +1; //increment the dupcount value
mainSheet.getRange(cc2).setValue("dupCount"+dupCount); //dupcount check value
var rowIterStart = 5; //RowIterStart is used to add to rowiter, EG if your data is 20 columns wide, and you start transposing from column 4, then this will want to be about 17
var rowIter = 1;
for (j in row){
if (jCount >= 2){ //the integer here is the column where it will begin to transpose data
mainSheet.getRange(dumpRow-2,(rowIterStart*dupCount)+(rowIter)).setValue(row[j]); //startRow+(iter-dupCount)
mainSheet.getRange(cc1).setValue("dumprow"+dumpRow);
}
rowIter+=1;
jCount +=1;
}
}
else{
var dupCount = 1;
dumpRow +=1;
}
prevIdNum = (row[0]); //sets the most recently processed rows ID number
}
iter +=1;
}
}
Hopefully someone else who wants to do a similar thing can make use of this too.
I have equal strings coming up as not equal in a google apps script function and can't figure out why. Other answers to this question on the forum are in other languages I don't understand yet. Here's the code:
function testForMatch() {
var ss = SpreadsheetApp.getActive();
var masterSheet = ss.getSheetByName('Leaderboard');
var masterLength = masterSheet.getMaxRows() - 3;
var masterData = masterSheet.getRange(4, 2, masterLength).getValues(); //gets all the student names from the leaderboard
var titleSheet = ss.getSheetByName('Title - Standard scores');
var titleLength = titleSheet.getMaxRows() -3;
var titleData = titleSheet.getRange(4, 2, titleLength).getValues(); //gets all the student names from the standard score sheet
for ( i = 0; i < masterLength; i ++) {
if(masterData[i] != titleData[i]) {
var row = i + 1;
var error = "Uh oh! It looks like " + masterData[i] + " and " + titleData[i] + " do not match in row " + row;
var ui = SpreadsheetApp.getUi();
var response = ui.alert(error);
}
}
}
even if I just type simple strings into the matching cells it tells me they don't match. Likewise for numbers.
Only thing I can think is that my script isn't actually comparing the values. If that's true, how do I get it to do that?
Thanks for helping!
if(masterData[i][0] != titleData[i][0]) {
...would compare the cell values