How to update Google Group Settings with Google Apps Script - google-apps-script

I have a Google Apps Script which has all the permissions it needs, with the Group Settings API on and working, but does not change certain things. There are no errors given, but the only thing that changes is the name, and the rest does nothing. This is the script:
function modgroup() {
var groupKey = 'finaltest#school.edu.mx';
var resource = {
name: "finalfour",
whoCanContactOwner: "ALL_MEMBERS_CAN_CONTACT",
whoCanJoin: "INVITED_CAN_JOIN",
whoCanViewMembership: "ALL_MEMBERS_CAN_VIEW",
whoCanViewGroup: "ALL_MEMBERS_CAN_VIEW",
whoCanInvite: "ALL_MANAGERS_CAN_INVITE",
whoCanAdd: "ALL_MANAGERS_CAN_ADD",
allowExternalMembers: false,
whoCanPostMessage: "ALL_MEMBERS_CAN_POST",
allowWebPosting: false
}
AdminDirectory.Groups.update(resource, groupKey);
}

Ok, after a bit of investigation and experimentation, I found that there was another API and another format that had to be used so that it will work. You need to activate the Groups Settings API (not the Admin Directory API) and you can see the documentation here.
The format is the following:
function editGroup(){
var groupId = 'finaltest#school.edu.mx';
var group = AdminGroupsSettings.newGroups();
group.name = 'NAME';
group.description = 'DESCRIPTION';
group.whoCanAdd = 'NONE_CAN_ADD';
group.whoCanJoin = 'INVITED_CAN_JOIN';
group.whoCanViewMembership = 'ALL_MEMBERS_CAN_VIEW';
group.whoCanViewGroup = 'ALL_MEMBERS_CAN_VIEW';
group.whoCanInvite = 'ALL_MANAGERS_CAN_INVITE';
group.allowExternalMembers = false;
group.whoCanPostMessage = 'ALL_MEMBERS_CAN_POST';
group.allowWebPosting = true;
group.showInGroupDirectory = false;
group.allowGoogleCommunication = false;
group.membersCanPostAsTheGroup = false;
group.includeInGlobalAddressList = false;
group.whoCanLeaveGroup = 'NONE_CAN_LEAVE';
AdminGroupsSettings.Groups.patch(group, groupId);
}

Related

Cannot add created label to threads

I tried to write a function in Google Apps Script that creates a new label in Gmail and adds it to threads.
I have two problems:
When I run the function for the first time (archivedLabel not existing yet) I cannot add it to the threads immediately after it is created.
archivedLabel = GmailApp.getUserLabelByName(labelText) at the end of the if statement will still return null and the script crashes.
If I run the script for the second time (label already created) everything works fine.
The new labels only appear in Gmail after the user refreshes the Gmail App in the browser. Is there a way to do this automatically or a method to refresh the labels and messages so I can see the new label in Gmail without manually reloading the page?
function addArchivedLabel(thread){
var labelText = "Backed up";
var archivedLabel = GmailApp.getUserLabelByName(labelText);
//create new archived label if not already existing
if(archivedLabel == null) {
var textColor = "#89d3b2"; // Please set this.
var backgroundColor = "#ffbc6b"; // Please set this.
var userId = "me";
var resource = Gmail.newLabel();
resource.labelListVisibility = "labelShow";
resource.messageListVisibility = "show";
resource.name = labelText;
var labelColor = Gmail.newLabelColor();
labelColor.textColor = textColor;
labelColor.backgroundColor = backgroundColor;
resource.color = labelColor;
Gmail.Users.Labels.create(resource, userId);
archivedLabel = GmailApp.getUserLabelByName(labelText);
}
archivedLabel.addToThread(thread); //add new label to archived emails
}
I just encountered the same problem
For some reason this is working :
function getOrCreateLabel() {
if (!GmailApp.getUserLabelByName(LABEL_NAME)) {
GmailApp.createLabel(LABEL_NAME)
}
console.log(GmailApp.getUserLabelByName(LABEL_NAME)) // not NULL
}
And this is not working as expected:
function getOrCreateLabel() {
if (!GmailApp.getUserLabelByName(LABEL_NAME)) {
Gmail.Users.Labels.create({
"labelListVisibility": "labelHide",
"messageListVisibility": "hide",
"name": LABEL_NAME
}, "me")
}
console.log(GmailApp.getUserLabelByName(LABEL_NAME)) // NULL
}
For the second function,It seems that appsscript cache the response of GmailApp.getUserLabelByName at runtime.
So in my opinion. You will need to create a trigger, here a working exemple:
function addArchivedLabel(thread){
var labelText = "Backed up";
var archivedLabel = GmailApp.getUserLabelByName(labelText);
const thread_id = UserProperties.getProperty("thread")
// Check if come from trigger
if (thread_id) {
// retrieve the thread
thread = GmailApp.getThreadById(thread_id)
// Clean property and trigger
UserProperties.deleteProperty("thread")
ScriptApp.getScriptTriggers().forEach((p) => {
if (p.getHandlerFunction() == "addArchivedLabel") {
ScriptApp.deleteTrigger(p)
}
})
}
//create new archived label if not already existing
if(archivedLabel == null) {
var textColor = "#89d3b2"; // Please set this.
var backgroundColor = "#ffbc6b"; // Please set this.
var userId = "me";
var resource = Gmail.newLabel();
resource.labelListVisibility = "labelShow";
resource.messageListVisibility = "show";
resource.name = labelText;
var labelColor = Gmail.newLabelColor();
labelColor.textColor = textColor;
labelColor.backgroundColor = backgroundColor;
resource.color = labelColor;
Gmail.Users.Labels.create(resource, userId);
UserProperties.setProperty("thread", thread.getId())
ScriptApp.newTrigger("addArchivedLabel").timeBased().everyMinutes(1).create()
return
}
archivedLabel.addToThread(thread); //add new label to archived emails
}
// fixture to simulate get thread
function main() {
const thread = GmailApp.getInboxThreads()
addArchivedLabel(thread[0])
}
Hope it will help

How to fix: API call to gmail.users.settings.filters.create failed with error: Empty response

I wrote a script in google apps scripts that creates two filters. I implemented it into web app and it works fine on couple of accounts that aren't that big. But when i try to use it on main account, where it supposed to work I am getting an error that can't really understand.
'API call to gmail.users.settings.filters.create failed with error: Empty response (vers 69, file„createFilter”)'
function createFitler(labelold,me){
var response = Gmail.Users.Labels.list('me');
var labelID = "";
var labelName = "";
for (var i = 0; i < response.labels.length; i++) {
var label = response.labels[i];
if(label.name == labelold)
{
labelID = label.id;
labelName = label.name;
}
}
var filter = Gmail.newFilter();
var filter2 = Gmail.newFilter();
filter.criteria = Gmail.newFilterCriteria();
filter.criteria.from = labelold;
filter2.criteria = Gmail.newFilterCriteria();
filter2.criteria.to = labelold;
filter.action = Gmail.newFilterAction();
filter.action.removeLabelIds = ['INBOX'];
filter.action.addLabelIds = [labelID];
filter2.action = Gmail.newFilterAction();
filter2.action.removeLabelIds = ['INBOX'];
filter2.action.addLabelIds = [labelID];
// Create the filter
Gmail.Users.Settings.Filters.create(filter, me);
Gmail.Users.Settings.Filters.create(filter2, me);
}
The weirdest part is that it works normal on my domain account but not on archive#mydomain.com account.

Permissions vs. EffectiveRights

I am using the calendar.Permissions to find out which rights the logged-in person has on another.
var myEmail = GetPrimaryMailForIdentity(User.Identity);
...
service.Credentials = new WebCredentials(serviceAccountName, serviceAccountPass);
service.AutodiscoverUrl(toEmail);
...
var calendar = CalendarFolder.Bind(service,WellKnownFolderName.Calendar)
foreach (var permission in calendar.Permissions) {
if(permission.UserId.PrimarySmtpAddress == myEmail) {
if(permission.ReadItems == FolderPermissionReadAccess.TimeOnly) permission = "TimeOnly";
...
}
}
This only gives me some of the various rights one person can have on another mailbox, since there could also be a FullAccess rule, or the mailbox could be that of a resource he owns/manages. So I could also impersonate the user for whom to get the permission, and check the effective rights:
var myEmail = GetPrimaryMailForIdentity(User.Identity);
...
service.Credentials = new WebCredentials(serviceAccountName, serviceAccountPass);
service.AutodiscoverUrl(toEmail);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, myEmail);
...
var calendar = CalendarFolder.Bind(service,WellKnownFolderName.Calendar)
var permissions = calendar.EffectiveRights;
if(permissions.HasFlag(EffectiveRights.Read) {
permission = "TimeOnly"; // TODO
}
But how can I find out WHAT he is allowed to read - all details, or only time?
The FolderPermissionReadAccess enumeration seems to be what you're looking for. EffectiveRights doesn't expose that level of information.

Trigger UiApp-builder callback within another routine

Serge's solution here seemed like the way to go about this, but I'm a bit afraid that my circumstances may be too different...
I have a button where users can add a new set of rows with controls to a FlexTable, in order to allow them to insert a new member into a record set. After designing and building the app to do this (and despite assurances to the contrary), a requirement was then added for the users to be able to edit the record sets at a later date.
I've finally managed to get the data retrieved and correctly displayed on the Ui - for single member record sets. As a final stage, I am now attempting to extend this to accommodate record sets having more than one member. Obviously this requires determining how many members there are in the record set, and then adding the new rows/control group to the FlexTable, before loading the member into each control group.
So within this routine, (depending on how many members there are) I may need to trigger the same callback, which the user normally does with a button. However, the difference with Serge's fine example, is that his code triggers the checkbox callback at the end of his routine once all the Ui components are in place. My situation needs to do this on the fly - and so far I'm getting 'Unexpected error', which suggests to me that the Ui is not able to update with the added FlexTable controls before my code attempts to assign values to them.
Does anyone have any insight into this problem? Is my only recourse to completely re-build a fixed Ui and dispense with the dynamic rowset model?
Code follows -
1. event for adding controls:
var app = UiApp.getActiveApplication();
var oFlexGrid = app.getElementById('ExpenseDetail');
var oRowCount = app.getElementById('rowCount');
var oScriptDBId = app.getElementById('scriptDBId');
var iRows = parseInt(e.parameter.rowCount);
var sVId = e.parameter.scriptDBId;
var vGridDefs = loadArrayById(sVId); //retrieve upload definition array from ScriptDB
var vControlNames = [];
if (isOdd(iRows)){
var sColour = 'AliceBlue';
} else {
var sColour = 'LavenderBlush';
};
oFlexGrid.insertRow(0);
oFlexGrid.insertRow(0);
oFlexGrid.insertRow(0);
oFlexGrid.insertRow(0);
oFlexGrid.setRowStyleAttributes(0,{'backgroundColor':sColour});
oFlexGrid.setRowStyleAttributes(1,{'backgroundColor':sColour});
oFlexGrid.setRowStyleAttributes(2,{'backgroundColor':sColour});
oFlexGrid.setRowStyleAttributes(3,{'backgroundColor':sColour});
var vExpenseDef = Get_NamedRangeValues_(CONST_SSKEY_APP,'UIAPP_GridExpense');
iRows = iRows+1;
vControlNames = CreateGrid_MixedSet_(iRows, vExpenseDef, oFlexGrid, app);
oRowCount.setText(iRows.toString()).setValue(iRows.toString());
//SOME INCONSEQUENTIAL CODE REMOVED HERE, LET ME KNOW IF YOU NEED IT
vGridDefs = vGridDefs.concat(vControlNames); // unify grid definition arrays
var sAryId = saveArray('expenseFieldDef', vGridDefs);
oScriptDBId.setText(sAryId).setValue(sAryId); //store array and save ScriptDB ID
if (e.parameter.source == 'btnExpenseAdd'){
hideDialog(); //IGNORE CHEKCBOX-DRIVEN CALLS
};
return app;
2. routine that calls the event
var app = UiApp.getActiveApplication();
var oPanelExpense = app.getElementById('mainPanelExpense');
var oPanelIncome = app.getElementById('mainPanelIncome');
var oPanelEdit = app.getElementById('mainPanelEdit');
var chkExpenseAdd= app.getElementById('chkExpenseAdd');
var bExpenseTrigger = e.parameter.chkExpenseAdd;
var sVoucherId = nnGenericFuncLib.cacheLoadObject(CACHE_EDIT_VOUCHERID);
var sVoucher = e.parameter.ListSearch1Vouchers;
var aryVoucherInfo = getVoucherEditDetail(sVoucherId);
//SAVE FOR RECORD MARKING CALLBACK
nnGenericFuncLib.cacheSaveObject(CACHE_EDIT_OLDRECORDS, JSON.stringify(aryVoucherInfo), CACHE_TIMEOUT);
sVoucher = nnGenericFuncLib.textPad(sVoucher, '0', 7);
var bExp = (sVoucher.substring(0,2) == '03')
var oRowCount = app.getElementById('rowCount');
var iRowCount = parseInt(e.parameter.rowCount);
var sControlName = '';
var vControlVal = '';
var iExpIdx = 0;
var sControlType = '';
var oControl = '';
var vSummaryTotal = 0;
for (var iVal in aryVoucherInfo){
sControlName = aryVoucherInfo[iVal][2];
vControlVal = aryVoucherInfo[iVal][3];
switch (sControlName){
case 'ESUM60':
vSummaryTotal = vControlVal;
break;
case 'EXUSRN':
continue; //DON'T OVERWRITE CURRENT USERNAME
break;
};
if (sControlName.indexOf('_')!=-1){ //TEST FOR CONTROL SET MEMBER
var aryControlSet = sControlName.split('_');
if (parseInt(aryControlSet[1])>iRowCount){//*** TRIGGER THE EVENT ***
Logger.log(bExpenseTrigger + ' - ' + !bExpenseTrigger);
chkExpenseAdd.setValue(!bExpenseTrigger, true);
iRowCount = iRowCount +1;
};
};
oControl = app.getElementById(sControlName);
var vCache = cacheSaveReturn(CACHE_UIEX_LISTS,sControlName);
if (typeof vCache == 'undefined'){
oControl.setValue(vControlVal);
oControl.setText(vControlVal);
//controlSetTextBox(oControl,vControlVal);
//controlSetDateBox(oControl,vControlVal);
} else {
if (!(nnGenericFuncLib.arrayIsReal(vCache))){
vCache = JSON.parse(vCache);
};
vCache = vCache.indexOf(vControlVal);
if (vCache != -1){
oControl.setSelectedIndex(vCache);
} else {
controlSetListBox(oControl,vControlVal);
};
};
};
//SOME CODE REMOVED HERE
hideDialog();
return app;
Mogsdad to the rescue!
The answer (see above) for those at the back of the class (with me) is to simply pass the app instance parameter (e) to the event function, calling it directly from the main routine, thus keeping the chronology in step for when it returns the app to complete the routine. No need for the checkbox in this situation.
This only took me all day, but thanks Mogsdad! :)
Snippet below taken from 1/2 way down code sample 2 in the OP:
if (sControlName.indexOf('_')!=-1){ //TEST FOR CONTROL SET MEMBER
var aryControlSet = sControlName.split('_');
if (parseInt(aryControlSet[1])>iRowCount){
eventAddExpense(e); //THAT'S ALL IT TAKES
iRowCount = iRowCount +1;
};
};

"Error Encountered: Invalid Argument" in handler function

I'm getting this error in my handler function but I've no clue what's causing it. I've copied the code and debugged it in a non-handler function and there was no error.
function _responseToNext(e) {
var app = UiApp.getActiveApplication();
app.getElementById('btnPrev').setEnabled(true);
var current = parseInt(CacheService.getPublicCache().get('currentItem'));
var agendaItems = Utilities.jsonParse(CacheService.getPublicCache().get('agenda'));
agendaItems[current]['notes'] = e.parameter.tAreaNotes;
agendaItems[current]['status'] = e.parameter.lboxStatus;
CacheService.getPublicCache().put('agenda', Utilities.jsonStringify(agendaItems));
current = current + 1;
CacheService.getPublicCache().put('currentItem', current);
fillAgendaDetail(app);
// only enabled 'Next' if there are more items in the agenda
if (current < agendaItems.length-1) {
app.getElementById('btnNext').setEnabled(true);
}
return app;
}
I suppose, the error cause is that the Cache get method returns null during the 1st execution when the cache is empty. The Utilities.jsonParse throws an exception and the cache becomes in any case empty. Try to use the following modified code.
function _responseToNext(e) {
var app = UiApp.getActiveApplication();
app.getElementById('btnPrev').setEnabled(true);
var cachedCurrent = CacheService.getPublicCache().get('currentItem');
var current;
if (cachedCurrent == null) {
current = 0;
}
else {
current = parseInt(cachedCurrent);
}
var cachedAgendaItems = CacheService.getPublicCache().get('agenda');
var agendaItems;
if (cachedAgendaItems == null) {
agendaItems = [][];
}
else {
agendaItems = Utilities.jsonParse();
}
agendaItems[current]['notes'] = e.parameter.tAreaNotes;
agendaItems[current]['status'] = e.parameter.lboxStatus;
CacheService.getPublicCache().put('agenda', Utilities.jsonStringify(agendaItems));
current = current + 1;
CacheService.getPublicCache().put('currentItem', current);
fillAgendaDetail(app);
// only enabled 'Next' if there are more items in the agenda
if (current < agendaItems.length-1) {
app.getElementById('btnNext').setEnabled(true);
}
return app;
}
Also please mention that the Public Cache (CacheService.getPublicCache()) is the same for all users of your script. In your case, this means, if two users user1#example.com and user2#example.com use the script they will have the same current and agendaItems variables values, i.e. it can be a situation when the _responseToNext handler is already executed under the user1 authority - the current variable is equal to 1, after the user2 executes the _responseToNext handler - the current variable is equal to 2 and so on. If you do not need such behaviour, use the CacheService.getPrivateCache().