.getEditors() returns 'DriveUser,DriveUser' - google-apps-script

Before adding new editors to a Google drive folder, I'd like to check first which ones already exist in that folder's editors. This is to avoid unnecessary share notification if user is already an existing editor.
However, .getEditors() always returns 'DriveUser,DriveUser' so all editors get added even if existing already.
Thanks to advise if you have a solution to this.
Here's my code:
var dropboxID = "zzxxccvv112233";
var folderList = DriveApp.getFolderById(dropboxID).getFoldersByName(employee);
if (folderList.hasNext()) {
var employeeFolder = folderList.next(); //folder already exists so just add new Editors, if any
var currentEditors = employeeFolder.getEditors();
Logger.log("currentEditors = " + currentEditors);
var newEditors = emailTo + "," + emailCc;
newEditors = newEditors.replace(/\s/g, '');
newEditors = newEditors.split(',');
var editorsToAdd = [];
for (var i=0 ; i<newEditors.length ; i++) {
if (currentEditors.indexOf(newEditors[i]) < 0) {
editorsToAdd.push(newEditors[i]);
}
}
employeeFolder.addEditors(editorsToAdd);
} else {
and Logger shows this:
[20-03-05 21:23:46:637 HKT] currentEditors = DriveUser,DriveUser

You should be comparing by email address with User.getEmail()
// Get all of the current editor emails in one array
var currentEditorEmails = currentEditors.map(function (editor) { return editor.getEmail() });
// Check if the new editor emails exist in the currentEditorEmails array
for (var i=0 ; i<newEditors.length ; i++) {
if (currentEditorEmails.indexOf(newEditors[i]) < 0) {
editorsToAdd.push(newEditors[i]);
}
}
If you're using V8, you could use an arrow function instead.
const currentEditorEmails = currentEditors.map(editor => editor.getEmail());

Related

How to continue running the script even if a user exists in Google Group using google scripts?

I need help with Google groups. The code currently checks if a member already exists but it stop when it does find one. How can I modify the code to allow it to process the next row without stopping?
TIA!
function updateGroup() {
const s = SpreadsheetApp.openById("ID HERE");
const sheet_name = s.getSheetByName("REPORT HERE");
const sheet_data = sheet_name.getRange(2,1,sheet_name.getLastRow(),sheet_name.getLastColumn());
const sheet_dataVal = sheet_data.getValues();
for (var i = 0; i < sheet_dataVal.length-1; i++) {
var member_Email = sheet_dataVal[i][9]; // REWS Projects Email Address
var groupEmail = "GROUP EMAIL ADDRESS HERE";
var member_Role = "MEMBER";
var member_Type = "USER";
var group = GroupsApp.getGroupByEmail(groupEmail);
var comment = sheet_dataVal[i][13];
if (comment === "Member Added to Group" || comment === "Member already exists") {continue;}
var checkMembers = AdminDirectory.Members.list(groupEmail).members
for (var m in checkMembers) {
if (checkMembers[m].email == member_Email);
return sheet_name.getRange(i+2,14).setValue("Member already exists");
}
addNewMembersToGroup(member_Email,member_Role,groupEmail,i,sheet_name);
}
}
function addNewMembersToGroup(member_Email,member_Role,groupEmail,i,sheet_name) {
/* Member does not exists in group, add */
var addNewMember = {
kind: "admin#directory#member",
email: member_Email,
role: member_Role
};
AdminDirectory.Members.insert(addNewMember,groupEmail);
sheet_name.getRange(i+2,14).setValue("Member Added to Group");
}
When you are using a return, the execution of the function is stopped - hence the issue you are getting.
In order to fix this, you should remove the return and have the if statement like this:
if (checkMembers[m].email == member_Email);
sheet_name.getRange(i+2,14).setValue("Member already exists");
Reference
JavaScript return statement.

GmailApp (Google Apps Script) Displays Inline Images as Attachments

Hello friendly StackOverflow folks,
I am having the most difficult time getting the GmailApp sendEmail() method to successfully use an existing email (e.g. a draft) that contains images inline as a template for new messages.
It seems like this is a problem, as I have found devs having this problem here and here and proposed solutions here, here, and here. Each of these solutions is 4+ years old, so perhaps they're out of date. I have been unable to use any of these solutions to replicate a success.
Currently, I'm running this code from my Google Scripts backend:
function generateMessageFromTemplate () {
var selectedTemplate = GmailApp.getMessageById('MESSAGE_ID');
//////////////////////////////////////////////////////////////////////////////
// Get inline images and make sure they stay as inline images (via Romain Vialard)
//////////////////////////////////////////////////////////////////////////////
var emailTemplate = selectedTemplate.getBody();
var rawContent = selectedTemplate.getRawContent();
var attachments = selectedTemplate.getAttachments();
var regMessageId = new RegExp(selectedTemplate.getId(), "g");
if (emailTemplate.match(regMessageId) != null) {
var inlineImages = {};
var nbrOfImg = emailTemplate.match(regMessageId).length;
var imgVars = emailTemplate.match(/<img[^>]+>/g);
var imgToReplace = [];
if(imgVars != null){
for (var i = 0; i < imgVars.length; i++) {
if (imgVars[i].search(regMessageId) != -1) {
var id = imgVars[i].match(/realattid=([^&]+)&/);
if (id != null) {
var temp = rawContent.split(id[1])[1];
temp = temp.substr(temp.lastIndexOf('Content-Type'));
var imgTitle = temp.match(/name="([^"]+)"/);
if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
}
}
}
}
for (var i = 0; i < imgToReplace.length; i++) {
for (var j = 0; j < attachments.length; j++) {
if(attachments[j].getName() == imgToReplace[i][0]) {
inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
attachments.splice(j, 1);
var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
emailTemplate = emailTemplate.replace(imgToReplace[i][1], newImg);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
GmailApp.sendEmail('test#email.com', selectedTemplate.getSubject(), '', {
attachments: attachments,
htmlBody: emailTemplate,
inlineImages: inlineImages
});
};
The Google Scripts documentation on the sendEmail() method is here.
This is the Output of this Function as Is
When I send emails from Apps Script as is, I get emails that look like this:
screenshot
I've replicated the test with an old yahoo.com email account and had the exact same results as a Gmail account.
Again, this dev also has this same issue.
If you can help, I would be extremely grateful!

Is there a limit to how many G-Forms i can link to a singe google spreadshee?

I have a script that is designed to create about 120 forms and link them to a single spreadsheet where I will analyze the data. I don't have any issues with the script until my spreadsheet has about a dozen forms linked to it. Then I get an error saying the destination ID is invalid, after logging the id, and entering it manually into a url I see no issues with the ID....
var ssSummaryId = '******redacted*******';
var form = FormApp.create('RM#' + rmNumber).setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
form.setDescription(valuesSummary[ii][2])
.setConfirmationMessage('Thanks for the update on room ' + rmNumber)
.setShowLinkToRespondAgain(false);
// form.setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
var formId = form.getId();
var formUrl = form.getPublishedUrl();
EDIT I'm adding my complete script and some additional info, just in case someone wants to check my code out and point out all it rookie mistakes.
I'm using Google scripts to build a spreadsheet and then create 120 slightly altered Google forms that are linked to a single spreadsheet, all the responses are by design on separate sheets named "form responses n". I consistently hit a wall once I exceed 10 forms linked to one sheet. Note; in initial testing I remember having a spreadsheet with 46 forms (and therefore sheets) linked to it. As you can see in the code below, I have the app restart from where it's left off after every 5 forms are created, so I'm not getting any 'extended runtime errors". Just the error below, typically after the script runs twice from Google Scripts IDE.
I'm just finally getting the hand of basic javascript after years of using and modifying js in web developing. So I apologize in advanced for the poor code.
Failed to set response destination. Verify the destination ID and try again. (line 54, file "Code")
function spreadsheet_builder() {
var ssSummaryId = '<<REDACTED>>';
var ssFormDataId = '<<REDACTED>>';
var batchSize = 5;
var buildStatus = false;
var ssSummary = SpreadsheetApp.openById(ssSummaryId);
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
if (ssSummary.getSheetByName('Summary') == null) {
var sheetSummary = ssSummary.getSheetByName('Sheet1');
} else {
var sheetSummary = ssSummary.getSheetByName('Summary');
}
var rangeSummary = sheetSummary.getDataRange();
var valuesSummary = rangeSummary.getValues();
buildStatus = get_last_position(valuesSummary, buildStatus); //either returns last position in array or 'true' if task is complete
if (buildStatus != true || buildStatus > 0) {
var formCreation = [];
var formData = get_form_data(ssFormDataId); // Get form questions from form Data ss, might be better to keep everything on the same sheet
batchSize = buildStatus + batchSize;
for ( var ii = buildStatus; ii < batchSize; ii++ ) {
if (valuesSummary[ii][1] != '') {
var formCreationReturn = form_builder(formData, valuesSummary, ii, ssSummaryId);
formCreation.push(formCreationReturn);
}
}
var aSum = [ssSummary, sheetSummary, rangeSummary];
final_storing_operation(formCreation, aSum, buildStatus);
}
if (sheetSummary.getName() != 'Summary') {
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
sheetSummary.activate().setName('Summary');
sheetSummary.setFrozenColumns(3);
sheetSummary.setFrozenRows(1);
sheetSummary.hideColumns(1);
//var sSumIndex = sheetSummary.getIndex();
}
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
sheetSummary.activate();
ssSummary.moveActiveSheet(1);
}
function form_builder(formData, valuesSummary, ii, ssSummaryId) {
var lastFormCreated = ii;
var rmNumber = valuesSummary[ii][1];
var form = FormApp.create('RM#' + rmNumber).setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
form.setDescription(valuesSummary[ii][2])
.setConfirmationMessage('Thanks for the update on room ' + rmNumber)
.setShowLinkToRespondAgain(false);
// form.setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
var formId = form.getId();
var formUrl = form.getPublishedUrl();
var sectionHeader = 'SECTION_HEADER'; //preformatted form question types.
var list = 'LIST';
var paragraphText = 'PARAGRAPH_TEXT';
for (var j = 1; j < formData.length; j++) { //top row is header
switch (formData[j][0]) {
case sectionHeader:
form.addSectionHeaderItem().setTitle(formData[j][1]);
break;
case list:
var item = form.addListItem();
item.setTitle(formData[j][1]).setHelpText(formData[j][2]);
item.setChoices([
item.createChoice(formData[j][3]),
item.createChoice(formData[j][4]),
item.createChoice(formData[j][5])
]);
break;
case paragraphText:
form.addParagraphTextItem().setTitle(formData[j][1]);
break;
default:
form.addSectionHeaderItem().setTitle('OOPS u\'don MESSED up');
break;
}
}
return [formId, formUrl, lastFormCreated, rmNumber];
}
function final_storing_operation(formCreation, aSum, buildStatus) {
SpreadsheetApp.setActiveSpreadsheet(aSum[0]);
aSum[1].activate();
var startRow = formCreation[0][2] + 1;
var newRange = aSum[1].getRange(startRow, 1, formCreation.length, 2); //row, clmn, rows, columns
var newValues = [];
for ( var ij = 0; ij < formCreation.length; ij++) {
var values = [formCreation[ij][0], "\=HYPERLINK(\"" + formCreation[ij][1] + "\", \"RM#" + formCreation[ij][3] + "\")"];
newValues.push(values);
}
newRange.setValues(newValues);
}
function get_last_position (valuesSummary, buildStatus) {
var rowPos = 1; // start at 1 to ignore headers
while (valuesSummary[rowPos][1] != '') {
if (valuesSummary[rowPos][0] == '') {
return rowPos;
}
rowPos++;
}
if(valuesSummary[rowPos][0] != '' && valuesSummary[rowPos][1] != '') {
buildStatus = true;
return buildStatus;
}
}
function get_form_data (ssFormDataId) {
var ssFormData = SpreadsheetApp.openById(ssFormDataId);
SpreadsheetApp.setActiveSpreadsheet(ssFormData);
var sheetFormData = ssFormData.getSheets()[0];
var rangeFormData = sheetFormData.getDataRange();
var valuesFormData = rangeFormData.getValues();
return valuesFormData;
}
As an alternative, you could create the forms, and intentionally not link them to a spreadsheet, then have some code that looped through every form, and extracted the data. You'd probably want to put the forms into their own folder.
Or, you'd need to build a form with Apps Script HTML Service, embed it into a Apps Script Gadget in a Google Site, and have everyone fill out the form from Sites.

Remove duplicates values in array Google Apps Script

I would like to know how to remove duplicates values from 2 arrays, combined into one main array.
This values must NOT be removed from the sheets or document, just in the array, thats why I didnt use clear() or clearContents() built in functions;
Ive also tried to modify the removeDuplicates() function from the GAS tutorials, but it throws me rows inside columns from A to Z, instead filtered rows...a total mess.
Notes:
Parameters from getClients() are from others functions, and works ok.
newClients list clients from the sheet 'Users' and newUsers list users from another sheet called 'Data'.
Boths sheets belongs to the same spreadsheet.
newClients and newUsers: both arrays only contains strings (usernames), and in both there are duplicated values.
So the goal is identified and remove those values, the original and the duplicate.
Should be easier I think, but Im new in JS, so everything Ive been tried, didnt worked.
Thanks
The Code
function getAllClientsFromData(body,project){
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Active the Sheets
var sheet= ss.getSheets()[1,3];
// Access data in Sheet "Data"
var rangeC = ss.getRangeByName("clients"); //A:2:C273
var rangeU = ss.getRangeByName("names"); //A5:A
var clients = rangeC.getValues();
var users = rangeU.getValues();
var lastRow = sheet.getLastRow()-2;
body += "<h2>Total of " + lastRow + " clients in " + project + "</h2>"
body += "<table style=" + STYLE.TABLE + ">";
body += getClients(ss,clients,users,project);
body += "</table>";
return body;
}
function getClients(ss,clients,users,project){
var body = "";
var newClients = [];
for( var g = 0; g < clients.length; g++ ){
var currentProject = clients[g][2];
if( clients[g]!= "" ){
newClients.push(clients[g][0]);
}
} // end for
var newUsers = [];
for( var u = 0; u < users.length; u++ ){
if( users[u] != "" ){
newUsers.push(users[u][0]);
}
} // end for
var allData = newUsers.concat(newClients);
var uniqueData = allData.sort();
body += "<tr><td style=" + STYLE.TD + ">" + uniqueData.join("</td><tr><td style=" + STYLE.TD + ">") + "</td></tr></tr>";
return body;
}
UPDATES!
The answers works great filtering, but Im getting the same result as on my previous tries: displaying the filtered results. I need to remove them from the array. example:
var array = ['aa','bb','aa','ff','pp', 'pp'];
filtering code...
var array = ['bb','ff'];
I try to add splice() js method but the params I pass, does not working ok.
The array you are working on is not a 2D array anymore since you extracted the fields before sorting... so you can use a very simple duplicate removal function as shown below with an example and some added Logger.log to see how it works.
function test(){
var array = ['aa','bb','cc','aa','dd','cc']
Logger.log(removeDups(array));
}
function removeDups(array) {
var outArray = [];
array.sort():
outArray.push(array[0]);
for(var n in array){
Logger.log(outArray[outArray.length-1]+' = '+array[n]+' ?');
if(outArray[outArray.length-1]!=array[n]){
outArray.push(array[n]);
}
}
return outArray;
}
in your code this would replace the line
var uniqueData = allData.sort();
that would become :
var uniqueData = removeDups(allData);
EDIT :
If letter case is an issue, you can modify this code to ignore it. You should change the condition and the sort function so that they both ignore the case in your names but preferably keep the original letter case.
This could be achieved with the code below :
function test(){
var array = ['aa','bb','Cc','AA','dd','CC'];// an example with Upper and Lower case
Logger.log(removeDups(array));
}
function removeDups(array) {
var outArray = [];
array.sort(lowerCase);
function lowerCase(a,b){
return a.toLowerCase()>b.toLowerCase() ? 1 : -1;// sort function that does not "see" letter case
}
outArray.push(array[0]);
for(var n in array){
Logger.log(outArray[outArray.length-1]+' = '+array[n]+' ?');
if(outArray[outArray.length-1].toLowerCase()!=array[n].toLowerCase()){
outArray.push(array[n]);
}
}
return outArray;
}
Logger result :
EDIT 2 :
Here is another version that keeps only unique values (I didn't understand correctly your request in the first version as it kept one element from the duplicates...)
I simply added an else if condition to remove the elements that were part of a group of duplicates.(I kept the case insensitive version but you can remove it easily)
function test(){
var array = ['aa','dd','hh','aa','bb','Cc','cc','cc','bb','nn','bb','AA','dd','CC'];// an example with Upper and Lower case
Logger.log('original array = '+array);
Logger.log('unique result = '+removeDups2(array));
}
function removeDups2(array) {
var uniqueArray = []
array.sort(lowerCase);
function lowerCase(a,b){
return a.toLowerCase()>b.toLowerCase() ? 1 : -1;// sort function that does not "see" letter case
}
var temp = array[0];
for(var n=1 ;n<array.length ; n++){
Logger.log(temp+' = '+array[n]+' ?');
if(temp.toLowerCase()!=array[n].toLowerCase()){
uniqueArray.push(array[n]);
temp = array[n];
}else if(uniqueArray[uniqueArray.length-1]==temp){
uniqueArray.pop();// remove it from result if one of the duplicate values
}
}
return uniqueArray;
}
Logger result new code :
In your code you are not doing anything to filter the duplicate values.
This line will just sort the data and won't give you unique data.
var uniqueData = allData.sort();
You can do something like this on your merged array, after you 'installing' 2DArray lib: https://sites.google.com/site/scriptsexamples/custom-methods/2d-arrays-library
var uniqueData = unique(allData);
Another option is to create a loop and check for duplicate values, but you should remember to transform all the values of the string to lowercase before you do these matches.
I created this function and it worked.
function removeDups(data) {
var newData = [];
data.forEach(function(value) {
if (newData.indexOf(value) == -1) {
newData.push(value);
}
});
return newData;
}
Yet another solution:
function removeDups(array) {
array.sort()
var lastValue = !array[0]
var outArray = array.filter(function(value) {
if (value == lastValue)
return false
lastValue = value
return true
})
return outArray
}
This also works correctly for empty arrays whereas some earlier solutions yield [null] in this special case.

Combine Google Docs documents

Is it possible to merge 100 Google Docs documents into one?
I've tried copy-pasting, but it seems too long and it's not possible to copy comments.
This can be done with Google Apps Script. See this example. The most relevant parts (example assumes nothing but Google Docs in the folder):
function combine() {
var folder = DriveApp.getRootFolder();
if (folder == null) { Logger.log("Failed to get root folder"); return; }
var combinedTitle = "Combined Document Example";
var combo = DocumentApp.create(combinedTitle);
var comboBody = combo.getBody();
var hdr = combo.addHeader();
hdr.setText(combinedTitle)
var list = folder.getFiles();
while (list.hasNext()) {
var doc = list.next();
var src = DocumentApp.openById(doc.getId());
var srcBody = src.getBody();
var elems = srcBody.getNumChildren();
for (var i = 0; i < elems; i++ ) {
elem = srcBody.getChild(i).copy();
// fire the right method based on elem's type
switch (elem.getType()) {
case DocumentApp.ElementType.PARAGRAPH:
comboBody.appendParagraph(elem);
break;
case // something
}
}
}
}
Note that you don't copy the source document's contents in one lump; you have to loop through them as individual elements and fire the correct append* method to add them to the merged/destination file.
I expanded on #noltie's answer to support merging docs in a folder structure recursively, starting from an arbitrary folder (not necessarily the root folder of google docs) and guard agains script failures on too many unsaved changes.
function getDocsRec(rootFolder) {
var docs = [];
function iter(folder) {
var childFolders = folder.getFolders();
while (childFolders.hasNext()) {
iter(childFolders.next());
}
var childFiles = folder.getFiles();
while (childFiles.hasNext()) {
var item = childFiles.next();
var docName = item.getName();
var docId = item.getId();
var doc = {name: docName, id: docId};
docs.push(doc);
}
}
iter(rootFolder);
return docs;
}
function combineDocs() {
// This function assumes only Google Docs files are in the root folder
// Get the id from the URL of the folder.
var folder = DriveApp.getFolderById("<root folder id>");
if (folder == null) { Logger.log("Failed to get root folder"); return; }
var combinedTitle = "Combined Document Example";
var combo = DocumentApp.create(combinedTitle);
var comboBody = combo.getBody();
// merely get the files recursively, does not get them in alphabetical order.
var docArr = getDocsRec(folder);
// Log all the docs we got back. Click "Edit -> Logs" to see.
docArr.forEach(function(item) {
Logger.log(item.name)
});
// this sort will fail if you have files with identical names
// docArr.sort(function(a, b) { return a.name < b.name ? -1 : 1; });
// Now load the docs into the combo doc.
// We can't load a doc in one big lump though;
// we have to do it by looping through its elements and copying them
for (var j = 0; j < docArr.length; j++) {
// There is a limit somewhere between 50-100 unsaved changed where the script
// wont continue until a batch is commited.
if (j % 50 == 0) {
combo.saveAndClose();
combo = DocumentApp.openById(combo.getId());
comboBody = combo.getBody();
}
var entryId = docArr[j].id;
var entry = DocumentApp.openById(entryId);
var entryBody = entry.getBody();
var elems = entryBody.getNumChildren();
for (var i = 0; i < elems; i++) {
var elem = entryBody.getChild(i).copy();
switch (elem.getType()) {
case DocumentApp.ElementType.HORIZONTAL_RULE:
comboBody.appendHorizontalRule();
break;
case DocumentApp.ElementType.INLINE_IMAGE:
comboBody.appendImage(elem);
break;
case DocumentApp.ElementType.LIST_ITEM:
comboBody.appendListItem(elem);
break;
case DocumentApp.ElementType.PAGE_BREAK:
comboBody.appendPageBreak(elem);
break;
case DocumentApp.ElementType.PARAGRAPH:
comboBody.appendParagraph(elem);
break;
case DocumentApp.ElementType.TABLE:
comboBody.appendTable(elem);
break;
default:
var style = {};
style[DocumentApp.Attribute.BOLD] = true;
comboBody.appendParagraph("Element type '" + elem.getType() + "' could not be merged.").setAttributes(style);
}
}
// page break at the end of each entry.
comboBody.appendPageBreak();
}
}
You can create and run a script with the above code on https://script.google.com/home
Both the above fail for me with the script returning a red lozenge:
Service unavailable: Docs Dismiss
(the documents in the folder are found, as are the document id's, and the combined doc is created, but empty)
Fixed that - had a document in the list that wasn't owned by me or was created by conversion. Removed that and away we go.
Google Docs does not support any type of merge, yet.
You can select all 100 docs, download them and try to merge them offline.
Download all the files as Docx, then use Microsoft Word or Open Office to merge the documents using the "master document" feature. (Word also refers to this as "Outline.")