Save Gmail attachments on Google Drive - javascript

I would like to save gmail attachments(.pdf files) from a specific email in a specific Google Drive folder. I also need to rename file with a string composed by some string of the email.
I have developed a simple script using Google Apps Script with some functions.
This is the main function I have wrote:
function GmailToDrive() {
var query = '';
query = 'in:inbox from:noreply#agyo.io has:nouserlabels ';
var threads = GmailApp.search(query);
var label = getGmailLabel_(labelName);
var parentFolder;
if (threads.length > 0) {
parentFolder = getFolder_(folderName);
}
var root = DriveApp.getRootFolder();
for (var i in threads) {
var mesgs = threads[i].getMessages();
for (var j in mesgs) {
//get attachments
var attachments = mesgs[j].getAttachments();
var message_body = mesgs[j].getBody();
for (var k in attachments) {
var attachment = attachments[k];
var isDefinedType = checkIfDefinedType_(attachment);
if (!isDefinedType) continue;
var attachmentBlob = attachment.copyBlob();
var file = DriveApp.createFile(attachmentBlob);
file.setName(renameFile_(attachment, message_body))
parentFolder.addFile(file);
root.removeFile(file);
}
}
threads[i].addLabel(label);
}
}
The checkIfDefinedType_(attachment) function checks if the attachments is a .pdf file and the renameFile_(attachment, message_body) rename the attachment extracting some string from the email.
The script seems to be correctly developed but sometimes I have two or more same attachments saved in my google drive folder.

Stefano, I had the same issue, if this is the same code as adapted from here.
I removed the line for (var i in fileTypesToExtract) { which was causing duplicates for me. It was running the query for each of the file types.

// `has:pdf` searches for messages with PDF attachments
var query = 'has:pdf in:inbox from:noreply#agyo.io has:nouserlabels ';
var results = Gmail.Users.Messages.list(userId, {q: query});
results.messages.forEach(function (m) {
var msg = GmailApp.getMessageById(m.id);
msg.getAttachments().forEach(function (a) {
var fileName = a.getName();
fileName = saveAttachmentToFolder(folder, a, fileName, msg.getDate(), input.tz);
});
});
function saveAttachmentToFolder(folder, attachment, fileName, date, timezone) {
if (timezone) {
fileName = standardizeName(attachment, date, timezone);
}
Logger.log(fileName);
folder.createFile(attachment.copyBlob()).setName(fileName);
}
The code snippet above is based on a Gmail add-on that I created, specifically for saving attachments to labeled folders in Drive: https://github.com/ellaqezi/archiveByLabel/blob/main/Code.gs#L24
In the label field, you can define nested directories to create in Drive e.g. foo/bar.
In the query field, you can copy the parameters as you would use them in Gmail's search bar.

Related

Adding image from spreadsheet into an e-mail with Apps Script

I'm creating an automatic e-mail which includes many data that change every week.
I'm new in Apps Script and I would like to add an image at the end of the e-mail.
Here the code :
// Drive where is stored the image
const folder = DriveApp.getFolderById("1XXXXXXXXX");
// Retrieve ID file where is stored the image
const file = folder.getFilesByName("file")
const fileIDs = [];
while (file.hasNext()) {
var files = file.next();
fileIDs.push(files.getId());
}
var ssFile = SpreadsheetApp.openById(fileIDs[0]);
SpreadsheetApp.setActiveSpreadsheet(ssFile);
//Spreadsheet
var mail = ssFile.getSheetByName("Mail");
//Retrieve image from the spreadsheet
var retrieveImage = mail.getImages()[0];
var arrayImage = new Array();
var image = {};
arrayImage[0] = retrieveImage.getAs('image/png')
image["image"+0] = arrayImage[0];
//Fonction to send mail
function sendEmailS(){
var message = "Test";
message += "<img src='cid:image" +0+ "'> <br>";
GmailApp.sendEmail("email#email.com", "subject", "",
{
htmlBody: message,
inlineImages: image
}
);
}
I've got the error that getAs is not a function. Could help me or give me any clue to finish my script ?
Issue and workaround:
From your showing script and the error of I've got the error that getAs is not a function., I thought that the reason for your issue is due to that the image cannot be retrieved as a blob from Spreadsheet.
In the current stage, unfortunately, there is no method for directly retrieving the image on Spreadsheet as a blob. So, in this answer, I would like to propose a workaround. In this workaround, a Google Apps Script library is used. This library supports for the processes that the current Google services cannot directly achieve.
Usage:
1. Install Google Apps Script library.
Please install DocsServiceApp of Google Apps Script library. You can see how to install it at here.
2. Modified script.
When your script is modified using this library, it becomes as follows.
function sendEmailS() {
// Drive where is stored the image
const folder = DriveApp.getFolderById("1XXXXXXXXX");
// Retrieve ID file where is stored the image
const file = folder.getFilesByName("file")
const fileIDs = [];
while (file.hasNext()) {
var files = file.next();
fileIDs.push(files.getId());
}
var ssFile = SpreadsheetApp.openById(fileIDs[0]);
SpreadsheetApp.setActiveSpreadsheet(ssFile);
//Spreadsheet
var mail = ssFile.getSheetByName("Mail");
//Retrieve image from the spreadsheet
var retrieveImage = mail.getImages()[0];
var arrayImage = new Array();
var image = {};
const anchor = retrieveImage.getAnchorCell().getA1Notation();
const res = DocsServiceApp.openBySpreadsheetId(fileIDs[0]).getSheetByName("Mail").getImages();
const obj = res.find(({ range: { a1Notation } }) => a1Notation == anchor);
if (!obj) return;
arrayImage[0] = obj.image.blob;
image["image" + 0] = arrayImage[0];
//Fonction to send mail
var message = "Test";
message += "<img src='cid:image" + 0 + "'> <br>";
GmailApp.sendEmail("email#email.com", "subject", "",
{
htmlBody: message,
inlineImages: image
}
);
}
3. Testing.
When this script is run, an image of mail.getImages()[0] is retrieved as a blob. And, an email is sent using the retrieved image blob.
Reference:
DocsServiceApp of Google Apps Script library
Maybe some ideas here for you?....
...this gets image file from G.drive and emails it...
function emailImage(){
fileList = DriveApp.getFilesByName('imageNameInDrive.jpg');
while (fileList.hasNext()) { image = fileList.next().getId(); }
var insertImage = DriveApp.getFileById(image).getBlob();
var message = 'Test<br>';
message += '<img src="cid:insertImage" > <br>';
GmailApp.sendEmail("email#gmail.com", "subject", "",
{
htmlBody: message,
inlineImages: {
insertImage: insertImage
}
}
);
}
In addition to Tanaike's answer, which in my opinion would be a good workaround, there is an open Feature Request for converting Spreadsheet images to BlobSource.
Remember to hit the +1 button to tell Google that you are also interested.
Update OverGridImage to support BlobSource interface

Google Apps Script - batch download sheets as CSV file

This is a question that has been asked before but I'm struggling to adapt the answers to my needs.
references:
How to export to CSV from spreadsheet to drive or download folder
https://gist.github.com/mrkrndvs/a2c8ff518b16e9188338cb809e06ccf1
Initiate a download from google apps script
The structure seems to be:
create function in code.gs
create download.html
My workflow:
I have a bunch of files (20+) which I edit both manually and using a GAS.
To start with I've created a folder on Google Drive which I upload my CSV files to. I then run a standalone GAS to add a sum formula in column F to each file:
function addFormula() {
const folder = DriveApp.getFolderById('folderID');
const list = [];
const files = folder.getFiles();
while (files.hasNext()){
file = files.next();
let ssnameid = [];
ssnameid.push(file.getName(),file.getId());
list.push(ssnameid);
}
for (let g=0,len=list.length;g<len;g++) {
let id = list[g][1]; // list of Spreadsheet names[0] and ID numbers[1]
let csv = DriveApp.getFileById(id);
let docName = csv.getName();
let ss = SpreadsheetApp.openById(id);
let sheet = ss.getSheetByName(docName+'.csv');
let contents = sheet.getRange('A1:K100').getValues().filter(e => e[0]);
let rows = contents.length;
console.log(docName+' - number of rows: '+rows);
let cellF = 'F'+(rows+1);
let formulaF = '=SUM($F$2:$F$'+rows+')';
sheet.getRange(cellF).setValue(formulaF);
}
Then I go through each file, check if there are any other edits I need to make, and download as a CSV (File > Download > Comma Separated Values (.csv)). I was hoping to save time by also writing a function to download all the files as CSV.
So after making any manual edits, I then want to run a function in a standalone GAS to download all the files in the Google Drive folder as CSV files.
The answers I've found generally involve adding menu items and having pop-ups, and I don't know enough to make them suitable for a standalone GAS - I don't want any menu items or pop-ups, I just want to run a function which downloads a CSV.
For instance, how would I adapt this answer from Dr-Bracket?
Or this answer from soMarios, which works but only saves it to another folder in Google Drive, rather than downloading.
The reason I feel that having an HTML file work with a GS is that I've created a standalone function with this structure to send out emails. Using an HTML email template, I created a function in a standalone GAS to send out emails.
Is this the right approach for batch downloading files as CSV?
Thank you
Further references/clues:
https://developers.google.com/apps-script/guides/html/templates#code.gs https://developers.google.com/apps-script/guides/html/reference/run#index.html https://developers.google.com/apps-script/reference/drive/file#getDownloadUrl()
EDIT - My Solution
The workaround is to send all the files to a folder on Google Drive and then download the folder. So the benefit is only downloading one folder rather than downloading each file. Here's the code adapted from the soMarios answer linked to above:
function saveCSV() {
/** sourceFolder contains all the Google Sheets you want to save as CSV files */
const sourceFolder = DriveApp.getFolderById('folderID');
const list = [];
const files = sourceFolder.getFiles();
while (files.hasNext()){
file = files.next();
let ssnameid = [];
ssnameid.push(file.getName(),file.getId());
list.push(ssnameid);
}
console.log(list);
for (let g=0,len=list.length;g<len;g++) {
let id = list[g][1]; // list of Spreadsheet names[0] and ID numbers[1]
let csv = DriveApp.getFileById(id);
let docName = csv.getName();
let ss = SpreadsheetApp.openById(id);
let sheet = ss.getSheetByName(docName+'.csv');
/** save files as CSV to Google Drive folder */
let requestData = {"method": "GET", "headers":{"Authorization":"Bearer "+ScriptApp.getOAuthToken()}};
let sheetID = sheet.getSheetId().toString();
let url = "https://docs.google.com/spreadsheets/d/"+id+"/export?gid="+sheetID+"&format=csv"
let result = UrlFetchApp.fetch(url, requestData);
let resource = {
title: docName+'.csv',
mimeType: 'application/vnd.csv',
parents: [{ id: 'downloadFolderID' }]
}
Drive.Files.insert(resource,result)
}
}
Note that for this to work you need to add Drive API (Services > Add a Service > Drive API)
To download a sheet as csv whitout any further manipulation, try this auto-download script
gs
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('M E N U')
.addItem('auto download', 'autoDownload')
.addToUi();
}
function autoDownload() {
var html = HtmlService.createHtmlOutputFromFile('download');
SpreadsheetApp.getUi().showModalDialog(html, 'CSV download interface');
}
function saveAsCSV() {
var ssid = 'your spreadsheet id';
var folderID = 'temporary folder id'
var csv = "";
var ss = SpreadsheetApp.openById(ssid)
ss.getSheets()[0].getDataRange().getValues().forEach(function (r) {
csv += r.join(",") + "\n";
});
var url = DriveApp.getFolderById(folderID)
.createFile(ss.getName() + '.csv', csv, MimeType.CSV)
.getDownloadUrl()
.replace("?e=download&gd=true", "");
return url;
}
download.html
<!DOCTYPE html>
<html>
<body>
Auto Download CSV ... please wait
</body>
<script>
function executeDownload(url) {
window.location.href = url;
}
window.onload=function() {
google.script.run
.withSuccessHandler(executeDownload)
.saveAsCSV();
window.setTimeout(function(){google.script.host.close()},9000);
}
</script>
</html>
tested with chrome
you can modify ssid, temporary folder id and setTimeout parameter to optimize

CSV File Sender Information with App Script

I am trying to extract the details of Emails(Email Address,Subject,Date)from Gmail Inbox that contain CSV file in attachment. But I have managed to extract CSV file from certain email address. Is there any way that we can get the information of all emails that have CSV file in attachment? Any help would be appreciated.
function import() {
var threads = GmailApp.search('in:inbox from:"example#gmail.com"');
if (threads.length===0)
return;
var messages = threads[0].getMessages();
var message = messages[messages.length - 1];
var attachment = message.getAttachments()[0];
attachment.setContentType('text/csv');
// Is the attachment a CSV file
if (attachment.getContentType() === "text/csv") {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet3");
var csvData = Utilities.parseCsv(attachment.getDataAsString(), ",");
// Remember to clear the content of the sheet before importing new data
sheet.clearContents().clearFormats();
sheet.getRange(1, 1, csvData.length, csvData[0].length).setValues(csvData);
// GmailApp.moveMessageToTrash(message);
}
}
var messages = threads[0].getMessages(); indicates that you are running your code only for the first thread.
If you want to retrieve attachements from all threads, you need to iterate over your threads with a loop.
In order to receive the sender of a message you can use the getFrom() method.
The following sample retrieves the senders of all the last messages of your threads that have a CSV attachment. The senders are stored in an array:
function import() {
var threads = GmailApp.search('in:inbox from:"example#gmail.com"');
if (threads.length==0)
return;
var senders = [];
for( var i = 0; i < threads.length; i++){
var messages = threads[i].getMessages();
var message = messages[messages.length - 1];
var attachment = message.getAttachments()[0];
attachment.setContentType('text/csv');
// Is the attachment a CSV file
if (attachment.getContentType() == "text/csv") {
var sender = message.getFrom();
senders.push(sender);
// do whatever else you want to do
...
}
}
Logger.log(senders);
}
This sample will iterate through the last message of each thread. If
you want to iterate through all messages of all threads, you need to
implement a second, nested loop.

GAS - Change Folder Permissions

I've been working on a script that lists the name and folder ids of the all the folders in a specific folder in my drive. Once the list is created, I run the function to get the id and change the folder permissions to private (only specific people can view), remove editors who do not have the company domain and switches them to viewers, and then it should change the permissions for any files in the folder as well. The initial step of creating the ids works fine. Unfortunately, the updatePermissions() function only seems to infinitely loop through the first folder and I'm not sure what next steps to take to ensure that the script pulls the next folder id from the list in the spreadsheet. Any help would be greatly appreciated.
function listSchoolFolders(){
var folderId = 'enter folder id here';
var myspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var parentFolder = DriveApp.getFolderById(folderId);
var childFolders = parentFolder.getFolders();
// List folders inside the folder
while (childFolders.hasNext()) {
var childFolder = childFolders.next();
var data = [
childFolder.getName(),
childFolder.getId()
];
//Write
myspreadsheet.appendRow(data)
}
}
//------------------------- New Function -------------------------//
function updatePermissions() {
var myspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var schoolRows = myspreadsheet.getDataRange();
var schoolNumRows = schoolRows.getNumRows();
var schoolValues = schoolRows.getValues();
//Loop through List of Schools
var row_num = schoolValues[0][2];
while(row_num<schoolNumRows){
//Retrieve folder id and go to folder
var folderId = schoolValues[row_num][1];
var folderName = schoolValues[row_num][0];
Logger.log(folderName);
try {
var schoolFolder = DriveApp.getFolderById(folderId);
}
catch (err){
Logger.log(folderName + ": " + err.message);
row_num = row_num+1;
myspreadsheet.getRange(1,3).setValue(row_num);
continue;
};
};
//Loop through folders and set permissions
var childFolders = DriveApp.getFolderById(folderId).getFolders();
while (childFolders.hasNext()) {
var childFolder = childFolders.next();
var childFolderPermissions = childFolder.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
var files = DriveApp.getFolderById(folderId).getFiles();
while (files.hasNext()) {
Logger.log(files.next().getName());
var fileFolderPermissions = files.next().setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
//check for rogue editors
var viewEditors = schoolFolder.getEditors();
for (i in viewEditors) {
var email = viewEditors[i].getEmail();
var emailSource = email.split("#")[1]
if (emailSource != "tester.com") {
// add as a viewer or remove completely?
addViewer(email)
};
};
};
};
// Recursive call for any sub-folders
getChildFolders(childFolder);
The error was in the logic checking for child folders. If there was no child folder, the script completed. I've added two conditional checks on each top-level folder, one for child folders and one for files. This script functions with one-level depth.
function updatePermissions() {
// define several variables to use throughout the script
var editors, domain, email;
var myspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var schoolValues = myspreadsheet.getDataRange().getValues();
// Loop through List of folders
// Skip row 1
for(var i=1; i<schoolValues.length; i++) {
//Retrieve folder id and go to folder
var folderId = schoolValues[i][1];
var folderName = schoolValues[i][0];
Logger.log(folderName);
var schoolFolder = DriveApp.getFolderById(folderId);
// Get the children
var childFolders = schoolFolder.getFolders();
// test for child folders.
if(!childFolders.hasNext()) {
// There is no child folder, so test for files
if(!schoolFolder.getFiles().hasNext()) {
// There are no files, so get the folder editors and loop
editors = schoolFolder.getEditors();
for(var j=0; j<editors.length; j++) {
email = editors[j].getEmail();
domain = editors[j].getDomain(); // easier than using a split function
// Check the domain. Remove if no match, add as viewer if there is a match
if(domain !== "testdomain.com") {
schoolFolder.removeEditor(email)
} else {
schoolFolder.removeEditor(email).addViewer(email);
}
}
}
// There are child folders, loop through and change permissions
} else {
while (childFolders.hasNext()) {
var childFolder = childFolders.next();
// Set permissions on the folder
childFolder.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
// Get the files in the child folder and loop
var files = childFolder.getFiles();
while (files.hasNext()) {
files.next().setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
var viewEditors = schoolFolder.getEditors();
// Loop the array of editors
for (var j=0; j<viewEditors.length; j++) {
email = viewEditors[j].getEmail();
domain = viewEditors[j].getDomain();
if (domain !== "testdomain.com") {
// add as a viewer or remove completely?
Logger.log("add " + email + " as a viewer");
files.next().addViewer(email);
} else {
// Remove the editor
Logger.log("removed " + email + " from folder");
files.next().removeEditor(email);
}
};
}
}
};
}
// Recursive call for any sub-folders
//getChildFolders(childFolder);
};

email Google Doc as PDF attachment

abc#example.comAfter struggling for a while with this I am calling for help from the crowd.
I am trying to attach 2 Google Docs to an email as PDFs. I see many examples in regard to Google Sheets and have successfully been able to email copies of sheets as PDF, but I have not been able to port this over to Docs.
In multiple different scenarios, the PDF attachments are either a copy of my original template Doc or an image capture of "One Account. All of Google" sign in page. When I look at the links that are generated (Logger.log) and use them, a copy of the correct Doc is downloaded.
Below is an example of my scenario in which I am trying to create the email attachments. See function emailDocAsPDF() towards the bottom
Thanks in advance for any guidance on this issue.
function myFunctionUpdated() {
var testTemplateId = searchDriveFile('Test Template');
Logger.log('test template id = ' + testTemplateId);
var fileName = 'Copy of Test Template'
Logger.log(fileName);
DriveApp.getFileById(testTemplateId).makeCopy(fileName);
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = ss.getRange(1, 1, 3, 2).getValues();
var copyNewTemplateId = searchDriveFile(fileName);
var copyTemplate = DocumentApp.openById(copyNewTemplateId);
var copyTemplateBody = copyTemplate.getBody().editAsText();
for (i=0; i<range.length; i++) {
copyTemplateBody.replaceText(range[i][0], range[i][1]);
}
copyTemplate.saveAndClose();
emailDocAsPDF(fileName)
}
// Searched Google Drive for a file name and returns the file ID.
function searchDriveFile(fileName) {
var files = DriveApp.searchFiles(
'title = "'+ fileName +'"');
while (files.hasNext()) {
var file = files.next();
var id = file.getId();
return id;
}
}
// Send document in an email as PDF
function emailDocAsPDF(fileName) {
var staticDoc = 'FILE-ID';
var attachmentDoc = UrlFetchApp.fetch("https://docs.google.com/document/d/" + copyTemplateId + "/export?format=pdf");
Logger.log("https://docs.google.com/document/d/" + copyTemplateId + "/export?format=pdf");
var attachmentStaticDoc = UrlFetchApp.fetch("https://docs.google.com/document/d/" + staticDoc + "/export?format=pdf");
Logger.log("https://docs.google.com/document/d/" + staticDoc + "/export?format=pdf");
var fileBlob = [];
fileBlob[0] = attachmentDoc.getBlob().getAs('application/pdf');
fileBlob[1] = attachmentStaticDoc.getBlob().getAs('application/pdf');
var body = "Bird is the WORD!! <br>" +
"<a href='http://www.example.com'>Visit Example</a>";
if (MailApp.getRemainingDailyQuota() > 0)
GmailApp.sendEmail("email#example.com", "Test Documents Email", body, {
htmlBody: body,
attachments:[fileBlob[0],fileBlob[1]]
});
}
EDIT -- Successful Updates with code provided by Sandwich.
// Send document in an email as PDF
function emailDocAsPDF(fileName) {
var staticDoc = 'FILE-ID';
var copyTemplateId = searchDriveFile(fileName);
var blobs = [];
var doc = DriveApp.getFileById(copyTemplateId);
blobs[0] = doc.getBlob().getAs('application/pdf').setName('MyAttachment1.pdf');
var doc = DriveApp.getFileById(staticDoc);
blobs[1] = doc.getBlob().getAs('application/pdf').setName('MyAttachment2.pdf');
var zipBlob = Utilities.zip(blobs).setName('Documents.zip');
var recipient = 'abc#example.com';
var subject = 'Test Email';
var body = 'Bird is the WORD';
MailApp.sendEmail(recipient, subject, body, {attachments: [zipBlob]});
}
To attach any kind of document as a PDF, call getBlob on the file, specify content type as with .getAs('application/pdf') and optionally set a name with setName. That's all:
var doc = DriveApp.getFileById('ID_HERE');
var blob = doc.getBlob().getAs('application/pdf').setName('MyAttachment.pdf');
var recipient = 'user#example.com';
var subject = 'Email subject';
var body = 'Text here';
MailApp.sendEmail(recipient, subject, body, {attachments: [blob]});
You were trying to use "export as PDF via URL query", which is a poorly documented feature that is sometimes useful for Sheets (setting the boundaries of exported region), but is unnecessary for Docs, if it even exists for them. The above method should work for any kind of content stored in Google Drive.
By the way, it is preferable to use MailApp instead of GmailApp when sending email, because it keeps the level of authorization to what you need: sending emails. With GmailApp, the script is also granted permission to access and delete all of your existing emails, which is unadvisable for scripts composed by trial, error, and copying code from the Internet.

Categories