I'm playing with getting the revision history of a document through Google Apps Script and I'm looking for some advice on how to programmatically access the content of the revision.
Using the Drive API, I can access an array of revisions on the document and iterate based on user. The returned object does not include the content of the revision, just an ID. But, you can get a download URL for various content types (pdf, plaintext, etc).
I'd like to retrieve a download URL using UrlFetchApp and get that content to append to a document. The problem is that the fetch app returns the entire document markup (HTML and CSS) and I'd only like the content of the file.
Script
function revisionHistoryLite() {
var doc = DocumentApp.getActiveDocument();
var eds = doc.getEditors();
var body = doc.getBody();
var revs = Drive.Revisions.list(doc.getId())
var editsList = [];
for(var i=0; i<revs.items.length; i++) {
var revision = revs.items[i];
editsList.push([revision.id, revision.kind, revision.modifiedDate, revision.lastModifyingUser.emailAddress]);
if(revision.lastModifyingUser.emailAddress == "bbennett#elkhart.k12.in.us") {
var revUrl = Drive.Revisions.get(doc.getId(), revision.id).exportLinks["text/plain"];
// revUrl returns https://docs.google.com/feeds/download/documents/export/Export?id=docIdString&revision=1&exportFormat=txt
var revString = UrlFetchApp.fetch(revUrl, { contentType: "text/plain", }).getContentText();
Logger.log(revString); // Contains full HTTP markup
// Append the body contents to a temporary document for further processing
// var tempDoc = DocumentApp.create("Temp").getBody().appendParagraph(revString);
}
}
}
When it downloads files from exportLinks using UrlFetchApp.fetch(), the authorization is required. So please modify your script as follows.
From :
var revUrl = Drive.Revisions.get(doc.getId(), revision.id).exportLinks["text/plain"];
var revString = UrlFetchApp.fetch(revUrl, { contentType: "text/plain", }).getContentText();
To :
var revUrl = Drive.Revisions.get(doc.getId(), revision.id).exportLinks["text/plain"] + "&access_token=" + ScriptApp.getOAuthToken();
var revString = UrlFetchApp.fetch(revUrl).getContentText();
By this, you can download text data from the revision data.
Edit :
function revisionHistoryLite() {
var doc = DocumentApp.getActiveDocument();
var eds = doc.getEditors();
var body = doc.getBody();
var revs = Drive.Revisions.list(doc.getId())
var editsList = [];
for(var i=0; i<revs.items.length; i++) {
var revision = revs.items[i];
editsList.push([revision.id, revision.kind, revision.modifiedDate, revision.lastModifyingUser.emailAddress]);
if(revision.lastModifyingUser.emailAddress == "### mail address ###") {
var revUrl = Drive.Revisions.get(doc.getId(), revision.id).exportLinks["text/plain"] + "&access_token=" + ScriptApp.getOAuthToken();
var revString = UrlFetchApp.fetch(revUrl).getContentText();
Logger.log(revString); // Contains full HTTP markup
}
}
}
Updated: February 7, 2020
From January, 2020, the access token cannot be used with the query parameter like access_token=###. Ref So please use the access token to the request header instead of the query parameter. It's as follows.
var res = UrlFetchApp.fetch(url, {headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}});
Related
I am very new to Javascript but I will try to put this in convenient way. I am having this api where I am fetching the rank of a crypto (Ripple; currently ranked 7 and is subject to change overtime ), code below:
function myFunction() {
var url = "https://api.coinpaprika.com/v1/coins/xrp-xrp";
var XRPresponse = UrlFetchApp.fetch(url);
var XRPjson = XRPresponse.getContentText();
var XRPdata = JSON.parse(XRPjson);
var XRPrank = XRPdata.rank;
}
Now this is another function for an api where I extract other infos (having 5000+ crytos listed, including ripple)
function myXRP() {
var url = "https://api.coinpaprika.com/v1/tickers";
var response = UrlFetchApp.fetch(url);
var json = response.getContentText();
var data = JSON.parse(json);
var XRP = data[7].symbol;
// Here instead of [7], I need to put the value extracted from XRPrank above so that whenever the rank is changed I get the latest value on data.[].
If someone could please advise.
In JavaScript there are several ways to achieve what you are looking for. The following is an adaptation of your current code with what I think are the minimal changes that you have to do, 1. use return followed by XRPrank 2. Call myFunction from myXRP and replace the data index by XRPrank.
function myFunction() {
var url = "https://api.coinpaprika.com/v1/coins/xrp-xrp";
var XRPresponse = UrlFetchApp.fetch(url);
var XRPjson = XRPresponse.getContentText();
var XRPdata = JSON.parse(XRPjson);
var XRPrank = XRPdata.rank;
return XRPrank; // add this
}
function myXRP() {
var url = "https://api.coinpaprika.com/v1/tickers";
var response = UrlFetchApp.fetch(url);
var json = response.getContentText();
var data = JSON.parse(json);
var XRPrank = myFunction(); // add this
// var XRP = data[7].symbol; instead of this
var XRP = data[XRPrank].symbol; // use this
}
Resources
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
I am using Facebook Graph API to create a Facebook ads campaign with Google Apps Script.
I need to upload an image to my Facebook ad account. I have already tried to use the image bytes as a Base64 UTF-8 string, but when I call the API I get:
Exception: Limit Exceeded: URLFetch URL Length.
Basically, the string is too long.
I am using the following code:
function uploadTest2() {
var image_id = 'blabla';
var image_blob = DriveApp.getFileById(image_id).getBlob();
var input = image_blob.getBytes();
var docImg = Utilities.base64Encode(input);
var account_id = '1111111111111';
var facebookUrl =
'https://graph.facebook.com/v7.0' +
'/act_' + account_id +
'/adimages?bytes=' + docImg +
'&access_token=' + TOKEN;
Logger.log(facebookUrl);
//var encodedFacebookUrl = encodeURI(facebookUrl);
var options = {
'method' : 'post'
};
var response = UrlFetchApp.fetch(facebookUrl, options);
var results = JSON.parse(response);
Logger.log(response);
}
The image does not exceed 5MB and I have already check the bytes string with an online decoder to verify it.
Do you have any idea on how to use the image URL directly in the post request?
The second version of the code:
function uploadTest2() {
var image_id = 'blabla';
var image_blob = DriveApp.getFileById(image_id).getBlob();
var input = image_blob.getBytes();
var docImg = Utilities.base64Encode(input);
var account_id = '1111111111111';
var facebookUrl =
'https://graph.facebook.com/v7.0' +
'/act_' + account_id +
// '/adimages?bytes=' + encodedImage +
// '&access_token=' + TOKEN;
'/adimages?access_token=' + TOKEN;
Logger.log(facebookUrl);
//var encodedFacebookUrl = encodeURI(facebookUrl);
var options = {
'method' : 'post',
'payload' : image_blob
};
var response = UrlFetchApp.fetch(facebookUrl, options);
var results = JSON.parse(response);
Logger.log(response);
}
Solution
In order to make a post request of an image with UrlFetchApp.fetch() you must provide the method, payload (i.e the body you want to POST) and sometimes the content type (if what we are passing is not a JavaScript object).
If you want to pass a base64Encode object obtained from a blob you should stringify this JSON object.
What the original poster was missing was to pass the payload and after my contribution and his work he finally solved the issue by editing the options variable such as:
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload': JSON.stringify({"bytes": docImg,"name" : 'Test'})};
}
Documentation reference : Class UrlFetchApp
I am trying to create a function in Google Apps Script that finds the file path of the folder.
Here is the error: TypeError: Cannot read property 'appendParagraph' of null (line 20, file "Code")
function getRoot() {
var doc = DocumentApp.getActiveDocument();
var header = DocumentApp.getActiveDocument().getHeader();
var name = doc.getName();
var id = doc.getId();
var txt = "Master Folder";
var parents = [];
var folders = DriveApp.getFileById(id).getParents();
while (folders.hasNext()){
var parent = folders.next();
var n = parent.getName();
parents.push(n);
}
var pLen = parents.length;
for (i = 0; i < pLen; i++){
txt = txt + "//" + parents[i];
}
var headerPar = header.appendParagraph(txt);
}
I believe your goal as follows.
You want to know the reason of the error message of TypeError: Cannot read property 'appendParagraph' of null.
You want to remove the error.
You want to retrieve the folder path of the active Google Document.
For this, how about this answer?
Modification points:
In your script, I think that the reason of your error message is the header is not existing in the Google Document. By this, header retrieved by getHeader() is null, and then, the error occurs. So in this case, please add the header using addHeader().
doc of var doc = DocumentApp.getActiveDocument(); can be used for getHeader().
If you want to retrieve the folder path from the root folder, I think that your script is required to be modified. In your current script, when the Google Document is put in the nested folders, only parent of the Document is retrieved.
When your script is modified using above points, it becomes as follows.
Modified script:
function getRoot() {
var doc = DocumentApp.getActiveDocument();
var header = doc.getHeader() || doc.addHeader(); // Modified
var name = doc.getName();
var id = doc.getId();
var txt = "Master Folder";
var parents = [];
var folders = DriveApp.getFileById(id).getParents();
// --- I modified below script
while (folders.hasNext()) {
var folder = folders.next();
parents.push(folder.getName());
folders = folder.getParents();
}
parents = parents.reverse();
// ---
var pLen = parents.length;
for (i = 0; i < pLen; i++){
txt = txt + "//" + parents[i];
}
var headerPar = header.appendParagraph(txt);
}
By this modification, when the header is existing, doc.getHeader() is used. When the header is not existing, doc.addHeader() is used. And, when the active Document is put to the folder path like root -> folder1 -> folder2, Master Folder//MyDrive//folder1//folder2 is put to the header.
References:
getHeader()
addHeader()
I need to create dynamic pdf with html templates in servicenow, but my problem is that these pdf must contain images and styles, and I have not been able to solve it.
try using the api of GeneralPDF of servicenow and get the template converted to pdf but only when it contains text. when I put images I get the following error:
This error appears to me when executing my code:
ExceptionConverter: java.io.IOException: The document has no pages.:
org.mozilla.javascript.JavaScriptException: ExceptionConverter:
java.io.IOException: The document has no pages.:
this is in a script include and is called from UI Action
my code to convert the html to pdf is the following:
create : function (sys_id){
var carta = new GlideRecord('x_solsa_casos_plant_doc');
carta.addQuery('sys_id','6f1e4ac8db29f300ab7c0f95ca96197a');
carta.query();
if(carta.next()){
var parsedBody = carta.body;
var gr = new GlideRecord('x_solsa_casos_x_solsa_casos');
gr.get('sys_id',sys_id);
var sampleString=parsedBody.toString();
var reg = new SNC.Regex('/\\$\\{(.*?)\\}/i');
var match = reg.match(sampleString);
var count =0;
var variables = [];
var values = [];
var tmpValue;
while (match != null)
{
variables.push(match.toString().substring(match.toString().indexOf(',')+1));
match = reg.match();
values.push(variables[count]);
gs.log("array values : " + values);
if(gr.getDisplayValue(values[count])==null || JSUtil.nil(gr.getDisplayValue(values[count])))
{
tmpValue='';
}else{
tmpValue=gr.getDisplayValue(values[count]);
gs.log("tmpValue :" +tmpValue);
}
parsedBody = parsedBody.replace('${'+variables[count]+'}', tmpValue);
count++;
gs.log("parsedBody : " + parsedBody);
}
this.createPDF(parsedBody,'x_solsa_casos_x_solsa_casos',sys_id,'carta.pdf');
}
},
createPDF : function(html, table, sys_id, filename) {
var pdfDoc = new GeneralPDF.Document(null, null, null, null, null, null);
this._document = new GeneralPDF(pdfDoc);
this._document.startHTMLParser();
this._document.addHTML(html);
this._document.stopHTMLParser();
this.saveAs(table, sys_id, filename);
},
saveAs : function (table, sys_id, filename){
var att = new GeneralPDF.Attachment();
att.setTableName(table);
att.setTableId(sys_id);
att.setName(filename);
att.setType('application/pdf');
att.setBody(this._document.get());
GeneralPDF.attach(att);
},
Looks like parsedBody is empty or doesn't always contain HTML. According to this answer, paseXHtml (which ServiceNow probably uses and should be in the complete stack trace) expects HTML tags, not just text:
https://stackoverflow.com/a/20902124/2157581
I went through this guide: https://developers.google.com/apps-script/guides/rest/quickstart/target-script to create a quick start target for Google Apps Script. At the end of this section I followed the Node.js tutorial to execute this script from a local node.js server: https://developers.google.com/apps-script/guides/rest/quickstart/nodejs
It all worked!
But, then I replaced the default code in Google Apps Script from this:
function getFoldersUnderRoot() {
var root = DriveApp.getRootFolder();
var folders = root.getFolders();
var folderSet = {};
while (folders.hasNext()) {
var folder = folders.next();
folderSet[folder.getId()] = folder.getName();
}
return folderSet;
}
to this:
function getPressInfo() {
var spreadsheet = SpreadsheetApp.openById("MY_SHEET_ID");
var sheets = spreadsheet.getSheets();
var activeSheet = null;
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var name = sheet.getName();
if (/published/i.test(name)) {
activeSheet = sheet;
break;
}
}
if (!sheet) {
return null;
}
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
return sheet.getSheetValues(1, 1, lastRow, lastCol);
}
I updated my target version and renamed my function resource to getPressInfo in my node.js script. Now I get an authorization error... I can't tell if this is in reference to the Google Sheet (Set to Publicly Visible), the Google Apps Script (Set Access to Anyone), or something entirely different. Error reads:
The API returned an error: { [Error: ScriptError]
code: 401,
errors:
[ { message: 'ScriptError',
domain: 'global',
reason: 'unauthorized' } ] }
Anyone else run into this issue? I don't think it's the Google Apps Script, because when I roll back to the target with the default example it still works. If it helps I can recreate with dummy data.., but I suspect there's something simple in my code that is actually triggering the error.
Okay, I was totally over thinking the task to begin with. Google Sheets has a GET request for particular formats. I used tsv, but they also accept csv. This was my node.js script — no need for Google Apps Script whatsoever:
var https = require('https');
var path = require('path');
var fs = require('fs');
var format = 'tsv';
var id = 'ID_OF_GOOGLE_SHEET';
https.get('https://docs.google.com/spreadsheets/d/' + id + '/export?format=' + format + '&id=' + id, function(resp) {
var body = '';
resp
.on('data', function(data) {
body += ab2str(data);
})
.on('end', function() {
var json = [];
var rows = body.split(/\r\n/i);
for (var i = 0; i < rows.length; i++) {
json.push(rows[i].split(/\t/i));
}
fs.writeFileSync(path.resolve(__dirname, './sheet.json'), JSON.stringify(json));
console.log('Generated sheet.json');
});
});
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
Most notably this requires your Google Sheet to be publicly viewable.