Using private sheets with tabletop.js - google-apps-script

I've used tabletop.js [1] in the past and is amazing! You can simply do anything you want seriously.
The only problem I saw is that you need to publish your spreadsheets to the web, which of course is really risky if you are working with sensitive data.
I'm in need now of using it in a project with sensitive data, so I was hoping someone can guide me on how to use it with spreadsheets that are not published to the web.
I've been searching for this for a long time without any success but seems that tabletop.js does support private sheets (here's the pull request that added this option [2]).
In fact, looking at the documentation they included it [1]:
authkey
authkey is the authorization key for private sheet support.
ASK: How am I suppose to use the authkey? can someone provide me with an example so I can try?
Thanks in advance!
[1] https://github.com/jsoma/tabletop
[2] https://github.com/jsoma/tabletop/pull/64

How about this answer?
Issue and workaround:
At "tabletop.js", from the endpoint (https://spreadsheets.google.com/feeds/list/###/###/private/values?alt=json) of request, it seems that "tabletop.js" uses Sheets API v3. And when authkey is used, oauth_token=authkey is added to the query parameter. In this case, unfortunately, it seems that the private Spreadsheet cannot be accessed with it. From this situation, unfortunately, I thought that in the current stage, "tabletop.js" might not be able to use the private Spreadsheet. But I'm not sure whether this might be resolved in the future update. Of course, it seems that the web-published Spreadsheet can be accessed using this library.
So, in this answer, I would like to propose the workaround for retrieving the values from Spreadsheet as the JSON object.
Pattern 1:
In this pattern, Google Apps Script is used. With Google Apps Script, the private Spreadsheet can be easily accessed.
Sample script:
When you use this script, please copy and paste it to the script editor and run the function myFunction.
function myFunction() {
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const sheetName = "Sheet1"; // Please set the sheet name.
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
const header = values.shift();
const object = values.map(r => r.reduce((o, c, j) => Object.assign(o, {[header[j]]: c}), {}));
console.log(object) // Here, you can see the JSON object from Spreadsheet.
}
I thought that this might be the simple way.
Pattern 2:
In this pattern, the Web Apps created by Google Apps Script is used. When the Web Apps is used, the private Spreadsheet can be easily accessed. Because the Web Apps is created with Google Apps Script. In this case, you can access to the Web Apps from outside by logging in to Google account. And, the JSON object can be retrieved in HTML and Javascript.
Usage:
Please do the following flow.
1. Create new project of Google Apps Script.
Sample script of Web Apps is a Google Apps Script. So please create a project of Google Apps Script. In order to use Document service, in this case, Web Apps is used as the wrapper.
If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.
2. Prepare script.
Please copy and paste the following script (Google Apps Script) to the script editor. This script is for the Web Apps.
Google Apps Script side: Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("index");
}
function getObjectFromSpreadsheet(spreadsheetId, sheetName) {
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
const header = values.shift();
const object = values.map(r => r.reduce((o, c, j) => Object.assign(o, {[header[j]]: c}), {}));
return object;
}
HTML&Javascript side: index.html
<script>
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const sheetName = "Sheet1"; // Please set the sheet name.
google.script.run.withSuccessHandler(sample).getObjectFromSpreadsheet(spreadsheetId, sheetName);
function sample(object) {
console.log(object);
}
</script>
spreadsheetId and sheetName are given from Javascript side to Google Apps Script side. From this situation, in this case, getObjectFromSpreadsheet might be instead of "tabletop.js".
3. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Select "Only myself" for "Who has access to the app:".
In this case, in order to access to the Web Apps, it is required to login to Google account. From your situation, I thought that this might be useful.
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
4. Run the function using Web Apps.
You can test above scripts as follows.
Login to Google account.
Access to the URL of Web Apps like https://script.google.com/macros/s/###/exec using your browser.
By this, you can see the retrieved JSON object at the console.
Note:
When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Related

How to access data on different Google Spreadsheet through Google Apps Script for user without Editor access?

I have 2 Spreadsheets, the 1st will Search on the 2nd spreadsheet with data using a google script function.
I want to keep the 2nd spreadsheet with data to be hidden (no editor access) from a user, but he/she will be able to Search on it via the google script function only.
I'm using google script Openbyurl to do it, but it won't let this user to run the Openbyurl unless he/she has editor access to the 2nd spreadsheet.
how should I deal with this?
Below function is in the 1st Spreadsheet, openByUrl links to 2nd Spreadsheet:
function onSearch(SN) {
var ss = SpreadsheetApp.openByUrl('docs.google.com/spreadsheets/...');
var sheets = ss.getSheets();
// search for data in ss sheets . . .
// return array of found data
}
I believe your goal is as follows.
You have 2 Google Spreadsheets "A" and "B".
You want to make the user retrieve values from Spreadsheet "B" with the script of Spreadsheet "A".
You don't want to share Spreadsheet "B" while Spreadsheet "A" is shared with the user.
In this case, as a workaround, how about accessing Spreadsheet "B" using Web Apps? By this, you can make the user access Spreadsheet "B" without sharing the Spreadsheet with the user. When this is reflected in your script, it becomes as follows.
Flow:
1. For Spreadsheet "B":
Please copy and paste the following script to the script editor of Spreadsheet "B".
function doGet(e) {
var SN = e.parameter.sn; // You can use the value of SN here.
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Your 2nd Spreadsheet.
var sheets = ss.getSheets();
// search for data in ss sheets . . .
// return array of found data
var returnValues = ["sample"]; // Please replace this value for your actual script.
return ContentService.createTextOutput(JSON.stringify(returnValues));
}
2. Deploy Web Apps.
Please run this flow on the script editor of Spreadsheet "B". The detailed information can be seen at the official document.
On the script editor, at the top right of the script editor, please click "click Deploy" -> "New deployment".
Please click "Select type" -> "Web App".
Please input the information about the Web App in the fields under "Deployment configuration".
Please select "Me" for "Execute as".
This is the importance of this workaround.
Please select "Anyone" for "Who has access".
In this case, the user is not required to use the access token. So please use this as a test case.
Of course, you can also access your Web Apps using the access token. Please check this report.
Please click "Deploy" button.
Copy the URL of the Web App. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
3. For Spreadsheet "A":
Please copy and paste the following script to the script editor of Spreadsheet "A". And, set your Web Apps URL. In this case, please give the value to SN.
function onSearch(SN) {
var url = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL here.
var res = UrlFetchApp.fetch(`${url}?sn=${SN}`);
var ar = JSON.parse(res.getContentText()); // This is the returned value from Spreadsheet "B".
// do something.
}
4. Testing:
When you run the script of onSearch, the value of SN is sent to Spreadsheet "B" and run the script of Spreadsheet "B", and the result values are returned. By this flow, you can retrieve the values from Spreadsheet "B" without sharing Spreadsheet "B" with the user.
Note:
In this sample script, when you directly run onSearch, the value of SN is not declared. So please be careful about this.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
My proposed script is a simple script. So please modify it for your actual situation.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Google sheets: is it possible to protect a range of cell such that it can only be modify with a script?

Hi I have a shared google sheet that enters date and time when a button is push. However, once enter I do not want the user to be able to modify the date. I know I can protect a range of cells but in this case I still need the macro/script to be able to enter the time.
Is this possible?
thanks.
I believe your goal as follows.
There are you (owner of Spreadsheet) and users.
Users run the script by clicking a button and the script puts the date value to a cell. At this time, you want to have already protected the cells for putting the date value from the users.
But, when the users click the button, you want to put the date values to the protected cells.
In your situation, Google Apps Script can be used.
From your replying, I could confirm my understanding is correct.
Issue and workaround:
When the script is run by clicking a button on Google Spreadsheet, the script is run as the user who clicked the button. So the authorization for scopes is required to be done as the users. I thought that this might be the answer of your replying I'm confused because would the script have the same permission as the person clicking the button?.
Under this situation, when the script puts a value to the cell protected by the owner, an error like You are trying to edit a protected cell or object. Please contact the spreadsheet owner to remove protection if you need to edit. occurs. So in order to avoid this error, it is considered that when the script is run as the owner, the issue will be avoided.
In this answer, I would like to propose a workaround. This workaround is as follows.
When the user runs the script by clicking a button on Spreadsheet, it runs the script as the owner by using Web Apps. When this workaround is used, please do the following flow.
Usage:
1. Prepare Spreadsheet.
Please create new Spreadsheet and create a button and assign the function of runWithWorkaround to the button. And, please protect the cell "A1" as the user of only owner. In this sample, the target cell is "A1".
2. Prepare script.
Please copy and paste the following script to the script editor of Google Spreadsheet. And, please set the sheet name which has the button.
// This function puts a date to cell "A1".
function putValue() {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("A1").setValue(new Date());
}
// This function is for Web Apps.
function doGet() {
putValue();
return ContentService.createTextOutput();
}
// This function is used for testing "without using this workaround.".
function runWithoutWorkaround() {
putValue();
}
// This function is used for testing "with using this workaround.".
function runWithWorkaround() {
const url = "https://script.google.com/macros/s/###/exec"; // <--- Please replace this URL with your Web Apps URL.
UrlFetchApp.fetch(url, {headers: {authorization: `Bearer ${ScriptApp.getOAuthToken()}`}});
// DriveApp.getFiles() // This comment line is used for automatically detecting the scope "https://www.googleapis.com/auth/drive.readonly" for using Web Apps.
}
3. Deploy Web Apps.
The detail information can be seen at the official document.
On the script editor, at the top right of the script editor, please click "click Deploy" -> "New deployment".
Please click "Select type" -> "Web App".
Please input the information about the Web App in the fields under "Deployment configuration".
Please select "Me" for "Execute as".
This is the important of this workaround.
Please select "Anyone with Google account " for "Who has access".
In this case, the user is required to use the access token for requesting to Web Apps.
Please click "Deploy" button.
When "The Web App requires you to authorize access to your data" is shown, please click "Authorize access".
Automatically open a dialog box of "Authorization required".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Copy the URL of Web App. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
Copy and paste the retrieved Web Apps URL to the above script.
Because the script of Web Apps is modified, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps.
4. Testing.
When above workaround is used, the following result is obtained.
Note:
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
When several users use the button simultaneously, to use the lock service might be suitable. When the lock service is used, the function runWithWorkaround is as follows.
function runWithWorkaround() {
var lock = LockService.getDocumentLock();
if (lock.tryLock(10000)) {
try {
const url = "https://script.google.com/macros/s/###/exec";
UrlFetchApp.fetch(url, {headers: {authorization: `Bearer ${ScriptApp.getOAuthToken()}`}});
} catch(e) {
throw new Error(e);
} finally {
lock.releaseLock();
}
}
// DriveApp.getFiles() // This comment line is used for automatically detecting the scope for using Web Apps.
}
Above sample script is the simple sample script for explaining the methodology of this workaround. Please be careful this. So if you use this workaround, please modify your actual script using this workaround.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Import and parse a HTML file from Drive to Sheets

I have a bunch of HTML files in the google drive, but I need to extract tables from them and put into Gsheets.
So far I saw ImportHTML function but it does not work with the drive link.
How can I import and parse HTML files from my Drive? Thank you
You want to put the values of the table from HTML data using Google Apps Script and/or the built-in functions of Spreadsheet.
The HTML files are put in your Google Drive.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Pattern 1:
In this pattern, IMPORTXML is used for the tables deployed with Web Apps.
Usage:
1. copy and paste the following script to the script editor.
function doGet(e) {
var fileId = e.parameter.id;
var html = DriveApp.getFileById(fileId).getBlob().getDataAsString();
var html = "<sample>" + html.match(/<table[\w\s\S]+?<\/table>/gi).join("") + "</sample>";
return ContentService.createTextOutput(html).setMimeType(ContentService.MimeType.XML);
}
2. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
Select "Anyone, even anonymous" for "Who has access to the app:".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
3. Put the formula.
Please put the following formula to a cell.
=IMPORTXML("https://script.google.com/macros/s/###/exec?id=###fileId###","//tr")
###fileId### is the file ID of HTML file on Google Drive.
Pattern 2:
In this pattern, the HTML tables are retrieved from the HTML data, and the tables are put to the Spreadsheet using Sheets API.
Usage:
1. copy and paste the following script to the script editor.
Please set the variables of fileId, spreadsheetId and sheetName.
function myFunction() {
var fileId = "###"; // Please set the file ID of HTML file.
var spreadsheetId = "###"; // Please set the Spreadsheet ID for putting the values.
var sheetName = "Sheet1"; // Please set the sheet name for putting the values.
// Retrieve tables from HTML data.
var html = DriveApp.getFileById(fileId).getBlob().getDataAsString();
var values = html.match(/<table[\w\s\S]+?<\/table>/gi);
// Put the HTML tables to the Spreadsheet.
var ss = SpreadsheetApp.openById(spreadsheetId);
var sheet = ss.getSheetByName(sheetName);
var sheetId = sheet.getSheetId();
var rowIndex = 0;
values.forEach(function(e) {
var resource = {requests: [{pasteData: {html: true, data: e, coordinate: {sheetId: sheetId, rowIndex: rowIndex}}}]};
Sheets.Spreadsheets.batchUpdate(resource, spreadsheetId);
rowIndex = sheet.getLastRow();
})
}
2. Enable Sheets API.
Please enable Sheets API at Advanced Google services.
3. Run the script.
When you run the function myFunction, the values are retrieved from HTML data and they are put to the Spreadsheet.
Note:
These are the simple sample scripts. So please modify them for your actual situation.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Advanced Google services
spreadsheets.batchUpdate
Unfortunately, from your question, I cannot understand about your actual HTML data. So if an error occurs and this was not the direction you want, I apologize.

Getting data from a public readable Google Sheet in a script

I'm trying to create a Google Script to access a range of cells from a Google Docs spreadsheet that I don't own but is shared. The spreadsheet is shared in a way that means it is publicly readable, but can't be written to. (Normal users can comment, but not edit)
When I try to run this script, I get an error: "You do not have permissions to access the requested document." I've authorized Google Scripts to my account, but that doesn't seem to be enough to do a getValues() call.
Here's my script so far:
function myFunction() {
var spreadsheet = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1g24uo61ITTveZrUGx4ZY5nMoJp-n1NCxq5r4WMhAJU0/");
// I can access the name, this runs fine:
Logger.log(spreadsheet.getName());
SpreadsheetApp.setActiveSpreadsheet(spreadsheet);
SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
// fetch this sheet
sheet = spreadsheet.getActiveSheet();
var range = sheet.getRange(3,3,22,1);
// This next line fails:
var slicesAndCounts = range.getValues();
Logger.log(slicesAndCounts);
}
(The URL is the actual public spreadsheet, so this code should run as is)
Is there some way I can access the values in the spreadsheet from Google Scripts? I've considered trying to make a private copy of the spreadsheet, but I only need it for the duration of the script and I'm not sure if I can create a temporary copy easily. That solution also seems a bit heavy, considering I feel like I should be able to read the spreadsheet given I can access it via my browser or the Google Sheets app.
How about using Sheets API? At SpreadsheetApp, it seems that users cannot retrieve values from the shared spreadsheet. I think that the reason may be due to that the access token cannot use at SpreadsheetApp. In my environment, the same error also occurs. So I would like to propose to use Sheets API. When Sheets API is used, the access token is also used. By this, users can retrieve values from the shared spreadsheet.
In order to use this sample script, please enable Sheets API at Advanced Google Services and API console.
Enable Sheets API v4 at Advanced Google Services
On script editor
Resources -> Advanced Google Services
Turn on Google Sheets API v4
Enable Sheets API v4 at API console
On script editor
Resources -> Cloud Platform project
View API console
At Getting started, click Enable APIs and get credentials like keys.
At left side, click Library.
At Search for APIs & services, input "sheets". And click Google Sheets API.
Click Enable button.
If API has already been enabled, please don't turn off.
If now you are opening the script editor with the script for using Sheets API, you can enable Sheets API for the project by accessing this URL https://console.cloud.google.com/apis/library/sheets.googleapis.com/
About sample script :
The result of this sample is the same to that of your script. The values are retrieved from the range of 3,3,22,1 of 1st sheet in the shared spreadsheet.
In this sample script, it uses file ID of Spreadsheet. In your case, that is 1g24uo61ITTveZrUGx4ZY5nMoJp-n1NCxq5r4WMhAJU0 for https://docs.google.com/spreadsheets/d/1g24uo61ITTveZrUGx4ZY5nMoJp-n1NCxq5r4WMhAJU0/.
In this sample script, a1Notation is used for the range. In your case, getRange(3,3,22,1) is C3:C24.
For this script, when {ranges: "C3:C24"} is used, it means the 1st sheet of the spreadsheet. If you want to retrieve values from more than 2nd sheet, please use the sheet name like sheet2!C3:C24 and sheet3!C3:C24.
Script :
function myFunction() {
var spreadsheetId = "1g24uo61ITTveZrUGx4ZY5nMoJp-n1NCxq5r4WMhAJU0";
var slicesAndCounts = Sheets.Spreadsheets.Values.batchGet(spreadsheetId, {ranges: "C3:C24"}).valueRanges[0].values;
Logger.log(slicesAndCounts);
}
References :
Advanced Google Services : https://developers.google.com/apps-script/guides/services/advanced
Sheets API v4: https://developers.google.com/sheets/api/
If this was not useful for you, I'm sorry.

Add to Google Sheets with a Google Apps Script Web App

I am just getting started with Google Apps Script and I want to be able to add to a Google Sheet through a web app, but I'm having issues. This is what I have so far:
function addRow(input) {
var sheet = SpreadsheetApp.openByUrl('https://spreadsheeturl');
sheet.appendRow([input]);
}
function doGet(e) {
var params = JSON.stringify(e);
addRow(params);
return HtmlService.createHtmlOutput(params);
}
When I type in the URL they give me with the parameters I want to add to the spreadsheet it doesn't add anything to the spreadsheet, but if I don't pass any parameters it adds it to the spreadsheet.
For example, https://script.google.com/.../exec adds {"parameter":{},"contextPath":"","contentLength":-1,"queryString":"","parameters":{}} to the spreadsheet, but https://script.google.com/.../exec?user=jsmith doesn't add anything to the spreadsheet.
Reading the documentation I can see something about URL parameters and event objects, but they give no further information. What's the problem and how can I fix it?
Thanks,
Tomi
I think that your script works. In your script,
When https://script.google.com/.../exec is requested, {"parameter":{},"contextPath":"","contentLength":-1,"queryString":"","parameters":{}} is added to the last row at the 1st sheet of the Spreadsheet.
When https://script.google.com/.../exec?user=jsmith is requested, {"parameter":{"user":"jsmith"},"contextPath":"","contentLength":-1,"queryString":"user=jsmith","parameters":{"user":["jsmith"]}} is added to the last row at the 1st sheet of the Spreadsheet.
So can you confirm the following points on your script editor again?
At Publish -> Deploy as web app, redeploy as a new version for Project version:.
When the scripts for Web Apps was modified, the modified script is required to be redeployed as a new version. By this, the modified script is reflected to Web Apps.
At Publish -> Deploy as web app, it confirms whether Execute the app as: and Who has access to the app: are "me" and "Anyone, even anonymous", respectively.
If I misunderstand your question, I'm sorry.