Trying to email a PDF of a single Google Sheet [duplicate] - javascript

For almost a year we have been using the below code to export a Google Sheets sheet (held in theSheetName) to a PDF named as specified in InboundID.
This last week, one at a time various users can no longer produce the PDF. I get a failure at the line containing "var newFile = DriveApp.createFile(blob);" with the error being:
"Conversion from text/html to application/pdf failed."
And sure enough, the UrlFetchApp.fetch is returning HTML instead of a PDF. Again, only for some users. Does anyone have any thoughts as to why my users might be seeing this?
function sendPDFToDrive(theSheetName, InboundID)
{
var theSpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var theSpreadsheetId = theSpreadSheet.getId();
var thisSheetId = theSpreadSheet.getSheetByName(theSheetName).getSheetId();
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
var theOutFileName = "GASFILE_M_" + (Math.floor(Math.random() * 8997) + 1000) + '.pdf'
//export as pdf
var url_ext = 'export?exportFormat=pdf&format=pdf'
+ (thisSheetId ? ('&gid=' + thisSheetId) : ('&id=' + theSpreadsheetId))
// following parameters are optional...
+ '&size=A4' // paper size
+ '&scale=2' // 1= Normal 100% / 2= Fit to width / 3= Fit to height / 4= Fit to Page
+ '&portrait=true' // orientation, false for landscape
+ '&horizontal_alignment=CENTER' //LEFT/CENTER/RIGHT
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=true' // hide gridlines
+ '&printnotes=false' // don't show notes
+ '&fzr=true'; // repeat row headers (frozen rows) on each page
// Setup options
var options =
{
headers:
{
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(),
}
}
// Build the file
var response = UrlFetchApp.fetch(url_base + url_ext, options);
var blob = response.getBlob().setName(theOutFileName);
var folder = DriveApp.getFolderById("ABC123FooBarID");
var newFile = DriveApp.createFile(blob); //Create a new file from the blob
newFile.setName(InboundID + ".pdf"); //Set the file name of the new file
newFile.makeCopy(folder);
}

I was having this exact problem. After some debugging I saw that my URL was being created incorrectly.
My code was nearly identical to yours. Where I found the culprit was the following line:
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
This was not actually clearing out the 'edit' to the end of the line like it had for years. I cannot say why this is, but the proof was in the logs. So, instead I crafted the url by hand:
var url = 'https://docs.google.com/spreadsheets/d/'+SpreadsheetApp.getActiveSpreadsheet().getId()+'/';
This seemed to solve the problem. This is not a perfect futureproof resolution, because if Google changes how the URLs are crafted, this will fail. But it works for now.
I hope this helps. You can send the url your code is creating to logs and check them to see if you have the same issue I did.

To expand on Jesse's accepted answer - the culprit is definitely in this line:
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
The reason why the replace(/edit$/,'') call no longer clears out edit like before is because the URL returned by theSpreadSheet.getUrl() used to end with edit, but now returns a URL with additional parameters on the end - ouid=1111111111111111111111&urlBuilderDomain=yourdomain.com.
While rebuilding the URL entirely should also work, you can also patch the script with some small changes to the regular expression. Instead of looking for edit$ (meaning edit as the final characters in the string), you can look for edit + any additional characters like so:
var url_base = theSpreadSheet.getUrl().replace(/edit.*$/,'');

better also avoid using comma for the last property of the header pointing below:
-- 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(), --

Related

Using Google Apps Script, how can I download each presentation slide as a pdf?

I'm a bit new to this, so I apologize in advance for any newb related annoyances.
What I'm trying to do is create a google presentation with various images, and then download each slide as a separate pdf. I'm trouble with the downloading as a pdf part. The presentation is being constructed correctly. I've tried a couple different things, but haven't found a working solution yet. The simplest one I tried was:
var newFolder=rootFolder.createFolder(sourceSpreadsheet.getName() + ' - Functionals').setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
var deck = SlidesApp.create(NAME); // name determined separately
var presentationID = deck.getId();
...
var blob = DriveApp.getFileById(presentationID).getBlob();
newFolder.createFile(blob);
This did create a pdf, but it looks like it was just one blank page. I'm unsure if maybe it needs to run on each slide individually rather than the presentation as a whole. I couldn't find anything to indicate that to be the case though.
The second thing I tried was based on a similar solution I found for a spreadsheet. I don't really understand how changing the URL downloads it as a pdf, and maybe that's related to the issue with it, which is this is resulting in a 404.
var presentation = SlidesApp.openById(presentationID);
var url = presentation.getUrl();
url = url.replace(/edit$/,'');
var url_ext = 'export?exportFormat=pdf&format=pdf' + '&muteHttpExceptions=true' //export as pdf
var response = UrlFetchApp.fetch(url + url_ext, {
headers: {
'Authorization': 'Bearer ' + token
}
});
I've used the second method to create pdfs of Google Sheets.
The following function could be adapted to create pdfs of your slides. This uses the REST API so that's why you need to construct the URL with parameters according to how you want to format the pdf.
Your formated url will need to look something like this: https://docs.google.com/presentation/d/****presentationId****/export?exportFormat=pdf&format=pdf
You can find other optional parameters for formating the pdf in this function.
function exportPDF(fileId) {
var ss = SpreadsheetApp.openById(fileId);
// Base URL
var url = "https://docs.google.com/spreadsheets/d/SS_ID/export?".replace("SS_ID", ss.getId());
/* Specify PDF export parameters
From: https://code.google.com/p/google-apps-script-issues/issues/detail?id=3579
https://stackoverflow.com/questions/46088042/margins-parameters-for-spreadsheet-export
*/
var url_ext = 'exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&size=letter' // paper size legal / letter / A4
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=true&source=labnol' // fit to page width, false for actual size
+ '&top_margin=0.25' //All four margins must be set!
+'&bottom_margin=0.25' //All four margins must be set!
+'&left_margin=0.25' //All four margins must be set!
+'&right_margin=0.25' //All four margins must be set!
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenumbers=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=true' // do not repeat row headers (frozen rows) on each page
+ '&gid='; // the sheet's Id
var token = ScriptApp.getOAuthToken();
var sheet = ss.getSheets()[0]; //get first sheet
// Converts to PDF
var response = UrlFetchApp.fetch(url + url_ext + sheet.getSheetId(), {
headers: {
'Authorization': 'Bearer ' + token
}
});
//convert the response to a blob and store in our array
var blob = response.getBlob().setName(sheet.getName() + '.pdf');
var folderId = '**********your folder id here*******************';
var folder = DriveApp.getFolderById(folderId);
return folder.createFile(blob).getId();
}
Converting all of the slides in a presentation to individual pdfs
function convertingSlideImagesToPDF() {
var fldr=DriveApp.getFolderById("FolderID");
var ss=SlidesApp.openById("PresentationID");
var slds=ss.getSlides();
var n=0;
for(var i=0;i<slds.length;i++) {
var sldImgA=slds[i].getImages();
if(sldImgA) {
for(var j=0;j<sldImgA.length;j++) {
var imgName=sldImgA[j].getTitle();
var base64=Utilities.base64Encode(sldImgA[j].getBlob().getBytes());
var html='<img src="data:image/jpg;base64,'+base64+'">';
var blob=null;
blob=Utilities.newBlob(html, MimeType.HTML).setName('Image' + n++ + ".pdf");
blob=blob.getAs(MimeType.PDF);
var file=fldr.createFile(blob);
}
}
}
}
Helpful Reference

MWS Post Request with Google Scripts

I am trying to make a post request through google scripts to amazon to collect information.
We are trying to get our orders to MWS and transfer them to sheets automatically.
I got to the last step which is signing the request.
A few things I wasnt sure about:
They say we use the secret key for hashing,I only see a client secret
and an access key id, which do I use?
Do I add the URL as part of whats getting signed? On the MWS Scratch pad they add this as shown:
POST
mws.amazonservices.com
/Orders/2013-09-01
Does it have to be on separate lines does it need post and the rest of the stuff. Its a little unclear.?
I read online that the sha256 byte code gets base64 encoded, not the string literal, is that true?
I tried to hash the string that amazon gave to me with an online tool and compare it to the hash they provided and the base64 encode, thing matched. I tried decoding as well, nothing matched
Can someone please send me an example that works, so I can understand what happens and how it works?
Thank you!
Below is what I have so far:
function POSTRequest() {
var url = 'https:mws.amazonservices.com/Orders/2013-09-01?';
var today = new Date();
var todayTime = ISODateString(today);
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
yesterday.setHours(0,0,0,0);
var yesterdayTime = ISODateString(yesterday);
var dayBeforeYesterday = new Date();
dayBeforeYesterday.setDate(today.getDate() - 2);
dayBeforeYesterday.setHours(0,0,0,0);
var dayBeforeYesterdayTime = ISODateString(dayBeforeYesterday);
var unsignedURL =
'POST\r\nhttps:mws.amazonservices.com\r\n/Orders/2013-09-01\r\n'+
'AWSAccessKeyId=xxxxxxxxxxx' +
'&Action=ListOrders'+
'&CreatedAfter=' + dayBeforeYesterdayTime +
'&CreatedBefore' + yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=xxxxxxxxxxxx'+
'&MarketplaceId.Id.1=ATVPDKIKX0DER' +
'&SellerId=xxxxxxxxxxx'+
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+ ISODateString(new Date) +
'&Version=2013-09-0';
var formData = {
'AWSAccessKeyId' : 'xxxxxxxxx',
'Action' : "ListOrders",
'CreatedAfter' : dayBeforeYesterdayTime,
'CreatedBefore' : yesterdayTime,
'FulfillmentChannel.Channel.1' : 'AFN',
'MWSAuthToken' : 'xxxxxxxxxxxx',
'MarketplaceId.Id.1' : 'ATVPDKIKX0DER',
'SellerId' : 'xxxxxxxxxx',
'SignatureMethod' : 'HmacSHA256',
'SignatureVersion' : '2',
'Timestamp' : ISODateString(new Date),
'Version' : '2013-09-01',
'Signature' : calculatedSignature(unsignedURL)
};
var options = {
"method" : "post",
"muteHttpExceptions" : true,
"payload" : formData
};
var result = UrlFetchApp.fetch(url, options);
writeDataToXML(result);
Logger.log(result);
if (result.getResponseCode() == 200) {
writeDataToXML(result);
}
}
function calculatedSignature(url) {
var urlToSign = url;
var secret = "xxxxxxxxxxxxxxxxxxx";
var accesskeyid = 'xxxxxxxxxxxxxxx';
var byteSignature = Utilities.computeHmacSha256Signature(urlToSign, secret);
// convert byte array to hex string
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Logger.log("URL to sign: " + urlToSign);
Logger.log("");
Logger.log("byte " + byteSignature);
Logger.log("");
Logger.log("reg " + signature);
var byte64 = Utilities.base64Encode(byteSignature)
Logger.log("base64 byte " + Utilities.base64Encode(byteSignature));
Logger.log("");
Logger.log("base64 reg " + Utilities.base64Encode(signature));
return byte64;
}
Step 1, creating the string to be signed
The string_to_sign is the combination of the following:
The string POST followed by a NEWLINE character
The name of the host, mws.amazonservices.com, followed by a NEWLINE
The API URL, often just /, or somthing like /Orders/2013-09-01, followed by a NEWLINE
An alphabetical list of all parameters except Signature in URL encoding, like a=1&b=2, not followed by anything
The minimum parameters seem to be the following:
AWSAccessKeyId is a 20-character code provided by Amazon
Action is the name of your API call, like GetReport
SellerId is your 14-character seller ID
SignatureMethod is HmacSHA256
SignatureVersion is 2
Timestamp is a date like 20181231T23:59:59Z for one second before new year UTC
Version is the API version like 2013-09-01
additional parameters may be needed for your call, depending on the value of Action
Please note:
The NEWLINE character is just "\n", not "\r\n"
The name of the host should not include "https://" or "http://"
The Signature parameter is necessary for the actual call later (see step 3), but is not part of the string_to_sign.
Your string_to_sign should now look somewhat like this:
POST
mws.amazonservices.com
/Orders/2013-09-01
AWSAccessKeyId=12345678901234567890&Action=ListOrders&CreatedAfter .... &Version=2013-09-01
Step 2, signing that string
Calculate a SHA256 hash of above string using the 40-character Secret Key
Encode this hash using Base64
In Pseudocode: signature = Base64encode( SHA256( string_to_sign, secret_key ))
Step 3, send the call
Send a HTTPS POST request, using the full alphabetical list of parameters, now including above signature as Signature somewhere in the middle, because you need to keep ascending alphabetical order.
https://mws.amazonservices.com/Orders/2013-09-01?AWSAccessKeyId....Version=2013-09-01
Step 4, processing the result
You should be getting two things back: a response header and a XML document. Be sure to evaluate the HTTP status in the header as well as all contents of the XML document. Some error messages are hidden deeply in XML while HTTP returns "200 OK".
Step 5, Advanced Stuff
If you use calls that require you to send a document, like SendFeed, you need to do the following additional steps:
Calculate the MD5 hash of your document
Encode this hash using Base64
In Pseudocode: contentmd5= Base64encode( MD5( document ))
Add Content-Type: text/xml (or whatever fits your document) as HTTP header
Add Content-MD5: plus the base64 encoded hash as HTTP header
This code was a great help for building a similar application. I corrected some small issues to bring it up to work:
before signing the ISODate need to be worked out to replace the ":" chars
var todayTime = ISODateString(today);
var todayISO = todayTime;
var todayTime_ = todayTime.replace(":", "%3A");
todayTime = todayTime_.replace(":","%3A");
The same for the calculated signature (quick & dirty solution, replace only 3 appearences and need to updated for more chars)
Logger.log(unsignedURL);
var tmpsignature = calculatedSignature(unsignedURL);
var orsignature = tmpsignature;
// encode special chars
tmpsignature = encodeURIComponent(orsignature);
}
I added a header and dropped the form, put all parameters in the url
var header = {
"x-amazon-user-agent": "GoogleSheets/1.0 (Language=Javascript)",
"Content-Type": "application/x-www-form-urlencoded"
// "Content-Type": "text/xml"
};
var options = {
"method" : "post",
"muteHttpExceptions" : true,
// "payload" : formData,
"header":header
};
And changed the call to url encoded (Form was not working, do not know why)
var url = 'https:mws-eu.amazonservices.com/Orders/2013-09-01?'+
'AWSAccessKeyId=<your stuff>'+
'&Action=GetOrder'+
'&SellerId=<your stuff>+
'&MWSAuthToken=<your token>'+
'&SignatureVersion=2'+
'&Timestamp='+todayTime'+ // remember to replace the ":" thru hex
'&Version=2013-09-01'+
'&Signature='+ tmpsignature+
'&SignatureMethod=HmacSHA256'+
'&AmazonOrderId.Id.1='+<your order;
parsed the response:
var result = UrlFetchApp.fetch(url, options);
//writeDataToXML(result);
Logger.log(result);
var xml = result.getContentText();
var document = XmlService.parse(xml);
This worked! :-)
I checked the signature also with
https://mws-eu.amazonservices.com/scratchpad/index.html

Can i set custom page and margins while generating PDF ? I tried but no luck app script

I am working on a project where i want to generate a pdf of size 52 mm x 25 mm. I use the following code to generate pdf and works fine except the page sizes.
function CreaPDF() {
//The function prints an invoice to PDF. First it copies spreadsheet to a new document.
//Deletes all sheet except the one to print. Saves it to PDF.
//It overwrites any existing doc with same name.
var sourceSpreadsheet = SpreadsheetApp.getActive();
var sheetName = "Factura";
var folderID = getParentFolder(); // Folder id to save in a folder.
var sourceSheet = sourceSpreadsheet.getSheetByName(sheetName);
var folder = DriveApp.getFolderById(folderID);
var numf = sourceSpreadsheet.getRangeByName("NumeroFactura").getValue();
var anof = numf.split("/",2); // Seeks number and year -> filename
var pdfName = anof[1] +"_Factura_" + anof[0]+ "_Dra_Salazar"; // Nombre del documento;
SpreadsheetApp.getActiveSpreadsheet().toast('Creando PDF');
// export url
var url = 'https://docs.google.com/spreadsheets/d/'+sourceSpreadsheet.getId()+'/export?exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&size=A4' // paper size legal / letter / A4
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=false' // fit to page width, false for actual size
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenumbers=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=false' // do not repeat row headers (frozen rows) on each page
+ '&gid='+sourceSheet.getSheetId(); // the sheet's Id
var token = ScriptApp.getOAuthToken();
// request export url
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token
}
});
var theBlob = response.getBlob().setName(pdfName+'.pdf');
// delete pdf if already exists
var files = folder.getFilesByName(pdfName);
while (files.hasNext())
{
files.next().setTrashed(true);
}
// create pdf
var newFile = folder.createFile(theBlob);
return true;
}
What i want is to set a custom page size in the link and custom margins. I want to send this pdf to Dymo Label printing automatically.
Any help regarding this will be highly appreciated. Thank you so much.

Display thumbnailPhoto from Active Directory using Javascript only - Base64 encoding issue

Here's what I'm trying to do:
From an html page using only Javascript I'm trying to query the Active Directory and retrieve some user's attributes.
Which I succeded to do (thanks to some helpful code found around that I just cleaned up a bit).
I can for example display on my html page the "displayName" of the user I provided the "samAccountName" in my code, which is great.
But I also wanted to display the "thumbnailPhoto" and here I'm getting some issues...
I know that the AD provide the "thumbnailPhoto" as a byte array and that I should be able to display it in a tag as follow:
<img src="data:image/jpeg;base64," />
including base64 encoded byte array at the end of the src attribute.
But I cannot manage to encode it at all.
I tried to use the following library for base64 encoding:
https://github.com/beatgammit/base64-js
But was unsuccesful, it's acting like nothing is returned for that AD attribute, but the photo is really there I can see it over Outlook or Lync.
Also when I directly put that returned value in the console I can see some weird charaters so I guess there's something but not sure how it should be handled.
Tried a typeof to find out what the variable type is but it's returning "undefined".
I'm adding here the code I use:
var ADConnection = new ActiveXObject( "ADODB.connection" );
var ADCommand = new ActiveXObject( "ADODB.Command" );
ADConnection.Open( "Data Source=Active Directory Provider;Provider=ADsDSOObject" );
ADCommand.ActiveConnection = ADConnection;
var ou = "DC=XX,DC=XXXX,DC=XXX";
var where = "objectCategory = 'user' AND objectClass='user' AND samaccountname='XXXXXXXX'";
var orderby = "samaccountname ASC";
var fields = "displayName,thumbnailPhoto";
var queryType = fields.match( /,(memberof|member),/ig ) ? "LDAP" : "GC";
var path = queryType + "://" + ou;
ADCommand.CommandText = "select '" + fields + "' from '" + path + "' WHERE " + where + " ORDER BY " + orderby;
var recordSet = ADCommand.Execute;
fields = fields.split( "," );
var data = [];
while(!recordSet.EOF)
{
var rowResult = { "length" : fields.length };
var i = fields.length;
while(i--)
{
var fieldName = fields[i];
if(fieldName == "directReports" && recordSet.Fields(fieldName).value != null)
{
rowResult[fieldName] = true;
}
else
{
rowResult[fieldName] = recordSet.Fields(fieldName).value;
}
}
data.push(rowResult);
recordSet.MoveNext;
}
recordSet.Close();
console.log(rowResult["displayName"]);
console.log(rowResult["thumbnailPhoto"]);
(I replaced db information by Xs)
(There's only one entry returned that's why I'm using the rowResult in the console instead of data)
And here's what the console returns:
LOG: Lastname, Firstname
LOG: 񏳿က䙊䙉Āā怀怀
(same here Lastname & Firstname returned are the correct value expected)
This is all running on IE9 and unfortunetly have to make this compatible with IE9 :/
Summary:
I need to find a solution in Javascript only
I know it should be returning a byte array and I need to base64 encode it, but all my attempts failed and I'm a bit clueless on the reason why
I'm not sure if the picture is getting returned at all here, the thing in the console seems pretty small... or if I'm nothing doing the encoding correctly
If someone could help me out with this it would be awesome, I'm struggling with this for so long now :/
Thanks!

Array element assignment in Google (Apps) Script

I'm associating a Google (Apps) Script with a Google Spreadsheet.
The entire script works except for the following line:
headers[0] = headers[0] + ":";
Removing this line allows the script to run. Adding it makes it fail.
The array is initialized beforehand as follows.
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
What's wrong with my element assignment, and how do I fix it?
Thanks.
The very simple test function:
function whenOpen() {
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
Logger.log('Array of headers: ' + headers);
Logger.log('First header: ' + headers[0]);
headers[0] = headers[0] + ":";
Logger.log('Modified header: ' + headers[0]);
}
Runs perfectly for me: http://prntscr.com/58a6fx
I don't get any errors, so I suspect the issue is more complex then this one line, as it does not appear to be the culprit. If you try it yourself in a fresh script (Bound to a spreadsheet, with some values in the headers, ofc), it should run with issue.

Categories

Resources