Attach multiple files - Google Sheets/Gmail/Drive - javascript

I currently have a script to create draft emails and also attach a file to them. Currently, the script does not work for multiple file attachments and also does not work if there are no attachments.
Any tips on how to get (1) and (2) [below]
Updated for more info:
Main objective is to add a multiple file names in Col[3] and attaching the files to the draft using the files names provided in Col[3]. Example: if I type the following into Col[3]: "test.pdf, test2.pdf"
it will attach the files "test.pdf" & "test2.pdf" for that particular draft.
At the moment, the script does attach a single file to a draft, but if I enter multiple file names separated by comas it does not create a draft at all.
I've attach a screenshot of how the spreadsheet looks like below when I run the script (I currently cannot embed pictures):
https://imgur.com/a/sVoHO3Q
When I run the script using the info from the screenshot, the 2nd row does not create a draft at all, the 3rd row creates a draft with the attachment and the 4th row does not create a draft at all.
Would it be possible to create a draft and attach multiple files using filenames separated by comas and also create drafts when some cells in Col[3] is empty?
TLDR:
How to attach multiple files to individual drafts
How to create a draft without any attachments
function SaveDrafts() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var rows = 1000;
var dataRange = sheet.getRange(startRow, 1, rows, 5);
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0];
var subject = row[1];
var message = row[2];
var pdfName = row[3];
var cc = row[4];
var list = DriveApp.getFilesByName(pdfName);
if (list.hasNext()) {
var File = list.next();
GmailApp.createDraft(emailAddress, subject, message, {
cc: cc,
attachments: [File]
});
}
}
}

Update:
First off, I performed a split() on the values of the pdfName cell so I could search for and add one file at a time.
The draft creation would only run if there were files because it was inside the list if statement, no files meant it would not run.
I pulled it out of that and created an interator for list and a files array that can be given to the createDraft() function. The iterator allows multiple Files to be added to the files array if the DriveApp.getFilesByName(pdfName) returns multiple Files.
function SaveDrafts() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var rows = 1000;
var dataRange = sheet.getRange(startRow, 1, rows, 5);
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0];
var subject = row[1];
var message = row[2];
var pdfName = row[3].split(','); //split the values taken from cell into array
var cc = row[4];
var files = []; //initialize files as empty array.
for(var j in pdfName){ //run through cell values and perform search
var results = DriveApp.getFilesByName(pdfName[j]); //Perform the search,results is a FileIterator
while(results.hasNext()) { //Interate through files found and add to attachment results
files.push(results.next()); //Add files to array
}
}
GmailApp.createDraft(emailAddress, subject, message, {
cc: cc,
attachments: files //the attachments option takes our files array
});
}
}

Related

Define multiple get Ranges and Sheet Names with javascript

I am using below script to pull date from all spreadsheets in a folder. It is strong and working.
now i would like to expand this script to define multiple different sheet names instead of one. that is because we have the same file in 3 different langueses, now when it is running and there is a file from an other langues it cannot find the sheet(name) so the script gives the error that GetRange is null. The data is the same, So Ill go through the files looking for
The sheets: "Gegevenskaart" || "DataCard" || "Datenkarte"
The Same for Range. in this specified Sheet I have 6 different ranges. they are all the same size and all have the same kind of data but there are rows that I dont want to use. (20 rows data, 25 empty rows in between)
The ranges to get data from are : [A15:BC34 ,A63:BC82 ,A11:BC130, A159:BC178 ,A207:BC226 ,A255:BC274, A300:3BC319]
(also these data rows have a string in colomn A while the other rows dont have a string)
function CombineDataToMasterFile() {
var folder = DriveApp.getFolderById("1234567890234567891234567");
var filesIterator = folder.getFiles();
var file;
var fileType;
var ssID;
var combinedData = [];
var data;
while(filesIterator.hasNext()){
file = filesIterator.next();
fileType = file.getMimeType();
if(fileType === "application/vnd.google-apps.spreadsheet"){
ssID = file.getId();
data = getDataFromSpreadsheet(ssID);
data = data.map(function(r){return r.concat([file.getName()]); });
combinedData = combinedData.concat(data);
}
}
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Datatest");
ws.getRange("A2:BC").clearContent();
ws.getRange(2, 1, combinedData.length, combinedData[0].length).setValues(combinedData);
}
function getDataFromSpreadsheet(ssID){
var ss = SpreadsheetApp.openById(ssID);
var ws = ss.getSheetByName("Blad1");
var data = ws.getRange("A15:BC34").getValues();
return data;

"The JavaScript runtime exited unexpectedly" error when parsing through Google Sheets

I have a function getFnF() that iterates through a Google Drive folder and all of it's subfolders. When getFnF() encounters a Google Sheets file, I have the script parse through that Google Sheets file and extract out any URL Links it finds using my function getLinksFromSheet(). The functions both work, but after about 10 minutes of iterating through the Drive folder and calling getLinksFromSheet() on the Google Sheets files encountered, I get a The JavaScript runtime exited unexpectedly. error. Does anybody have any ideas what would be causing this error? The Google Drive folder is quite large (~500 files total in the subfolders, ~75 of which are Google Sheets). Code below:
function getFnF(folder) {
var folder= folder || DriveApp.getFolderById("0AFZNRhJpE8LKUk9PVA"); //hard goded DEP-Gotham folder
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var files=folder.getFiles();
while(files.hasNext()) {
var file=files.next();
var firg=sh.getRange(sh.getLastRow() + 1,level + 1);
firg.setValue(Utilities.formatString('File: %s', file.getName()));
Logger.log(file.getName())
//if (file.getMimeType() == 'application/vnd.google-apps.document') {getAllLinks(file.getId(), false);};
//if (file.getMimeType() == 'application/vnd.google-apps.presentation') {getLinksFromSlides(file.getId());};
if (file.getMimeType() == 'application/vnd.google-apps.spreadsheet') {getLinksFromSheet(file.getId());};
}
var subfolders=folder.getFolders()
while(subfolders.hasNext()) {
var subfolder=subfolders.next();
var forg=sh.getRange(sh.getLastRow() + 1,level + 1);
forg.setValue(Utilities.formatString('Fldr: %s', subfolder.getName()));
level++;
getFnF(subfolder);
}
level--;
}
function getLinksFromSheet(sheetId){
var ss = SpreadsheetApp.openById(sheetId);
var sheets = ss.getSheets();
var parentDocName = ss.getName();
var destSs=SpreadsheetApp.getActive();
var destSh=destSs.getSheetByName('Extracted Links');
sheets.forEach(sheet => {
var rangeData = sheet.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, lastColumn);
//var rangeValues = searchRange.getValues();
var rangeValues = searchRange.getRichTextValues();
for (var i = 0; i < lastRow; i++){
for (var j = 0; j < lastColumn; j++){
const runs = rangeValues[i][j].getRuns();
for (const v of runs) {
var nextLink = v.getLinkUrl();
if (nextLink != null) {
var row = destSh.getLastRow() + 1;
var r1=destSh.getRange(row, 1);
r1.setValue(parentDocName);
var r2=destSh.getRange(row, 2);
r2.setValue(nextLink);
};
}
}
}
});
Issue stems most likely from exceeding the limitation of runtime execution.
You can use getFilesByType to further whittle down the list and save time. Also did modify a bit in your code but should be able to do the same thing. They should have comments above them. Kindly check.
Usage:
function getFnF(folder) {
var folder = folder || DriveApp.getFolderById("0AFZNRhJpE8LKUk9PVA"); //hard goded DEP-Gotham folder
var ss = SpreadsheetApp.getActive();
var sh = ss.getSheetByName('Sheet1');
// limit files to only google sheets
var files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
// assign getLastRow to lessen method calls
var lastRow = sh.getLastRow();
// initialize level value
var level = 1;
while (files.hasNext()) {
var file = files.next();
// I can use appendRow here, but I did't since column has a variable
// and you might change it. Feel free to update if necessary
var firg = sh.getRange(lastRow + 1, level + 1);
firg.setValue(Utilities.formatString('File: %s', file.getName()));
getLinksFromSheet(file.getId());
// iterate lastRow
lastRow++;
}
var subfolders = folder.getFolders()
while (subfolders.hasNext()) {
var subfolder = subfolders.next();
// I can use appendRow here, but I did't since column has a variable
// and you might change it. Feel free to update if necessary
var forg = sh.getRange(lastRow + 1, level + 1);
forg.setValue(Utilities.formatString('Fldr: %s', subfolder.getName()));
level++;
getFnF(subfolder);
// iterate lastRow
lastRow++;
}
// not sure what this does but you can freely remove this if not being used
level--;
}
function getLinksFromSheet(sheetId) {
var ss = SpreadsheetApp.openById(sheetId);
var sheets = ss.getSheets();
var parentDocName = ss.getName();
var destSs = SpreadsheetApp.getActive();
var destSh = destSs.getSheetByName('Extracted Links');
sheets.forEach(sheet => {
// getDataRange already gets all the data
var rangeData = sheet.getDataRange();
// Flatten 2d array
var rangeValues = rangeData.getRichTextValues().flat();
rangeValues.forEach(v => {
var link = v.getLinkUrl();
if(link)
// Use appendRow instead. Adjust array if needed to be in a different column.
destSh.appendRow([parentDocName, link]);
});
});
}
Run time difference:
The 6-second runs is the optimized one above while the 7-second runs is your code.
Test Conditions:
Two sheet files in parent folder, one sheet file in 1 sub-folder:
Each spreadsheet has 2 sheets, each sheet has 1 link.
Parent folder has one non-sheet file.
Note:
Given the significant runtime difference on a lesser number of files, certainly this will have greater impact on a huge number of files.
If you want to include other type of files, then you need to create a separate loop to process each filetype if you have different type of process for each.
You can also join 2 getFilesByType outputs into 1 array but getting links from different file types might vary so separate loop will be much safer.
To get around the time problems you are having, I'd suggest one script to write all the spreadsheet ids and then another script to go down and process them (run getLinksFromSheet()) a line at a time and then mark each line as done so you can rerun it until you can finish.
Getrichtextvalue > getlinkURL is just slowwwwwwwww and you can't get around that.
I think this could also be a memory size, or too much data in a single variable type of issue. I'm getting the same error after seconds of starting a debugging session, but no indication of what is causing it.

How to loop through event IDs in Google Sheets to retrieve attachment Ids?

Using the following answer, I have discovered how to retrieve a Google Calendar attachment ID using an event ID with Google Apps Script and the Calendar API.
I have a list of calendar event IDs in a column on a spreadsheet that I'm looking to loop through and return the event attachment ID in the adjacent column (and skip over any events that do not have attachments). Could anyone please provide assistance?
Code that got me the code to retrieve 1 event attachment ID at a time:
You want to retrieve file IDs from an event. If my understanding is correct, how about this sample script?
var inStorePartiesCalendarID = "### calendar ID ###";
var eventId = "### Event ID ###";
var res = Calendar.Events.get(inStorePartiesCalendarID, eventId, {fields: "attachments/fileId"});
var fileIds = res.attachments.map(function(e){return e.fileId});
Logger.log(fileIds)
The question that got me the code to retrieve 1 event attachment ID at a time:
How to get Google calendar event attachment using Apps Script
The OP's starting point was code developed by #Tanaike for How to get Google calendar event attachment using Apps Script. The resulting code shown here is an enhancement of that code/logic to enable:
-looping through a range of eventID on a sheet,
-counting the number (if any) of fileIDs attached to that event,
-retrieving each of the fileIDs,
-updating the sheet for the number of files, and the respective file IDs.
It's possible to attach up to 25 attachments to an event. This code is dynamic; it will report and display as many attachments as many exist for any given eventID.
Note: the eventID is the id field NOT the iCalUID.
Also, the code relies on having enabled Calendar API under "Advanced Google Services" (Script Editor Menu: Resources> Advanced Google Services) as well as on the "Google Calendar API" on the "Google Cloud Platform API Dashboard."
I've left a number of Logger statements in the code should the OP wish/need to identify key values at various stages of the function.
function so53877465() {
// setup the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("testsheet");
// get the number of EventIDs in Column A
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row on A = " + Alast);// DEBUG
//Loop though each event ID
for (var r = 1; r < Alast; r++) {
// Logger.log("DEBUG: Aval row " + r + " = " + Avals[r][0]);//DEBUG
// substitute your own Calendar ID
var myCalendarID = "<<inset your Calendar ID>>";
// get the eventID
var eventId = Avals[r][0];
// get the Even info
var response = Calendar.Events.get(myCalendarID, eventId);
// just focus on the attachments
var events = response.attachments;
// setup some variables
var filecount = 0; //count the number of files in an event
var filearray = []; // create an array for the file could and fileIDs
// ccount the number of attachments for this eventID
for (key in events) {
filecount++;
}
//Logger.log("DEBUG: For row = " + r + ", the file count is " + filecount);//DEBUG
// push the filecount onto the empty array
filearray.push(filecount);
// loop through the fileIDs
for (i = 0; i < filecount; i++) {
// Logger.log("DEBUG: i: " + i);// DEBUG
// get the attachments
var event = events[i];
//get the file iD
var myfileid = event.fileId;
// Logger.log("DEBUG: The file id: " + myfileid);//DEBUG
// push the fileid onto the array
filearray.push(myfileid);
}
// adjust the array for 2d
var values = [
filearray
];
// define the target range
var range = sheet.getRange((r + 1), 2, 1, (filecount + 1)); //(row, column, numRows, numColumns)
// Logger.log("DEBUG: the target range is " + range.getA1Notation());//DEBUG
// update the target range with the array values.
range.setValues(values);
}
}
BEFORE
AFTER

Google script trigger to run only on Monday/Wednesday/Friday

I'm running a SendEmail script with a 3 triggers to be sent out on Mondays, Wednesdays and Fridays.
I have 10 sheets on the spreadsheet (each one contains an SentEmail script and each needs to be sent out on those days but I have only 20 trigger limitation)
This is the code:
function sendEmail() {
var s = SpreadsheetApp.getActive().getSheetByName('BCX');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getActiveSheet().getDataRange();
var range = s.getRange('B5:Q20');
var row = ss.getSheetByName('BCX').getRange("J1").getValue();
var to = "info#google.com";
var body = '';
var htmlTable = SheetConverter2.convertRange2html(range);
var body = "Hi Team!"
+ htmlTable
+ "<br/><br/><b><i>**This is an automated email**</i></b><br/><br/>Any question please let me know.<br/><br/>Regards,<br/><br/>";
var subject = "Google | Report " + row;
MailApp.sendEmail(to, subject, body, {htmlBody: body});
};
But if I use something like the following script it will create 3 triggers each week until it reaches 20 triggers (trigger limit).
function createTriggers() {
var days = [ScriptApp.WeekDay.MONDAY,
ScriptApp.WeekDay.WEDNESDAY,
ScriptApp.WeekDay.FRIDAY];
for (var i=0; i<days.length; i++) {
ScriptApp.newTrigger("sendEmail")
.timeBased().onWeekDay(days[i])
.atHour(7).create();
}
};
One solution to this question would be to combine the various scripts into a single script that can be triggered to run on Monday, Wednesday and Friday.
Within the script, the sequence of processing would be:
1) loop through the spreadsheets in a given folder/sub-folders of Google Drive. - this provides the unique spreadsheet ID.
2) for each spreadsheet, get the ID and use the openById(ID) to open the spreadsheet.
3) get the sheets for the spreadsheet
4) for each sheet, use the original code to build and send an email.
5) rinse and repeat for the next sheet, and next spreadsheet.
The following untested code combines the search for every spreadsheet within a specific folder and sub-folders, opening the spreadsheet and getting the sheets, and then looping through each sheet. The questioner need only add the name of the Google Drive Folder to initiated the search, and put their own code in the two places indicated.
function 53383834() {
/* Adapted from Code written by #hubgit https://gist.github.com/hubgit/3755293
Updated since DocsList is deprecated https://ctrlq.org/code/19854-list-files-in-google-drive-folder
*/
// List all files and sub-folders in a single folder on Google Drive
// declare the folder name
var foldername = 'XXXXXXXXXXXXXXXXXX'; // enter the folder name
// declare this sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActivesheet();
// getFoldersByName = Gets a collection of all folders in the user's Drive that have the given name.
// folders is a "Folder Iterator" but there is only one unique folder name called, so it has only one value (next)
var folders = DriveApp.getFoldersByName(foldername);
var foldersnext = folders.next();
// list files in this folder
var myfiles = foldersnext.getFiles();
// spreadsheets have a unique MIME-Type = application/vnd.google-apps.spreadsheet
var searchTerm = 'spreadsheet';
// loop through files in this folder
while (myfiles.hasNext()) {
var myfile = myfiles.next();
var fname = myfile.getName();
var fid = myfile.getId();
// get the MIME-Type and test whether the file is a spreadsheet
var ftype = myfile.getMimeType();
var indexOfFirst = ftype.indexOf(searchTerm);
if (indexOfFirst != -1) {
var ssid = fid;
// open the spreadsheet
var sso = SpreadsheetApp.openById(ssid);
// get the sheets
var sheets = sso.getSheets();
var sheetlen = sheets.length;
for (var i = 0; i < sheetlen; i++) {
// get the sheets, one by one
var thissheet = sso.getSheets()[i];
<<
insert questioners code here >>
}
}
}
// Now get the subfolder
// subfolders is a Folder Iterator
var subfolders = foldersnext.getFolders();
// now start a loop on the SubFolder list
while (subfolders.hasNext()) {
var subfolderdata = [];
var mysubfolders = subfolders.next();
var mysubfolder = mysubfolders.getName();
// Get the files
var mysubfiles = mysubfolders.getFiles();
// now start a loop on the files in the subfolder
while (mysubfiles.hasNext()) {
var smyfile = mysubfiles.next();
var sfname = smyfile.getName();
var sfid = smyfile.getId();
var sftype = smyfile.getMimeType();
var indexOffolder = sftype.indexOf(searchTerm);
if (indexOffolder != -1) {
var ssid = sfid;
// open the spreadsheet
var sso = SpreadsheetApp.openById(ssid);
// get the sheets
var sheets = sso.getSheets();
var sheetlen = sheets.length;
for (var i = 0; i < sheetlen; i++) {
// get the sheets, one by one
var thissheet = sso.getSheets()[i];
<<
insert questioners code here >>
}
}
}
}
}

Reading multiple spreadsheets in google app script

I'm trying to get all text from a specific cell of all spreadsheets in a folder. My current issue is I can only read them in as a file type which doesn't allow me to access the getRange() function.
Here's my code so far.
function createLog() {
var folder = DriveApp.getFolderById("id#");//not placing actual id for privacy
var contents = folder.getFiles();
var data; //array for relevant text
var file;
var d = new Date();
var log = SpreadsheetApp.create("Events log "+d.getMonth()+"/"+d.getDay());
while(contents.hasNext()) {
file = contents.next();
file.getRange("A6");//error is here because it is a file type not a spreadsheet
}
for(var i =0; i<contents.length;i++){
log.getRange(0,i).setValue(data[i]);
}
}
Once you have the list of files you need to open them with SpreadsheetApp. Then you can work on Spreadsheet using the Sheet and Range functions.
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheetByName('your sheet name');
var value = sheet.getRange("A6");
See:
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app
https://developers.google.com/apps-script/reference/drive/file#getId()
Cameron's answer is correct but I suggest to open spreadsheets by ID instead of names because in Google Drive many files can have the same name...
Below is a simplified demo code to test the idea and a few comments to highlight what needs to be adjusted
function createLog() {
var folder = DriveApp.getFolderById("0B3###############ZMDQ");//not placing actual id for privacy
var contents = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
var data; //array for relevant text
var fileID,file,sheet;
var data = [];
var d = new Date();
var log = SpreadsheetApp.create("Events log "+d.getMonth()+"/"+d.getDay());
while(contents.hasNext()) {
file = contents.next();
Logger.log('Sheet Name = '+file.getName());
fileID = file.getId();
sheet = SpreadsheetApp.openById(fileID).getSheets()[0];// this will get the first sheet in the spreadsheet, adapt to your needs
data.push([sheet.getRange("A6").getValue()]);// add a condition before this line to get only the data you want
}
log.getActiveSheet().getRange(1,1,data.length, data[0].length).setValues(data);
}

Categories