so I have created a small script here for my google sheets. Since google sheets doesn't allow you to use password protection on individual sheets, I was wondering if there was a way to protect my script with a password so that only certain people can use it. Here is my code.
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Custom Menu')
.addItem('Record', 'Record')
.addItem('Cancelation', 'Cancel')
.addToUi();
}
function Record() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Nightly Stats'),
row = sheet.getLastRow()
range = sheet.getRange("A3:G3");
sheet.insertRowAfter(row);
range.copyTo(sheet.getRange(row + 1, 1), {contentsOnly:true});
}
I would greatly appreciate any suggestions that you can provide.
Ok so I actually figured out how to do a password system via prompting. Here was what I did in case anyone needs this in the future.
function Cancel() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
// first prompt
var presult = ui.prompt(
"Please Enter the Password to Use this Feature.",
ui.ButtonSet.OK_CANCEL);
var password = "Test";
var pbutton = presult.getSelectedButton();
var ptext = presult.getResponseText();
// User clicked "OK" on first prompt
if (pbutton == ui.Button.CANCEL) {
ui.alert('The Process Was Ended.');
} else if (pbutton == ui.Button.CLOSE) {
ui.alert('The Process Was Ended.');
} else if (ptext != password) {
Password();
} else {
"Insert whatever action you would want them to do after the password works here"
}
}
function Password() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
var response = ui.alert("The Password is Incorrect. Retry?",
ui.ButtonSet.OK_CANCEL);
if (response == ui.Button.CANCEL) {
ui.alert("The Process Was Ended.");
} else if (response == ui.Button.CLOSE) {
ui.alert("The Process Was ended.");
} else {
Cancel();
}
}
I only gave a piece of the code so sorry if it looks a little weird. I just didn't want to give the whole code and make you search for everything. Hope that helps :)
It is a trick but it's useful to free account user!
Use onOpen() function and little code below.
var inputPassword;
function checkPassword(){
var refAddress = 'https://script.google.com/d/' + ScriptApp.getScriptId() + '/edit';
var curAddress = refAddress;
Logger.log(curAddress);
var referPassword = 1234;
if( refAddress == curAddress){
while(referPassword != inputPassword){
inputPassword = Browser.inputBox('[Password Check]', '[Input Password!]', Browser.Buttons.OK_CANCEL);
}
}
}
Refer to my blog for more information.
So you're trying to protect the script itself?
Host the script in a different location and use a library:
https://developers.google.com/apps-script/guides/libraries
Related
I'm looking at a cooldown to a function I've created so that once the function has been ran and the "else' tag has been ran that it wouldn't attempt to run again for 15 minutes.
Is there a simple way of doing this. My code is below and I've inserted a add here section for where I would want it to run.
function Cancel() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
// first prompt
var presult = ui.prompt(
"Please Enter the Password!",
ui.ButtonSet.OK_CANCEL);
var password = "Money2022";
var pbutton = presult.getSelectedButton();
var ptext = presult.getResponseText();
// User clicked "OK" on first prompt
if (pbutton == ui.Button.CANCEL) {
ui.alert('Wrong Answer Buddy!');
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (pbutton == ui.Button.CLOSE) {
ui.alert('Wrong Answer Buddy!');
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (ptext != password) {
Password();
} else {
**!!INSERT FUNCTION HERE!!**
}
}
function Password() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Wrong Answer Buddy!",
ui.ButtonSet.OK_CANCEL);
if (response == ui.Button.CANCEL) {
ui.alert("BYE BYE!");
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (response == ui.Button.CLOSE) {
ui.alert("BYE BYE!");
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else {
Cancel();
}
}
Seems like Cooper's suggestion of using Utilities.sleep() already worked for you but I'd like to add a little more information and another way to do this for anyone else with a similar question.
Since you mentioned that you wanted to cooldown for 15 minutes you should know that the maximum sleep time for Utilities.sleep() is 300000 milliseconds or 5 minutes. You can find this documented here.
As an alternative to bypass this limit you can consider using the Properties service. This allows you to save properties for either the users or the entire script, so you can set a property with a time x minutes from now and the script can check if it's passed this time before running. This allows you to set the cooldown as long as you want, and choose between having different cooldowns for each user or lock the entire document.
Here's an example of how you could do it:
function yourProcess(){
var properties = PropertiesService.getUserProperties() //gets all properties
var cooldownprop = properties.getProperty("cooldown") //gets cooldown property
var now = new Date()
//if cooldown has not been set it just sets the current time so the user can go through
var cooldown = cooldownprop == null ? now : new Date(cooldownprop)
if (now >= cooldown){
//run your code
//this sets the cooldown if needed, add the time in milliseconds
properties.setProperty("cooldown", new Date(now.getTime()+900000))
} else {
//tell the user they're on cooldown
}
}
For more information and samples on how to use the properties you can check out the documentation.
Sources:
Utilities.sleep()
Properties Service
In the below code, promptUPDATE, promptUPDATE2, and promptUPDATE3 all output "The user clicked "No" or the dialog's close button" after I press OK. I think there may be something wrong with my if statements but I'm not sure what. The code should only output that message when I close the box or click No.
function TextBox() {
// SET UI
var ui = SpreadsheetApp.getUi();
// SET HOT & WARM WORKSHEET
const sheet = SpreadsheetApp.openById('1JZ-v5n5m0_lyaoLgsHDAa78AtYNP5AsUDo2NRpJbwG4').getSheetByName('HOT WARM CLIENTS');
// SORT WORKSHEET BY DATE OF NEXT ACTIVITY
sheet.sort(9)
var today = Utilities.formatDate(new Date(), "GMT+1", "MM/dd/yy")
var ui = SpreadsheetApp.getUi();
var valuesToCopy = sheet.getRange("D5:J5").getDisplayValues()
var response = ui.alert(valuesToCopy,"Did we do work for this client today?", ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
const variable6 = sheet.getRange("G5").setValue(today);
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}
function promptUPDATE3(){
// Prompt for the value
var UPDATE = SpreadsheetApp.getUi().prompt(valuesToCopy+"\n\n What did we help this client with today?").getResponseText();
// Get the sheet that you want store the value in and set the value in the cell B3
if (response == ui.Button.OK) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("HOT WARM CLIENTS").getRange("PREVIOUS").setValue( UPDATE );
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}}
{
promptUPDATE3();
}
function promptUPDATE()
{
// Prompt for the value
var UPDATE = SpreadsheetApp.getUi().prompt(valuesToCopy+"\n\n When should we follow up with this client next? (MM/DD/YY)").getResponseText();
// Get the sheet that you want store the value in and set the value in the cell B3
if (response == ui.Button.OK) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("HOT WARM CLIENTS").getRange("I5").setValue( UPDATE );
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}}
{
promptUPDATE();
}
function promptUPDATE2()
{
// Prompt for the value
var UPDATE = SpreadsheetApp.getUi().prompt(valuesToCopy+"\n\n Next Follow Up Activity?").getResponseText();
// Get the sheet that you want store the value in and set the value in the cell B3
if (response == ui.Button.OK) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("HOT WARM CLIENTS").getRange("J5").setValue( UPDATE );
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}}
{
promptUPDATE2();
};};
About In the below code, promptUPDATE, promptUPDATE2, and promptUPDATE3 all output "The user clicked "No" or the dialog's close button" after I press OK. I think there may be something wrong with my if statements but I'm not sure what.,
Modification points:
In your script, the same value of response of var response = ui.alert(valuesToCopy,"Did we do work for this client today?", ui.ButtonSet.YES_NO); is used with all 4 if (response == ui.Button. And, at var UPDATE =,,,, the button is not set, and response is not changed.
I thought that this might be the reason for your issue.
sheet and ui can be used with another part in the same function.
I thought that in your situation when the message and the range are populated as an array, the script might be simpler.
When these points are reflected in your script, how about the following modification?
Modified script:
function TextBox() {
var ui = SpreadsheetApp.getUi();
const sheet = SpreadsheetApp.openById('1JZ-v5n5m0_lyaoLgsHDAa78AtYNP5AsUDo2NRpJbwG4').getSheetByName('HOT WARM CLIENTS');
sheet.sort(9)
var today = Utilities.formatDate(new Date(), "GMT+1", "MM/dd/yy")
var ui = SpreadsheetApp.getUi();
var valuesToCopy = sheet.getRange("D5:J5").getDisplayValues()
var response = ui.alert(valuesToCopy, "Did we do work for this client today?", ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
const variable6 = sheet.getRange("G5").setValue(today);
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}
// I modified the below script.
// This is from your showing script.
var ar = [
{ // promptUPDATE3
message: valuesToCopy + "\n\n What did we help this client with today?",
range: "PREVIOUS"
},
{ // promptUPDATE
message: valuesToCopy + "\n\n When should we follow up with this client next? (MM/DD/YY)",
range: "I5"
},
{ // promptUPDATE2
message: valuesToCopy + "\n\n Next Follow Up Activity?",
range: "J5"
}
];
ar.forEach(({ message, range }) => {
var res = ui.prompt(message, ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() == ui.Button.OK) {
sheet.getRange(range).setValue(res.getResponseText());
SpreadsheetApp.getUi(); // If you want to show the updated situation during the script is run, please use this.
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}
});
}
References:
prompt(prompt, buttons)
Class PromptResponse
I need a Google script (GAS) to send an email where the student who passed the quiz on google forms can find his grade, for example if he passed the quiz and his grade is 10/10 he will receive an email on his address-email: "Hi, you've already passed your quiz and you got a 10/10"
Thank you
function sendEmail(e) { //respond //getRespondentEmail()
var html = HtmlService.createTemplateFromFile("email.html");
var htmlText = html.evaluate().getContent();
var emailTo = e.response.getRespondentEmail();
var subject = "Merci pour votre participation";
var textBody = "This email requires HTML support. Please make sure you open with a client that support it."
var options = { htmlBody: htmlText }; Logger.log(emailTo); if(emailTo !== undefined){ GmailApp.sendEmail(emailTo, subject, textBody, options);
}
}
Email Results of Quiz to respondent
From the Form Trigger
function onMyFormSubmit(e) {
const form = FormApp.getActiveForm();
const r = e.response;
r.getGradableItemResponses().forEach((item,i) => {
Logger.log('Question: %s Response: %s Score: %s',item.getItem().asTextItem().getTitle(),item.getResponse(),item.getScore());
});
let email = r.getRespondentEmail();
GmailApp.sendEmail(email,"Quiz Response",Logger.getLog())
Logger.log(r);
}
function createOnFormSubmitTrigger() {
const form = FormApp.getActiveForm();
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == "onMyFormSubmit").length == 0) {
ScriptApp.newTrigger("onMyFormSubmit").forForm(form).onFormSubmit().create();
}
}
From the Spreadsheet Trigger
function onMyFormSubmit(e) {
Logger.log(JSON.stringify(e));
const ss = SpreadsheetApp.getActive();
const sh = e.range.getSheet();
const hA = sh.getRange(1,2,1,6).getValues().flat();
let s = '';
hA.forEach(h => {
s+= `\nQuestion: ${h} Answer: ${e.namedValues[h][0]}`
});
s += `\nYour Score is: ${e.namedValues.Score}`;
//Logger.log(s);
GmailApp.sendEmail(e.namedValues['Email Address'][0],"Quiz Result",s);
}
Email:
I believe your goal is as follows.
When the form is submitted, you want to send an email when the grade is 10/10.
From function sendEmail(e) { and var emailTo = e.response.getRespondentEmail();, your script is the container-bound script of Google Form. And, your function of sendEmail is installed as OnSubmit trigger.
Modification point:
In order to check if he passed the quiz and his grade is 10/10, it is required to calculate the grade of all items.
When this point is reflected in your script, it becomes as follows.
Modified script:
In this script, it supposes that the function sendEmail is run by the OnSubmit trigger. So please confirm whether the function sendEmail has already been installed as OnSubmit trigger again.
function sendEmail(e) {
var maxGrade = 10; // This is from "10/10" in your question.
var grade = e.response.getGradableItemResponses().reduce((p, e) => p += e.getScore(), 0);
var emailTo = e.response.getRespondentEmail();
if (grade < maxGrade || !emailTo) return;
var subject = "Sample subject"; // Please set the subject.
var textBody = "Hi, you've already passed your quiz and you got a 10/10.";
GmailApp.sendEmail(emailTo, subject, textBody);
}
Note:
From function sendEmail(e) { and var emailTo = e.response.getRespondentEmail();, I understood that your script is the conteiner-bound script of Google Form. So please be careful about this.
This modified script can be used by the OnSubmit trigger. So when you test this, please submit the form. When you directly run this script, an error occurs. Please be careful about this.
References:
Installable Triggers
reduce()
I have a spreadsheet with a bound script that tries to identify users currently making edits to the sheet, and appends edit info into specified columns of the sheet.
It seems that users always have to undergo the initial script authorization (that Google popup that asks for permissions) by opening the Script Editor and running a function manually via the "play" button. Invoking the function via a custom UI menu doesn't work and throws an error: "You do not have access to perform that action. Please ask the owner of this item to grant access to you."
Is there any way to invoke authorization in a more user-friendly way that doesn't require the users opening the Scripts Editor?
I thought making the menu was the point of that (manual execution vs. onEdit() or onOpened() triggered execution), but as it stands, this workaround is useless - I don't need the menu if the users have to open the Editor to achieve the same thing.
Full code:
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var labels = e.source.getRangeByName("labels");
if (labels == null) {
Logger.log("Error: couldn't find named range 'labels'!");
return;
}
var found = false;
for(var i = 1; i <= labels.getNumColumns(); i++){
if (labels.getCell(1,i).getValue() == "last edit") {
found = true;
break;
}
}
if (!found) {
Logger.log("Error: couldn't find column 'last edit'!");
return;
}
var minrow = labels.getRowIndex();
var range = sheet.getActiveRange();
var row = range.getRowIndex();
// don't track edits above the labels
if (row <= minrow) { return; }
var range_depth = range.getNumRows();
var rangeA1 = range.getA1Notation();
var timestamp = Utilities.formatDate(new Date(), "GMT+2", "YYYY-MM-dd HH:mm");
// the elegant way - requires the sheet owner and users to be on the same domain:
// var user = Session.getActiveUser().getEmail();
// the workaround - requires the user to identify themselves manually:
var user = PropertiesService.getUserProperties().getProperty("ID");
if (user == null) {
Browser.msgBox('Current user unidentified. Run the identification script via the "Edit tracking" menu (next to "Help").');
user = 'unknown';
}
for (var j = 0; j < range_depth; j++) {
sheet.getRange(row+j, i).setValue(timestamp);
sheet.getRange(row+j, i+1).setValue(user);
sheet.getRange(row+j, i+2).setValue(rangeA1);
}
SpreadsheetApp.flush();
}
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Edit tracking')
.addItem('Identify current user', 'showIdentify')
.addItem('Forget current user', 'showForget')
.addToUi();
}
function showIdentify() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'Edit tracking',
'Please enter your name:',
ui.ButtonSet.OK_CANCEL);
var button = result.getSelectedButton();
var text = result.getResponseText();
if (button == ui.Button.OK) {
PropertiesService.getUserProperties().setProperty("ID", text);
} else {
ui.alert('Look, you\'re not dodging this, I\'ll nag you again on the next edit.');
}
}
function showForget() {
var ui = SpreadsheetApp.getUi();
PropertiesService.getUserProperties().deleteProperty("ID");
ui.alert('You\'re anonymous now.');
}
It seems to me like a bug in google sheets that the script is not shared along with the sheet.
But to make it more user friendly you can
A) publicly share your sheet (Anyone on the internet can find and edit) or
B) (using "anyone with the link can edit" sharing) In the script editor get the share link File->Share... and have the user open that link.
Then it will work.
So, I made a Google Spreadsheet for a group of people to use to keep track of weekly "counts" for a large group of people on a Reddit sub. The things I'm trying to automate are two things. The one I'm having problems with is the one I thought would be the easiest, just copying the values from one set (G2:G200) to overwrite the values in another (E2:E200). I'm having some other issues as well, but I'd be more interested in an explanation for what I'm doing wrong there than just an answer. The biggest one is that this is supposed to be making a custom menu on the sheet, and I can't seem to get that working, even though I basically copied the script from the Google Tutorial for that. I've tried the script for this two ways, one using the same script as Excel printed out when recording the same basic thing:
function UpdateLore_() {
var ui = SpreadsheetApp.getUi(); // Same variations.
var result = ui.alert(
'Please confirm',
'Only do this once per week, at end of updates.',
ui.ButtonSet.YES_NO);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
Range("G2:G200").Select;
Selection.Copy;
Range("E2:E200").Select;
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _ :=False, Transpose:=False;
} else {
// User clicked "No" or X in the title bar.
ui.alert('No Changes Made.');
}
}
This returns an arror on the "Selection.PasteSpecial" line. The other way I tried it was using what I could find online for this:
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
function copyFunction () {
var inputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("G2:G200");
var inputValue = inputRange.getValue();
var outputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("E2:E200");
}
The top part of the code looks like this:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Weekly Update')
.addItem('Update for Lore', 'UpdateLore')
.addItem('Update for XP Master', 'UpdateMaster')
}
I feel like I'm missing something very obvious, especially with the whole "doesn't seem to change the sheet in anyway" part. Thanks for any help
Got some answers and now it works, thanks for all the help:
Got it, thanks for all the help. New code looks like this:
function UpdateLore() {
var ui = SpreadsheetApp.getUi(); // Same variations.
var result = ui.alert(
'Please confirm',
'Only do this once per week, at end of updates.',
ui.ButtonSet.YES_NO);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
copyFunction ();
}
function copyFunction () {
var inputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("G2:G200");
var inputValues = inputRange.getValues();
var outputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("E2:E200").setValues(inputValues);
}
if (result ==ui.Button.NO) {
// User clicked "No" or X in the title bar.
ui.alert('No Changes Made.');
}
}
To add data to a sheet you need to use:
setValue()
setValues()
appendRow()
You've got a function inside of the if body:
if (result == ui.Button.YES) {
// User clicked "Yes".
function copyFunction () {
. . . .
}
}
If you want to call another function at that point, you could use:
if (result == ui.Button.YES) {
// User clicked "Yes".
copyFunction ();
};
function copyFunction () {
. . .
};
You need to set the values from the inputRange to the outputRange. Use the .setValues() on your outputRange to do this.
function copyFunction () {
var inputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("G2:G200");
var inputValues = inputRange.getValues();
var outputRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1").getRange("E2:E200").setValues(inputValues);
}
None of this is valid apps script code:
Range("G2:G200").Select;
Selection.Copy;
Range("E2:E200").Select;
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _ :=False, Transpose:=False;