Publishing / sharing a Google Sheet via Github - google-apps-script

I have a Google Sheet. It has a custom script (read: set of functions) associated with. I'd like to share this sheet template (i.e., tool), not with colleagues (i.e., adding them via email address as is a traditional Google drive share) but with anyone who would like a copy for themselves to use it for themselves (i.e., not my copy, their own copy). Ideally, I'd repo this project / tool on GitHub (or similar) and let them grab it there.
I can "Download As..." the sheet but the script doesn't stay "attached". Are such scripts now what Google considers Add-ons? If so, how to I keep the sheet + script as a "whole".
Also, as a temporary workaround, I tried to copy / paste the script from my working copy to another copy of the sheet (created via Download as and then opened again under a diff Google accnt). However, that didn't go as planned either. There's a function within my scrip that checks to make sure the sheet is on the first tab / sheet (i.e., getActiveSheet().getSheetId() == 0). This works on my dev / working copy. However once I copy / paste the SheetId return a 7 or 8 digit #. Is there a way to keep the SheetId relative to the sheet, and not all sheets (or whatever that Id represents.)
Note: I'm by no means a Google Sheets expert. This was just a side project for myself that I ended up building out to the point of wanting to share it with others. Please presume I know even less than you probably think I know. Thanks :)

To fix your problem with finding the first tab / sheet, use the sheet index instead of the sheet id. So instead of getActiveSheet().getSheetId() == 0, it'll be getActiveSheet().getIndex() == 0 instead.
As for downloading the script - it looks like you've created a container-bound script. There are two types of scripts that you can create, standalone and container-bound scripts. (See Google's explanation here). Standalone scripts are created by going directly to script.google.com, but I'm guessing (please correct me if I've assumed wrong) that you clicked Tools->Script editor, so that the script is locked to that specific spreadsheet. That's fine, but a) it means like you can't download it separately through Google Drive as I originally suggested, and b) when you're download the sheet, it downloads it as an Excel worksheet (which doesn't support Google App Script, so of course the script doesn't come with it).
Here's my suggestion for your use case:
Instead of downloading the spreadsheet, make a new copy of it named yourspreadsheet_public or something along those lines by going to File -> Make a Copy. (You can potentially skip this step if you just want to make your personal spreadsheet available to the world.)
Make the copy available to anyone to view by clicking Share -> Change -> Anyone with the Link (or Public on the Web) -> select "Can View" from the dropdown -> Save.
Now, you can distribute the link to whoever wants it. Anyone who has access to the file is able to make a copy in the same way you did in the first step to their own Google Drive, where they'll be able to edit their own private copy, including your script.
Let me know if that helps!

I don't understand why you want to use an external service to make copies of a spreadsheet. Since Google Scripts only run in Google spreadsheets I don't see any use case where it can be useful... but that's not the point, you do what you want.
That said, the easiest (and probably the only) one would be to share the document with someone and create an onOpen function that suggests to create a copy of it. This script should be executed after the required authorization and the copy will be their own copy, without any link to your G account anymore (which was the goal if I understood you well).
More simple and straightforward than that I can't imagine.
To avoid that their copy keeps the same onOpen behavior just setup a variable stored in userProperties so that when present this part of the onOpen doesn't execute.
This is a workflow I have already used and it works nicely.
edit :
I can suggest a completely different workflow to let other people get a personal copy of your SS.
Here is a test , give it a try and let me know if you're interested.
EDIT2 : since the other answer provides a similar workflow I decided to show the code I use in this answer to make that process more userfriendly.
I use 2 webApps :
one that runs as "me" that have access to my drive and runs without authorization for its user and that does nothing else than show a warning and a link.(accessible to anyone even anonymous)
And a second one that creates the copy and needs authorization to allow the SScopy creation in the user's own drive + a couple of links.(runs as the user accessing the app)
Code below (in 2 distinct projects of course) :
// APP 1 :
function doGet(){
var app = UiApp.createApplication().setTitle('Demo-App');
var link = app.createAnchor('Click this link to create your own copy <br>of my spreadsheet.<br>You will be asked for authorizations<br>tocreate a spreadsheet in your drive',true,'https://script.google.com/macros/s/AKfycbwQ5s_WWrsWXx_umZ1v91XGnm3RaO2Z7UQSXNiWFiaTwGuXIXqq/exec');
app.add(app.createVerticalPanel().setStyleAttribute('padding','50px').add(link));
return app;
}
// APP 2
function doGet(){
var ss = SpreadsheetApp.openById('0AnqSFd3iikE3dGNEUDdoLWhUZl9sZ3Z2Zm5XbjZzTkE');
var copy = ss.copy(ss.getName()); / the SS is shared to "anyone with the link can view"
var app = UiApp.createApplication().setTitle('SSCreate');
var panel = app.createVerticalPanel().setStyleAttribute('padding','50px');
panel.add(app.createHTML('A new spreadsheet has been created in your drive with name '+ss.getName()));
panel.add(app.createAnchor('Open it from <b>here</b>',true,copy.getUrl()));
panel.add(app.createAnchor('or from your own Drive','https://drive.google.com/?authuser=0#all'));
return app.add(panel);
}

Related

Disable Spreadsheet copy - Google Sheets

I would like to allow users to use my spreadsheet but not copy it as it contains intellectual property. I tried going to sharing settings and disabling:
Editors can change permissions and share
Viewers and commenters can see the option to download, print, and copy
But the sheet can still be copied. Ideas?
Unfortunately, it is not possible to disable copy / download for editors.
You can only do that for commenters and viewers.
As a workaround, I would advice you to keep your sensitive information into one master file and then importrange or copy via a script the shareable information into another file. So even if they copy or download the latter your sensitive information won't be copied / downloaded.
Related questions:
How to disable copy/ download access for editors in google sheets
Prevent editors from downloading the file
Disable download & Copy to Option in Google Spreadsheet
I think the simplest solution would be to copy and paste from the master file the range of values you want to share with the other document. In this scenario the editors of the other document won't have access to neither the code nor the full data of the master file since the latter won't be shared with them.
The copy and paste part can be done automatically via a script and a trigger mechanism to update the data automatically so you won't have to do anything manually and the master file won't be exposed to any user.
There isn't any sure way to hide your data. Once something is published on the internet, you should consider it saved on many devices all over the world. Consider some ways to get hidden spreadsheet data
Attack scenarios:
By far the easiest way is CTRLC and CTRLV(Copy and Paste)
Editor menu options: File->Copy and File->Export
Once your file id is visible, any editor or even viewer with access to the file can easily copy the file itself through
Url manipulation: Adding /copy at the end instead of /edit
google-drive-api: File:get and File:copy
google-sheets-api: Useful to directly get data as json
google-vizualization-api: Can get data as html,csv or json(google query). See endpoints
Screenshot and use OCR(Optical character recognition)
View source code in the browser and directly copy the table
web-scraping Simulate browser using selenium
Hiding data:
Data may be hidden from naive users. Data cannot be hidden from users, who know the basics of how the web works.
Add ?rm=minimal to url, when sharing the sheets file. This hides all menu options.See here
Frame the edior in a iframe in your own website and use css to hide the top portion of the web page.
Hiding Logic:
You may still be able to hide logic of your code.
IMPORTRANGE: This is a very basic and easy way to hide your logic. But there are limitations and any editor can access any part of your master spreadsheet.
You can implement a IMPORTRANGE like logic using custom functions and webapps. This gives more control over the connector and secures your master spreadsheet much better than IMPORTRANGE. Here,
Two web apps are created, each associated with a spreadsheet(Master and client).
You use two KEYs to communicate between them. One for access and other for encryption.
Once access is verified, the data from master spreadsheet is encrypted and sent back to the custom function. Simultaneously the encryption key is posted to the client webapp.
The key here is the Master/Server webapp posts the encryption key only to the published client web app link. So, no other sheet or anything else can intercept the key or decrypt the data. Furthermore, a random key is generated for each access.
Another option is to let go off the spreadsheet completely and use a single webapp to show the data. This hides the logic in server scripts and linked spreadsheets.
Comment thoughts:
Create a script onOpen to kill sheets if the file is wrong?
onOpen cannot post data anywhere without the new copy owner permission. It's not possible to kill sheets. But data can be erased.
/**
* Deletes all sheets on the copy, if a copy is made
*/
const onOpen = () => {
const ss = SpreadsheetApp.getActive();
const id = ss.getId();
const sheets = ss.getSheets();
ss.insertSheet(sheets.length);//insert a blank sheet at the end
if (id !== '###Original ID###') sheets.forEach(s => ss.deleteSheet(s));//will fail at the last sheet(doesn't matter)
};
But editor can modify the original script before making a copy. And a revision of the original spreadsheet will still be available. The new owner can revert to the original version, use api endpoints mentioned above to get the data. Also mobile apps don't support onOpen. New owners can simply use mobile versions to access data.
Use formula web-app to notify file owner, ?
Possible, but data is already copied and there's no specific information that can be used to accurately identify the new owner. You maybe able to get locale information though.

Recording any copies made of a google sheet

I would like to create a Google sheet that records any copies that have been made of another Google Sheet even when the user creates a copy by putting 'copy' into the URL (https://docs.google.com/spreadsheets/d/"spreadsheet-ID"/copy).
I have found a solution on the post: Track number of copies made from a google spreadsheet but this doesn't record copies made when changing the URL as the above example.
I would like to know the whole URL of the sheet preferably but just the Spreadhseet ID would still be enough for me.
Has anyone got any ideas how I can record all this data onto a spreadsheet please?
Basically you won't have access to see who is copying your sheet if you use the built-in Make Copy.
What you could do is integrate part of the question you have provided, while disabling the built-in sharing method. So the only way to copy your sheet could be through a custom function you can control and register every time it executes.

How to allow to run google script on a protected sheet (in a spreadsheet) [Can't use trigger and web app]

I'm coding a script for a spreadsheet. This script creates a menu.
Then by choosing an option in this menu a function (which uses API) will run in order to filter some columns and hide others.
The problem is:
This sheet is protected (because shared with coworkers) but I want to allow people to run the script, which is impossible without the permission.
I already looked at different solutions:
Using a trigger: Doesn't work because a trigger can't correctly call a function which uses API (yes, my functions use API).
Web App: When the script is run from the spreadsheet, the script is run as the current user, not the script editor. (the web app is efficient if the user uses the HTML page.)
Remove protection -> run the function -> Re-add protection: Can't modify the protection without permission, which is logical.
Add the current user in the list editor -> run the function -> Remove the current user from the list editor: Can't modify the editor list without permission, which is logical.
How can I solve this problem?
This is a pain that I have not seen a good workaround. Google should know that in a collaborative environment that the owner would create script and want users to be able to run those scripts while at the same time not messing with formulas or cells that you desire to protect. The only way I have found to solve this on the sheet itself is to Unprotect and then Protect the cell or range you are making modifications to during the script run. Do be mindful that if the script fails in the middle (after you have unprotected it) the cell remain unprotected. Might want to run within a "try" script.
Are you the spreadsheet owner?
If no, you are not authorized to bound an apps script on it.
But you can make a copy of the spreadsheet by duplicate it and save in your drive.
If the spreadsheet is created by you yourself, maybe you created it by another username. If it does, log in by that user and change the sharing option to allow the specific user can edit it.

Apps script library automatic updation on client side (on doc refresh)?

I have
an Apps Script Library 'MyLib'.
template Google Spread Sheet ('MyGSSheet'). Through Script Editor I added the library 'MyLib' to 'MyGSSheet'. I set 'Development mode' 'on'.
users get a 'copy' of this template ('MyGSSheet').
How can I have a setup wherein any changes I make to 'MyLib' get picked up across these copies automatically (once the Spreadsheet is reloaded)?
I thought having 'Development mode' 'on' is all that's needed for this continuous update of the code in all the Spreadsheets.
However, I don't see this happening. The copies aren't picking the latest code.
I also granted 'edit' permission to all users within our company domain.
I am not able to comment so I hope I am contributing enough to justify an answer..
So I tried to reproduce this:
I created a standalone App Script 'MyLib' and wrote a single function:
function myFunction()
{
SpreadsheetApp.getUi().alert("TEST");
}
Next I created a spreadsheet and added a script to it via Tools. I'll call it "Spreadsheet Script".
In the Spreadsheet Script I added the MyLib as a library and turned the development mode 'on'.
Also I added two functions to Spreadsheet Script:
function onOpen(e)
{
myFunction();
}
function myFunction()
{
MyLib.myFunction();
}
Ok, now I shared the Spreadsheet and the MyLib - script to my colleague with edit rights. He opened the spreadsheets and got the alert "TEST".
Now when I modified the alert text from the MyLib-script's myFunction to "TEST 2 " and just saved the file (File/Save, did not save a new version), my colleague saw the changes in the spreadsheet. Then, I made him to take a copy of the spreadsheet( To test the effect of the spreadsheet owner change).
I changed the alert the text to "TEST 3". The change was reflected in his copy of the spreadsheet.
Are you able to reproduce this or does this approach fail to update for the other users? I am choosing this kind of simple example as sometimes the reason might reside in the code too...

Is it possible to have one script for multiple spreadsheets?

I have one master spreadsheet and a number of copies. This master spreadsheet uses some scripting.
Is it possible to link all the copies of this master spreadsheet to the same script as in the master spreadsheet?
Objective:
changes in the scripting in the master spreadsheet are automatically used by the copies
aka: low maintenance
amleczko is right: you should use the new library feature in Google Apps script.
However, as of today, you won't be able to do exactly what you want (using the same script for several spreadsheets). What you can do instead is save a version of your script (Files > Manage Versions...), in order to create a library. Then, import this library in the other spreadsheets (Resources > Manage Libraries...). Switch on the "development mode" so every change made do the library will immediately take affect in the spreadsheets using this library. Otherwise, you will have to save a new version of the library for every change, and manually update the version number of the library in every spreadsheets using it.
The problem is, you need to write a script in every spreadsheets using your library, with skeleton functions like this:
function doSomething(){
myLibrary.doSomething();
}
best way is to publish as add-on, then install the add-on, it will appears in every spreadsheet you open. and you can publish as private, which only seen by yourself.
I think this has changed. According to Issue 40 starting from 22 May 2012 there is such a possibility. Please check:
https://developers.google.com/apps-script/guide_libraries
https://developers.google.com/apps-script/guide_versions
http://googleappsdeveloper.blogspot.it/2012/05/introducing-versions-and-libraries-in.html
It's not possible in this way that you're thinking. At least, not yet (see issue 40).
But, depending on your script usage, you may connect them "the hard way" or even better, use only one script. The script on the master spreadsheet can open the other spreadsheet files and do its job "remotely". It's not required that script to be hosted on a spreadsheet to interact with it (read/write on it). You only need a script hosted on the spreadsheet if you're going to use spreadsheet events triggers i.e. on-open, on-edit and on-form-submit.
Maybe you can develop a nice UI for the script on the master sheet and publish it as service. Then only have a link on the copies to access the same UI on a different browser tab. Adding parameters to the link the script UI can even adapt to the particular spreadsheet that is "triggering" it.
Well, that's all I can imagine now. But, unfortunately, there's some use cases that just don't fit this nice "workarounds". For those, one can only star issue 40 (to kind of vote and keep track of updates) and hope it's developed soon.
The solution I put in place in this context was to have a Google Site, where the Master Script is embedded, and where the Spreadsheet is embedded too
Then, the script, refering to a dedicated spreadsheet, looks for the Google Site Page's name, looks in the Master spreadsheet and get the ID of the spreadsheet which is embedded in the Page.
I have solved this problem when using a script which auto generates spreadsheets.
Typically, I will add a sheet to any spreadsheet with a script called "Info." I'll use that to store information that it important to the script. In my script which auto generates more spreadsheets, I keep track of the ID of the created sheet. This way, I can then quickly call up all of the "linked" sheets, and interact with them with using the same script. It might even be worth writing the script in one sheet, and keeping it totally separate from your Master sheet or it's children.
Take a look at this function, it might give you some ideas.
SpreadsheetApp.openById(id)