I want to get the inserted image (not image attachment) in a received Gmail mail. How is this done using Google Apps Script?
I have tried to use urlfetchapp to get the image data from the image link address:
https//mail.google.com/mail/u/0/?ui=2&ik=c348ccba97&view=att&th=13bb892c352c45dc&attid=0.2&disp=emb&realattid=ii_13bb88f3e56aae8c&zw&atsh=1
in the email html body, but can not bypass the google login page. The response content data always is login page.
Edit: While the code below works on email drafts and canned responses, it does not let you access inline images in received email.
This code snippet is from a working mail merge I have that supports inline images (both embedded blobs and external references) and attachments as well. It takes the inline images and attachments from a draft/canned response, adds them to a new email and sends it:
...
//selectedTemplate is a Gmail Message (draft/canned response)
var emailTemplate = selectedTemplate.getBody();
var attachments = selectedTemplate.getAttachments();
if(emailTemplate.search(/<\img/ != -1)){
var inlineImages = {};
var imgVars = emailTemplate.match(/<img[^>]+>/g);
for(i in imgVars){
var title = imgVars[i].match(/title="([^\"]+\")/);
if (title) {
title = title[1].substr(0, title[1].length-1);
var titleEncoded = title.replace(/ /g,"-");
for(j in attachments){
if(attachments[j].getName() == title){
inlineImages[titleEncoded] = attachments[j].copyBlob().setName(titleEncoded);
attachments.splice(j,1);
}
}
var newImg = imgVars[i].replace(/src="[^\"]+\"/,"src=\"cid:"+titleEncoded+"\"");
emailTemplate = emailTemplate.replace(imgVars[i],newImg);
}
}
}
...
GmailApp.sendEmail("test#example.com", "my subject", "my body",
{attachments: attachments,
inlineImages: inlineImages});
Hope this helps.
I don't know how you are accessing your emails, but it doesn't look like you are using the GmailApp from the Gmail service. Once you've found the email you're interested in then examine the html body to find the <img>. Either src will be a url (linked image) or a cid (embedded image) that references an attachment.
A more recent approach to solving this.
The issue
For example, here's an email body retrieved with .getBody()
<div dir="ltr"><div><img src="?view=att&th=1401f70d4881e07f&attid=0.3&disp=emb&realattid=ii_1401f6fc7824ebe1&zw&atsh=1" alt="Inline image 4" width="200" height="180"><br></div><div><br></div><img src="?view=att&th=1401f70d4881e07f&attid=0.2&disp=emb&realattid=ii_1401f6e6c1d46c4b&zw&atsh=1" alt="Inline image 2" width="200" height="65"><div><br></div><div>
jtykuykyu</div><div><br></div><div><img src="?view=att&th=1401f70d4881e07f&attid=0.1&disp=emb&realattid=ii_1401f6e9df3a4b1c&zw&atsh=1" alt="Inline image 3" width="200" height="82"><br><div><br></div><div><br></div></div></div>
And here is the list of attachments for the email (among which are our inline images):
[13-07-30 08:28:08:378 CEST] Screen Shot 2013-07-12 at 1.54.31 PM.png
[13-07-30 08:28:08:379 CEST] Screen Shot 2013-07-23 at 5.38.51 PM.png
[13-07-30 08:28:08:380 CEST] Screen Shot 2013-07-25 at 9.05.15 AM.png
[13-07-30 08:28:08:381 CEST] test2.png
As you can see, there's no link between the name of those images and the information available in the img tags, so there's no safe way to rebuild a correct email with only those information.
The solution
How to solve that ? We can use the method .getRawContent() to get the actual email and parse it to get the information we need. Specifically, this method give us a relationship between the name of an attachment and the 'realattid' available in the email body:
Content-Type: image/png; name="Screen Shot 2013-07-25 at 9.05.15 AM.png"
Content-Transfer-Encoding: base64
Content-ID:
X-Attachment-Id: ii_1401f6e9df3a4b1c
Code snippet
Here's a code snippet to:
-Retrieve the body & attachments of an email
-Get all the img tags inside the body and see which ones are linked to attachments in the email
-Get the 'realattid' of each image and use .getRawContent() to link this 'realattid' to the right attachment
-Replace the img tag to correctly link it to the right attachment
-Indicate that this attachment is no longer a simple attachment but an inline image
-Once all that is done you have all the data you need to send a copy of this email with the correct inline images displayed.
//////////////////////////////////////////////////////////////////////////////
// Get inline images and make sure they stay as inline images
//////////////////////////////////////////////////////////////////////////////
var emailTemplate = selectedTemplate.getBody();
var rawContent = selectedTemplate.getRawContent();
var attachments = selectedTemplate.getAttachments();
var regMessageId = new RegExp(selectedTemplate.getId(), "g");
if (emailTemplate.match(regMessageId) != null) {
var inlineImages = {};
var nbrOfImg = emailTemplate.match(regMessageId).length;
var imgVars = emailTemplate.match(/<img[^>]+>/g);
var imgToReplace = [];
if(imgVars != null){
for (var i = 0; i < imgVars.length; i++) {
if (imgVars[i].search(regMessageId) != -1) {
var id = imgVars[i].match(/realattid=([^&]+)&/);
if (id != null) {
var temp = rawContent.split(id[1])[1];
temp = temp.substr(temp.lastIndexOf('Content-Type'));
var imgTitle = temp.match(/name="([^"]+)"/);
if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
}
}
}
}
for (var i = 0; i < imgToReplace.length; i++) {
for (var j = 0; j < attachments.length; j++) {
if(attachments[j].getName() == imgToReplace[i][0]) {
inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
attachments.splice(j, 1);
var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
emailTemplate = emailTemplate.replace(imgToReplace[i][1], newImg);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
var message = {
htmlBody: emailTemplate,
subject: selectedTemplate.getSubject(),
attachments: attachments,
inlineImages: inlineImages
}
Related
I have problems to drag an image from one iframe (1) out of a dynamic table to another iframe (2) within a droppable area. I think there is no permission issue, but a "type" issue. The drop area on iframe (2) is working with files from anywhere except form the iframe (1). The iframe (1) is hosted by localhost. The iframe (2) is hosted by a different domain.
Three different functions I found to convert data uri to file or blob were tested. The setData arguments also have been tested with all possibilities, no success by now.
Interestingly, having opened the site with the two iframes on Chrome and firefox, I am able to drag an drop from firefox to Chrome, but the image will be converted to bmp type and renamed! The other way around it is not working.
What are the right arguments for setData / get Data to drop an image
data uri on a drop area?
Do I have to convert data uri to a file
resp. blob object?
If so, again, what arguments will do the job?
Any help would be appreciated very much!
var dataUri;
var file;
function mouseDown(event){
toDataURL(event.target.src, function(dataUrl) {
console.log('RESULT:', dataUrl.replace('data:text/plain;base64,', ''));
dataUri = dataUrl.replace('data:text/plain;base64,', '');
file = urltoFile(dataUrl, 'hello.jpg','image/jpeg')
.then(function(file){ console.log(file);});
})
}
function drag(event) {
// event.stopPropagation();
console.log(file);
console.log(dataUri);
event.dataTransfer.setData("text/plain", file);
//event.dataTransfer.mozSetDataAt("application/x-moz-file", file, 0);
event.dataTransfer.effectAllowed = "move";
}
function drop(event) {
//event.preventDefault();
console.log(file);
console.log(dataUri);
var data = event.dataTransfer.getData("text/plain");
//var data = event.dataTransfer.mozGetDataAt("application/x-moz-file", 0);
event.target.appendChild(document.getElementById(data));
document.getElementsById(data);
console.log(data);
}
function allowDrop(event) {
event.preventDefault();
}
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
function urltoFile(url, filename, mimeType){
return (fetch(url)
.then(function(res){return res.arrayBuffer();})
.then(function(buf){return new File([buf], filename,{type:mimeType});})
);
}
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var dw = new DataView(ab);
for(var i = 0; i < byteString.length; i++) {
dw.setUint8(i, byteString.charCodeAt(i));
}
// write the ArrayBuffer to a blob, and you're done
return new Blob([ab], {type: mimeString});
}
I want to add images to a google apps script hosted webpage.
I tried looking around the menus and checking google.
I want it to display the image at the desired size
Images from Albums in my Google Photo Library in a Web App
function doGet(e){
return displayAlbums(true);
}
//used for web app and dialog depending upon weather web parameter is true or not. If it's not provided then it's false.
function displayAlbums(web) {
var web=web||false;
//different color backgrounds for each album
var bgA=['#f3eeb3','#f3e2b3','#f3ceb3','#f3b3b6','#f3b3b6','#f3b3ef','#b3eaf3','#b3f3e3','#b3f3cb','#bdf3b3']
var html='';
var n=0;
var albumsA=listAlbums();
for(var i=0;i<albumsA.length;i++) {
html+='<html><head></head><body>';
html+=Utilities.formatString('<div id="d-%s" style="margin:auto;max-width:500px;background-color:%s;">',i,bgA[i]);
html+=Utilities.formatString('<h1>%s</h1>', albumsA[i].title);
var images=listImagesOfAnAlbum(albumsA[i].id);
for(var j=0;j<images.length;j++) {
html+=Utilities.formatString('<br />%s - %s<br /><img src="%s" width="500" />',j+1,images[j].filename, images[j].baseUrl);
}
html+='</div></body></html>';
}
if(!web) {
var userInterface=HtmlService.createHtmlOutput(html).setWidth(600).setHeight(500);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Displaying my Albums');
}else{
var output=HtmlService.createHtmlOutput(html).setWidth(600).setHeight(500);
return output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL).addMetaTag('viewport', 'width=360, initial-scale=1');
}
}
function listAlbums() {
var token=null;
var fA=[];
var n=0;
do{
var params = {muteHttpExceptions:true,headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()}};
var url=Utilities.formatString('https://photoslibrary.googleapis.com/v1/albums?pageSize=50%s',(token!=null)?"&pageToken=" + token:"");
var resp=UrlFetchApp.fetch(url,params);
var js=JSON.parse(resp.getContentText());
for(var i=0;i<js.albums.length;i++) {
fA.push({id:js.albums[i].id,title:js.albums[i].title,count:js.albums.mediaItemsCount});
}
token=js.nextPageToken;
}while(token!=null);
Logger.log(fA);
return fA;
}
function listImagesOfAnAlbum(albumId) {
var albumId= albumId || 'Default Id for debugging';
var token=null;
var iA=[];
var n=0;
do{
var params = {
method:"post",
muteHttpExceptions:true,
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
payload:{"albumId": albumId,"pageSize":"50","pageToken":token}};
var url="https://photoslibrary.googleapis.com/v1/mediaItems:search";
var resp=UrlFetchApp.fetch(url,params);
var js=JSON.parse(resp.getContentText());
for(var i=0;i<js.mediaItems.length;i++) {
iA.push({filename:js.mediaItems[i].filename,baseUrl:js.mediaItems[i].baseUrl});
}
token=js.nextPageToken;
}while(token!=null);
return iA;
}
Google Photos API
I added this to the manifest file:
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/photoslibrary", "https://www.googleapis.com/auth/script.container.ui", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/script.scriptapp", "https://www.googleapis.com/auth/spreadsheets"]
Also adding this to your google scripts even though you don't need it will provoke the authenticator to added the need scopes. And also setup the Drive API in Resources Advanced Google Services.
//DriveApp.getFiles();
function listFiles() {
var files = Drive.Files.list({
fields: 'nextPageToken, items(id, title)',
maxResults: 10
}).items;
for (var i = 0; i < files.length; i++) {
var file = files[i];
Logger.log('\n%s-Title: %s Id: %s',i+1,file.title,file.id);
}
}
This is a technique describe by Bruce McPherson as borrowing a Token you can read about it here
I had already loaded the Oauth2 and GOA libraries. According the Mr. McPherson you will need to install the GOA Library although I never actively used it. He has a walk through here Just go through his little slide presentation. This may seem like a lot of trouble and it is. But it does provide you with programmatic access to the photo library. Fortunately, Google does all this for us on most of our libraries.
From Your Personal Computer to your Website with DataURI's
Another way to get images right off of your Google Drive and into your webapp website.
The Javascript in the <script> tags of your website:
google.script.run
.withSuccessHandler(function(iObj){
console.log(iObj);
for(var i=0;i<iObj.iA.length;i++) {
if(i==iObj.iA.length-1) {
$('#header').css('background-image','URL(' + iObj[iObj.iA[i]] + ')');
}else{
$('#' + iObj.iA[i]).attr('src',iObj[iObj.iA[i]]);
}
}
})
.getSimpleSiteImages();
});
The Google Apps Script:
function getSimpleSiteImages() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('SimpleSite');
var rg=sh.getDataRange();
var vA=rg.getValues();
var oObj={iA:[]};
for(var i=0;i<vA.length;i++) {
oObj.iA[i]=vA[i][2];
oObj[oObj.iA[i]]=getDataURI(vA[i][1]);
}
return oObj;
}
function getDataURI(fileId) {
var file=DriveApp.getFileById(fileId);
return file.getBlob().getDataAsString();
}
The Google Apps Script That Makes the DataURI:
function convImageUrl(url){
var blob=UrlFetchApp.fetch(url).getBlob();
var b64Url='data:' + blob.getContentType() + ';base64,' + Utilities.base64Encode(blob.getBytes());
return b64Url;
}
Just save on your google drive and upload them into your images.
I'm trying to insert images into Google Docs (other GSuite apps later) from an Add-On. I've succeeded in fetching the image and inserting it when getCursor() returns a valid Position. When there is a selection (instead of a Cursor), I can succeed if it's text that's selected by walking up to the Parent of the selected text and inserting the image at the start of the paragraph (not perfect, but OK).
UPDATE: It seems that I was using a deprecated method (getSelectedElements()), but that didn't fix the issue. It seems the issue is only with wrapped images as well (I didn't realize that the type of the object changed when you changed it to a wrapped text).
However, when an wrapped-text Image (presumably a PositionedImage) is highlighted (with the rotate and resize handles visible in blue), both getSelection() and getCursor() return null. This is a problem as I would like to be able to get that image and replace it with the one I'm inserting.
Here's my code... any help would be great.
var response = UrlFetchApp.fetch(imageTokenURL);
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection)
{
Logger.log("Got Selection");
var replaced = false;
var elements = selection.getRangeElements();
if (elements.length === 1
&& elements[0].getElement().getType() === DocumentApp.ElementType.INLINE_IMAGE)
{
//replace the URL -- this never happens
}
//otherwise, we take the first element and work from there:
var firstElem = elements[0].getElement();
Logger.log("First Element Type = " + firstElem.getType());
if (firstElem.getType() == DocumentApp.ElementType.PARAGRAPH)
{
var newImage = firstElem.asParagraph().insertInlineImage(0, response);
newImage.setHeight(200);
newImage.setWidth(200);
}
else if (firstElem.getType() == DocumentApp.ElementType.TEXT)
{
var p = firstElem.getParent();
if (p.getType() == DocumentApp.ElementType.PARAGRAPH)
{
var index = p.asParagraph().getChildIndex(firstElem);
var newImage = p.asParagraph().insertInlineImage(index, response);
newImage.setHeight(200);
newImage.setWidth(200);
}
}
} else {
Logger.log("Checking Cursor");
var cursor = DocumentApp.getActiveDocument().getCursor();
if (cursor)
{
Logger.log("Got Cursor: " + cursor);
var newImage = cursor.insertInlineImage(response);
var p = cursor.getElement();
var size=200;
newImage.setHeight(size);
newImage.setWidth(size);
}
}
You are using the deprecated 'getSelectedElements()' method of the Range class. You may notice it's crossed out in the autocomplete selection box.
Instead, use the 'getRangeElements()' method. After selecting the image in the doc, the code below worked for me:
var range = doc.getSelection();
var element = range.getRangeElements()[0].getElement();
Logger.log(element.getType() == DocumentApp.ElementType.INLINE_IMAGE); //logs 'true'
I have an application that attempts to dynamically build a Javamail message, assembling Mime body parts that are available at mail time. Each image is to have a 'GPC' image at the top, followed by some HTML text(body), a closing constructed by HTML, a closing 'Brand' image, and final part of the closing, also in HTML. File attachments may or may not be included. An error disclaimer(HTML) may precede the first image if applicable.
The disclaimer, body, closing, & attachment(s) are body parts, while the images are nested multiparts, consisting of HTML containers holding CIDs, and the image body parts themselves. When the email is sent, it appears correct in Gmail & Lotus Notes, while in Outlook(Hotmail) the GPC image is overwritten at the top by the Brand image. The order of the body parts is preserved, with the exception of the images, which always seem to revert to the top.
I cannot figure this out. It seems to me that the 'parent' multipart should be subtype 'Mixed' as the order of the body parts is important, and they are relatively unrelated, whereas the nested multiparts are 'Related' as one body part references the other. My code:
public MimeMultipart email_content_builder() throws MessagingException {
MimeMultipart mp = new MimeMultipart();
MimeBodyPart txt_part = new MimeBodyPart(), att_part, err_part, gpc_img_location_part, gpc_img_part, brand_img_location_part, brand_img_part, closing_pt1_part, closing_pt2_part;
DataSource att_source, gpc_img_src, brand_img_src;
String gpc_img_container_html, brand_img_container_html;
//Insert error disclaimer, if applicable:
if (!error_disclaimer.isEmpty()) {
err_part = new MimeBodyPart();
err_part.setText(error_disclaimer, "ISO-8859-1", "html");
mp.addBodyPart(err_part);
}
//Insert GPC logo image, if applicable:
if (GPC_logo.length > 0) {
MimeMultipart gpc_imagery_mp = new MimeMultipart("related");
MimeBodyPart gpc_imagery_part = new MimeBodyPart();
//When resizing the GPC image, the height should be roughly 23% of the width; use this factor: .2331
gpc_img_container_html = "<html><body><img src='cid:gpc_logo' height=\"23px\" width=\"100px\"></body></html>";
gpc_img_location_part = new MimeBodyPart();
gpc_img_location_part.setContent(gpc_img_container_html, "text/html");
gpc_imagery_mp.addBodyPart(gpc_img_location_part);
gpc_img_part = new MimeBodyPart();
gpc_img_src = new ByteArrayDataSource(GPC_logo, "image/jpeg");
gpc_img_part.setDataHandler(new DataHandler(gpc_img_src));
gpc_img_part.setHeader("Content-ID", "<gpc_logo>");
gpc_img_part.setDisposition(MimeBodyPart.INLINE);
gpc_imagery_mp.addBodyPart(gpc_img_part);
gpc_imagery_part.setContent(gpc_imagery_mp);
mp.addBodyPart(gpc_imagery_part);
}
//Insert main body of email:
txt_part.setText(body, "ISO-8859-1", "html");
mp.addBodyPart(txt_part);
//Insert the first part of the closing, if applicable:
if (!Closing_Part1.isEmpty()) {
closing_pt1_part = new MimeBodyPart();
closing_pt1_part.setText(Closing_Part1, "ISO-8859-1", "html");
mp.addBodyPart(closing_pt1_part);
}
//Insert brand logo image, if applicable:
if (brand_logo.length > 0) {
MimeMultipart brand_imagery_mp = new MimeMultipart("related");
MimeBodyPart brand_imagery_part = new MimeBodyPart();
//When resizing the brand image, the height should be roughly 43% of the width; use this factor: .4294
brand_img_container_html = "<html><body><img src='cid:brand_logo' height=\"64px\" width=\"150px\"></body></html>";
brand_img_location_part = new MimeBodyPart();
brand_img_location_part.setContent(brand_img_container_html, "text/html");
brand_imagery_mp.addBodyPart(brand_img_location_part);
brand_img_part = new MimeBodyPart();
brand_img_src = new ByteArrayDataSource(brand_logo, "image/jpeg");
brand_img_part.setDataHandler(new DataHandler(brand_img_src));
brand_img_part.setHeader("Content-ID", "<brand_logo>");
brand_img_part.setDisposition(MimeBodyPart.INLINE);
brand_imagery_mp.addBodyPart(brand_img_part);
brand_imagery_part.setContent(brand_imagery_mp);
mp.addBodyPart(brand_imagery_part);
}
//Insert the second part of the closing, if applicable:
if (!Closing_Part2.isEmpty()) {
closing_pt2_part = new MimeBodyPart();
closing_pt2_part.setText(Closing_Part2, "ISO-8859-1", "html");
mp.addBodyPart(closing_pt2_part);
}
//Insert attachments, if applicable:
if (attachments != null) {
for (int j = 0; j < attachments.size(); j++) {
att_part = new MimeBodyPart();
att_source = new FileDataSource((attachments.get(j)).getPath());
att_part.setDataHandler(new DataHandler(att_source));
att_part.setFileName((attachments.get(j)).getPath());
mp.addBodyPart(att_part);
}
}
return mp;
}
You have very limited control over how different mail readers will display the multiple body parts in your message. You best bet is to put all of the non-attachment content into a single text/html body part, using html to format the message. You can include images in your message using multipart/related, but it's often simpler to reference images on the web.
I'm trying to allow users to upload images and then store the images, base64 encoded, in firebase. I'm trying to make my firebase structured as follows:
|--Feed
|----Feed Item
|------username
|------epic
|---------name,etc.
|------images
|---------image1, image 2, etc.
However, I can't get the remote data in firebase to mirror the local data in the client. When I print the array of images to the console in the client, it shows that the uploaded images have been added to the array of images...but these images never make it to firebase. I've tried doing this multiple ways to no avail. I tried using implicit syncing, explicit syncing, and a mixture of both. I can;t for the life of me figure out why this isn;t working and I'm getting pretty frustrated. Here's my code:
$scope.complete = function(epicName){
for (var i = 0; i < $scope.activeEpics.length; i++){
if($scope.activeEpics[i].id === epicName){
var epicToAdd = $scope.activeEpics[i];
}
}
var epicToAddToFeed = {epic: epicToAdd, username: $scope.currUser.username, userImage: $scope.currUser.image, props:0, images:['empty']};
//connect to feed data
var feedUrl = "https://myfirebaseurl.com/feed";
$scope.feed = angularFireCollection(new Firebase(feedUrl));
//add epic
var added = $scope.feed.add(epicToAddToFeed).name();
//connect to added epic in firebase
var addedUrl = "https://myfirebaseurl.com/feed/" + added;
var addedRef = new Firebase(addedUrl);
angularFire(addedRef, $scope, 'added').then(function(){
// for each image uploaded, add image to the added epic's array of images
for (var i = 0, f; f = $scope.files[i]; i++) {
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
var filePayload = e.target.result;
$scope.added.images.push(filePayload);
};
})(f);
reader.readAsDataURL(f);
}
});
}
EDIT: Figured it out, had to connect to "https://myfirebaseurl.com/feed/" + added + "/images"