How do I use momentsjs in Google Apps Script? - google-apps-script

I'm trying to utilize the momentjs library in Google Apps Script but I'm not clear on how to do so. I'm not sure how to add the library, so obviously running something like the following results in "Reference Error: 'moment' is not defined":
var a = moment([2007, 0, 29]);
var b = moment([2007, 0, 28]);
var difference = a.diff(b);

Most people try to use the library with the key ending in 48. That library is pretty dated (it is version 2.9 which is pretty old).
Using eval and UrlFetchApp.fetch moment.js or any other external library can be used easily in google app scripts.
function testMoment() {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var date = moment().format("MMM Do YY");
Logger.log(date)
}
You may either host the moment.js on your own server, or use a CDN like cloudflare CDN to reference the library.
For cloudflare, here is the page which shows moment.js versions and their urls:
https://cdnjs.com/libraries/moment.js/
As of writing this post 2.18.1 is the latest version.
For the example posted by OP it will look like this:
function testMomentDifference() {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var a = moment([2007, 0, 29]);
var b = moment([2007, 0, 28]);
var difference = a.diff(b);
Logger.log(difference);
}

The moment script ID for the Google Apps Script IDE has changed. It is now "15hgNOjKHUG4UtyZl9clqBbl23sDvWMS8pfDJOyIapZk5RBqwL3i-rlCo"

You can add moment and moment.tz to app scripts by creating a new Script file and adding the following code:
var cacheExpire = 3600;
var momentCache = "momentCache";
var momentUrl = "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js"
var momentTzCache = "momentTzCache";
var momentTzUrl = "https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.16/moment-timezone-with-data-2012-2022.min.js"
useCachedOrLive(momentCache,momentUrl);
useCachedOrLive(momentTzCache,momentTzUrl);
function useCachedOrLive(cacheToCheck, url){
var cache = CacheService.getUserCache();
var cachedData = cache.get(cacheToCheck);
console.log(cacheToCheck);
if(cachedData !== null){
console.log("using cached " + cacheToCheck)
eval(cachedData);
}
else
{
console.log("getting live " + cacheToCheck);
var response = UrlFetchApp.fetch(url).getContentText();
cache.put(cacheToCheck, response, cacheExpire);
eval(response);
}
}
This uses the cache service to reduce round trip calls and you can modify it to include a subset of data if you want.
Thanks to apadana for getting me started!

There is a better and best way to use moment
Do not use UrlFetchApp, to avoid quota exceeded, caching, and server issues
Download moment.min.js and momemt-timzone.min.js in last versions
and integrate the full files in apps script like the below screen
There is no problems in long run with this approach, just update the files any time when you need.
After adding the two files, just publish a new version and include it in any other script
For example:
I will create a script with name "MomentAPI" and include the two
files mentioned, and publish a new version.
in other script with name "myScript" I will include the library
"MomentAPI" with its script id as known
then will use it like the below examples
const moment = MomentAPI.moment; // init the library
const start = moment().startOf('day').toDate(); // Dec 06 00:00:00
const end = moment().endOf('day').toDate(); // Dec 06 23:59:59
const d = moment(1767139200000).tz('America/New_York').format('ha'); // 7am EST

Using external Javascript library is not so easy... Depending on the context in which you want to use it (a webapp of a document embedded script) the approach will be different.
I didn't try it in client JavaScript and I'm not sure caja will allow it but I found this post that shows a possible way to include it using a Google Script Library that a user has build and if I read the post it seems to work...
The "user" is a Google developper so he knows for sure what he is talking about ;) please update here if it worked for you.

Related

Why is this Importxml formula not working?

The following formula does work for some, but not for others:
=IFNA(VALUE(IMPORTXML("https://finance.yahoo.com/quote/C2PU.SI", "//*[#class=""D(ib) Mend(20px)""]/span[1]")))
If used without IFNA, it says 'Resource at url not found'.
Here's the value I'm trying to pull in:
I appreciate if you could point me to the right direction.
Thank you!
It does not return any values even for simple importxml.
It seems the site is generated by javascript or protected so it can't be scraped by importxml.
Don't use the "inspect" tool as it will show the DOM as it's being rendered by the web browser including modifications to the source code by client-side JavaScript, instead look at the source code.
Resources
How to know if Google Sheets IMPORTDATA, IMPORTFEED, IMPORTHTML or IMPORTXML functions are able to get data from a resource hosted on a website?
The structure of the DOM is generated by javascript. Nevertheless, all informations you need are contained by a json string called here root.App.main. You can get all the data by these way
function extract(url){
var source = UrlFetchApp.fetch(url).getContentText()
return source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
}
and then retrieve the data by conventionnal json parsing. This will give you the value
[![function marketPrice() {
var code = 'C2PU.SI'
var url='https://finance.yahoo.com/quote/' + code
var source = UrlFetchApp.fetch(url).getContentText()
var jsonString = source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
var data = JSON.parse(jsonString)
var regularMarketPrice = data.context.dispatcher.stores.StreamDataStore.quoteData.item(code).regularMarketPrice.raw
Logger.log(regularMarketPrice)
}
Object.prototype.item=function(i){return this\[i\]};][1]][1]

Return Collection of Google Drive Files Shared With Specific User

I'm trying to get a collection of files where user (let's use billyTheUser#gmail.com) is an editor.
I know this can be accomplished almost instantly on the front-end of google drive by doing a search for to:billyTheUser#gmail.com in the drive search bar.
I presume this is something that can be done in Google App Scripts, but maybe I'm wrong. I figured DriveApp.searchFiles would work, but I'm having trouble structuring the proper string syntax. I've looked at the Google SDK Documentation and am guessing I am doing something wrong with the usage of the in matched to the user string search? Below is the approaches I've taken, however if there's a different method to accomplishing the collection of files by user, I'd be happy to change my approach.
var files = DriveApp.searchFiles(
//I would expect this to work, but this doesn't return values
'writers in "billyTheUser#gmail.com"');
//Tried these just experimenting. None return values
'writers in "to:billyTheUser#gmail.com"');
'writers in "to:billyTheUser#gmail.com"');
'to:billyTheUser#gmail.com');
// this is just a test to confirm that some string searches successfully work
'modifiedDate > "2013-02-28" and title contains "untitled"');
Try flipping the operands within the in clause to read as:
var files = DriveApp.searchFiles('"billyTheUser#gmail.com" in writers');
Thanks #theAddonDepot! To illustrate specifically how the accepted answer is useful, I used it to assist in building a spreadsheet to help control files shared with various users. The source code for the full procedure is at the bottom of this post. It can be used directly within this this google sheet if you copy it.
The final result works rather nicely for listing out files by rows and properties in columns (i.e. last modified, security, descriptions... etc.).
The ultimate purpose is to be able to update large number of files without impacting other users. (use case scenario for sudden need to immediately revoke security... layoffs, acquisition, divorce, etc).
//code for looking up files by security
//Posted on stackoverlow here: https://stackoverflow.com/questions/62940196/return-collection-of-google-drive-files-shared-with-specific-user
//sample google File here: https://docs.google.com/spreadsheets/d/1jSl_ZxRVAIh9ULQLy-2e1FdnQpT6207JjFoDq60kj6Q/edit?usp=sharing
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FileList");
const clearRange = true;
//const clearRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("ClearRange").getValue();
//if you have the named range setup.
function runReport() {
//var theEmail= SpreadsheetApp.getActiveSpreadsheet().getRangeByName("emailFromExcel").getValue();
//or
var theEmail = 'billyTheUser#gmail.com';
findFilesByUser(theEmail);
}
function findFilesByUser(theUserEmail) {
if(clearRange){
ss.getDataRange().offset(1,0).deleteCells(SpreadsheetApp.Dimension.ROWS)
}
var someFiles = DriveApp.searchFiles('"' + theUserEmail + '" in writers');
var aListOfFiles = []
while(someFiles.hasNext()){
var aFile = someFiles.next();
aListOfFiles.push([aFile.getId()
,aFile.getName()
,aFile.getDescription()
,aFile.getSharingAccess()
,aFile.getSharingPermission()
,listEmails(aFile.getEditors())
,listEmails(aFile.getViewers())
,aFile.getMimeType().replace('application/','').replace('vnd.google-apps.','')
,aFile.getDateCreated()
,aFile.getLastUpdated()
,aFile.getSize()
,aFile.getUrl()
,aFile.getDownloadUrl()
])
}
if(aListOfFiles.length==0){
aListOfFiles.push("no files for " + theUserEmail);
}
ss.getRange(ss.getDataRange().getLastRow()+1,1, aListOfFiles.length, aListOfFiles[0].length).setValues(aListOfFiles);
}
function listEmails(thePeople){
var aList = thePeople;
for (var i = 0; i < aList.length;i++){
aList[i] = aList[i].getEmail();
}
return aList.toString();
}

Google Sheets Advanced Google Services URL Shortener 403 Error: Forbidden

I am trying to create a small application in in Google Sheets to sorten URLs on my personal google account. I am using the following code which I found here: Google Sheets Function to get a shortened URL (from Bit.ly or goo.gl etc.)
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Shorten")
.addItem("Go !!","rangeShort")
.addToUi()
}
function rangeShort() {
var range = SpreadsheetApp.getActiveRange(), data = range.getValues();
var output = [];
for(var i = 0, iLen = data.length; i < iLen; i++) {
//var url = UrlShortener.Url.insert({longUrl: data[i][0]});
var url = UrlShortener.Url.insert({longUrl: 'www.google.com'});
output.push([url.id]);
}
range.offset(0,1).setValues(output);
}
I created a new Google Cloud Project and enabled the URL shortener API in the project and on the Google sheet. The problem is that when I try and run the code I get an err on the line: var url = UrlShortener.Url.insert({longUrl: 'www.google.com'});
error 403, message:forbidden
when i try an execute the rangeShort() function. I have no idea how to fix this. Any ideas would be most appreciated! Thanks!
As it turns out, like Ruben mentioned, Google has moved away from their URL shortener. So after much research ans testing here is the solution:
Step 1
Migrate Google Cloud Project over to Firebase or create a new Firebase Project. See steps here
Step 2
Create a dummy project in order to create a base URL for the shortening. See this youtube video
Step 3
Get the Web API Key from your new Firebase Project (not the app you just created)
Step 4
Check the left side menu on the screen and navigate to Grow->Dynamic Links. You should see the new application you created and a URL at the top of the application. This will become the base of the new shortened URLs.
Step 5
Create the code in Google Apps Script inside the code builder from within Google Sheets. Here is the code that worked for me (I passed the url into this function) (This code is based on the answer found here):
function api_call(url){
var req='https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=[YOUR PROJECT WEB API KEY FROM STEP 3]';
var formData = {
"longDynamicLink": "[YOUR APPLICATION URL BASE FROM STEP 4]?link=" + url,
"suffix" : {
"option" : "UNGUESSABLE"
}
};
var options = {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(formData)
};
var response = UrlFetchApp.fetch(req, options);
var res=JSON.parse(response);
return res.shortLink;
}
Additional Information
Documentation on Creating Dynamic Links in Firebase
Documentation on using UrlFetchApp() in Google Apps Script
If the url shortener service was used in your project before March 30,2018
Instead of
www.google.com
use
https://www.google.com
Reference: https://developers.google.com/url-shortener/v1/url/insert
but if your project was created on or after March 30, 2018
From https://developers.google.com/url-shortener/v1/
Starting March 30, 2018, we will be turning down support for goo.gl URL shortener. Please see this blog post for detailed timelines and alternatives.
Just to be clear, please note, from the linked blog post:
For developers
Starting May 30, 2018, only projects that have accessed URL Shortener
APIs before today can create short links.
I can attest to #alutz's answer here with a small addition/correction to their code.
Use encodeURIcomponent() for the input url while assigning it to the Long Dynamic Link in case you have more than one custom parameters.
"longDynamicLink": "[YOUR APPLICATION URL BASE FROM STEP 4]?link=" + encodeURIcomponent(url),
This allowed me to pass in multiple arguments for my telegram bot like chat_id, text and parse_mode.

Why Google Apps Script throwing an "internal error" for Sites Services?

Here is my script, which basically iterate through drive folder, and put the file's blob as a attachment to some page on google site. It was working fine till day before yesterday, suddenly stop working after that.
function myFunction() {
var testpage = SitesApp.getSiteByUrl(siteURL).getChildByName("test");
var photofolder = DriveApp.getFolderById(folder_ID);
var filesinpf = photofolder.getFiles();
while(filesinpf.hasNext()){
var file = filesinpf.next();
var fblob = file.getBlob();
testpage.addHostedAttachment(fblob); //This line generating an error
}
}
Please help!
I had the same problem yesterday.
I have been working with this for a week, and yesterday I got an internal error in the last line:
function myFunction() {
var myFolder = DriveApp.getFolderById
("0B-ZOMOQnNEDOU9sWEV5SzlXVTQ");
var myFile =
myFolder.getFilesByName("Data.txt").next();
var myBlob = myFile.getBlob();
var myPage =
SitesApp.getSiteByUrl("https://sites.google.com/site/
demo ").getChildByName
("home/demoFileCabinet");
myPage.addHostedAttachment(myBlob);
}
Maybe a problem in Google Sites??. The code is correct.
I created an Issue Tracker too.
You can reproduce this error by using the sample code provided by google:
https://developers.google.com/apps-script/reference/sites/page#addHostedAttachment(BlobSource)
I created an enterprise support Ticket.
Keep you updated: [Case #14128120] Google Sites addHostedAttachment() not working
I got the following response from google:
Let me confirm that this is an issue on our end, filed with issue ID #68842220.
Please notice that our Engineering Team has already found the root cause for this.
At the moment, still, I can't confirm you when it will be fixed, but let me provide you with a simple workaround that will work while we wait for the fix: swap the "domain.com" and the "macros" parts of the URL when entering the URL in the dialog.
Sample, for url:
https://script.google.com/a/domain.com/macros/s/AKfycbwJfGpXIiWHfsCi-j66RuPMNx6kTFsdjYIbNOyufZptGA1tirm6/exec
try
https://script.google.com/a/macros/domain.com/s/AKfycbwJfGpXIiWHfsCi-j66RuPMNx6kTFsdjYIbNOyufZptGA1tirm6/exec

Web Apps Script: No output

I am trying to display a Google Visualization GeoMap on my website. I created the code in the Code Playground and saved it as an Apps Script in my website. The page goes through the load process but nothing is displayed.
Here is the code:
function doGet() {
var app = UiApp.createApplication();
function drawVisualization() {
var query = new google.visualization.Query(
'https://docs.google.com/a/mantisnetworks.co/spreadsheet/ccc?key=0AoNHsySj5NxGdGt0dmxva3ZPb3dLYVpVZ2Z4TThNbGc&usp=drive_web#gid=0');
query.send(handleQueryResponse);
}
function handleQueryResponse(response){
if(response.isError()){
alert('Error in Query:' + response.getMessage()+''+response.getDetailedMessage());
return;
}
var data = response.getDataTable();
var geochart = new google.visualization.GeoMap(document.getElementById('visualization'));
var options = {};
options['dataMode'] = 'regions';
options['resolution'] = 'provinces';
options['region'] = 'LS';
options['width'] = '600px';
options['height'] = '300px';
geochart.draw(data, options);
}
app.close();
return app;
}
Google Apps Script is based on Javascript, but as a server-side environment it does not have access to all client-side javascript constructs. The Google visualizations, for instance, are provided as the Charts Service. Using that service, you'll find support for much of the visualization API. However, you won't find GeoMap.
The code you've provided in your question needs to be reworked considerably to work properly in Google Apps Script. Start with the example given on the Charts Service page, then adapt to your situation.
You do have another alternative within Google Apps Script, which is to use the HTML service to "host" an HTML page containing "real" javascript. Javascript that's embedded in HTML pages can be made to run on the client browser, so the example you cooked up in the playground should work. A full run-down of this option is beyond the scope of your question, but if you're interested in it you could start by scanning previous questions about the HTML Service.