Is it possible to make a getCommenters() functionality in Google Apps Script? - google-apps-script

The DriveApp > File class of Google Apps Script has these methods available:
getViewers()
getEditors()
But getCommenters method is not available. (This is interesting since addCommenter, addCommenters, and removeCommenter methods are available.)
I'm making a script where I'm changing file and folder permissions based on email addresses in a Spreadsheet. I would like to have a getCommenters functionality so I can compare if the commenter emails in the Spreadsheet are already commenters to the file, so there will be no need to again push those emails using addCommenters. (Pushing it will generate a new email to the user.) There is no problem in doing this with Viewers and Editors, as the methods are available.
Question: Is it possible to create a function that will mimic the supposed functionality of getCommenters? Or at least pull an array of email addresses of the commenters?

getViewers() returns viewers and commentators. You can filter the list using getAccess():
const getCommenters = (file) =>
file.getViewers().filter(user=>
file.getAccess(user) == "COMMENT")

Getting the right nudge from #TheMaster's answer, this code worked for what I needed:
let file = DriveApp.getFileById('xxxxxxxxxxx-FILE_ID-xxxxxxxxxxx');
let userlist = file.getViewers().filter((user) => file.getAccess(user) == 'COMMENT');
let emaillist = [];
for (var i in userlist) {
emaillist.push(userlist[i].getEmail());
}
Thereafter, emaillist becomes an array of email addresses having COMMENT permissions.

Related

Using addEditor(s) with users outside your workspace

I used appscript to create copies of a document and share them with a particular user, this works fine in that the code creates the document and the user receives an invitation to edit, but I have found that at least some users are not able to edit the document. When I view the sharing options on the individual docs, I can see that they are restricted to my workspace only.
I had the same issue when doing this process manually, but thought addEditor would be able to override this, as the shared Gdrive is not restricted in this way overall (i.e, we can share docs with individuals by adding them as editor, from outside our organisation.) . I do not want to have to individually share >100 docs per week manually, so any fixes/workarounds would be very appreciated.
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
All of the users I am sharing the document are external to the organisation.
I tried making the master copy of the file accessible to anyone who had the link, hoping that this would mean they could access the file without manually approving the request, but it looks like this doesn't transfer to the copies. I'm not sure enough of the actual issue to know that this would solve it.
This is how the code looks:
function createCopy() {
let file = DriveApp.getFileById("ID of file to copy");
let sheet = SpreadsheetApp.openByUrl('Sheet with names and emails').getSheetByName("Sheet1");
let range = sheet.getRange('D2:D5');
let email = sheet.getRange('C2:C5').getValues().flat();
let values = range.getValues();
let folder = DriveApp.getFolderById("Destination Folder");
for (let i = 0; i < values.length; i++) {
Logger.log(values[i]);
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
}
}
From the question
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
Try this
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
by
file.makeCopy(values[i].toString(), folder)
.addEditor(email[i])
.setSharing(DriveApp.Access.DOMAIN, DriveApp.Permission.EDIT)
References
https://developers.google.com/apps-script/reference/drive/file#setsharingaccesstype,-permissiontype

Previously working Google App Script now produces permission error

I cannot find anything about Google having updated policies on April 2022. This script was working without any issues before, then in the middle of the day, permissions needed to be re-added. Even with said permissions granted, Google still says access is denied. It was later discovered that the macro has not ran successfully since March 31st.
var newJobFile = DriveApp.getFileById(newJobID);
newJobFile.AddEditors(['group#gmail.com','admin#gmail.com');
newJobFile.setOwner('admin#gmail.com');
The last line is what generates the error. When debugging, I can see as the variable gets created, I cannot see any information about it. For example, if I add a "Filecreated.getName();" before the line with the error, the variable menu remains blank. I am not sure if this is normal. The "newid" variable is confirmed to have the spreadsheet ID. I can copy it into the URL and it takes me to the page. Why is this suddenly an issue and how can I fix it? I did not have a appscripts.json file before with OAuthScopes and it worked fine then. I've added it with the proper permissions, but it does not change anything. I've added several permission scopes to try to resolve this but none of them do. Any advice?
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets"
]
EDIT: I am still having this issue. Even with everything controlled under one account, I cannot transfer ownership to 'Central_account#gmail.com' due to 'Exception : Access Denied : DriveApp'. I have found just one other person experiencing this same issue on the Google App Script group page. Is anyone else not having issues with transferring ownership starting in April on a regular, non-work account?
EDIT 2: Sorry for not uploading my code earlier, I had to clean it up first.
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('Add') //creates toolbar entry to the right of 'Help'
.addItem('Capital project','newProject')
.addToUi();
}
function newProject(){
var dashboard = SpreadsheetApp.getActiveSpreadsheet(); //Stores dashboard into a variable for later
var template = SpreadsheetApp.openById('xxxxxx') //Opens the project sheet template in the backend
var nameEntry = getName(); //Prompts user for the name of the new job file
if (nameEntry == null){
return;
}
SpreadsheetApp.setActiveSpreadsheet(template); //Template is now the active sheet
var newJobID = copySheet(nameEntry); //Creates a copy of the New Project template
var newJobFile = DriveApp.getFileById(newJobID);
newJobFile.AddEditors(['group#gmail.com','admin#gmail.com');
newJobFile.setOwner('admin#gmail.com');
}
function getName(){
var ui = SpreadsheetApp.getUi();
var name = ui.prompt( //Prompts the user for an input name
'',
'?????-? Project Description',
ui.ButtonSet.OK_CANCEL);
var cancelCheck = name.getSelectedButton();
if (cancelCheck == ui.Button.CANCEL || cancelCheck == ui.Button.CLOSE) {
return null;
}
var sheetName = name.getResponseText();
return sheetName;
}
function copySheet(name) {
var activeSS = SpreadsheetApp.getActive();
var newFile = activeSS.copy(name) //Creates a copy of the New Project template
SpreadsheetApp.setActiveSpreadsheet(newFile); //Resets active spreadsheet to the recent copy
activeSS = SpreadsheetApp.getActive();
activeSS.getRange('A1').activateAsCurrentCell();
activeSS.getCurrentCell().setValue(name); //Set cell A1 to the name of the file
var newHyperlink = '=HYPERLINK("' + activeSS.getUrl() + '#gid=15580246",A1)';
activeSS.getRange('A2').activateAsCurrentCell();
activeSS.getCurrentCell().setValue(newHyperlink);
return activeSS.getId();
}
With logs, I can see that everything works as intended except for the .setOwner() method. It returns the Access Denied error. I have checked all google accounts and each has enabled CustomScripts to access their drive. Has .setOwner() been deprecated for non workspace accounts?
I've updated my original snippets to match my code.
Apparently the process to transfer file ownership between consumer accounts has changed. According to this guide, the prospective new owner needs to accept the transfer request. See example below:
And I found this article where you can see that the process was different a few months ago, you could set a new owner immediately without sending invitation.
I tested the setOwner() method with a Google Workspace account and it works as within Google Workspace you can directly transfer file ownership between users within the same organization, then I tested the same script with a Gmail account and tried to set another Gmail account as the new owner and I got the same error message: "Exception: Access denied: DriveApp".
Based on all the information, it seems that this behavior is expected as the process to change a file ownership for Gmail accounts is different now, you can’t set a new owner directly, the person you invite to own the file must accept your request to complete the transfer.
I don't have enough reputation to comment, but I'd second what Lorena Gomez said that it's likely an issue with your OAuthScopes. According to the Apps Script documentation, the setOwner() method on the File class requires the /auth/drive authorization scope.
It looks like you have both the /auth/drive and the /auth/drive.file scope configured, and I think the /auth/drive.file scope, which is narrower, is overriding the broader /auth/drive scope, which is required to call setOwner().
Have you tried removing the /auth/drive.file scope and running the script only with /auth/drive?

Script to automatically make a copy of a Google Document for editing

I feel like a total noob posting here. I know CSS, HTML, and XML pretty well but have always avoided JS. I know very little javascript and recently started a Lynda.com course to catch up. Sorry for my ignorance. As such, I am really struggling learning Google Apps Script. Obviously, I need to learn JS before I can make sense of any of it.
The school I work for (5000 students) has set up an online curriculum. I created the curriculum in the form of thousands of google document worksheets. These worksheets are linked on various websites.
The problem we are facing is that when students open the documents, they have to make a copy of them before they can edit them (I of course don't want them to be able to edit the originals). This really sucks for students using mobile browsers on their tablets as making a copy in Google Docs doesn't really work well when using the desktop UI on mobile devices.
I know this kind of thing can be automated with script. I've looked here, and low and behold, it works! I'm pissing my pants with joy as I've been searching for such functionality for three years. (Yes, I know that's sad).
So, what I'm asking is, would anyone be willing to help a noob figure out how to adapt this code so that students click a button on a website lesson and it automatically makes and opens a copy of the worksheet in a new tab?
/**
* Copy an existing file.
*
* #param {String} originFileId ID of the origin file to copy.
* #param {String} copyTitle Title of the copy.
*/
function copyFile(originFileId, copyTitle) {
var body = {'title': copyTitle};
var request = gapi.client.drive.files.copy({
'fileId': originFileId,
'resource': body
});
request.execute(function(resp) {
console.log('Copy ID: ' + resp.id);
});
}
Spending all day yesterday learning Javascript, I've still got a long way to go. Not sure how long it'll take for me to be able to figure this out on my own.
You can certainly do this with Apps Script. Only takes a couple of lines. In fact, you can use just the version I wrote below.
Here is how I would do it -
Ensure you original document is at least read enabled for the folks that will be accessing it.
Grab the fileId from the URL -
Write a web app in Apps Script with the following code -
function doGet(e) {
//file has to be at least readable by the person running the script
var fileId = e.parameters.fileId;
if(!fileId){
//have a default fileId for testing.
fileId = '1K7OA1lnzphJRuJ7ZjCfLu83MSwOXoEKWY6BuqYitTQQ';
}
var newUrl = DocsList.getFileById(fileId).makeCopy('File copied to my drive').getUrl();
return HtmlService.createHtmlOutput('<h1>Open Document</h1>');
}
Deploy it to run as the person accessing the app.
One key thing to remember is that a web app built by Apps Script cannot force open a new window automatically. Instead we can show a link which is clickable into the document in edit mode.
You can see it in action here (will create some dummy file) -
https://script.google.com/macros/s/AKfycbyvxkYqgPQEb3ICieywqWrQ2-2KWb-V0MghR2xayQyExFgVT2h3/exec?fileId=0AkJNj_IM2wiPdGhsNEJzZ2RtZU9NaHc4QXdvbHhSM0E
You can test this by putting in your own fileId.
Since DocsList is deprecated, currently you can make a copy of a file using the following code:
File file=DriveApp.getFileById(fileId).makeCopy(fileName, folder);
where fileId can be obtained as explained in the answer by Arun Nagarajan.
Update as of 2015, Google Script removed the fileId for reasons unknown. The previous method of appending "/copy" to the URL of the Google doc has been re-enabled. Ex) https://docs.google.com/document/d/1GTGuLqahAKS3ptjrfLSYCjKz4FBecv4dITPuKfdnrmY/copy
Here is the code to do it properly (works in 2019,2020,2021):
/**
* Create custom menu when document is opened.
*/
function onOpen() {
DocumentApp.getUi()
.createMenu('For Students')
.addItem('Make a copy', 'makeACopy')
.addToUi();
}
function makeACopy() {
var templateId = DocumentApp.getActiveDocument().getId();
DriveApp.getFileById(templateId).makeCopy();
}
Here is the code I use to make an auto copy of my google Docs.
function makeCopy() {
// generates the timestamp and stores in variable formattedDate as year-month-date hour-minute-second
var formattedDate = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd' 'HH:mm:ss");
// gets the name of the original file and appends the word "copy" followed by the timestamp stored in formattedDate
var name = DocumentApp.getActiveDocument().getName() + " Copy " + formattedDate;
// gets the destination folder by their ID. REPLACE xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx with your folder's ID that you can get by opening the folder in Google Drive and checking the URL in the browser's address bar
var destination = DriveApp.getFolderById("16S8Gp4NiPaEqZ0Xzz6q_qhAbl_thcDBF");
// gets the current Google Docs file
var file = DriveApp.getFileById(DocumentApp.getActiveDocument().getId())
Add a trigger and enjoy!
The same code works for google Sheets. Only you need to replace
DocumentApp.getActiveDocument().getName()
with
SpreadsheetApp.getActiveSpreadsheet()
You can find more details here.

Changing the state of apps script webapp

I'm making standalone web app in Google Apps Script. I have somekind of task flow in the app. First search something from spreadsheet, then do some selections what to do with the collected data and after that fill spreadsheet row with some userinputs. So I need to run between few states.
I'm fairly sure I don't know enough about web tech so this might be very stupid question but here it goes.
I know that the e.parameters comes from the url like this www.google.com&value=helloworld
, e.parameters.value returns helloworld.
So now the problem: How do I set parameter e values in doGet(e) and call this same page or even different page/script in same script project? In otherwords how do I call self&value=helloworld ?
Is there some other way to do this? In GWT I just store everything to database and keep session in cookies. But cookies are not allowed so how to keep the state of the webapp in google apps script?
EDIT: This is how I pass the parameters for doGet(e).
function doGet(e) {
var app = UiApp.createApplication();
var newValue = 0;
if(e.parameter == undefined || e.parameter.value == undefined){
newValue = 1;
}else{
newValue = 1+parseInt(e.parameter.value);
}
var link = 'https://script.google.com/a/macros/domain.com/s/<insertidhere>/dev';
var anchor = app.createAnchor('Next',link+'?&value='+newValue);
anchor.setTarget('_self');
app.add(anchor);
var label = app.createLabel(newValue);
app.add(label);
return app;
}
The link format has changed a bit so instead of concatenate &value=... you need to add ? first like this ?&value1=helloworld&value2=....
Failing to use ? led me to think that there is bug and I need to use old format for the link and using that old format forbit any other than the script owner using it.
Hopefully this helps somebody to solve similar problems.
You've almost answered yourself. Append the URL paramenters to the end of your web app's URL and you can access them in your script.
Let's say you have a URL like
http://script.google.com/.../exec
When you hit the URL
http://script.google.com/.../exec?value=helloworld
,
inside doGet, you can read these URL parameters as
e.parameter.value;
IMO - instead of using ? to separate multiple parameters try keeping all the values in a single anchor with some other character separator of your choice such as # since after the first ? its possibly ignoring the other one and everything afterwards. Would be great to hear back on what you find.

Trigger Google Apps Script by email

I'm looking for examples of a pattern where a demon script running within a GoogleAppsForBusiness domain can parse incoming email messages. Some messages will will contain a call to yet a different GAScript that could, for example, change the ACL setting of a specific document.
I'm assuming someone else has already implemented this pattern but not sure how I go about finding examples.
thx
You can find script examples in the Apps Script user guide and tutorials. You may also search for related discussions on the forum. But I don't think there's one that fits you exactly, all code is out there for sure, but not on a single script.
It's possible that someone wrote such script and never published it. Since it's somewhat straightforward to do and everyone's usage is different. For instance, how do you plan on marking your emails (the ones you've already read, executed, etc)? It may be nice to use a gmail filter to help you out, putting the "command" emails in a label right away, and the script just remove the label (and possibly set another one). Point is, see how it can differ a lot.
Also, I think it's easier if you can keep all functions in the same script project. Possibly just on different files. As calling different scripts is way more complicated.
Anyway, he's how I'd start it:
//set a time-driven trigger to run this function on the desired frequency
function monitorEmails() {
var label = GmailApp.getUserLabelByName('command');
var doneLabel = GmailApp.getUserLabelByName('executed');
var cmds = label.getThreads();
var max = Math.min(cmds.length,5);
for( var i = 0; i < max; ++i ) {
var email = cmds[i].getMessages()[0];
var functionName = email.getBody();
//you may need to do extra parsing here, depending on your usage
var ret = undefined;
try {
ret = this[functionName]();
} catch(err) {
ret = err;
}
//replying the function return value to the email
//this may make sense or not
if( ret !== undefined )
email.reply(ret);
cmds[i].removeLabel(label).addLabel(doneLabel);
}
}
ps: I have not tested this code
You can create a google app that will be triggered by an incoming email message sent to a special address for the app. The message is converted to an HTTP POST which your app receives.
More details here:
https://developers.google.com/appengine/docs/python/mail/receivingmail
I havn't tried this myself yet but will be doing so in the next few days.
There are two ways. First you can use Google pub/sub and handle incomming notifications in your AppScrit endpoint. The second is to use the googleapis npm package inside your AppScript code an example here. Hope it helps.
These are the steps:
made a project on https://console.cloud.google.com/cloudpubsub/topicList?project=testmabs thing?
made a pubsub topic
made a subscription to the webhook url
added that url to the sites i own, i guess? I think I had to do DNS things to confirm i own it, and the error was super vague to figure out that was what i had to do, when trying to add the subscription
added permission to the topic for "gmail-api-push#system.gserviceaccount.com" as publisher (I also added ....apps.googleusercontent.com and youtrackapiuser.caps#gmail.com but i dont think I needed them)
created oauth client info and downloaded it in the credentials section of the google console. (oauthtrash.json)