Limiting OAuth scope for Google App Script - google-apps-script

I am trying to limit my OAuth scopes in my script that sends an email after a form has been submitted. I want to limit it so that it has the least permission needed. If I click run, it tries to authorize the correct permissions. If I set up the on form submit trigger, it wants to authorize read, change, delete on all spreadsheets and change on all forms.
If I give the script full access to sheets and forms, it runs as intended. I just want to reduce some of the permissions. The screenshot shows that it is asking for more permission than what is specified in the appsscript.json file.
This script is attached to the responses sheet generated from my form.
From my appsscript.json:
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/forms.currentonly",
"https://www.googleapis.com/auth/spreadsheets.currentonly"
]
The code:
/**
* #OnlyCurrentDoc
*/
function onFormSubmit(e) {
var values = e.namedValues;
var htmlBody = 'Hey ' + values['Name of Recipient'] + "!<br>";
htmlBody += values['Name of Sender'] + " thinks you deserve a shoutout! Thank you for being so awesome!";
htmlBody += '<br> <em>' + values['Shoutout'] + " - " + values['Name of Sender'] + "</em>";
htmlBody += '<br><br>';
GmailApp.sendEmail(values['Recipient Email'],'SHOUT OUT!!!!!!','',
{from:'email#domain.com',
htmlBody:htmlBody});
}
Google Form/Sheet Questions/Columns
Timestamp
Name of Sender
Name of Recipient
Name of Recipient's Boss
Shoutout
Recipient Email
Recipient's Boss Email
OAuth Permissions Screenshot:
Project Details OAuth Scopes:
View your email messages and settings https://www.googleapis.com/auth/gmail.readonly
Send email on your behalf https://www.googleapis.com/auth/gmail.send
See, edit, create, and delete only the specific Google Drive files you use with this app https://www.googleapis.com/auth/drive.file
View and manage forms that this application has been installed in https://www.googleapis.com/auth/forms.currentonly
View and manage spreadsheets that this application has been installed in https://www.googleapis.com/auth/spreadsheets.currentonly

By default, See, edit, create, and delete all your Google Sheets spreadsheets is a required scope if you added an Installable Trigger that has event source of From Spreadsheet and View and manage your forms in Google Drive is also added if the event type is On form submit. This is to give script the access to the changes that may happen in the spreadsheet caused by submitting response. As a result, it will return the user an Event Object containing information about the context that caused the trigger to fire.
The script will also work if you manually press Run but there is no Event Object that will be passed to the function parameter.
You can try using Time-driven as event source and it will show the same scope as you declared in appsscript.json since the trigger doesn't need to access the spreadsheet to execute the trigger.
Example:
Time Driven:
From spreadsheet and On Open:
From spreadsheet and On form submit:
References:
Event Object
Installable Triggers

I believe your goal is as follows.
From your question, you want to restrict the scopes for your showing script of The code: as follows.
/**
* #OnlyCurrentDoc
*/
function onFormSubmit(e) {
var values = e.namedValues;
var htmlBody = 'Hey ' + values['Name of Recipient'] + "!<br>";
htmlBody += values['Name of Sender'] + " thinks you deserve a shoutout! Thank you for being so awesome!";
htmlBody += '<br> <em>' + values['Shoutout'] + " - " + values['Name of Sender'] + "</em>";
htmlBody += '<br><br>';
GmailApp.sendEmail(values['Recipient Email'],'SHOUT OUT!!!!!!','',
{from:'email#domain.com',
htmlBody:htmlBody});
}
It seems that when GmailApp.sendEmail of Gmail Service is used, the scope is constant as https://mail.google.com/. It seems that this is the current specification. So when you want to restrict the scopes, I think that you can achieve it using Gmail API. When your script is converted using Gmail API, it becomes as follows.
Modified script:
Before you use this script, please enable Gmail API at Advanced Google services.
/**
* #OnlyCurrentDoc
*/
// This is from https://stackoverflow.com/a/66088350
function convert_(toEmail, fromEmail, subject, textBody, htmlBody) {
const boundary = "boundaryboundary";
const mailData = [
`MIME-Version: 1.0`,
`To: ${toEmail}`,
`From: ${fromEmail}`,
`Subject: =?UTF-8?B?${Utilities.base64Encode(subject, Utilities.Charset.UTF_8)}?=`,
`Content-Type: multipart/alternative; boundary=${boundary}`,
``,
`--${boundary}`,
`Content-Type: text/plain; charset=UTF-8`,
``,
textBody,
``,
`--${boundary}`,
`Content-Type: text/html; charset=UTF-8`,
`Content-Transfer-Encoding: base64`,
``,
Utilities.base64Encode(htmlBody, Utilities.Charset.UTF_8),
``,
`--${boundary}--`,
].join("\r\n");
return Utilities.base64EncodeWebSafe(mailData);
}
function onFormSubmit(e) {
var htmlBody = 'Hey ' + values['Name of Recipient'] + "!<br>";
htmlBody += values['Name of Sender'] + " thinks you deserve a shoutout! Thank you for being so awesome!";
htmlBody += '<br> <em>' + values['Shoutout'] + " - " + values['Name of Sender'] + "</em>";
htmlBody += '<br><br>';
var raw = convert_(values['Recipient Email'], 'email#domain.com', 'SHOUT OUT!!!!!!', "", htmlBody);
Gmail.Users.Messages.send({raw: raw}, "me");
}
In this script, only tne scope of https://www.googleapis.com/auth/gmail.send can be used.
In this sample script, it supposes that e is the correct value you want to use. Please be careful about this.
Note:
About other scopes of "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/forms.currentonly", "https://www.googleapis.com/auth/spreadsheets.currentonly", when I saw your script, those scopes are not required to be used. But, when Google Form is used, https://www.googleapis.com/auth/forms.currentonly might be required to be used. Please be careful this.
For example, when you want to use other methods, please add the scopes for them. My answer is for your showing script in your question. Please be careful about this.
Reference:
Method: users.messages.send

Related

GmailApp.sendEmail() won't send email with a Bitly URL in body

I have a Google apps script which sends an email with a long URL and a Bitly shortened one. However the GmailApp.sendEmail function silently fails if I include a Bitly URL. Simple example of problem code:
function myFunction() {
var sMail = "(my email address)";
var longURL = "https://www.google.com";
var shortURL = "(bitly URL)";
// Send email with information
GmailApp.sendEmail(
sMail,
"URL Send Test",
"Original URL: " + longURL + "\n" +
"Short URL: " + shortURL
);
}
This code executes without error, but I never receive the email or any notification that it was blocked. If I remove shortURL from the body then email is received.
Obviously these shortened links are a source of abuse (in fact I can't include it in my post) but is there a way around this? My script generates a long custom URL and shortening it is an important function.
Thanks for any suggestions.

How to obtain initial OAuth2.0 code from browser?

I have made a javascript script in Google Apps Script, attached to a google sheet. I ultimately want to connect the Google Sheet to the Google Fit API and have my fitness data automatically inputted to the Google Sheet. At step 0, I made my Google Console project and OAuth2.0 client ID & secret. I am at step 1, where I need to obtain the authentication code from the initial 'GET' request.
My request is correct and I can send the request correctly using the callback notation; the code below is run in a callback in a timed for loop such that the html object (html_objet) remains active for a certain amount of time, waiting to get the code. I can see the code in the browser url when I have finished approving with the Google popup, but I do not know how to import this code value from the client browser(popup) into my javascript program. I open the client browser(popup) using :
var html_texte = '<html><head><script>'
+ 'const winRef = window.open("'+js2html_data+'");'
+ 'winRef ? google.script.host.close() : window.alert("Allow popup to redirect to url");'
+ 'window.onload=function(){document.getElementById("'+js2html_data+'").href ="'+js2html_data+'";};'
+ '</script></head><body>'
+ '</body></html>';
var html_objet = HtmlService.createHtmlOutput(html_texte).setWidth(90).setHeight(1);
html_objet.js2html_data = js2html_data;
SpreadsheetApp.getUi().showModalDialog(html_objet, "Opening ...");
This works. But, I can not do anything with the window once it is open (ie: reload the window, get the current url).
I tried modifying the html_texte variable to the text below, such that I can return the url of the authenticated page. It does not work, how can I update the js2html_data variable such that it shows the url of the final Google user authenticated page? Or, pass the url to another html variable (html2js_data) out to my javascript program?
var html_texte = '<html><head><script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>'
+ '<script>'
+ 'const winRef = window.open("'+js2html_data+'");'
+ 'winRef ? google.script.host.close() : window.alert("Allow popup to redirect to url");'
+ 'window.onload=function(){document.getElementById("'+js2html_data+'").href ="'+js2html_data+'";};'
// + '$(document).ready(function getUrl(){ document.getElementById("'+html2js_data+'").innerHTML=window.location.href; });'
+ '$(document).ready(function getUrl(){ document.getElementById("'+html2js_data+'").href=window.location.href; });'
//+ '$(document).ready(function getUrl(){ document.getElementById(<?="'+html2js_data+'"?>).href=window.location.href; });'
+ '</script></head><body>'
// + '<h3 id="html2js_data" onclick="getUrl()">OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO</h3>'
+ '<div id="html2js_data"></div>'
+ '</body></html>';
Any suggestions would be great...I tried a lot of things...

Create hyperlink in Telegram message sent by bot via script / google sheets

My current script is:
function EnviarTelegram(botSecret, chatId, body) {
var response = UrlFetchApp.fetch("https://api.telegram.org/bot" + botSecret + "/sendMessage?text=" + encodeURIComponent(body) + "&chat_id=" + chatId + "&parse_mode=HTML");
}
The formula in Google Sheets I use to send the message is:
=EnviarTelegram("Code to Bot","Code to ChatId","Created Message")
I would like to be able to create the following message:
"Full list of games tomorrow
Click here to access"
In Click here to access I wish there was a hyperlink for example: www.google.com/testtesttesttest
Is there any way to be able to adjust the script or the text created for this?
In your query parameter, parse_mode=HTML is used. So I thought that in this case, HTML tags can be used for text. When you want to use =EnviarTelegram("Code to Bot","Code to ChatId","Created Message"), how about the following modification?
From:
=EnviarTelegram("Code to Bot","Code to ChatId","Created Message")
To:
=EnviarTelegram("Code to Bot","Code to ChatId","Full list of games tomorrow\n\n<a href='https://www.google.com/testtesttesttest'>Click here to access</a>")

Issue making a response from a google form showing error ""TypeError: Cannot read property "values" from undefined. (line 2, file "Code")""

In Google Forms, I am attempting to create an email response that includes a case number that is generated in the form responses (column titled code) but unfortunately, I have come across the error:
typeError: Cannot read property "values" from undefined. (line 2, file "Code")
I am a complete novice regarding this but I have set the trigger function and am given this error in the email that is sent in a reply.
Find a copy of the responses docs below:
https://docs.google.com/spreadsheets/d/1w8cUkErljf6JzQm9q5aFwa-m9jX4o5qrAg5K7ib9Keg/edit?usp=sharing
This is the code I'm using:
function myFunction(e){
var userName = e.namedValues.Name;
var userEmail = e.namedValues.Tag;
var date = e.namedValues.Timestamp;
var subject = e.namedValues.Code;
var message = "Thanks, " + userName + " for submiting your data remember to take note of your Case number "+ subject + "You'll need this to for you or the customer to find the case again" +date;
MailApp.sendEmail (userEmail, subject, message);}
I expect it to generate an email, use the code as a subject and include it the text in the body.
Different Google Applications can have triggers with the same name, but different event objects.
To access the latest form response you need the trigger onFormsubmit(), which can be both a Google Sheets and Google Form trigger.
e.namedValues is a Google Sheets onFormsubmit() event object, while a Google
Form onFormsubmit() event object would be e.response.
You have two options:
Bind your Apps Script to the Spreadsheet where your form responses are being saved and access the responses with e.namedValues as intended
Bind your Apps Script to the Form itself and access the responses with e.response as following:
var userName = e.response.getItemResponses()[0].getResponse();
var userEmail= e.response.getItemResponses()[1].getResponse();
...
Keep in mind that onFormsubmit() is an installable Trigger.

How to add "Edit Response" link to Google Forms emails?

I have a simple Google Form that collects data, and, using AppScript, sends confirmation emails to users who fill it out. After user submits the form, on confirmation, s/he will see a link to edit his/her response.
I'd like to include that link as a part of the confirmation email (Right now, it only shows up on the page.) How can I obtain the URL to edit a submitted response?
I am able to get the link to the Form through SpreadsheetApp.getActiveSpreadsheet().getFormUrl(). It gives me the following format: https://docs.google.com/a/domain.com/spreadsheet/viewform?formkey=<formKey>
The link however doesn't include the edit key, which is required for users to edit his/her response. The expected URL should look like this: https://docs.google.com/a/domain.com/spreadsheet/viewform?formkey=<formKey>&edit=<editKey>
Thanks for the help in advance!
-K
Edited:
Added a feature request on this: http://code.google.com/p/google-apps-script-issues/issues/detail?id=1345&thanks=1345&ts=1337773007
The answer that this wasn't possible by #Henrique Abreu was true until very recently. Google seems to have added getEditResponseUrl() to the FormResponse class and with that it becomes possible to use code like this to get the edit URL for a bunch of existing forms:
function responseURL() {
// Open a form by ID and log the responses to each question.
var form = FormApp.openById('1gJw1MbMKmOYE40Og1ek0cRgtdofguIrAB8KhmB0BYXY'); //this is the ID in the url of your live form
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
Logger.log(formResponse.getEditResponseUrl());
}
}
To make it automatically email the user as they respond one could add a trigger on form submit. As The situation I'm working with doesn't require people to log in with an apps account I don't have access to an email address automatically so I have a text question that captures the user's email address.
It does ask the question about whether or not editing the forms is what you want. I've been grappling with the relative advantages of editing an existing response or sending a prefilled form using toPrefilledUrl() so that I can see how things have changed over time. I guess this comes down to the value that tracking this will provide you.
If you are using Google Apps your responders can edit there form responses.
See: How to Edit Form Responses
--edit this is now possible. See other answers.
After user submits the form, on confirmation, s/he will see a link to
edit his/her response. I'd like to include that link as a part of the confirmation email
That is not possible, period.
That link is not accessible anywhere and one can't guess/construct it. But, there's some workarounds that might suit you (some suggested here that I'll re-phrase), e.g.
Send a per-populated form link and have the user re-send it. You'd need to have some kind of control field (e.g. the username), so you can know and delete/ignore his older submits. Possibly automatically via a script.
You could also develop and publish an apps-script GUI and send a link to this apps script plus a parameter that you generate where you can determine which entry you should edit. The down-side of this approach is that it's somewhat cumbersome and overkill to re-design the whole form on Apps Script. But again, it works.
At last, you could open an "Enhancement Request" on Apps Script issue tracker and wait until they and Google Spreadsheet/Forms team get together to develop a solution.
Here is a clear blog post that shows you how to do it step by step and explains what's going on under the hood for AppsScripts newbies:
http://securitasdato.blogspot.com/2014/11/sending-confirmation-emails-from-google.html
While collectively you can get there from the all the excellent answers provided here, the script from that post worked best for me.
Does this help - I haven't tried it but I was looking for the same thing a while ago and noticed this.
From this page
https://developers.google.com/apps-script/reference/forms/
code from there contains this:
Logger.log('Published URL: ' + form.getPublishedUrl());
Logger.log('Editor URL: ' + form.getEditUrl());
Jon
Great, script works! Thanks.
For newbies, like me: Just paste the andre's code for function SendConfirmationMail(e) into your spreadsheet's code editor and set 'on form submit' trigger to run it. That's in spreadsheet script editor, not form script editor.
You need to hack in some values. Read the code. For me the confusing one was the need to replace the ********COLUMN SEQUENCE EX 14****** with the sheet column number where you want the edit urls to end up. I used 39 which is one column more than my form was using up.
However, I got runtime probs in this part:
for (var i in headers) {
value = e.namedValues[headers[i]].toString();
// Do not send the timestamp and blank fields
if ((i !== "0") && (value !== "")) {
message += headers[i] + ' :: ' + value + "<br>";
}
}
Dunno why, but I replaced it with this:
for (var keys in columns) {
var key = columns[keys];
if ( e.namedValues[key]) {
message += key + ' :: '+ e.namedValues[key] + "<br>";
}
}
Works for me.
Try This: (Credits is not for me, because i merge two solutions of the third part)
Source: Send Confirmation Email with Google Forms
/* Send Confirmation Email with Google Forms */
function Initialize() {
var triggers = ScriptApp.getScriptTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendConfirmationMail")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendConfirmationMail(e) {
var form = FormApp.openById('***YOUR FORM CODE***');
//enter form ID here
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('***SHEET NAME***');
//Change the sheet name as appropriate
var data = sheet.getDataRange().getValues();
var urlCol = ***************COLUMN SEQUENCE EX 14******; // column number where URL's should be populated; A = 1, B = 2 etc
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [], url;
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0]?urls[timestamps.indexOf(data[j][0].setMilliseconds(0))]:'']);
url = resultUrls[i-1]
}
sheet.getRange(2, urlCol, resultUrls.length).setValues(resultUrls);
try {
var ss, cc, sendername, subject, headers;
var message, value, textbody, sender;
// This is your email address and you will be in the CC
cc = Session.getActiveUser().getEmail();
// This will show up as the sender's name
sendername = "****YOUR NAME******";
// Optional but change the following variable
// to have a custom subject for Google Docs emails
subject = "Registro de Oportunidade submetido com sucesso";
// This is the body of the auto-reply
message = "Nós recebemos seu registro de oportunidade.<br>Muito Obrigado!<br><br>";
ss = SpreadsheetApp.getActiveSheet();
headers = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
// This is the submitter's email address
sender = e.namedValues["********COLUMN NAME OF DESTINATION E-MAIL************"].toString();
for (var i in headers) {
value = e.namedValues[headers[i]].toString();
// Do not send the timestamp and blank fields
if ((i !== "0") && (value !== "")) {
message += headers[i] + ' :: ' + value + "<br>";
}
}
message += "<br>Link to edit" + ' :: ' + url + "<br>";
textbody = message.replace("<br>", "\n");
GmailApp.sendEmail(sender, subject, textbody,
{cc: cc, name: sendername, htmlBody: message});
} catch (e) {
Logger.log(e.toString());
}
}
you can try to populate a form with the values given from that email address than delete previous answers ...
it's not a beautiful way but it can works ...
I don't think we have access to what that value is through the Spreadsheet API (which means Apps Script doesn't have it either). The closest I can think of would be the "key" value in this feed. You'd have to test to find out though. There's no other alternative that I know of other than accessing the Spreadsheet API directly. So first, you'd have to get the last row through the api use ?reverse=true&max-results=1