Google Apps Script - Gmail - Error Handling When No Message/Thread - google-apps-script

Hoping someone can help me out with handling 'errors' in my script when there is no message/thread found. I thought I had done that with the IF statement based on message.lengh but its not working and still errors when no email is found matching the inputted search criteria (e). It works perfectly every time a message IS found.
Many thanks.
Moz
function ImportCDWFiles(e){
var Threads = GmailApp.search(e,0,1);
var Message = Threads[0].getMessages()[0];
var Attachment = Message.getAttachments()[0];
if (Threads.length >0){
Logger.log(Threads.length);
Logger.log(Attachment.getName())
var SourceFile = Folder.createFile(Attachment.copyBlob())
var SourceID = SourceFile.getId();
var XlsxBlob = Attachment[0]; //
var ConvertedSpreadsheetId = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS}, SourceFile).id; //XlsxBlob
Logger.log("Converted File ID= "+ ConvertedSpreadsheetId)
var Sheet = SpreadsheetApp.openById(ConvertedSpreadsheetId).getSheets()[0]; // There is the data in 1st tab.
var Data = Sheet.getDataRange().getValues();
//var Data = Sheet.getDataRange().offset(headerRowNumber, 0, sheet.getLastRow() - headerRowNumber).getValues();
var sheet = SpreadsheetApp.openById('SheetIDHere').getSheetByName('SheetName');
var range = sheet.getRange(LastRow+1, 1, Data.length, Data[0].length);
range.setValues(Data);
Drive.Files.remove(ConvertedSpreadsheetId); // Remove the converted file.
}
}

Modification points:
At var Message = Threads[0].getMessages()[0], when the length of Threads is 0, an error occurs.
If the attachment files are not included, an error occurs.
In your script, Folder and LastRow are not declared.
When these points are reflected in your script, how about the following modification?
Modified script:
function ImportCDWFiles(e) {
var Folder = DriveApp.getFolderById("###"); // Please set your folder ID.
var Threads = GmailApp.search(e, 0, 1);
if (Threads.length == 0) return;
var Message = Threads[0].getMessages()[0];
var Attachment = Message.getAttachments();
if (Attachment.length == 0) return;
Attachment = Attachment[0];
Logger.log(Attachment.getName())
var SourceFile = Folder.createFile(Attachment.copyBlob());
var ConvertedSpreadsheetId = Drive.Files.insert({ mimeType: MimeType.GOOGLE_SHEETS }, SourceFile).id;
Logger.log("Converted File ID= " + ConvertedSpreadsheetId);
var Sheet = SpreadsheetApp.openById(ConvertedSpreadsheetId).getSheets()[0];
var LastRow = Sheet.getLastRow();
var Data = Sheet.getDataRange().getValues();
var sheet = SpreadsheetApp.openById('###').getSheetByName('###'); // Please set your values.
var range = sheet.getRange(LastRow + 1, 1, Data.length, Data[0].length);
range.setValues(Data);
Drive.Files.remove(ConvertedSpreadsheetId);
}
Note:
Unfortunately, from your question, I cannot know the mimeType of the attachment file. So, I couldn't implement checking the mimeType of the attachment file. If you can know, when this check is implemented, the error related to the mimeType might be able to be avoided.

Related

Coping CSV file from Gmail Attachment by name

I want to only copy the CSV attachment to google sheets from an email.
The email has multiple attachments, in different file formats.
The attachment I want to copy over is consistently the same name in csv format.
I am using this code but its not pulling the attachment I want to copy to google sheet.
Can someone help me with this.
function getCSV() {
var myLabel = GmailApp.getUserLabelByName("I-Data New"); // specify label in gmail
var threads = myLabel.getThreads(0,1);
var msgs = GmailApp.getMessagesForThreads(threads);
var attachments = msgs[0][0].getAttachments();
var csv = attachments[0].getDataAsString();
var data = Utilities.parseCsv(csv);
var a = data.length ;
var b = data[0].length;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.openById('12ApSC0WVn1sZxb9VF6_CZR9L5xuGY5WHMLyyGMc').getSheetByName('IData');
sheet.getRange('A2:AM6026').clearContent();
var range = sheet.getRange(2,1, data.length,data[0].length);
range.setValues(data);
}
Loop over the attachments and get the filename. Quick edit to your existing code:
function getCSV() {
var myLabel = GmailApp.getUserLabelByName("I-Data New"); // specify label in gmail
var threads = myLabel.getThreads(0, 1);
var msgs = GmailApp.getMessagesForThreads(threads);
var attachments = msgs[0][0].getAttachments();
attachments.forEach(att => {
const name = att.getName()
if (name == "myFileName.csv") {
var data = Utilities.parseCsv(att.getDataAsString());
var sheet = SpreadsheetApp.openById('12ApSC0WVn1sZxb9VF6_CZR9L5xuGY5WHMLyyGMc').getSheetByName('IData');
sheet.getRange('A2:AM6026').clearContent();
var range = sheet.getRange(2, 1, data.length, data[0].length);
range.setValues(data);
}
})
}

Update a cell and return PDF file of the sheet

I've this spreadsheet that is of 2 sheets, Data nd Report.
function doPost(e) {
var DATA_SHEET = "Data";
var REPORT_SHEET = "Report";
var FILE_Id = "1sXSWMQfPkkwDS4lsSpnV2y54lhKFpdeY8brqyGtuc_k";
// Prevent concurrent access overwritting data
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
// As we are passing JSON in the body, we need to unpairse it
var jsonString = e.postData.getDataAsString();
e.parameter = JSON.parse(jsonString);
try {
// next set where we write the data - you could write to multiple/alternate destinations
// var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var doc = SpreadsheetApp.openById(FILE_Id);
var sheet = doc.getSheetByName(DATA_SHEET);
var report = doc.getSheetByName(REPORT_SHEET);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var lastRow = sheet.getLastRow()
var nextRow = lastRow + 1; // get next row
var row = [];
if(lastRow < 10){
RefID = "PRF.00" + lastRow
} else {
if(lastRow < 100){
RefID = "PRF..0" + lastRow
} else {
RefID = "PRF.." + lastRow
}
}
// loop through the header columns
for (i in headers){
if (headers[i] == "Ref"){ // special case if you include a 'Timestamp' column
row.push(RefID);
} else { // else use header name to get data
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
}
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
/*********************************************/
// report.getRange("D4").setValue(RefID);
// Need to create the PDF here and send it back to the client
/*********************************************/
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
The Report sheet looks like this:
And the Data sheet looks like this:
What I'm trying to do is:
[Done] Receiving data from client and post in the Data sheet, with assigning RefID and Timestamp which is done correctly.
[Done] Update the data in the merged cells (D4:F4) at the Report sheet with the latest RefID that had been added, I worked it withreport.getRange("D4").setValue(RefID);
[No idea how to make it] Once step 2 is done, some vlookup statements are working at the Report sheet, I want to generate a PDF from this sheet and send it back to the client.
UPDATE
I tried this to get the PDF, but did not work, it created copy of the sheet, but failed in converting the vlookup merged cells into text, and failed to generate the pdf file:
function printPDF() {
var file = SpreadsheetApp.openById(FILE_Id);
var sourceSheet = file.getSheetByName(REPORT_SHEET);
var folder = DriveApp.getFolderById(FOLDER_Id);
//Copy whole spreadsheet
var destSpreadsheet = SpreadsheetApp.open(DriveApp.getFileById(FILE_Id).makeCopy("tmp_convert_to_pdf", folder))
//delete redundant sheets
var sheets = destSpreadsheet.getSheets();
for (i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() != REPORT_SHEET){
destSpreadsheet.deleteSheet(sheets[i]);
}
}
var destSheet = destSpreadsheet.getSheets()[0];
//replace cell values with text (to avoid broken references)
var sourceRange = sourceSheet.getRange(1,1,20,8);
var sourcevalues = sourceRange.getValues();
var destRange = destSheet.getRange(1, 1, 20, 8);
destRange.setValues(sourcevalues);
//save to pdf
var theBlob = destSpreadsheet.getBlob().getAs('application/pdf').setName(REPORT);
var newFile = folder.createFile(theBlob);
//Delete the temporary sheet
DriveApp.getFileById(destSpreadsheet.getId()).setTrashed(true);
}
Modification points:
If you want to make the additional script of printPDF() work correctly, please put SpreadsheetApp.flush() before the blob is retrieved from Spreadsheet.
In the case of the Google Docs (Document, Spreadsheet and Slides), when the blob is retrieved from the file, the format is automatically changed to PDF format. It seems that this is the current specification.
When above points are reflected to your script, it becomes as follows.
Modified script:
From:
var theBlob = destSpreadsheet.getBlob().getAs('application/pdf').setName(REPORT);
var newFile = folder.createFile(theBlob);
To:
SpreadsheetApp.flush(); // Added
var theBlob = destSpreadsheet.getBlob().setName(REPORT); // Modified. This is the same with destSpreadsheet.getBlob().getAs('application/pdf').setName(REPORT)
var newFile = folder.createFile(theBlob);
Then your full code be as:
function printPDF(RefID) {
var file = SpreadsheetApp.openById(FILE_Id);
var sourceSheet = file.getSheetByName(REPORT_SHEET);
sourceSheet.getRange("D4").setValue(RefID);
var folder = DriveApp.getFolderById(FOLDER_Id);
//Copy whole spreadsheet
var destSpreadsheet = SpreadsheetApp.open(DriveApp.getFileById(FILE_Id).makeCopy("tmp_convert_to_pdf", folder))
// Copy sheet data into values to avoid reference issues
var destSheet = destSpreadsheet.getSheetByName(REPORT_SHEET);
var sourceRange = sourceSheet.getRange(1,1,destSheet.getMaxRows(), destSheet.getMaxColumns());
var sourcevalues = sourceRange.getValues();
var destRange = destSheet.getRange(1, 1, destSheet.getMaxRows(), destSheet.getMaxColumns());
destRange.setValues(sourcevalues);
//delete redundant sheets
var sheets = destSpreadsheet.getSheets();
for (i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() != REPORT_SHEET){
destSpreadsheet.deleteSheet(sheets[i]);
}
}
//save to pdf
SpreadsheetApp.flush(); // Added
var theBlob = destSpreadsheet.getBlob().setName(REPORT); // Modified. This is the same with
// destSpreadsheet.getBlob().getAs('application/pdf').setName(REPORT)
var newFile = folder.createFile(theBlob);
//Delete the temporary sheet
DriveApp.getFileById(destSpreadsheet.getId()).setTrashed(true);
}
Reference:
flush()
getBlob().getAs('application/pdf') will not work. You need to use a Google API for converting a sheet to PDF.
You need to do something along the following lines:
const url = `https://docs.google.com/spreadsheets/d/${FILE_Id}/export?exportFormat=pdf&format=pdf`;
const options = {
headers: {
Authorization: `Bearer ${ScriptApp.getOAuthToken()}`
}
};
const blob = UrlFetchApp.fetch(url, options).setName(`${REPORT}`);

Google Apps Script Drive.file.insert question

I wrote a simple Google Apps Script to select an Excel file, convert it to Google Sheets via Blob, and then copy some cells from that to another Google Sheet. It doesn't work. 3 Questions:
Does Drive.Files.insert return a file object? I get an error saying
source.getSheetByName() is not a function right after setting source
to a Drive.Files.insert. (See below)
Whenever I try to put breakpoints in this section of code, they never break there, even though the code runs. How do I use the debugger/breakpoints here?
How would I specify a different location for the created Google Sheet ?
Thanks!
function convertExceltoGoogleSpreadsheet(e) {
var blob = Utilities.newBlob(e.bytes, e.mimeType, e.filename);
var excelFile = DriveApp.createFile(blob);
var fileName = e.filename;
var excelFile = DriveApp.getFilesByName(fileName).next();
var fileId = excelFile.getId();
var folderId = Drive.Files.get(fileId).parents[0].id;
var blob = excelFile.getBlob();
var resource = {
title: excelFile.getName(),
mimeType: MimeType.GOOGLE_SHEETS,
parents: [{id: folderId}],
};
var source = Drive.Files.insert(resource, blob);
var sourceSheet = source.getSheetByName('Sheet1');
var target = SpreadsheetApp.getActiveSpreadsheet();
var targetSheet = target.getSheetByName("Sheet10");
Logger.log("The values are " + sourceSheet.getRange(1, 1, 10, 1).getValues());
Logger.log("The values are " + targetSheet.getRange(1, 1, 10, 1).getValues());
var targetrange = targetSheet.getRange(1, 1, 3, 5); //sourceSheet.getLastColumn());
var rangeValues = sourceSheet.getRange(1,1, 3,5).getValues(); //sourceSheet.getLastRow()-3, sourceSheet.getLastColumn()).getValues();
targetrange.setValues(rangeValues);
}

Google Sheets Email - Vlookup showing REF value in PDF

Hoping you could help me with this, I have created a system that automatically sends the current sheet as an attachment in an email with body and subject text.
I have one problem that I cant seem to solve; the Vlookup used in the sheet generates either a blank column or a REF! on the emailed PDF. And any info directing to another value from a different sheet (eg ='Sheet2'!D2)
Here is the code used on the sheet triggering the PDF attachment :
function mailCCB() {
var originalSpreadsheet = SpreadsheetApp.getActive(); //get active spreadsheet
var message = "Good Day." + "\n\n" + "Please find attached certificate"; //email body
var certNo = originalSpreadsheet.getRange("C7").getValue(); //certificate number
var cust = originalSpreadsheet.getRange("E10").getValues(); //customer name
var subject = "Certificate " + certNo + " | " + cust; //email subject
var emailTo = originalSpreadsheet.getRange("E13").getValue(); //email address
var sheets = originalSpreadsheet.getSheets(); //get all sheets
var sheetName = originalSpreadsheet.getActiveSheet().getName(); //get current sheet name
var sourceSheet = originalSpreadsheet.getSheetByName(sheetName); //source sheet
// Get folder containing spreadsheet to save pdf in.
var parents = DriveApp.getFileById(originalSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
SpreadsheetApp.getActiveSpreadsheet().toast("Working on it.. 50%", "Busy",12);
// Copy whole spreadsheet.
var destSpreadsheet = SpreadsheetApp.open(DriveApp.getFileById(originalSpreadsheet.getId()).makeCopy("tmp_convert_to_pdf", folder))
// Delete redundant sheets.
var sheets = destSpreadsheet.getSheets();
for (i = 0; i < sheets.length; i++) {
if (sheets[i].getSheetName() != sheetName){
destSpreadsheet.deleteSheet(sheets[i]);
}
}
var destSheet = destSpreadsheet.getSheets()[0];
var sourceRange = sourceSheet.getRange(1,1,sourceSheet.getMaxRows(),sourceSheet.getMaxColumns());
var sourcevalues = sourceRange.getValues();
SpreadsheetApp.getActiveSpreadsheet().toast("Working on it.. 60%", "Busy",12);
var destRange = destSheet.getRange(1, 1, destSheet.getMaxRows(), destSheet.getMaxColumns());
destRange.setValues(sourcevalues);
var theBlob = destSpreadsheet.getBlob().getAs('application/pdf').setName(subject);
var newFile = folder.createFile(theBlob);
//*****************************
SpreadsheetApp.getActiveSpreadsheet().toast("Working on it.. 75%", "Busy",12);
// Create a new Spreadsheet and copy the current sheet into it.
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet = originalSpreadsheet.getActiveSheet();
sheet.copyTo(newSpreadsheet);
var pdf = DriveApp.getFileById(originalSpreadsheet.getId()).getAs('application/pdf').getBytes();
var attach = {fileName:'Klerkscale Certificate',content:pdf, mimeType:'application/pdf'};
MailApp.sendEmail(emailTo, subject, message, {attachments:[theBlob]});
DriveApp.getFileById(newSpreadsheet.getId()).setTrashed(true);
originalSpreadsheet.toast("Certificate will be sent shortly to " + emailTo,"Success",20);
}
This is the cropped received email result :
Ok I saw your problem, you're deleting all the sheets which are being referenced by the active sheet in destSpreadsheet thus they will not be available and the results will be #REF! for each cell referencing those deleted sheets. To solve that problem you could use the getDisplayValues() method, which retrieves the current displayed value of the cell instead of getValues() which retrieves the function(VLOOKUP in this case) set in the cell. Change this line:
var sourcevalues = sourceRange.getValues();
For this:
var sourcevalues = sourceRange.getDisplayValues();
Also, don't delete the other sheets which are being referenced until you have copyied the values to destRange in this line:
destRange.setValues(sourcevalues);

Determining the extension of an e-mail attachment automatically (whether it's XLSX or CSV) and applying the right parsing technique

I have two Google Script codes that automate the process of importing email attachments from Gmail (it's automatically labeled) into Google Sheets. So far I managed to make it work for CSV and XLSX files separately. Please see both codes below. My question is: how do I combine those two codes into one, so that it could determine the file extension automatically and apply the right parsing technique when copying the contents of the files in the respective Google Sheet. Thank you!
For XLSX files:
function getXLSX() {
var thread = GmailApp.getUserLabelByName("Invoicing").getThreads(0,1);
/* var message = thread[0].getMessages()[0]; // Get first message */
var messages = thread[0].getMessages();
var len = messages.length;
var message=messages[len-1] //get last message
var attachments = message.getAttachments(); // Get attachment of first message
var xlsxBlob = attachments[0]; // Is supposes that attachments[0] is the blob of xlsx file.
var convertedSpreadsheetId = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS}, xlsxBlob).id;
var sheet = SpreadsheetApp.openById(convertedSpreadsheetId).getSheets()[0]; // There is the data in 1st tab.
var data = sheet.getDataRange().getValues();
Drive.Files.remove(convertedSpreadsheetId); // Remove the converted file.
//var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet_new = SpreadsheetApp.openById("1jRh8sj_jAaZKH4xbdpI9Q4to1tKuGWTTO2MzHlU").getSheetByName("Data drop");
/*for (var i = 0; i > sheet_new.length; i++) {
var range = sheet_new[i].getRange("A:I");
range.clearContents();
}*/
sheet_new.clearContents();
var range = sheet_new.getRange(1,1, data.length,data[0].length);
range.setValues(data);
}
For CSV files:
function getCSV() {
var thread = GmailApp.getUserLabelByName("Invoicing").getThreads(0,1);
/* var message = thread[0].getMessages()[0]; // Get first message */
var messages = thread[0].getMessages();
var len = messages.length;
var message=messages[len-1] //get last message
var attachments = message.getAttachments(); // Get attachment of first message
var csv = attachments[0].getDataAsString();
var data = Utilities.parseCsv(csv);
var a = data.length ;
var b = data[0].length;
//var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.openById("1jRh8sj_jAaZKH4xbdpI9Q4to1tKuGWTTO2MzHlU").getSheetByName("Data drop");
sheet.getRange("A:J").clear();
var range_final = sheet.getRange(1,1, data.length,data[0].length);
range_final .setValues(data);
}
How about this modification? In this modification, the mimeType is compared and each script is run. Please think of this as just one of several answers.
Modified script:
function myFunction() {
var thread = GmailApp.getUserLabelByName("Invoicing").getThreads(0,1);
var messages = thread[0].getMessages();
var len = messages.length;
var message = messages[len-1]; //get last message
var attachments = message.getAttachments(); // Get attachment of first message
var blob = attachments[0]; // Is supposes that attachments[0] is the blob of xlsx file.
blob.setContentTypeFromExtension();
if (blob.getContentType() == MimeType.MICROSOFT_EXCEL) {
// Process for XLSX
var convertedSpreadsheetId = Drive.Files.insert({mimeType: MimeType.GOOGLE_SHEETS}, blob).id;
var sheet = SpreadsheetApp.openById(convertedSpreadsheetId).getSheets()[0]; // There is the data in 1st tab.
var data = sheet.getDataRange().getValues();
Drive.Files.remove(convertedSpreadsheetId); // Remove the converted file.
var sheet_new = SpreadsheetApp.openById("1jRh8sj_jAaZKH4xbdpI9Q4to1tKuGWTTO2MzHlU").getSheetByName("Data drop");
sheet_new.clearContents();
var range = sheet_new.getRange(1,1, data.length,data[0].length);
range.setValues(data);
} else if (blob.getContentType() == MimeType.CSV) {
// Process for CSV
var csv = blob.getDataAsString();
var data = Utilities.parseCsv(csv);
var a = data.length ;
var b = data[0].length;
var sheet = SpreadsheetApp.openById("1jRh8sj_jAaZKH4xbdpI9Q4to1tKuGWTTO2MzHlU").getSheetByName("Data drop");
sheet.getRange("A:J").clear();
var range_final = sheet.getRange(1,1, data.length,data[0].length);
range_final.setValues(data);
}
}
Note:
This modified script supposes as follows.
The index of 0 of attachment files is the file you need.
The index of 0 of attachment files is XLSX file or CSV file.
Filenames of XLSX and CSV files have the extension of .xlsx and .csv, respectively.
Reference:
setContentTypeFromExtension()
If I misunderstood your question and this was not the result you want, I apologize.