I have written a script that, when published as a web app, will count the number of emails in my inbox, along with the date of the earliest sent message, and output as a .csv:
function doGet() {
// get all threads in inbox
var threads = GmailApp.getInboxThreads();
var dataLength = threads.length
var earliestMessage = new Date();
for (var y = 0; y < dataLength; y++) {
var message = threads[y]
//sets last message date in thread y
var msgDate = message.getLastMessageDate();
//checks last message date to current value of earliestMessage, updates if necessary
if(msgDate.valueOf() < earliestMessage.valueOf()) {
earliestMessage = msgDate
}
}
//save to external files
return ContentService.createTextOutput(dataLength + '\n' + Utilities.formatDate(earliestMessage, 'GMT', 'dd/MM/yyyy')).downloadAsFile('emailData.csv');
}
This works fine, but I would now like this script to return the same information from a shared mailbox that I have access to. Since this is a shared mailbox, I do not have the option of just accessing its Google Drive and saving the script there (please let me know if I am mistaken in this!), so the script will only ever return information about my own account.
I have tried searching for similar issues, but the closest I have found are the following, neither of which seem to have clear solutions:
How do I process emails in a shared mailbox using Google Scripts
Google Help Forum -
How do I run a Google Script in a shared mailbox
Will the script need to be rewritten (and if so, how?), or is there a way of directing the GmailApp class to the shared mailbox instead of my own?
So I believe that your shared mailbox should be setup as just a plain old google user. If so then there is no reason why you can't setup a google drive for them.
The second part of this problem is what you want to create with this script, If you just want the information from your shared mailbox you can either:
Place the script in the shared user's drive and run it as them
Leave the script in your drive, set it to run as User accessing the web app(in Publish -> Deploy as web app...) and when you run it, make sure that you are logged in as the shared user.
Now if you wanted to combine the two feeds of information, that's a different story, I don't believe that you can, in the same script, run as two different users. I have also tested Libraries and they seem to use the same session user, so as far as I can see you really only have one option. Creating a webapp script to extract the information and run it as your shared user, and return it as json/xml (or even CSV if you are just appending it), then use a HTTP call(using URLFetchApp) in your existing script to retrieve that information and process it and do whatever you need with it (running as you). This will be a slow option, but at least it will do what you want.
Don't know if you found a solution to your issue.
Nor do I know if this would solve your problem, but a feature request has been placed on Google Cloud Community and on Google Issue Tracker to enable access to Gmail Apps Script add-ons from delegated mailboxes.
The more upvotes, the more chances Google seriously looks at this issue ;).
Related
Is it possible to process multiple selected emails by an addon?
The code from Google shows how to process only one selected email. I suppose 'e' would be some kind of array of accesstokens but I don't know how to access it.
function getContextualAddOn(e) {
var accessToken = e.messageMetadata.accessToken;
Logger.log("token:"+ accessToken);
GmailApp.setCurrentMessageAccessToken(accessToken);
var messageId = e.messageMetadata.messageId;
var card = createCard();
return [card.build()];
}
Thanks.
Your Apps Script code never runs on the client device, where the messages are selected in the Gmail UI - it runs on Google's servers. You can't access UI information from add-ons (there's no GmailApp.getSelectedThreads(), CardService.getActiveEmails(), etc.). You are only able to access the opened message / draft, and others in the same thread (or mailbox, depending on your add-on's scopes).
The event object received by your triggered callback function will only have a single access token. Until additional manifest triggers are added (currently only contextual and compose exist), this won't change.
As this is not Google, no one here can tell you if or when other triggers may or may not be added. You best bet is to request the feature.
Hopefully this is quite a simple question!
I've made a Google Script that writes to cells in a separate sheet "MasterSheet" (helped by several useful Q&As from here). This will ultimately be deployed embedded to multiple different sheets that I'm giving to individual users.
It works perfectly when the user has edit permissions on "MasterSheet", but I need that to remain private - i.e. not even viewable to anyone but me.
As background: In each 'user sheet', IMPORTRANGE is used to import the columns from 'MasterSheet' that that user is allowed to view, and then the script allows the user to add a comment to the MasterSheet.
I can view MasterSheet to see all the columns with comments from various users on one unified sheet, but the individual users shouldn't be able to view this.
The specific script for writing to the sheet is fairly generic:
function saveCommentToMasterSheet(form){
var company = form.company,
contact = form.contactselect,
comment = form.message ,
ss = SpreadsheetApp.openById("MASTERSHEET_ID").getSheetByName('MasterSheet');
if(company=="all"){
var row = findCell(contact);
} else {
var row = findCell2(contact,company);
}
//^The above finds the specific row number relating to the entry the user wants to comment on.
var cell2 = ss.getRange(row,11);
// ^In this case '11' is the column related to this sheet's specific user, I've made separate sheets for each user that are identical except this column number
cell2.setValue(comment);
}
I believe that I could make MasterSheet editable to anyone with the link, but I'd rather avoid that, particularly as the script is embedded in each spreadsheet so if the users just looked at it they'd find the MasterSheet id.
I understand that it's possible to run a script as me using the execute API, but if I'm honest, I struggled a little to figure out make that work.
Sorry if I'm asking a simple question - I've given it a good search and can't figure it out.
Many thanks!
Alex
N.B. This Running a google script from within a spreadsheet, but as a different user? looks like a similar question, but I'd really like to keep the comment system within the user's spreadsheet.
You can make a POST request to the master spreadsheet from the spreadsheets distributed to the users:
Apps Script Documentation - UrlFetchApp.fetch()
function saveCommentToMasterSheet(form) {//Function in the
//spreadsheets distributed to the users
var options,responseCode,url;
url = "https://script.google.com/macros/s/File_ID/exec";//Get from publishing
options = {};
options.method = 'post';
options.payload = form;
responseCode = UrlFetchApp.fetch(url, options).getResponseCode();
Logger.log('responseCode: ' + responseCode);//View the Logs
};
The above code will trigger the doPost(e) function in the master spreadsheet, and put the data into the event object e.
Then you can get the data out of e and write the data directly to what is the active spreadsheet, which is the master spreadsheet. Publish the Web App to run as "Me".
There are two versions of the published Web App; the "dev" version and the "exec" version. The "dev" version is always live with the latest changes, but should never be used in production. The "exec" version has a new version every time that you publish a the script again. To use the latest "exec" version in production, you must keep publishing the latest code.
I want my UiApp (GAS) application accessible only by people I explicitely invite.
I don't want to publish my application for public access, but prefer to provide some people a link (desktop shortcut) to an html file on a shared disk (so I only need to update the link to my application if I update it).
While trying to achieve that, I experience several problems.
Problem-1
I don't want the user being able to see my code.
How can I achieve that?
Problem-2
If I execute the application as me I can't retrieve the actual user (Session.getActiveUser() and Session.getEffectiveUser() both return 'me') and because of that I don't know what emails nor calendar to display.
How can I retrieve info that allows to display emails and calendar of the actual user (so NOT 'me') ?
Even if this requires an extra parameter while starting the application, it's acceptable to me.
Problem-3
I noticed that if the project uses a library, the application won't run unless the user explicitely defines the library himself. To me it seems the user should have nothing to do with that. How can avoid this problem (apart from not using libraries)?
In my case, I use a library for logging some info. This library is not located on a shared disk. Even if I disable the library (so logs will go to the system default logger) the application fails to run as a message appears stating the library is missing (and reffered in 'Resources').
What can I do to avoid this problem?
This question is rather long to answer but I'll try to make it short :
You don't need to share the code, keep it private.
The webapp should be deployed as "the user executing the app" so that all calendars, docs etc will be those of the user. You should allow anybody to access the app and create a login identification in your app itself : when the user tries to run the app you get his email (getActiveUser()) and compare this email with a list stored in scriptProperties. If the mail is not in the list then return a message saying this user is not allowed to run the app and let the script send you an alert so you know who has tried.
The Library should not be an issue, I have script using libraries that work for other users without them having to do anything except authorize.
To illustrate, here is a piece of code I use to control access of a webapp :
function doGet() {
var clientOK = false;
var client = PropertiesService.getScriptProperties().getProperty('clients').split();
var email = Session.getActiveUser().getEmail();
var app = UiApp.createApplication().setTitle('Order form').setStyleAttribute('background', 'beige');
var mainPanel = app.createAbsolutePanel().setStyleAttribute('padding','25');
var reject = app.createHTML("Hello,<BR><BR>You are connected with the address <B>"+email+"</B> that is not allowed to access this form,<BR><BR>"+
"Please contact the webMaster if you think this is an error.<BR><BR>Thank you.").setStyleAttribute('padding','25');
if(clients.join().match(email) == email){ clientOK = true ; var nom = email };
if(!clientOK){
app.add(reject)
MailApp.sendEmail('myEmail#gmail.com', 'attempt to log in form', email+' tried to access the form and has been rejected. Think about asking him what happened !')
return app
}
// continue normal code...
EDIT about problem 3 : if the Library you use writes to a spreadsheet (as a logger destination), this spreadsheet has to be shared with these users as well. Even if they don't need to open that spreadsheet from their drive, they will be able to do it as it will appear in their 'shared with me' view in their drive. That said, maybe you can get rid of that as soon as the development phase is over ?
I have a gradebook web app script which looks at a logged in student's email address, finds the email in the gradebook, and then displays the student's grades based on the column the email is in. The only problem is this only works if the spreadhseet is made public. How can I keep the spreadhseet private and still make this script work? I know that if I choose "Anyone with the link" it is unlikely someone will find the spreadsheet, but I'd prefer it to stay private. In addition, from the "Deploy as Web App" interface, the app must be executed as the user, not myself. Any ideas?
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheet/ccc?key=ID');
var sh1 = ss.getSheetByName('StudentGrades');
var logsheet = ss.getSheetByName('logsheet');
var data = sh1.getDataRange().getValues();
var user = Session.getEffectiveUser()
Logger.log(user)
function doGet() {
var app = UiApp.createApplication();
if(!getcol(user)){
var warn = app.createTextBox().setWidth('500').setValue("Your results are not available or you don't have permission to view these data");// if user is not in the list, warning + return
app.add(warn)
return app
}
var grid = app.createGrid(data.length, 2).setWidth('300px').setBorderWidth(1).setCellPadding(0).setCellSpacing(0).setStyleAttribute('borderCollapse','collapse').setId('grid');
var text = app.createLabel(user).setWidth('300px');
var col = getcol(user)
grid.setWidget(0,1,text).setText(0, 0, 'Results for');
grid.setStyleAttribute('textAlign','center')
for(n=1;n<data.length;++n){
grid.setText(n, 0, string(data[n][0]));
grid.setText(n, 1, string(data[n][col]));
grid.setStyleAttributes(n-1, 0, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//left column css attributes
grid.setStyleAttributes(n-1, 1, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//right column css attributes
}
app.add(grid);
return app
}
function string(value){
Logger.log(typeof(value))
if (typeof(value)=='string'){return value};// if string then don't do anything
if (typeof(value)=='number'){return Utilities.formatString('%.1f / 20',value)};// if number ther format with 1 decimal
if (typeof(value)=='object'){return Utilities.formatDate(value, Session.getTimeZone(), "MM-dd")};//object >> date in this case, format month/day
return 'error'
}
function getcol(mail){
if(data[0].toString().indexOf(mail.toString())!=-1){
for(zz=1;zz<data[0].length;++zz){
if(data[0][zz] == mail){var colindex=zz;break}
}
return colindex
}
return false
}
As I was suggesting in another of your posts you could setup a 'manual login' that will allow you to keep the spreadsheet private while running the webapp as yourself.
That implies that you'll have to create a list of user/password keys/values and that each student will have to enter his user name and password (you'll have to send them that information by email along with the link to the webapp) in a front end login screen before they gain access to the results display part.
I guess the best place to hold that list would be in script properties in the form of key:value.
If you need more that this to implement that solution (and if you think you'll go that way) feel free to ask.
EDIT following your comment :
Ok, I understand it can be unpleasant :)
Just wondering, wouldn't it be easier to store these data in the script itself in a scriptDB that you could automatically update with the spreadsheet values ?
The update would have to be executed by you (or by a timer trigger you create so that it runs on your account) and so the spreadsheet would remain private.
The students could then access the webapp with their accounts without accessing the spreadsheet.
From that service accessed as the user, call through urlget another apps script service published as yourself. That one does the ss-related work.
Might get timeouts on the service call though.
Why not setup a google group for each of your classes? Then add the google group to the SHARE permissions for the appropriate spreadsheet(s). This would have the added benefit of allowing you to easily send emails to your various classes via the appropriate google group. Under this scenario, your grade spreadsheets remain private and are only accessible by you and those email addresses listed in your google group.
I am a domain admin running the Google Apps for Education suite with many similar applications in place. You don't mention if you have administration rights for the domain or not, but I assume you do?
I keep all spreadsheets private and run the web apps as myself (giving the script the necessary access) which I think is precisely what you are aiming for?
I think your main issue stems from using .getEffectiveUser() which isn't providing you with what you need. I use Session.getActiveUser.getEmail() and then iterate through student objects (this is essentially just a sheet of student details returned as objects using the standard getRowsData function) to match the email value (if not found user = unknown).
This works perfectly for me with a student base of over 2000, all the data is private and the apps respond quickly serving personalised information.
If you'd like to see an example app please let me know, it's a little large to post here.
I've developed a web app with google script and code this in my gs.
var email = Session.getActiveUser().getEmail();
Logger.log(email);
// then do something to render the Email address on the page
After publishing the script, I log in with another account and execute the script.
Then the page display this:
This application was created by another user, not by Google.
This application has access to the following personal information: email address.
But still nothing in Log and the page display nothing.
I just don't get it...
Although it's not stated explicitly in the reference documentation for Session.GetActiveUser(), my testing confirmed that your web app needs to execute as the user accessing the web app to have access to getActiveUser(). I used the code below and the logging library described here.
Depending on the use case of your application, perhaps you could create a library containing a centralized ScriptDB instance to capture the active user email. Then, create another project that works with your private spreadsheet and the same scriptDB library.
var LOG_FILENAME = 'your-log-doc'
var LOG_PATH = 'folder/subfolder/third-folder'
function doGet() {
//Change variable above to a good path and filename for you
var x = LogLibrary.InitializeLogging(LOG_PATH, LOG_FILENAME)
Logger.log(x)
var email = Session.getActiveUser().getEmail();
Logger.log("Start email logging")
Logger.log(email);
LogLibrary.fnSaveLog() //Write the save and flush the logger content
// then do something to render the Email address on the page
var HTMLToOutput = '<html><h1>A header</h1><p>' + email + '</p></html>';
return HtmlService.createHtmlOutput(HTMLToOutput);
}
I've made some tests because I am looking for a solution about the same problem,and I conclude that :
so that function can works, the web app should be executed as "user accessing the web app", and for that, the web app should be configured so that can be executed as user accessing the web app
Note that : the user will be asked to authorise the script to access, edit and
manage it's drive also receiving and sending mails (which is not evident !! for all most of users of corse").
In the end that it depend of what you will use it for.
And in google documentation nothing is mentioned about this issue!!
For a simple function that returns the email address as a text to use as script or in google html forms.. etc :
function onOpen(){
var emailName = Session.getActiveUser().getEmail();
var optionsHtml = emailName.toString().split(",");
return optionsHtml;
}
Look in the gas issues forum (google). It hax been addressed many times. Basically you need to set it as run as user accessing the app if u expect users from outside your domain. Changing that setting will qlikely break your app depending on the permissions it needs
If your app is from before the 25th of June 2013, and you are using getEffectiveUser() or getUser() methods you will need to re-authorize the script because of the new authorization experience.
You can see the documentation here.
https://developers.google.com/apps-script/scripts_google_accounts#understandingPermissions
And do not forget to execute the script before deploying.
If you want to access the user's email, make sure you have added the "https://www.googleapis.com/auth/userinfo.email" email access policy in the appsscript .json
You can see the documentation here.
https://developers.google.com/apps-script/reference/base/session#authorization