How to Get Calendar Event global status? - google-apps-script

This is concerning Google script to access Calendar event info
I'm looking for a method to get a Calendar Event global status? like for a Calendar Event with as status of attendess:
10 guests ==> 4 Yes, 3 MayBe, 1 No, 2 Awaiting
I've searched but I find only the way to get the status by guest, then implement a code to count individualy the global status.
Is there a way to get directly the global status?
Thanks in advance.

I happen to have written a function that does this. Here's an example of usage:
var summary = guestSummary(event.getGuestList());
Logger.log(summary);
...
[13-06-26 22:39:58:253 EDT] {respondedMaybe=0.0, awaiting=0.0, additional=0.0, respondedNo=0.0, respondedYes=1.0, invited=1.0}
I elected to use the guest list as a parameter, in order to support both CalendarEvent and CalendarEventSeries.
/**
* Return an object enumerating guest list summary information.
*
* #param {EventGuest[]} guestlist Array of EventGuests.
*
* #returns {object} guest list summary
*/
function guestSummary( guestlist ) {
var invited = guestlist.length;
var respondedYes = 0;
var respondedMaybe = 0;
var respondedNo = 0;
var awaiting = 0;
var additional = 0;
guestlist.forEach( function (guest) {
switch (guest.getGuestStatus()) {
case CalendarApp.GuestStatus.INVITED:
awaiting++;
break;
case CalendarApp.GuestStatus.YES:
respondedYes++;
break;
case CalendarApp.GuestStatus.NO:
respondedNo++;
break;
case CalendarApp.GuestStatus.MAYBE:
respondedMaybe++;
break;
default:
break;
}
additional += guest.getAdditionalGuests();
});
return {
invited : invited,
respondedYes : respondedYes,
respondedMaybe : respondedMaybe,
respondedNo : respondedNo,
awaiting : awaiting,
additional : additional
};
}

Related

Retrieve all my subscriptions in Google Sheets

I'm using YouTube Data API v3 and Google Apps Script for retrieve all my subscriptions.
The problem I'm facing is that - using the following code, the response brings duplicated channels:
do {
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
mine: true,
//channelId: "<MY_CHANNEL_ID>",
maxResults: 50,
fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))"
});
if (!mySubsResponse || mySubsResponse == undefined) {
Logger.log('No subscriptions found.');
SpreadsheetApp.getUi().alert("No subscriptions found.");
break;
}
// Loop all my subscriptions found in the response:
for (let j = 0; j < mySubsResponse.items.length; j++) {
const mySubItem = mySubsResponse.items[j];
sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title);
sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId);
incrSub++;
}
nextPageToken = mySubsResponse.nextPageToken;
} while (nextPageToken);
I believe this is due each item in the response is actually the video uploaded by the channel I'm subscribed to - I don't think it's a problem with the page token.
In the code above, I've commented the channelId parameter and I've testted with both: mine:true and channelId:<MY_CHANNEL_ID> and, the totalResults shows me I have 479 subscriptions, but, when I'm looping the results,
For example, I'm subscribed to the channel called "Channel_1"; this
channel had uploaded three videos today. The response of the code
above brings me "Channel_1" three times, when it should be only 1 -
because I'm subscribed to "Channel_1" once.
What I want to get is a list of all channels I'm subscribed to.
I've checked the subscriptions:list documentation, but, it's not clear how I can get my subscriptions only.
If the subscriptions:list endopint is not the correct one for this task, which endpoint enables me to bring the desired results?1
1a list of all channels I'm subscribed to.
After checking more closely (and, I admit, after a little break I have), I finally found the problem and the solution:
The problem is: I wasn't using the nextPageToken in every loop, so, basically, I was requesting the same page without actually making any pagination.
In this section:
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
mine: true,
//channelId: "<MY_CHANNEL_ID>",
maxResults: 50,
fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))"
});
Can be seen that the pageToken: nextPageToken is not defined.
Then, the solution is:
Modify the code for sending the nextPageToken obtained.
This is the modified code:
// Call my subscriptions:
/** Token pagination. */
var nextPageToken = "";
/** Row position where to start writing the results. */
var incrSub = 6;
/**
* Get all my subscriptions.
*/
do {
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
channelId: "<MY_CHANNEL_ID>", // also works with "mine: true".
maxResults: 50,
// Here, the first time the call is made, the "nextPageToken" value
// is empty. In every iteration (if "nextPageToken" is retrieved),
// the "nextPageToken" is used - in order to get the next page.
pageToken: nextPageToken,
fields: "nextPageToken,items(snippet(title,resourceId(channelId)))"
});
if (!mySubsResponse || mySubsResponse == undefined) {
Logger.log('No subscriptions found.');
SpreadsheetApp.getUi().alert("No subscriptions found.");
break;
}
// Write the subscriptions returned in the response:
for (let j = 0; j < mySubsResponse.items.length; j++) {
const mySubItem = mySubsResponse.items[j];
sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title);
sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId);
incrSub++;
}
// Check the token:
try {
if (mySubsResponse.nextPageToken != null || mySubsResponse.nextPageToken != undefined) {
nextPageToken = mySubsResponse.nextPageToken;
} else {
nextPageToken = undefined;
break;
}
} catch (ex_page) {
// An error occurred. Check closely the code.
}
} while (nextPageToken != undefined);
With this modified code, all of my subscriptions are returned successfully.

Google Calendar API App Script - "Error: Not Found: Skipping"

I'm using this App Script Populate a team vacation calendar
I'm receiving the following error for 1 person in a Google Group of 50 users.
'Error retriving events for email#email.com, vacation:
GoogleJsonResponseException: API call to calendar.events.list failed
with error: Not Found; skipping'
When setting a breakpoint, it fails on this import event.
function importEvent(username, event) {
event.summary = '[' + username + '] ' + event.summary;
event.organizer = {
id: TEAM_CALENDAR_ID,
};
event.attendees = [];
console.log('Importing: %s', event.summary);
try {
Calendar.Events.import(event, TEAM_CALENDAR_ID);
} catch (e) {
console.error('Error attempting to import event: %s. Skipping.',
e.toString());
}
}
do {
params.pageToken = pageToken;
let response;
try {
response = Calendar.Events.list(user.getEmail(), params);
} catch (e) {
console.error('Error retriving events for %s, %s: %s; skipping',
user, keyword, e.toString());
continue;
}
events = events.concat(response.items.filter(function(item) {
return shoudImportEvent(user, keyword, item);
}));
pageToken = response.nextPageToken;
} while (pageToken);
return events;
}
This is the stack:
"GoogleJsonResponseException: API call to calendar.events.list failed
with error: Not Found at findEvents (Code:111:34) at Code:48:20 at
Array.forEach () at Code:47:14 a…"
This is the full code
// Set the ID of the team calendar to add events to. You can find the calendar's
// ID on the settings page.
let TEAM_CALENDAR_ID = 'CALENDAR ID';
// Set the email address of the Google Group that contains everyone in the team.
// Ensure the group has less than 500 members to avoid timeouts.
let GROUP_EMAIL = 'GROUP EMAIL';
let KEYWORDS = ['vacation', 'ooo', 'out of office', 'offline'];
let MONTHS_IN_ADVANCE = 3;
/**
* Sets up the script to run automatically every hour.
*/
function setup() {
let triggers = ScriptApp.getProjectTriggers();
if (triggers.length > 0) {
throw new Error('Triggers are already setup.');
}
ScriptApp.newTrigger('sync').timeBased().everyHours(1).create();
// Runs the first sync immediately.
sync();
}
/**
* Looks through the group members' public calendars and adds any
* 'vacation' or 'out of office' events to the team calendar.
*/
function sync() {
// Defines the calendar event date range to search.
let today = new Date();
let maxDate = new Date();
maxDate.setMonth(maxDate.getMonth() + MONTHS_IN_ADVANCE);
// Determines the time the the script was last run.
let lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
lastRun = lastRun ? new Date(lastRun) : null;
// Gets the list of users in the Google Group.
let users = GroupsApp.getGroupByEmail(GROUP_EMAIL).getUsers();
// For each user, finds events having one or more of the keywords in the event
// summary in the specified date range. Imports each of those to the team
// calendar.
let count = 0;
users.forEach(function(user) {
let username = user.getEmail().split('#')[0];
KEYWORDS.forEach(function(keyword) {
let events = findEvents(user, keyword, today, maxDate, lastRun);
events.forEach(function(event) {
importEvent(username, event);
count++;
}); // End foreach event.
}); // End foreach keyword.
}); // End foreach user.
PropertiesService.getScriptProperties().setProperty('lastRun', today);
console.log('Imported ' + count + ' events');
}
/**
* Imports the given event from the user's calendar into the shared team
* calendar.
* #param {string} username The team member that is attending the event.
* #param {Calendar.Event} event The event to import.
*/
function importEvent(username, event) {
event.summary = '[' + username + '] ' + event.summary;
event.organizer = {
id: TEAM_CALENDAR_ID,
};
event.attendees = [];
console.log('Importing: %s', event.summary);
try {
Calendar.Events.import(event, TEAM_CALENDAR_ID);
} catch (e) {
console.error('Error attempting to import event: %s. Skipping.',
e.toString());
}
}
/**
* In a given user's calendar, looks for occurrences of the given keyword
* in events within the specified date range and returns any such events
* found.
* #param {Session.User} user The user to retrieve events for.
* #param {string} keyword The keyword to look for.
* #param {Date} start The starting date of the range to examine.
* #param {Date} end The ending date of the range to examine.
* #param {Date} optSince A date indicating the last time this script was run.
* #return {Calendar.Event[]} An array of calendar events.
*/
function findEvents(user, keyword, start, end, optSince) {
let params = {
q: keyword,
timeMin: formatDateAsRFC3339(start),
timeMax: formatDateAsRFC3339(end),
showDeleted: true,
};
if (optSince) {
// This prevents the script from examining events that have not been
// modified since the specified date (that is, the last time the
// script was run).
params.updatedMin = formatDateAsRFC3339(optSince);
}
let pageToken = null;
let events = [];
do {
params.pageToken = pageToken;
let response;
try {
response = Calendar.Events.list(user.getEmail(), params);
} catch (e) {
console.error('Error retriving events for %s, %s: %s; skipping',
user, keyword, e.toString());
continue;
}
events = events.concat(response.items.filter(function(item) {
return shoudImportEvent(user, keyword, item);
}));
pageToken = response.nextPageToken;
} while (pageToken);
return events;
}
/**
* Determines if the given event should be imported into the shared team
* calendar.
* #param {Session.User} user The user that is attending the event.
* #param {string} keyword The keyword being searched for.
* #param {Calendar.Event} event The event being considered.
* #return {boolean} True if the event should be imported.
*/
function shoudImportEvent(user, keyword, event) {
// Filters out events where the keyword did not appear in the summary
// (that is, the keyword appeared in a different field, and are thus
// is not likely to be relevant).
if (event.summary.toLowerCase().indexOf(keyword.toLowerCase) < 0) {
return false;
}
if (!event.organizer || event.organizer.email == user.getEmail()) {
// If the user is the creator of the event, always imports it.
return true;
}
// Only imports events the user has accepted.
if (!event.attendees) return false;
let matching = event.attendees.filter(function(attendee) {
return attendee.self;
});
return matching.length > 0 && matching[0].responseStatus == 'accepted';
}
/**
* Returns an RFC3339 formated date String corresponding to the given
* Date object.
* #param {Date} date a Date.
* #return {string} a formatted date string.
*/
function formatDateAsRFC3339(date) {
return Utilities.formatDate(date, 'UTC', 'yyyy-MM-dd\'T\'HH:mm:ssZ');
}

Getting past permissions for a file through the API/Apps Script

I'm trying to create a list of files stored in my Google Drive and also a list of their current and previous permissions. Specifically, I want to create a list of files in my Google Drive which at any point in the past have had the 'Anyone with a link can view/edit (etc)' permission set.
I have created a Google Apps Script to do this and I can iterate through all the files OK and I can get files which currently have that permission set, but I can't see a way to get the history of the file's permissions.
I have found and activated the revisions list API: https://developers.google.com/drive/api/v2/reference/revisions/list
This gets revisions but I can't see anywhere that it lists the sharing history of a revision.
Is what I'm attempting to do possible?
It's definitely possible using the Drive Activity API. You can use the Quickstart for Google Apps Script to view all the activity of an item (file or folder) or done by a User. In this case I modified the Quickstart to show the Permissions changes of a given Drive Id.
function listDriveActivity() {
var request = {
itemName: "items/1bFQvSJ8pMdss4jInrrg7bxdae3dKgu-tJqC1A2TktMs", //Id of the file
pageSize: 10};
var response = DriveActivity.Activity.query(request);
var activities = response.activities;
if (activities && activities.length > 0) {
Logger.log('Recent activity:');
for (var i = 0; i < activities.length; i++) {
var activity = activities[i];
var time = getTimeInfo(activity);
var action = getActionInfo(activity.primaryActionDetail);
var actors = activity.actors.map(getActorInfo);
var targets = activity.targets.map(getTargetInfo);
if (action == "permissionChange"){ //Only show permissionChange activity
Logger.log(
'%s: %s, %s, %s', time, truncated(actors), action,
truncated(targets));
}
}
} else {
Logger.log('No activity.');
}
}
/** Returns a string representation of the first elements in a list. */
function truncated(array, opt_limit) {
var limit = opt_limit || 2;
var contents = array.slice(0, limit).join(', ');
var more = array.length > limit ? ', ...' : '';
return '[' + contents + more + ']';
}
/** Returns the name of a set property in an object, or else "unknown". */
function getOneOf(object) {
for (var key in object) {
return key;
}
return 'unknown';
}
/** Returns a time associated with an activity. */
function getTimeInfo(activity) {
if ('timestamp' in activity) {
return activity.timestamp;
}
if ('timeRange' in activity) {
return activity.timeRange.endTime;
}
return 'unknown';
}
/** Returns the type of action. */
function getActionInfo(actionDetail) {
return getOneOf(actionDetail);
}
/** Returns user information, or the type of user if not a known user. */
function getUserInfo(user) {
if ('knownUser' in user) {
var knownUser = user.knownUser;
var isMe = knownUser.isCurrentUser || false;
return isMe ? 'people/me' : knownUser.personName;
}
return getOneOf(user);
}
/** Returns actor information, or the type of actor if not a user. */
function getActorInfo(actor) {
if ('user' in actor) {
return getUserInfo(actor.user)
}
return getOneOf(actor);
}
/** Returns the type of a target and an associated title. */
function getTargetInfo(target) {
if ('driveItem' in target) {
var title = target.driveItem.title || 'unknown';
return 'driveItem:"' + title + '"';
}
if ('drive' in target) {
var title = target.drive.title || 'unknown';
return 'drive:"' + title + '"';
}
if ('fileComment' in target) {
var parent = target.fileComment.parent || {};
var title = parent.title || 'unknown';
return 'fileComment:"' + title + '"';
}
return getOneOf(target) + ':unknown';
}
Remember to enable the Drive Activity API in Resources > Advanced Google Services
In my example this returns the logs:
You can also look deeper into the Permissions by using the permissionChange Parameters in the query.
If you have a business/enterprise/edu account the admin audit logs will tell you this for 6 months of data. Or it will at least tell you when a permission was changed from x to y.
Can't think of a method for personal.

Service invoked too many times in a short time: exec qps. - Google Sheets

I have been using custom functions to break out simple mathematics into readable JavaScript, but am getting the following error:
Service invoked too many times in a short time: exec qps. Try Utilities.sleep(1000) between calls. (line 0).
I have tried sleeping for a random time, but that doesn't help. My functions look like this:
function conversationGrowthRate(clientCount, initialGrowthRate, conversationCount) {
//Utilities.sleep(Math.random() * 30000);
for (var i = clientCount; i > 10; i--) {
if (initialGrowthRate > 0) {
conversationCount += initialGrowthRate
initialGrowthRate -= 0.000003
}
}
return conversationCount;
}
function conversionGrowth(clientCount, conversationCount, initialConversionRate, conversionGrowthRate, maxConversionRate, coversPerBooking, initialConversationGrowthRate, initialConversationCount) {
//Utilities.sleep(Math.random() * 30000);
if (clientCount <= 50) {
return coversPerBooking * conversationCount * initialConversionRate;
}
else {
var firstFiftyClientConversations = conversationGrowthRate(50,initialConversationGrowthRate, initialConversationCount)*30*50; //~30
var additionalConversionGrowthRate = (clientCount-50) * conversionGrowthRate;
var totalConversionRate = initialConversionRate + additionalConversionGrowthRate;
var additionalClientConversations = conversationGrowthRate(clientCount-50, initialConversationGrowthRate, initialConversationCount) * 30 * (clientCount-50);
if (totalConversionRate < maxConversionRate) {
return coversPerBooking * ((firstFiftyClientConversations * initialConversionRate) + (additionalClientConversations * totalConversionRate));
}
else {
return coversPerBooking * (conversationCount * maxConversionRate);
}
}
}
function salesProductivity(currentExecs, prevMonthExecs, prevTwoMonthExecs, prevThreeMonthExecs, prevFourMonthExecs, salesPerExecPerMonth) {
//Utilities.sleep(Math.random() * 30000);
var firstMonthHires = currentExecs - prevMonthExecs;
var secondMonthHires = prevMonthExecs - prevTwoMonthExecs;
var thirdMonthHires = prevTwoMonthExecs - prevThreeMonthExecs;
var fourthMonthHires = prevThreeMonthExecs - prevFourMonthExecs;
var longerHires = prevFourMonthExecs;
return (secondMonthHires * (0.33 * salesPerExecPerMonth)) + (thirdMonthHires * (0.66 * salesPerExecPerMonth)) + (fourthMonthHires * (1 * salesPerExecPerMonth)) + (longerHires * salesPerExecPerMonth);
}
I changed nothing before it started working.
Google Sheets runs custom functions remotely through a service called exec. If there are "too many" calls (according to some undocumented quota, or, at least, I haven't found any such documentation) to any custom functions, this error will be emitted.
If the calls are happening because many cells are calling the custom functions, I don't see how Utilities.sleep(milliseconds) can help since exec would be called before sleep is (indeed, it'll be exec that'll be calling sleep whether directly or indirectly).
As some comments have mentioned, converting the function to accept ranges and return arrays (and changing the sheet to accommodate said function) is the way to go (until Google add smarter throttling).
This should work for any google service that's hitting a usage quota.
while(true) {
try {
return '<whatever's hitting the quota>';
}
catch(error) {
Utilities.sleep(1000);
}
}
It doesn't give up
It doesn't impose a delay unless needed

"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().