How do I deploy a Google Script project so that all my Google Sheets can access it? - google-apps-script

I have a simple VIN Decoder script that I built for my Vehicle DB Sheet. I want to allow other sheets to use the functions defined in the script without copying the code to the script containers for each spreadsheet. I guess I essentially want to have a private (to my account or domain) add-on. I have tried reading about how to deploy an add-on to Google Workplace but all the tutorials are either old or just provide sample code that doesn't answer how to do it. I am sure this is not a huge project to deploy this code as an add-on. Anyone?
Here is the code I am trying to deploy...
const nhtsaGateway = 'https://vpic.nhtsa.dot.gov/api/';
const nhtsaVINDecode = '/vehicles/DecodeVin/';
function decodeVIN(theVIN,theVariable) {
var response, jsonData, retValue, success;
success = false;
if (typeof(theVIN) === 'undefined') {
theVIN = 'WD4PF0CD3KP053982';
Logger.log('No VIN Submitted -- Assuming this is a test\nUsing Test VIN = [' + theVIN + ']');
}
response = UrlFetchApp.fetch(nhtsaGateway + nhtsaVINDecode + theVIN +'?format=JSON');
jsonData = JSON.parse(response.getContentText());
Logger.log(jsonData.Message);
if (typeof(theVariable) === 'undefined') {
Logger.log(jsonData);
return(jsonData);
}
jsonData.Results.every(function(element, index) {
Logger.log('<<<' + index + '>>>');
Logger.log(element.Value);
Logger.log(element.ValueId);
Logger.log(element.Variable);
Logger.log(element.VariableId);
if (element.Variable === theVariable) {
Logger.log('Found theVariable = ' + element.Variable);
retValue = element.Value;
success = true;
return (false);
} else {
return (true);
}
})
if (success) {
Logger.log(retValue);
return (retValue);
} else {
Logger.log('We should not be here --> ' + theVariable + ' <-- is not defined in the NHTSA response.');
}
}
function vinYear(theVIN) {return (decodeVIN(theVIN,'Model Year'))}
function vinMake (theVIN) {return (decodeVIN(theVIN,'Make'))}
function vinSeries (theVIN) {return (decodeVIN(theVIN,'Series'))}
function vinModel (theVIN) {return (decodeVIN(theVIN,'Model'))}
function vinGVWR (theVin) {return (decodeVIN('1FTYR2CM2KKB15306', 'Gross Vehicle Weight Rating From'))}
So the usage in the target spreadsheet would be this formula in a cell
=vinModel("1FTYR2CM2KKB15306")

you don't need to make an addon or an extension, just a library:
https://developers.google.com/apps-script/guides/libraries

Try adding the name of the library in front of your function after adding the library, e.g.
Mylibrary.decodeVIN()

Related

How to get all pages of a Google site with many pages

I have a Google site and I use Google Apps Script to get all the pages of the site and export their data to JSON format.
I use the getAllDescendants function with a code similar to this:
function getAllSitePages(site) {
var result = [], i = 0;
while(true) {
var pages = site.getAllDescendants({start: i});
if(!pages || pages.length == 0) break;
result = result.concat(pages);
i += pages.length;
};
return result;
}
But this only gets me the first 891 (?!) pages. If my sites has around 1000 pages, is there a way to get all of them with the Sites Service?
For now, I was able to bypass the problem by using the getChildren function instead (as I currently don't have any page (including root), that has more than 800 direct children):
function getAllSitePages(root, result) {
result = result || []
var start = 0;
while (true) {
var pages = root.getChildren({ start });
if (!pages || pages.length == 0) break;
result.push(...pages);
pages.forEach(page => getAllSitePages(page, result));
start += pages.length;
};
return result;
}

Getting past permissions for a file through the API/Apps Script

I'm trying to create a list of files stored in my Google Drive and also a list of their current and previous permissions. Specifically, I want to create a list of files in my Google Drive which at any point in the past have had the 'Anyone with a link can view/edit (etc)' permission set.
I have created a Google Apps Script to do this and I can iterate through all the files OK and I can get files which currently have that permission set, but I can't see a way to get the history of the file's permissions.
I have found and activated the revisions list API: https://developers.google.com/drive/api/v2/reference/revisions/list
This gets revisions but I can't see anywhere that it lists the sharing history of a revision.
Is what I'm attempting to do possible?
It's definitely possible using the Drive Activity API. You can use the Quickstart for Google Apps Script to view all the activity of an item (file or folder) or done by a User. In this case I modified the Quickstart to show the Permissions changes of a given Drive Id.
function listDriveActivity() {
var request = {
itemName: "items/1bFQvSJ8pMdss4jInrrg7bxdae3dKgu-tJqC1A2TktMs", //Id of the file
pageSize: 10};
var response = DriveActivity.Activity.query(request);
var activities = response.activities;
if (activities && activities.length > 0) {
Logger.log('Recent activity:');
for (var i = 0; i < activities.length; i++) {
var activity = activities[i];
var time = getTimeInfo(activity);
var action = getActionInfo(activity.primaryActionDetail);
var actors = activity.actors.map(getActorInfo);
var targets = activity.targets.map(getTargetInfo);
if (action == "permissionChange"){ //Only show permissionChange activity
Logger.log(
'%s: %s, %s, %s', time, truncated(actors), action,
truncated(targets));
}
}
} else {
Logger.log('No activity.');
}
}
/** Returns a string representation of the first elements in a list. */
function truncated(array, opt_limit) {
var limit = opt_limit || 2;
var contents = array.slice(0, limit).join(', ');
var more = array.length > limit ? ', ...' : '';
return '[' + contents + more + ']';
}
/** Returns the name of a set property in an object, or else "unknown". */
function getOneOf(object) {
for (var key in object) {
return key;
}
return 'unknown';
}
/** Returns a time associated with an activity. */
function getTimeInfo(activity) {
if ('timestamp' in activity) {
return activity.timestamp;
}
if ('timeRange' in activity) {
return activity.timeRange.endTime;
}
return 'unknown';
}
/** Returns the type of action. */
function getActionInfo(actionDetail) {
return getOneOf(actionDetail);
}
/** Returns user information, or the type of user if not a known user. */
function getUserInfo(user) {
if ('knownUser' in user) {
var knownUser = user.knownUser;
var isMe = knownUser.isCurrentUser || false;
return isMe ? 'people/me' : knownUser.personName;
}
return getOneOf(user);
}
/** Returns actor information, or the type of actor if not a user. */
function getActorInfo(actor) {
if ('user' in actor) {
return getUserInfo(actor.user)
}
return getOneOf(actor);
}
/** Returns the type of a target and an associated title. */
function getTargetInfo(target) {
if ('driveItem' in target) {
var title = target.driveItem.title || 'unknown';
return 'driveItem:"' + title + '"';
}
if ('drive' in target) {
var title = target.drive.title || 'unknown';
return 'drive:"' + title + '"';
}
if ('fileComment' in target) {
var parent = target.fileComment.parent || {};
var title = parent.title || 'unknown';
return 'fileComment:"' + title + '"';
}
return getOneOf(target) + ':unknown';
}
Remember to enable the Drive Activity API in Resources > Advanced Google Services
In my example this returns the logs:
You can also look deeper into the Permissions by using the permissionChange Parameters in the query.
If you have a business/enterprise/edu account the admin audit logs will tell you this for 6 months of data. Or it will at least tell you when a permission was changed from x to y.
Can't think of a method for personal.

Scrape site to report css selector occurrence in HTML

I want to see how much of my team's code has been integrated into a large scale site.
I believe I can achieve this (albeit roughly), by getting statistics on the number of occurrences certain CSS selectors appear across all the HTML pages. I have some unique CSS class selectors that I would like to use when scraping the site to analyze:
On how many pages the selector occurs.
On any page it does, how many times.
I've looked around but can't find any tools - does anyone know of any, or could suggest any idea's that may help me quickly achieve this ?
Thanks in advance.
Thanks to everyone for their advice.
In the end I decided that there was no one tool that could help me gather the statistics in the way I described so I already started to build up the application I needed in Node. Although I've not used Node before I've found it quick to grasp with an intermediate knowledge of Javascript.
For anyone looking to do the same:
I've used Simplecrawler to run over the site and Cheerio to find selectors and from this I can create a simple report created in Json using FS.
I'd recommend you to use Google App Scripting. You might manage to crawl site's pages and count the CSS selector occurrences with regex. Modify he following code to search each page for CSS selector. The code explanation is here.
Code
function onOpen() {
DocumentApp.getUi() // Or DocumentApp or FormApp.
.createMenu('New scrape web docs')
.addItem('Enter Url', 'showPrompt')
.addToUi();
}
function showPrompt() {
var ui = DocumentApp.getUi();
var result = ui.prompt(
'Scrape whole website into text!',
'Please enter website url (with http(s)://):',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var url = result.getResponseText();
var links=[];
var base_url = url;
if (button == ui.Button.OK) { // User clicked "OK".
if(!isValidURL(url))
{
ui.alert('Your url is not valid.');
}
else {
// gather initial links
var inner_links_arr = scrapeAndPaste(url, 1); // first run and clear the document
links = links.concat(inner_links_arr); // append an array to all the links
var new_links=[]; // array for new links
var processed_urls =[url]; // processed links
var link, current;
while (links.length)
{
link = links.shift(); // get the most left link (inner url)
processed_urls.push(link);
current = base_url + link;
new_links = scrapeAndPaste(current, 0); // second and consecutive runs we do not clear up the document
//ui.alert('Processed... ' + current + '\nReturned links: ' + new_links.join('\n') );
// add new links into links array (stack) if appropriate
for (var i in new_links){
var item = new_links[i];
if (links.indexOf(item) === -1 && processed_urls.indexOf(item) === -1)
links.push(item);
}
/* // alert message for debugging
ui.alert('Links in stack: ' + links.join(' ')
+ '\nTotal links in stack: ' + links.length
+ '\nProcessed: ' + processed_urls.join(' ')
+ '\nTotal processed: ' + processed_urls.length);
*/
}
}
}
}
function scrapeAndPaste(url, clear) {
var text;
try {
var html = UrlFetchApp.fetch(url).getContentText();
// some html pre-processing
if (html.indexOf('</head>') !== -1 ){
html = html.split('</head>')[1];
}
if (html.indexOf('</body>') !== -1 ){ // thus we split the body only
html = html.split('</body>')[0] + '</body>';
}
// fetch inner links
var inner_links_arr= [];
var linkRegExp = /href="(.*?)"/gi; // regex expression object
var match = linkRegExp.exec(html);
while (match != null) {
// matched text: match[0]
if (match[1].indexOf('#') !== 0
&& match[1].indexOf('http') !== 0
//&& match[1].indexOf('https://') !== 0
&& match[1].indexOf('mailto:') !== 0
&& match[1].indexOf('.pdf') === -1 ) {
inner_links_arr.push(match[1]);
}
// match start: match.index
// capturing group n: match[n]
match = linkRegExp.exec(html);
}
text = getTextFromHtml(html);
outputText(url, text, clear); // output text into the current document with given url
return inner_links_arr; //we return all inner links of this doc as array
} catch (e) {
MailApp.sendEmail(Session.getActiveUser().getEmail(), "Scrape error report at "
+ Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd HH:mm:ss"),
"\r\nMessage: " + e.message
+ "\r\nFile: " + e.fileName+ '.gs'
+ "\r\nWeb page under scrape: " + url
+ "\r\nLine: " + e.lineNumber);
outputText(url, 'Scrape error for this page cause of malformed html!', clear);
}
}
function getTextFromHtml(html) {
return getTextFromNode(Xml.parse(html, true).getElement());
}
function getTextFromNode(x) {
switch(x.toString()) {
case 'XmlText': return x.toXmlString();
case 'XmlElement': return x.getNodes().map(getTextFromNode).join(' ');
default: return '';
}
}
function outputText(url, text, clear){
var body = DocumentApp.getActiveDocument().getBody();
if (clear){
body.clear();
}
else {
body.appendHorizontalRule();
}
var section = body.appendParagraph(' * ' + url);
section.setHeading(DocumentApp.ParagraphHeading.HEADING2);
body.appendParagraph(text);
}
function isValidURL(url){
var RegExp = /^(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?#)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?$/;
if(RegExp.test(url)){
return true;
}else{
return false;
}
}

Building drive app from apps script - Whats wrong in the below code

Below is the code taken from Arun Nagarajan's Example: I am tried the same code to check.. But Its not installing properly. (I removed my redirect url, client id and secret in the below). Please tell me what wrong in the below code.
var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth';
var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token';
var REDIRECT_URL = 'exec';
var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN';
var CLIENT_ID = '';
var CLIENT_SECRET = '';
function doGet(e) {
var HTMLToOutput;
if(e.parameters.state){
var state = JSON.parse(e.parameters.state);
if(state.action === 'çreate'){
var meetingURL = createMeetingNotes();
HTMLToOutput = '<html><h1>Meeting notes document created!</h1> <click here to open</html>';
}
else if (state.ids){
var doc = DocsList.getFileById(state.ids[0]);
var url = doc.getContentAsString();
HTMLToOutput = '"<html><a href="' +url+'"</a></html>"';
}
else {
zipAndSend(state.ecportIds.Session.getEffectUser().getEmail());
HTMLToOutput = '"<html><h1>Email sent. Check your Inbox.</h1></html>"';
}
}
else if(e.parameters.code){
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>App is installed. You can close this window now or navigate to your </h1>Google Drive</html>';
}
else {
HTMLToOutput = '<html><h1>Install this App into your google drive </h1>Click here to start install</html>';
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
function getURLForAuthorization() {
return AUTHORIZE_URL + '?response_type=code&client_id=' + CLIENT_ID + '&redirect_uri=' + REDIRECT_URL + '&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email';
}
function getAndStoreAccessToken(code) {
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
var tokenResponse = JSON.parse(response);
UserProperties.getProperty(tokenPropertyName, tokenResponse.access_token);
}
function getUrlFetchOptions() {
return {'contentType' : 'application/json',
'headers' : {'Authorization': 'Bearer ' + UserProperties.getProperty(tokenPropertyName),
'Accept' : 'application/json'}};
}
function IsTokenValid() {
return UserProperties.getProperty(tokenPropertyName);
}
The error showing is: Bad request:undefined
I think the error is inside the function called : getAndStoreAccessToken.
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
Please tell me the correct url format for payload.
The error seems in this line -
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
I think you want TOKEN_URL , parameters (note the comma)
First, if you are trying to access Google Drive from within google apps script, what is the purpose of the authorization? Google drive is available w/o authorization. Are you trying to make your application utilize the gDrive of other users (or on behalf of other users)?
Second, instead of manually performing the authorization, which is very hard to troubleshoot, you can take advantage of Class OAuthConfig which simplifies the authorization/request process. The only disadvantage is that OAuthConfig currently uses OAuth1.0 (which is currently deprecated). Although it's particular use is Fusion Tables, and not drive, this library makes great use of OAuthConfig and .fetch and I have used it to model my own OAuth functions. My example below works great. The googleAuth() function sets up the authorization and then the rest of the application can make authorized requests using UrlFetchApp.fetch(url,options) while google does all the authorization stuff in the background.
function googleAuth(oAuthFields) {
var oAuthConfig = UrlFetchApp.addOAuthService(oAuthFields.service);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/"+
"OAuthGetRequestToken?scope=" + oAuthFields.scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey(oAuthFields.clientId);
oAuthConfig.setConsumerSecret(oAuthFields.clientSecret);
return {oAuthServiceName:oAuthFields.service, oAuthUseToken:"always"};
}
function fusionRequest(methodType, sql, oAuthFields, contentType) {
var fetchArgs = OAL.googleAuth(oAuthFields);
var fetchUrl = oAuthFields.queryUrl;
fetchArgs.method = methodType;
if( methodType == 'GET' ) {
fetchUrl += '?sql=' + sql;
fetchArgs.payload = null;
} else{
fetchArgs.payload = 'sql='+sql;
}
if(contentType != null) fetchArgs.contentType = contentType;
Logger.log(UrlFetchApp.getRequest(oAuthFields.queryUrl, fetchArgs));
var fetchResult = UrlFetchApp.fetch(oAuthFields.queryUrl, fetchArgs);
if( methodType == 'GET' ) return JSON.parse(fetchResult.getContentText());
else return fetchResult.getContentText();
}

google script accessing a contact's custom label for an email address

(new to Google Script {about 2 weeks} so please be verbose)
I have tried everything I could find or think of to display the custom labels for a contact's list of email address. This has been very frustrating. When I search I get a lot of hits for gmail message labels, but nothing for custom labels for the email address within an individual contact.
Long term goal is to build an auto forwarder for my son’s Boy Scout Troop, taking baby steps to get there.
Some of the boys want to be notified by SMS, some by email. Since some have actual emails (used with attachments) and mobile phones (used for reminders), there is a need for custom contact email labels. I can make a list of all of the contact groups and I can make a list of all of the contact names within each group. I can even get all of the email address for each contact. But I can’t get the custom labels for a contact’s list of emails.
It is beyond me why the “getLabel” method does not do all of the “behind the curtain” work and return the label text regardless of the label type (custom or built in).
Any guidance would be appreciated.
function Get_Groups_Contacts(GroupList)
{
var groups = ContactsApp.getContactGroups(); //get the list of groups
for (var i in groups) //for each item in the group list ...
{
for (var n=0; n<=15; n++) //need to setup retries since the next part sometimes has server issues
{
try //trap errors
{
var cont = groups[i].getContacts() //get the list of contacts that belong to the group
var arrCont= [] //define the temp storage
for (var j in cont) //step through each contact
{
//I can store a list of contact names…
arrCont.push(cont[j].getFullName() ); //get the contact's full name
// but am trying to switch to specific email address….
var eml = cont[j].getEmails(); // the list of all email address for a contact
//now the messy part, trying to figure things out
//lists the built in labels but not the custom labels
for (k in eml) Logger.log(typeof eml[k].getLabel() + "??" + eml[k].getLabel() + "--" + eml[k].getAddress());
for (k in eml)
{
try
{
var x = eml[k].getLabel();
Logger.log(k + " !!" + typeof x + "??" + x + "--" + eml[k].getAddress() + "**" + "hello");
// var oneeml = eml[k];
var oneeml = cont[j].getEmails("Other");
Logger.log("xxxxxxxxxxxxxxxxxxxxxxxx");
Logger.log("oneeml " + oneeml);
//Logger.log(oneeml.getLabel())
Logger.log("zzzzzzzzzzzzzzzzzzzzzzzz");
for (zz in oneeml) Logger.log(oneeml[zz]);
for (zz in oneeml)
if (zz == "setAsPrimary") Logger.log(zz)
else if (zz == "setDisplayName") Logger.log(zz)
else if (zz == "setAddress") Logger.log(zz)
else if (zz == "setLabel") Logger.log(zz)
else if (zz == "deleteEmailField") Logger.log(zz)
else Logger.log(oneeml[zz]())
;
}
catch(ext)
{
Logger.log("inner catch");
Logger.log(ext.message);
}
}
}
//end of the messy part
GroupList[groups[i].getGroupName()] = arrCont //store the list in the property
break; //go on to the next group
} //end of try
catch(err) //catch the error here
{
// Logger.log (n + " error message" + err.message); //debug
Logger.log ("n=" + n); //debug
sleep((Math.pow(2,n) + (Math.random(0, 1000) / 100)) ); //increasing random sleep time waiting for the server
} //end of catch
finally //always do this part
{
cont = undefined; //cleanup
arrCont = undefined; //cleanup
} //end of error traping
} //end of retry loop
} //end for each group item
}; //end of function List_Groups_Contacts()