DuckDuckGo has a pretty good api for getting short abstracts given a query. I figured that it might be nice to use this to demonstrate how the caching can be used to pass in lieu of not having access to the copy/paste buffer in Google Docs.
There is also a video version of this post if you prefer.
http://youtube.com/watch?v=onNDUD2gxgwThis example is container bound and starts with a document that has some text in it. I’m using some text from the wikipedia entry on the oscars – here’ s what it looks like.
So let’s say that you want to add some stuff to a document that references some of it’s content. In this case, I want to be able to select the name of the film, do something, and have some additional info about the film added to my document. Let’s take ‘slumdog millionaire’.
First I select the text I want an abstract of.
Then I take the menu option
Then I go to where I want the information to be placed in the document – I’m choosing at the end – and take the other menu option.
And this gets inserted
Here’s how it works
Adding the menu options
Of course this could easily be developed as an Add-on, but since it’s just for playing around with, I’m adding it as a custom menu item in a container document.
function onOpen() { DocumentApp.getUi() .createMenu('Abstracts') .addItem('Get abstract', 'getAbstract') .addItem('Insert abstract', 'insertAbstract') .addToUi(); }
Using Cache
Cache is used for two purposes – when an abstract is retrieved from the DuckDuckGo API, it stores it in cache in case the same query is made later. The result of the latest query is also stored in the document cache so it can be used for copy pasting.
The code
The test document and code are available at this link.
Just make a copy.
Here’s the complete solution.
function getAbstract() { // get text from current selection var query = getAbstractQuery(); // do the query and write it to cache var result = Abstract.getDuck (query); // kind of copy paste but using cache Abstract.copy (result); } function insertAbstract() { var result = Abstract.paste(); if (result) { result.blob = Abstract.getBlob (result); Abstract.insertAbstract(result); } } /** * will get the active selection * create an abstract * write it to cache */ function getAbstractQuery() { // concat all the text in a range // var c=DocumentApp.getActiveDocument().getSelection().getRangeElements()[0] var selection = DocumentApp .getActiveDocument() .getSelection() return selection ? selection .getRangeElements() .reduce(function(p,c) { return p + getElementText ( c.getElement(), c.isPartial(), c.getStartOffset(), c.getEndOffsetInclusive() ); },'') : '' ; /** * display elements and their children * @param {Element} elem the element * @param {boolean} [partial=false] element is only partial * @param {number} [start] start of partial element * @param {number} [finish] finish of partial element */ function getElementText (elem ,partial,start,finish) { try { // not all elements can be cast as text return partial ? elem.asText().getText().slice (start, finish+1) : elem.asText().getText(); } catch(err) { return ''; } } } /** * gets an abstract of the active selection and stores it to cache */ var Abstract = Object.create(null, { settings: { value:{ PROVIDER:"https://api.duckduckgo.com/", HEADING_LEVEL:DocumentApp.ParagraphHeading.HEADING3, CACHE_KEY:'abstract-duck', IMAGE_WIDTH:100, COPY_CACHE:CacheService.getDocumentCache(), DUCK_CACHE:CacheService.getScriptCache(), MAX_SIZE:100000 } }, paste: { value: function () { // kind of copy past but using cache var result = this.settings.COPY_CACHE.get (this.settings.CACHE_KEY); return result ? JSON.parse(result) : null; } }, copy: { value: function (value) { // kind of copy past but using cache this.settings.COPY_CACHE.put ( this.settings.CACHE_KEY, JSON.stringify(value) ); } }, /** * insert an item at current position */ insertAbstract: { value:function (item) { var doc = DocumentApp.getActiveDocument(); var cursor = doc.getCursor(); if(!cursor) return null; var target = cursor.getElement(); var parent = target.getParent().asBody(); var body = doc.getBody(); // insert the heading parent.insertParagraph( parent.getChildIndex(target), item.query ) .setHeading(this.settings.HEADING_LEVEL); // insert the image if (item.blob) { var image = parent.insertImage(parent.getChildIndex(target),item.blob); this.imageScale(image,this.settings.IMAGE_WIDTH); if (item.url) { image.setLinkUrl (item.url); } } // insert the paragraph parent.insertParagraph(parent.getChildIndex(target), item.abstract); } }, /** * scale to given width * @param {InlineImage} inlineImage the image * @param {number} width the target width * @return {InlineImage} for chaining */ imageScale: { value:function (inlineImage, width) { inlineImage.setHeight( inlineImage.getHeight() * width / inlineImage.getWidth() ); inlineImage.setWidth(width); return inlineImage; } }, /** * execute a duckduckgo query and return cached abstract * @param {string} query the string to search duckduckgo.com on * @param {boolean} [useCache=true] whether to attempt to use cache * @return {object} the query abstract and image */ getDuck:{ value:function (query, useCache) { // use cache? var useCache = typeof useCache === typeof undefined ? true : useCache; var package = {}; if (query) { var url = this.settings.PROVIDER + "?q=" + encodeURIComponent(query) + "&format=json"; // see if its in cache var cacheResponse = this.settings.DUCK_CACHE.get(url); if (!cacheResponse) { // if its not there, then do a query var response = UrlFetchApp.fetch (url); if (response.getResponseCode() === 200) { var result = response.getContentText(); // convert data var data = JSON.parse (result); // get and clean the image url var imageUrl = data.Image ? data.Image.replace(/['"]+/g, '') : ''; // get the image blob var img = imageUrl ? this.getImageFromUrl(imageUrl) : null; // convert to b64 var b64 = img ? Utilities.base64Encode(img.getBytes()) : ''; package = { abstract: data.Abstract ? data.Abstract : 'no abstract found', image:imageUrl, url:data.Results && data.Results.length && Array.isArray(data.Results) ? data.Results[0].FirstURL : '', imageB64: b64, query:query }; // cacheable var packageString = JSON.stringify(package); if (packageString.length > this.settings.MAX_SIZE) { package.tooBig = true; package.imageB64 = ''; packageString = JSON.stringify(package); } this.settings.DUCK_CACHE.put (url, JSON.stringify(package)); } } else { package = JSON.parse(cacheResponse); } } return package; } }, getBlob: { value:function (package) { // we already have it if (!package.tooBig) { return package.imageB64 ? Utilities.newBlob( Utilities.base64Decode(package.imageB64)) : null; } else { return package.image ? this.getImageFromUrl(package.image) : null; } } }, /** * get an image from a url * @param {string} imageUrl the image url * @return {Blob|null} te image */ getImageFromUrl: { value:function (imageUrl) { if (!imageUrl) return null; return UrlFetchApp.fetch(imageUrl).getBlob() } } });
Of course you can use this for any subject matter that has been indexed by DuckDuckGo. This article is adapted from my Going Gas book.
Why not join our forum, follow the blog or follow me on twitter to ensure you get updates when they are available.