onOpen-function doesn't work correct - google-apps-script

Script-Editor: wrote a script with the onOpen-function (see below). When started with the script-editor, it works correctly. when started when the spreadsheet is opened, I get the log of stmt 31 "Logger.log(name)", nothing else happens. Why?
Here is the complete script:
function onOpen() {
n0="T22 ";
n1=n0+"aktuell"
n2=n0+"Master"
var y1=DayShift();
Browser.msgBox("Returned with "+y1);
};
function DayShift() {
var dt=Browser.inputBox("Datum (YYMMDD) eingeben");
if (dt=="cancel" || dt.length !=6) return("Input "+dt);
var f1=GetFL(n1,0);
if (f1=="cancel") return("F1 cancel");
Logger.log(f1);
var Rx=f1.getSheetByName("Sheet1").getRange("B9").getValue();
f1.rename(n0+dt);
Logger.log(Rx);
Logger.log(f1.getName()+" finished");
var f2=GetFL(n2,1);
if (f2=="cancel") return("F2 cancel");
Logger.log(f2);
f2.getSheetByName("Sheet1").getRange("B7").setValue(Rx);
Logger.log(f1.getName()+" finished");
return("OK");
};
function GetFL(name,typ) {
Logger.log(name);
var fx = DocsList.find(name);
Logger.log(name+" = "+fx.length);
if (fx.length != 1) return("cancel");
if (typ==1) {
var fy=fx[0].makeCopy(n1);
} else {
var fy=fx[0];
};
fy=SpreadsheetApp.openById(fy.getId());
Logger.log(fy);
return (fy);
};

As a complement to Jonathon's answer, you could use the installable onOpen trigger instead of the simple one. Just give this function another name to avoid confusion, for example IonOpen() or whatever... the installable triggers don't have the limitations Jonathon was mentioning.
See doc on Using Container-Specific Installable Triggers

running onOpen from the script editor treats as any other script. running it as a simple triggered function will not allow access to the DocsList API as this cannot be run anonymously. Even if you have authorised the script to run as you, simple triggers run anonymously and certain APIs will not allow this.
In this case onOpen (and onEdit) silently fail.
It won't fix this for you, but the explanation can be found in the GAS documentation.
It can be frustrating/mystifying when you first encounter this, but there are ways around it even if it means running scripts from menu commands or buttons: both confer identity of the user on the script.

Related

Check if a user has authorized a script before trying to execute anything that requires authorization

I've got a Google Sheet that has an onOpen(e) Simple Trigger. Obviously that'll run fine, without needing any authorization and it'll show the user the menu.
When the user clicks the menu item it'll execute a function that requires authorization, for example it will call DriveApp....
The first time the user clicks the menu item it'll ask them to authorize the script.
What I'd like to do is, before GAS asks the user to authorize if needed, check if the script does need to be authorized and do something else, that doesn't need authorization (like showing an alert).
I know I have to use getAuthorizationStatus() but I can't figure out how.
I started with the code below, but as soon as I select the one menu item, it asks me to authorize and doesn't log anything. So I'm not sure how to check authorization status...
function onOpen(e)
{
var ui = SpreadsheetApp.getUi();
ui.createMenu("Menu")
.addItem("One", "one")
.addItem("Two", "two")
.addToUi();
}
function one()
{
var a = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
console.log(a.getAuthorizationStatus());
console.log(a.getAuthorizationUrl());
var d = DriveApp.getFolders();
}
function two()
{
}
If I understand correctly, you're trying to request specific authorization only when the user calls the function(s) that require it. Unfortunately, Apps Script doesn't work like that.
Some functions like onOpen() and onEdit() can run without any authorization, but calling any other function will prompt authorization if authorization is required anywhere in the script. (Even if the code requiring it is commented out.)
So even if you were to follow an example like the one listed in the example here, your script would still prompt for authorization when selecting menu option "One". In the example below, I check for authorization, but because one() requires authorization, I'm not allowed to call the custom authorizeScript() function until after authorizing the script.
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu("Menu");
if (e && e.authMode == ScriptApp.AuthMode.LIMITED) { // Unauthorized
menu.addItem("Authorize", "authorizeScript");
} else { // Authorized
menu.addItem("One", "one");;
}
menu.addToUi();
}
function authorizeScript() {
console.log("authorizeScript"); // No auth required
}
function one() {
var d = DriveApp.getFolders(); // Auth required
}
Please also note that, to check authorization status, I'm using the event object described here.
The easiest way to do this is to create a another project without permissions:
Tools>Script editor> New> Project:
function onOpen(e) {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Readme!')
.addItem('Click here!', 'howTo')
.addToUi();
}
function howTo() {
SpreadsheetApp.getUi().alert(
`You would need to authorize this script first!
Our privacy policy is simple.... No data is captured or logged or sent to servers.
Anything that happens happens in your sheet`
);
}
Another much more complicated way to do this in a single project is by limiting scopes(to empty array) to the project by editing the manifest file. But then you would have no inbuilt auth flow. You would need a complicated oauth flow based only on alerts.

Using a variable from another script in the same document (apps scripting)

I am trying to access a variable from another script A.gs in script B.gs. they are both in the same document. How could I do this?
I am not sure how I should solve this problem, I am a beginner with apps scripting and I can't find anything on the internet about it.
code.gs:
ui = DocumentApp.getUi();
function onOpen () {
A = prompt('Hello');
}
code2.gs:
function onOpen () {
if (A === "123") {
ui.alert('Hello')
}
}
I want Hello to be output if 123 is entered into the prompt, but when I try to run the code I get the error:
ReferenceError: "A" is not defined. (line 3, file "code2")
In your situation, code.gs and code2.gs are in a project of a container-bound script type of Google Document.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
Modification points:
In your script, the scripts of code.gs and code2.gs are used as one project at the script editor. So in your script, there are 2 same functions of onOpen() in the project. In this case, only one of them is run. In your case, onOpen() of code2.gs is run and the error of ReferenceError: "A" is not defined. occurs.
Modified script:
If you want to modify your script and you want to work the functions when the Google Document is opened, how about the following modification?
1. Copy and paste the following script to code.gs and code2.gs of the script editor:
code.gs:
var ui = DocumentApp.getUi();
function installedOnOpen () {
A = prompt('Hello'); // or ui.prompt('Hello').getResponseText();
sample(A);
}
code2.gs:
function sample (A) {
if (A === "123") {
ui.alert('Hello')
}
}
Or, if you want to run independently 2 functions, how about the following modification? In this modification, the value is saved using PropertiesService.
code.gs:
var ui = DocumentApp.getUi();
function installedOnOpen () {
A = prompt('Hello'); // or ui.prompt('Hello').getResponseText();
PropertiesService.getScriptProperties().setProperty("key", A);
}
code2.gs:
function sample () {
var A = PropertiesService.getScriptProperties().getProperty("key");
if (A === "123") {
ui.alert('Hello')
}
}
Or, you can also modify as follows. But, in your situation, this might not be required.
function installedOnOpen () {
var ui = DocumentApp.getUi();
var A = ui.prompt('Hello').getResponseText();
if (A === "123") {
ui.alert('Hello');
}
}
2. Install OnOpen event trigger:
In order to run the function of installedOnOpen when the Google Document is opened, please install the OnOpen event trigger to the funciton of installedOnOpen as the installable trigger.
3. Run script:
In your case, there are 2 patterns for running script.
Pattern 1:
Open Google Document.
Pattern 2:
Run installedOnOpen at the script editor.
By above, installedOnOpen is run. And you can see the dialog at Google Document.
Note:
This modification supposes that the function of prompt() returns the value of 123 as the string value.
If you cannot provide the script of prompt(), as a test case, how about modifying from prompt('Hello'); to ui.prompt('Hello').getResponseText();?
References:
Access a variable across multiple script file under a GAS project
Also I think that this thread might be useful for you.
Installable Triggers
If I misunderstood your question and this was not the direction you want, I apologize.
As I can see you define onOpen twice. It does not make sense.
You also don't declare variables and this is reflected in the style of your code. Try declaring the variables and you will realize that your code has no effect.

Google Sheets Script: Combining onEdit with openById("OtherSheetsId")

I am pretty bad at this but i did search around quite alot before posting here. I found some answers and I am now stuck at this part.
In short: We have 2 seperate google doc sheets. When a specific cell gets changed in Spreadsheet1, the script must change a specific cell's value in Spreadsheet2.
function onEdit(e) {
var mainsheet = SpreadsheetApp.getActive().getSheetByName('Main');
var changeit = sheet.getRange("A1").getValue();
var offsheet = SpreadsheetApp.openById("spreadsheet2s id").getSheetByName('Echo').getRange("A1");
if (changeit == 'true'){
offsheet.setValue('true');
}
It basically does not work, and in scripts, when I go to Run -> Executions Transcript it tells me Execution failed: You do not have permission to call SpreadsheetApp.openById.
When I try this for the same sheet it works fine.
It seems that an installable trigger would do the trick, but i have not been able to find anything on what exactly to do here, as in how to apply an installable trigger.
It is said by google info that this will allow a function to change info on another sheet. How would we make this only apply to the onEdit(e) function listed above?
And the biggest question is, how do we make this work / tie it all together? What I have done is just pasted this underneath (as if it was its own function).
I have played around with it in various ways (such as trying to add the openByID info in this and afew other silly things) and I think I missing something simple here.
function createSpreadsheetEditTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('onEdit')
.forSpreadsheet(ss)
.onEdit()
.create();
}
Thank you in advance for your help.
Try this:
function ss1OnEdit(e){
if(e.range.getSheet().getName()!='Main') return;
SpreadsheetApp.openById("ss2id").getSheetByName("Echo").getRange(e.range.getA1Notation()).setValue(e.value);
}
//You can run this function all you want it will not create more than one trigger. But you must run it once to setup the first trigger.
function createSS1OnEditTrigger() {
var ss=SpreadsheetApp.getActive();
var trgs=ScriptApp.getProjectTriggers();
var found=false;
for(var i=0;i<trgs.length;i++) {
if(trgs[i].getHandlerFunction()=="ss1OnEdit") {
return;
}
}
if(!found) {
ScriptApp.newTrigger("ss1OnEdit").forSpreadsheet(ss.getId()).onEdit().create();
}
}

Setting up a trigger when a Google Sheet gets updated by IFTTT

I'm using IFTTT to update my Google Sheets when I receive a SMS. Now, I would like to take a step ahead and write a Google Apps Script that would do different things with the data updated by IFTTT into my Google Sheet. I tried to achieve the same using the Google Apps Script's onEdit function, but that does not work.
I did a lot of search on multiple forums regarding this problem, and I learnt that onEdit works only when a "user" makes the changes to the Google Sheet and not when the changes are done over an API request (I believe IFTTT uses the same). I could not see even a single post with a working solution.
Any ideas? Thanks!
After a lot of Google search, I found below code to be working for me. It is inspired by this answer by Mogsdad.
function myOnEdit(e) {
if (!e) throw new Error( "Event object required. Test using test_onEdit()" );
// e.value is only available if a single cell was edited
if (e.hasOwnProperty("value")) {
var cells = [[e.value]];
}
else {
cells = e.range.getValues();
}
row = cells[cells.length - 1];
// Do anything with the row data here
}
function test_onEdit() {
var fakeEvent = {};
fakeEvent.authMode = ScriptApp.AuthMode.LIMITED;
fakeEvent.user = "hello#example.com";
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
// e.value is only available if a single cell was edited
if (fakeEvent.range.getNumRows() === 1 && fakeEvent.range.getNumColumns() === 1) {
fakeEvent.value = fakeEvent.range.getValue();
}
onEdit(fakeEvent);
}
// Installable trigger to handle change or timed events
// Something may or may not have changed, but we won't know exactly what
function playCatchUp(e) {
// Build a fake event to pass to myOnEdit()
var fakeEvent = {};
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
myOnEdit(fakeEvent);
}
Hope this helps someone in future. Do note that the functions playCatchUp and myOnEdit must be set as "change" and "edit" action triggers respectively in Google Apps Script.

How do I create a timeBased Add-on trigger that runs every hour?

Extension of my previous post here, I just figured this was more specific and should be it's own post.
Add-ons have restrictions on triggers. One such is that it can only run once an hour. I cannot figure out how to make that work.
Running the below script will produce the error: "attempted to perform an action that is not allowed" when it is run as an add-on. So if the below is not the proper add-on method for a once an hour script, what is, or did I find a bug?
ScriptApp.newTrigger('updateDay').timeBased().everyHours(1).create();
I tried adding the authorization check as Spencer suggested, and outlined in the documentation here. It passes the authentication, but still produces the same error.
function installTrigger(e) {
var addonTitle = 'Lab Scheduler';
var props = PropertiesService.getDocumentProperties();
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
if (authInfo.getAuthorizationStatus() ==
ScriptApp.AuthorizationStatus.REQUIRED) {
var lastAuthEmailDate = props.getProperty('lastAuthEmailDate');
var today = new Date().toDateString();
if (lastAuthEmailDate != today) {
if (MailApp.getRemainingDailyQuota() > 0) {
var html = HtmlService.createTemplateFromFile('AuthorizationEmail');
html.url = authInfo.getAuthorizationUrl();
html.addonTitle = addonTitle;
var message = html.evaluate();
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Authorization Required',
message.getContent(), {
name: addonTitle,
htmlBody: message.getContent()
}
);
}
props.setProperty('lastAuthEmailDate', today);
}
} else {
// Authorization has been granted, so continue to respond to the trigger.
try{
var as = SpreadsheetApp.getActiveSpreadsheet();
var userTriggers = ScriptApp.getUserTriggers(as);
var userTriggerL = userTriggers.length;
if (userTriggers.length == 0){
ScriptApp.newTrigger('updateDay').timeBased().everyHours(1).create();
}
} catch(err){
catchToString_(err);
} // End try catch
}
}
The issue you are running against is the scope of authorization your Add-on is running in when the trigger gets created or ran. Installed Triggers are run in AuthMode.FULL. You need to test for the current level of authorization before you can run the trigger. You use the ScriptApp.getAutorizationInfo(authMode) to get the status of the authMode the add-on is running in.
https://developers.google.com/apps-script/reference/script/script-app#getAuthorizationInfo(AuthMode)
Here is an example bit of code from the Apps Script documentation:
https://github.com/googlesamples/apps-script-form-notifications-addon/blob/master/Code.gs
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
// Check if the actions of the trigger require authorizations that have not
// been supplied yet -- if so, warn the active user via email (if possible).
// This check is required when using triggers with add-ons to maintain
// functional triggers.
if (authInfo.getAuthorizationStatus() ==
ScriptApp.AuthorizationStatus.REQUIRED) {
// Re-authorization is required. In this case, the user needs to be alerted
// that they need to reauthorize; the normal trigger action is not
// conducted, since it authorization needs to be provided first. Send at
// most one 'Authorization Required' email a day, to avoid spamming users
// of the add-on.
sendReauthorizationRequest();
} else {
// All required authorizations has been granted, so continue to respond to
// the trigger event.
}
I think you are using the trigger for specific days at specific hour, so in your example you would need to specify the day (e.g. Mondays) and atHour(1) would make the trigger to run every monday at 1.
For specifying how often it should be triggered you would need to write:
ScriptApp.newTrigger('myFunction').timeBased().everyHours(1).create();
I have come to the conclusion that this is a bug or .timebased() triggers are not supported as an add-on, (which I thought they were).
Please star this issue to help get this working again.
https://code.google.com/p/google-apps-script-issues/issues/detail?id=4524&q=.timeBased()%20add-on%20trigger&colspec=Stars%20Opened%20ID%20Type%20Status%20Summary%20Component%20Owner