Access hidden Google calendars from CalendarApp? - google-apps-script

I have a web app that creates a hidden calendar
var cal = CalendarApp.createCalendar("Timetable");
cal.setSelected(true);
cal.setHidden(true);
But the same web app starts with code to look for an existing calendar and delete it first
var cals = CalendarApp.getCalendarsByName("Timetable");
for (var i = cals.length; i--; ) {
cals[i].deleteCalendar();
}
However, CalendarApp.getCalendarsByName does not find the "Timetable" calendar.
I have also tried
var cals = CalendarApp.getAllCalendars();
for (var i = cals.length; i--; ) {
if (cals[i].getName()=='Timetable') {
cals[i].deleteCalendar();
}
}
and
var cals = CalendarApp.getAllOwnedCalendars();
for (var i = cals.length; i--; ) {
if (cals[i].getName()=='Timetable') {
cals[i].deleteCalendar();
}
}
none of which are able to find the calendar called "Timetable."
However, the new calendar is created properly and listed in the UI under calendar settings, and if I check the box to make it visible again then all versions of this code find the "Timetable" calendar and delete it, before creating a new one.
Does anyone have any ideas on how I can get a handle to my hidden calendar based only on its name?
My web app executes as the person accessing the script, and is creating this secondary calendar in the user's own Google calendar.

I don't think this is possible using Apps Script built-in Calendar API (the CalendarApp).
You'll have to use the actual Calendar API directly. Luckily it is available as an "Advanced Service" (in your script see the menu Resources > Advanced Google Services, follow the instructions and enable the Calendar API).
Here's a snippet to get a hidden calendar by name:
function getCalIdFromName(name) {
var cals = Calendar.CalendarList.list({showHidden:true,
minAccessRole:'owner', fields:'items(id,summary)'}).items;
for( var i = 0; i < cals.length; i++ )
if( cals[i].summary == name )
return cals[i].id;
return null;
}
function deleteTimetable() {
Calendar.Calendars.remove(getCalIdFromName('Timetable'));
}
Trying to limit the CalendarList results I included the criteria to only return the users own calendars. Since this seems to be your scenario, but you can just omit that to retrieve all.
Another caveat, this simple code that does not handle pagination. Hopefully your users will not own more than 100 calendars (which would require pagination control).
Anyway, it is probably much easier to just store the calendar id right after you created it, using the Properties Service for example. And then retrieve it by id when you need to delete or operate on it (also because the user might change the calendar name).

Related

Google App Script - How to monitor for new users

I wondered if anyone could point me in the right direction here?
I want to monitor the Google Workspace estate, and when a new user has been created send them an email. I’ve looked through the APIs but nothing is jumping out at me. But I know there are 3rd party tools out there that do this, so there’s got to be something I have missed?
I just created this script in Google Apps Script which gets and prints the list of all the users that were created today.
You can use this as a guide and keep testing with it. To accomplish this I used the Reports API to get the admin logs and get the list of all the users that were created today.
function myFunction() {
var userKey = 'all';
var applicationName = 'admin';
var optionalArgs = {
eventName:'CREATE_USER',
startTime: "2022-03-23T12:00:00.000Z",
fields : "items.events.parameters.value"
};
var rep = AdminReports.Activities.list(userKey,applicationName,optionalArgs);
const A = (JSON.parse(rep));
var totalUsers = Object.keys(A.items).length;
for(var i=0; i<totalUsers; i++)
{
var userEmail = A.items[i].events[0].parameters[0].value;
Logger.log(userEmail);
}
}
You would just need to change the startTime value according to the date you need to use and implement the part of sending the email now that you have all the email addresses.
References
API method: activities.list
Apps Script reference: Reports API

Using Google Apps Script, how do you remove built in Gmail category labels from an email?

I'd like to completely undo any of Gmails built in category labels. This was my attempt.
function removeBuiltInLabels() {
var updatesLabel = GmailApp.getUserLabelByName("updates");
var socialLabel = GmailApp.getUserLabelByName("social");
var forumsLabel = GmailApp.getUserLabelByName("forums");
var promotionsLabel = GmailApp.getUserLabelByName("promotions");
var inboxThreads = GmailApp.search('in:inbox');
for (var i = 0; i < inboxThreads.length; i++) {
updatesLabel.removeFromThreads(inboxThreads[i]);
socialLabel.removeFromThreads(inboxThreads[i]);
forumsLabel.removeFromThreads(inboxThreads[i]);
promotionsLabel.removeFromThreads(inboxThreads[i]);
}
}
However, this throws....
TypeError: Cannot call method "removeFromThreads" of null.
It seems you can't access the built in labels in this way even though you can successfully search for label:updates in the Gmail search box and get the correct results.
The question...
How do you access the built in Gmail Category labels in Google Apps Script and remove them from an email/thread/threads?
Thanks.
'INBOX' and other system labels like 'CATEGORY_SOCIAL' can be removed using Advanced Gmail Service. In the Script Editor, go to Resources -> Advanced Google services and enable the Gmail service.
More details about naming conventions for system labels in Gmail can be found here Gmail API - Managing Labels
Retrieve the threads labeled with 'CATEGORY_SOCIAL' by calling the list() method of the threads collection:
var threads = Gmail.Users.Threads.list("me", {labels: ["CATEGORY_SOCIAL"]});
var threads = threads.threads;
var nextPageToken = threads.nextPageToken;
Note that you are going to need to store the 'nextPageToken' to iterate over the entire collection of threads. See this answer.
When you get all thread ids, you can call the 'modify()' method of the Threads collection on them:
threads.forEach(function(thread){
var resource = {
"addLabelIds": [],
"removeLabelIds":["CATEGORY_SOCIAL"]
};
Gmail.Users.Threads.modify(resource, "me", threadId);
});
If you have lots of threads in your inbox, you may still need to call the 'modify()' method several times and save state between calls.
Anton's answer is great. I marked it as accepted because it lead directly to the version I'm using.
This function lets you define any valid gmail search to isolate messages and enables batch removal labels.
function removeLabelsFromMessages(query, labelsToRemove) {
var foundThreads = Gmail.Users.Threads.list('me', {'q': query}).threads
if (foundThreads) {
foundThreads.forEach(function (thread) {
Gmail.Users.Threads.modify({removeLabelIds: labelsToRemove}, 'me', thread.id);
});
}
}
I call it via the one minute script trigger like this.
function ProcessInbox() {
removeLabelsFromMessages(
'label:updates OR label:social OR label:forums OR label:promotions',
['CATEGORY_UPDATES', 'CATEGORY_SOCIAL', 'CATEGORY_FORUMS', 'CATEGORY_PROMOTIONS']
)
<...other_stuff_to_process...>
}

Looking for a google apps script that permanently deletes from gmail trash

User is receiving a large volume of unwanted emails from a specific sender.
Blocking moves the email to spam, while filtering moves the email to trash. Both of these still expose the user to the emails if those folders are checked.
What I'm looking for is a script that will permanently delete emails from the specified email address either when the emails are received, or on a frequent schedule.
I have almost no familiarity with google scripts or js, the best I have as relates to code is some rudimentary vba.
Researching this issue brought me to using google apps as a potential solution as gmail does not provide any automated way to permanently delete email. Below is some code I found googling around, although I can't get very far with it due to my total lack of apps script knowledge.
function DeleteEmailByLabel(e) {personsemail#gmail.com}
var bannedLabel = 'BLOCKEDSENDER';
var deleteLabel = GmailApp.getUserLabelByName(bannedLabel);
if ( deleteLabel != null ) {personsemail#gmail.com}
var threads = deleteLabel.getThreads();
for (var i = 0; i < threads.length; i++) {
Gmail.Users.Messages.remove('me', threads[i].getId());
}
} else {
deleteLabel = GmailApp.createLabel(bannedLabel);
}
}
I expect the above code to run and remove email from my test account, from the trash folder. However instead I get this error. This looks like basic syntax stuff but I'm out of my league here.
Missing ; before statement. (line 1, file "filename")
Thanks in advance.
Before you could use this in the Apps Script, please note to do the following:
Create a filter in the user's Gmail account such that a specific & unique label is allocated to such emails (you can "mark them as read" or "send them to spam"; it wouldn't matter)
This function uses some of the advanced Gmail APIs and as such, require you to enable them from the script editor first, before running the script. To achieve this, go to:
Resources > Advanced Google Services... > Scroll all the way down to Gmail API > toggle the off button to on
function deleteEmails() {
var bannedLabel = 'BLOCKEDSENDER' // replace with the label name, as setup in filters
var deleteLabel = GmailApp.getUserLabelByName(bannedLabel);
if ( deleteLabel != null ) {
var threads = deleteLabel.getThreads();
for (var i=0; i<threads.length; i++) {
Gmail.Users.Threads.remove('me', threads[i].getId());
}
} else {
// do something
}
}

Dynamically fill form fields based on other responses before submit

I have a form I want to dynamically change, I have read through the documents but I cannot seem to find any definitive answer. Can I make my form remove choices from dropdown lists because they used radio button #2 for the 3rd question? Can I format text from question 1 and use it to pre-fill question 6 with the same answer (by default, needs to be changeable)?
Basically I need to use code to determine if the address was spelled with shortforms (st, rd, cres, ct) and lengthen and capitolize them (Street, Road). I don't even know if this is possible. If it is can anyone provide sample code or point me to the right help docs, it would be appreciated. If not is this doable on a webserver if some of my multiple choice options need to be read from a google spreadsheet? Could i do it through google Sites?
Have you looked at the Form Class of Google Apps Services?
Class Forms
It states:
Forms can be accessed or created from FormApp.
For example, you can use:
addTextItem()
OR:
createChoice(value)
Google Documentation
// Open a form by ID and add a new multiple choice item.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
var item = form.addMultipleChoiceItem();
item.setTitle('Do you prefer cats or dogs?')
.setChoices([
item.createChoice('Cats'),
item.createChoice('Dogs')
])
.showOtherOption(true);
You don't want to open a new form, but use the currently open one.
/**
* Adds a custom menu to the active form, containing a single menu item for
* invoking checkResponses() specified below.
*/
function onOpen() {
FormApp.getUi()
.createMenu('My Menu')
.addItem('Check responses', 'checkResponses')
.addToUi();
}
Check current responses?
/**
* Gets the list of responses and checks the average rating from the form
* created in createForm() above.
*/
function checkResponses() {
var form = FormApp.getActiveForm();
var responses = form.getResponses();
var score = 0;
for (var i = 0; i < responses.length; i++) {
var itemResponses = responses[i].getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
if (itemResponse.getItem().getType() == FormApp.ItemType.SCALE) {
score += itemResponse.getResponse();
}
}
var average = score / responses.length;
FormApp.getUi().alert('The score is ' + average);
}
}
You could use Apps Script HTML Service; write HTML to create a custom form; write the JavaScript code to do what you want; then add the Apps Script to the Google Site. Or just run the Apps Script HTML Service as a website on it's own. But this option requires you to be able to write HTML and JavaScript. What you want to do is possible.
As far as creating the custom form, checking the user input and changing it, that can be done in Apps Script HTML Service. Then you need to save the data somewhere. Google Forms is made to be user friendly to people who don't have programming knowledge.
It seems like you are just looking for general information on what is possible and not possible with different products so that you can make a choice.
For reading about HTML Service, click the following link as a place to start:
Google Documentation HTML Service

Cells containing custom functions return "NaN'" when spreadsheet is not open. (Apps Script webapp accessing google spreadsheet)

As a high school teacher, I record all of my grading in a Google spreadsheet. I have written custom functions within that spreadsheet that are accessed in the spreadsheet. That all works fine.
I also have a simple (but independent) web app written in google apps script that summarizes the grade information for each student that accesses it and returns it in a table. This has operated perfectly for about 8 months. However, students now get a "NaN" error when trying to check their grades. The "NaN" is only returned for cells that use custom functions. By simply opening the source spreadsheet, it fixes the problem temporarily. But soon after closing the spreadsheet the webapp begins returning "NaN" again.
I'm assuming that it has something to do with when/how these cells are recalculated but I can't figure out how to make the cells retain their value while the spreadsheet is closed. Any help would be much appreciated.
With Eric's advice, I implemented the following function (which runs early on in my app):
function refreshSheet(spreadsheet, sheet) {
var dataArrayRange = sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn());
var dataArray = dataArrayRange.getValues(); // necessary to refresh custom functions
var nanFound = true;
while(nanFound) {
for(var i = 0; i < dataArray.length; i++) {
if(dataArray[i].indexOf('#N/A') >= 0) {
nanFound = true;
dataArray = dataArrayRange.getValues();
break;
} // end if
else if(i == dataArray.length - 1) nanFound = false;
} // end for
} // end while
}
It basically keeps refreshing the sheet (using .getValues()) until all of the #N/A's disappear. It works fabulously but does add a small lag.