I am trying to update calendar events from google apps script. I have the calendar ID, event ID, and objects I am trying to update as a variable:
var eventinfo = {
"calendarId": calID
"eventId": eventID,
"resource": {
"description": "1234"
}
};
//update the event description and location
var updater;
try {
updater = Calendar.Events.update(eventinfo);
Logger.log('Successfully updated event: ' + i);
} catch (e) {
Logger.log('Fetch threw an exception: ' + e);
}
I am getting this error:
Fetch threw an exception: Exception: Invalid number of arguments provided. Expected 3-5 only
Previously, I had attempted to invoke the update method this way .update(calID, eventID, eventinfo) where event info was an object with just a description, but that was returning the error saying bad call.
I think I am missing something in my object argument.
Issues:
First of all, you forgot a comma in the definition of eventinfo
between the first and the second row.
However, I don't think your approach will work, because you don't
pass an event object in the Calendar.Events.update() function. The structure should be like that:
Calendar.Events.update(
event,
calendarId,
event.id
);
Solution/Example:
The following example updates the very first event in the future. In
particular, it updates the title (summary), description and place but
feel free modify that if you want:
function updateNextEvent() {
const calendarId = 'primary';
const now = new Date();
const events = Calendar.Events.list(calendarId, {
timeMin: now.toISOString(),
singleEvents: true,
orderBy: 'startTime',
maxResults: 1
});
var event = events.items[0]; //use your own event object here if you want
event.location = 'The Coffee Shop';
event.description = '1234';
event.summary = 'New event';
event = Calendar.Events.update(
event,
calendarId,
event.id
);
}
Of course, don't forget to switch on the Calendar API from Resources => Advanced Google services.
References:
Advanced Calendar Service
Related
I want to add / insert new event with extened properties in Google Script = .gs file.
I found code example for Calendar API - Events insert - see below. But the code uses the JavaScript client library. I want the code to run from GS file. I tried to modify but it did not work.
When using the code I want to be able to specify any calendar. Not only "primary".
// Refer to the JavaScript quickstart on how to setup the environment:
// https://developers.google.com/calendar/quickstart/js
// Change the scope to 'https://www.googleapis.com/auth/calendar' and delete any
// stored credentials.
var event = {
'summary': 'Google I/O 2015',
'location': '800 Howard St., San Francisco, CA 94103',
'description': 'A chance to hear more about Google\'s developer products.',
'start': {
'dateTime': '2015-05-28T09:00:00-07:00',
'timeZone': 'America/Los_Angeles'
},
'end': {
'dateTime': '2015-05-28T17:00:00-07:00',
'timeZone': 'America/Los_Angeles'
},
'recurrence': [
'RRULE:FREQ=DAILY;COUNT=2'
],
'attendees': [
{'email': 'lpage#example.com'},
{'email': 'sbrin#example.com'}
],
'reminders': {
'useDefault': false,
'overrides': [
{'method': 'email', 'minutes': 24 * 60},
{'method': 'popup', 'minutes': 10}
]
}
};
var request = gapi.client.calendar.events.insert({
'calendarId': 'primary',
'resource': event
});
request.execute(function(event) {
appendPre('Event created: ' + event.htmlLink);
});
Could someone please explain the difference between private and shared extended properties?
I am able to create new event using below code but looks like it will not store extended properties.
function getCalendar() {
var calendarId = 'processor#mydomain.com'
var calendar = CalendarApp.getCalendarById(calendarId)
Logger.log('The calendar is named "%s".', calendar.getName());
var eventOption = {
location: 'The Moon',
description: 'link na akci je https://us02web.zoom.us/j/83314336043',
extendedProperties: { // Extended properties of the event.
private: { // Properties that are private to the copy of the event that appears on this calendar.
creator: "Radek", // The name of the private property and the corresponding value.
},
}
}
var event = calendar.createEvent('test event from the script',
new Date(),
new Date(),
eventOption
);
var eventId = event.getId().replace(/#.*/,'') // // Remove #google.com from eventId
Logger.log('Event ID: ' + eventId)
calendarId = 'primary'
var eventSaved = Calendar.Events.get(encodeURIComponent(calendarId), eventId)
var testEx = event.extendedProperties
var test = event.extendedProperties.private["creator"];
}
Answer for question 1:
Could someone please explain the difference between private and shared extended properties?
The official document says as follows.
extendedProperties.private: Properties that are private to the copy of the event that appears on this calendar.
extendedProperties.shared: Properties that are shared between copies of the event on other attendees' calendars.
For example, when a new event is created with the values of extendedProperties.private and extendedProperties.shared by including the attendees, you can see both values. But, the attendees can see only the value of extendedProperties.shared.
Is this explanation useful?
Answer for question 2:
I want to add / insert new event with extened properties in Google Script = .gs file.
When I saw the official document of the method of createEvent(title, startTime, endTime, options), it seems that options has no property of extendedProperties. Ref I thought that this is the reason for your issue. If you want to create a new event including the values of extendedProperties.private and extendedProperties.shared, how about using Calendar API of Advanced Google services?
The sample script is as follows.
const calendarId = "###"; // Please set your calendar ID.
// Create a new event including extendedProperties.
const params = {
start: { dateTime: "2022-04-27T00:00:00Z" },
end: { dateTime: "2022-04-27T01:00:00Z" },
extendedProperties: {
private: { key1: "value1" },
shared: { key2: "value2" }
},
summary: "sample",
attendees: [{ email: "###" }] // Please set the email of attendee, if you want to include.
};
const res1 = Calendar.Events.insert(params, calendarId);
// Check the value of extendedProperties
const res2 = Calendar.Events.get(calendarId, res1.id);
console.log(res2.extendedProperties)
When this script is run by the owner of the calendar, you can see both values of extendedProperties.private and extendedProperties.shared.
When you get this event by the attendee, you can see only the value of extendedProperties.shared.
References:
createEvent(title, startTime, endTime, options)
Events: insert
Could someone please explain the difference between private and shared extended properties?
Based on documentation, shared extended properties are visible and editable by attendees while private set on one attendee's local "copy" of the event.
To add extended properties to events with Apps Script, you can do it with the advanced Calendar service. For this, you need to add the “Google Calendar API” service in your Apps Script project, on the left side of the screen, click on the “+” next to “Services”, search for “Google Calendar API”, click on it and click “Add”.
After completing the steps mentioned above, you can test this script I created as an example.
function createEvent() {
var calendarId = 'processor#mydomain.com' //you can specify the calendar with the calendar id
var start = new Date();
var end = new Date();
var event = {
"location": "The Moon",
"description": "link na akci je https://us02web.zoom.us/j/83314336043",
"start": {
"dateTime": start.toISOString(),
},
"end": {
"dateTime": end.toISOString()
},
"extendedProperties": {
"private": {
"creator": "Radek"
}
}
};
event = Calendar.Events.insert(event, calendarId);
Logger.log('Event ID: ' + event.id);
}
Update/Solution
There isn't a way to add a photo to a person object on creating a contact via People API. However you can updated the photo directly after using People.People.updateContactPhoto. updateContactPhoto has a limit of 60 queries per minute per user. If your script needs to update more than 60 contact photos, then you will need to add a delay in your code so that query limit isn't reached
Question:
I am creating a large list of contacts from a set of data, including contact photos for each contact. When I create contacts via people.createContact, if I have the photos field there it states "person.photos is a read only field." How I can I create the contact with a profile photo link as well?
Example Code
const contact = {names:..., addresses:..., emailAddressess:..., phoneNumbers:...,
photos: {"url":..., "primary": true}
}
People.People.createContact(contact)
this gives the error message
Update 1:
Currently this is what I'm doing and it works. The main problem now is I don't want to use the Utilities.Sleep function. I'm looping over this function hundreds of times, and that adds up quickly. If I don't use the sleep though around the 40th or 50th contact it throws an "Internal error encountered". The contact it errors on is different each time, so the error is not tied to a specific person. My assumption is the error comes from running too many requests too quickly, but how can I run this without forcing it to sleep?
function importPerson(person){
const photoData = encodePhotoData(person.photos[0].url)
delete person.photos
const newContact = People.People.createContact(person)
People.People.updateContactPhoto({
photoBytes: photoData,
},newContact.resourceName)
//This stops it from throwing an error
Utilities.sleep(1000)
}
You need to use people.updateContactPhoto to update a contact's person. You need to convert your photo's url into a raw photo bytes in base64-encoded string.
Sample Code:
var resource = {
names: [
{
givenName: "Sample1"
}
],
emailAddresses: [
{
value: "sample1#example.com"
}
]
};
//create contact
var response = People.People.createContact(resource);
//Get raw photo bytes in base64-encoded string
var url = "https://lh3.googleusercontent.com/-T_wVWLlmg7w/AAAAAAAAAAI/AAAAAAAABa8/00gzXvDBYqw/s100/photo.jpg"
var photoBlob = UrlFetchApp.fetch(url);
var base64EncodedBytes = Utilities.base64Encode(photoBlob.getBlob().getBytes());
var photoResource = {
photoBytes: base64EncodedBytes
}
//update photo
People.People.updateContactPhoto(photoResource,response.resourceName)
Output:
(UPDATE)
Here is the sample code where I tried to create 100 contacts:
function testContact(){
for(var i=0;i<100;i++){
var resource = {
names: [
{
givenName: "Sample1"
}
],
emailAddresses: [
{
value: "sample1#example.com"
}
],
photos:[
{
url: "https://lh3.googleusercontent.com/-T_wVWLlmg7w/AAAAAAAAAAI/AAAAAAAABa8/00gzXvDBYqw/s100/photo.jpg"
}
]
};
Logger.log(i);
importPerson(resource);
}
}
function importPerson(person){
const photoData = encodePhotoData(person.photos[0].url)
delete person.photos
const newContact = People.People.createContact(person)
People.People.updateContactPhoto({
photoBytes: photoData,
},newContact.resourceName)
//This stops it from throwing an error
//Utilities.sleep(1000)
}
I am trying to develop google calendar add-on like zoom meeting.
In appsscript.json file, below code is there.
"calendar": {
"conferenceSolution": [{
"onCreateFunction": "createConference",
"id": "1",
"name": "Meeting",
"logoUrl": "https://companyxyz.com/images/logo192.png"
}],
"eventOpenTrigger": {
"runFunction": "buildSimpleCard"
},
"currentEventAccess": "READ_WRITE"
}
}
In Calendar.gs, below code is there.
function createConference(e) {
Logger.log(e);
var dataBuilder = ConferenceDataService.newConferenceDataBuilder();
return dataBuilder.build();
}
/**
* Build a simple card with a button that sends a notification.
* This function is called as part of the eventOpenTrigger that builds
* a UI when the user opens a Calendar event.
*
* #param e The event object passed to eventOpenTrigger function.
* #return {Card}
*/
function buildSimpleCard() {
var buttonAction = CardService.newAction()
.setFunctionName('onSaveConferenceOptionsButtonClicked')
.setParameters(
{'phone': "1555123467", 'adminEmail': "joyce#example.com"});
var button = CardService.newTextButton()
.setText('Add new attendee')
.setOnClickAction(buttonAction);
var buttonSet = CardService.newButtonSet()
.addButton(button);
var section = CardService.newCardSection()
.setHeader("addon")
.addWidget(buttonSet);
var card = CardService.newCardBuilder()
.addSection(section)
//.setFixedFooter(footer);
return card.build();
// Check the event object to determine if the user can set
// conference data and disable the button if not.
// if (!e.calendar.capabilities.canSetConferenceData) {
// button.setDisabled(true);
// }
// ...continue creating card sections and widgets, then create a Card
// object to add them to. Return the built Card object.
}
/**
* Callback function for a button action. Sets conference data for the
* Calendar event being edited.
*
* #param {Object} e The action event object.
* #return {CalendarEventActionResponse}
*/
function onSaveConferenceOptionsButtonClicked(e) {
var parameters = e.commonEventObject.parameters;
// Create an entry point and a conference parameter.
var phoneEntryPoint = ConferenceDataService.newEntryPoint()
.setEntryPointType(ConferenceDataService.EntryPointType.PHONE)
.setUri('tel:' + parameters['phone']);
var adminEmailParameter = ConferenceDataService.newConferenceParameter()
.setKey('adminEmail')
.setValue(parameters['adminEmail']);
// Create a conference data object to set to this Calendar event.
var conferenceData = ConferenceDataService.newConferenceDataBuilder()
.addEntryPoint(phoneEntryPoint)
.addConferenceParameter(adminEmailParameter)
.setConferenceSolutionId(1)
.build();
return CardService.newCalendarEventActionResponseBuilder()
.setConferenceData(conferenceData)
.build();
}
I have published this add-on from Publish->Deploy from menifest.
Executing this code giving me error of ReferenceError: ConferenceDataService is not defined.
I have searched all the possible references, but not able to get any solution.
Please suggest me proper solution for this.
According to this comment from this issue here, it looks like there has been a change regarding this.
When testing the above code, the ReferenceError: ConferenceDataService is not defined. is not displayed anymore and the code runs as expected.
For other methods specific to the ConferenceDataService you can check the documentation here.
Reference
Apps Script ConferenceDataService.
Cannot create conference with 3rd party Conference Solution from conference solutions Dropdown in Google Calendar
Method UiInstance.getElementById(ID) always returns GenericWidget object, even if ID does not exist.
Is there some way how to find out that returned object does not exist in my app, or check whether UI contains object with given ID?
Solution for UI created with GUI builder:
function getSafeElement(app, txtID) {
var elem = app.getElementById(txtID);
var bExists = elem != null && Object.keys(elem).length < 100;
return bExists ? elem : null;
}
It returns null if ID does not exist. I didn't test all widgets for keys length boundary, so be careful and test it with your GUI.
EDIT: This solution works only within doGet() function. It does not work in server handlers, so in this case use it in combination with #corey-g answer.
This will only work in the same execution that you created the widget in, and not in a subsequent event handler where you retrieve the widget, because in that case everything is a GenericWidget whether or not it exists.
You can see for yourself that the solution fails:
function doGet() {
var app = UiApp.createApplication();
app.add(app.createButton().setId("control").addClickHandler(
app.createServerHandler("clicked")));
app.add(app.createLabel(exists(app)));
return app;
}
function clicked() {
var app = UiApp.getActiveApplication();
app.add(app.createLabel(exists(app)));
return app;
}
function exists(app) {
var control = app.getElementById("control");
return control != null && Object.keys(control).length < 100;
}
The app will first print 'true', but on the click handler it will print 'false' for the same widget.
This is by design; a GenericWidget is a "pointer" of sorts to a widget in the browser. We don't keep track of what widgets you have created, to reduce data transfer and latency between the browser and your script (otherwise we'd have to send up a long list of what widgets exist on every event handler). You are supposed to keep track of what you've created and only "ask" for widgets that you already know exist (and that you already know the "real" type of).
If you really want to keep track of what widgets exist, you have two main options. The first is to log entries into ScriptDb as you create widgets, and then look them up afterwards. Something like this:
function doGet() {
var app = UiApp.createApplication();
var db = ScriptDb.getMyDb();
// You'd need to clear out old entries here... ignoring that for now
app.add(app.createButton().setId('foo')
.addClickHandler(app.createServerHandler("clicked")));
db.save({id: 'foo', type: 'button'});
app.add(app.createButton().setId('bar'));
db.save({id: 'bar', type: 'button'});
return app
}
Then in a handler you can look up what's there:
function clicked() {
var db = ScriptDb.getMyDb();
var widgets = db.query({}); // all widgets
var button = db.query({type: 'button'}); // all buttons
var foo = db.query({id: 'foo'}); // widget with id foo
}
Alternatively, you can do this purely in UiApp by making use of tags
function doGet() {
var app = UiApp.createApplication();
var root = app.createFlowPanel(); // need a root panel
// tag just needs to exist; value is irrelevant.
var button1 = app.createButton().setId('button1').setTag("");
var button2 = app.createButton().setId('button2').setTag("");
// Add root as a callback element to any server handler
// that needs to know if widgets exist
button1.addClickHandler(app.createServerHandler("clicked")
.addCallbackElement(root));
root.add(button1).add(button2);
app.add(root);
return app;
}
function clicked(e) {
throw "\n" +
"button1 " + (e.parameter["button1_tag"] === "") + "\n" +
"button2 " + (e.parameter["button2_tag"] === "") + "\n" +
"button3 " + (e.parameter["button3_tag"] === "");
}
This will throw:
button1 true
button2 true
button3 false
because buttons 1 and 2 exist but 3 doesn't. You can get fancier by storing the type in the tag, but this suffices to check for widget existence. It works because all children of the root get added as callback elements, and the tags for all callback elements are sent up with the handler. Note that this is as expensive as it sounds and for an app with a huge amount of widgets could potentially impact performance, although it's probably ok in many cases especially if you only add the root as a callback element to handlers that actually need to verify the existence of arbitrary widgets.
My initial solution is wrong, because it returns false exist controls.
A solution, based on Corey's answer, is to add the setTag("") method and here is ready to use code. It is suitable for event handlers only, because uses tags.
function doGet() {
var app = UiApp.createApplication();
var btn01 = app.createButton("control01").setId("control01").setTag("");
var btn02 = app.createButton("control02").setId("control02").setTag("");
var handler = app.createServerHandler("clicked");
handler.addCallbackElement(btn01);
handler.addCallbackElement(btn02);
btn01.addClickHandler(handler);
btn02.addClickHandler(handler);
app.add(btn01);
app.add(btn02);
return app;
}
function clicked(e) {
var app = UiApp.getActiveApplication();
app.add(app.createLabel("control01 - " + controlExists(e, "control01")));
app.add(app.createLabel("control02 - " + controlExists(e, "control02")));
app.add(app.createLabel("fake - " + controlExists(e, "fake")));
return app;
}
function controlExists(e, controlName) {
return e.parameter[controlName + "_tag"] != null;
}
the geolocation implementation is quite good and got few steps to observe but only on thing is missing, i guess.
Im not able to see if the user accepted the request or not ( before i get the position object ), i dunno if the user just ignores my request ( during my timeout ) or if the request just get lost ( and the failure callback doesnt get called for no reason ).
It would be useful to set a timestamp when the user accepts the request, i couldnt find anything which gives me that kind of response.
Based on my new understanding of what you are after, you want something like this.
(Tested: in Opera - works, Firefox 3.6 & Chrome 8 - not so much (I need more time to debug))
Scenario:
Page attempts to get location... but user ignores the prompt completely thus there is no (accept or deny) and since the request for the location is never sent, there is no timeout either!
Based on this you may want to add your own logic to handle this scenario. For the sake of this example, I'm going to prototype my own "wrapper" method. (for the picky - I'm not condoning using globals etc. I was just trying to get something to work)
navigator.geolocation.requestCurrentPosition = function(successCB, errorCB, timeoutCB, timeoutThreshold, options){
var successHandler = successCB;
var errorHandler = errorCB;
window.geolocationTimeoutHandler = function(){
timeoutCB();
}
if(typeof(geolocationRequestTimeoutHandler) != 'undefined'){
clearTimeout(window['geolocationRequestTimeoutHandler']);//clear any previous timers
}
var timeout = timeoutThreshold || 30000;//30 seconds
window['geolocationRequestTimeoutHandler'] = setTimeout('geolocationTimeoutHandler()', timeout);//set timeout handler
navigator.geolocation.getCurrentPosition(
function(position){
clearTimeout(window['geolocationRequestTimeoutHandler']);
successHandler(position);
},
function(error){
clearTimeout(window['geolocationRequestTimeoutHandler']);
errorHandler(error);
},
options
);
};
function timeoutCallback(){
alert('Hi there! we are trying to locate you but you have not answered the security question yet.\n\nPlease choose "Share My Location" to enable us to find you.');
}
function successCallback(position){
var msg = '';
msg += 'Success! you are at: ';
msg += '\nLatitude: ' + position.coords.latitude;
msg += '\nLongitude: ' + position.coords.longitude;
msg += '\nAltitude: ' + position.coords.altitude;
msg += '\nAccuracy: ' + position.coords.accuracy;
msg += '\nHeading: ' + position.coords.heading;
msg += '\nSpeed: ' + position.coords.speed;
alert(msg);
}
function errorCallback(error){
if(error.PERMISSION_DENIED){
alert("User denied access!");
} else if(error.POSITION_UNAVAILABLE){
alert("You must be hiding in Area 51!");
} else if(error.TIMEOUT){
alert("hmmm we timed out trying to find where you are hiding!");
}
}
navigator.geolocation.requestCurrentPosition(successCallback, errorCallback, timeoutCallback, 7000, {maximumAge:10000, timeout:0});
The concept is to set up a timer first (defaults to 30 seconds if not set). If the user doesn't do anything before the timer expires, a timeoutCallback is called.
Notes:
Some UI's (e.g. iPhone/iPad/iPod Safari) may make the Allow/Deny prompt modal - thus the user can't really continue until they pick something (I'd suggest to leave these users alone and let the default UI handle things
If the user Allows the request (late), the timeout may still fire before the response comes back - I don't think there is anything you can do about this
Code above is an example only... it needs cleaning up.
It is part of the Geolocation API:
// navigator.geolocation.getCurrentPosition(successCallback, errorCallback, options);
navigator.geolocation.getCurrentPosition(
function(position){
//do something with position;
}, function(){
//handle condition where position is not available
//more specifically you can check the error code...
//error.code == 1
if(error.PERMISSION_DENIED){
alert("you denied me! ");
}
});
If you specify the errorCallback... then you can track if the user has declined to provide access.
Possible error codes include:
error.PERMISSION_DENIED (numeric value 1)
error.POSITION_UNAVAILABLE (numeric value 2)
error.TIMEOUT (numeric value 3)
Tested it successful in FF 3.5, Opera 10.6, Chrome8, IE6-8..
var succeed = function(obj) {
navigator.geolocation.received = true;
!navigator.geolocation.timedout?alert('GOT YAH'):alert('GOT YAH but user was to slow');
};
var failed = function(obj) {
navigator.geolocation.received = true;
!navigator.geolocation.timedout?alert('just failed'):alert('failed and user was to slow as well, tzz ._.');
};
var timedout = function() {
navigator.geolocation.timedout = true; // could be used for other callbacks to trace if its timed out or not
!navigator.geolocation.received?alert('Request timed out'):null;
}
// Extend geolocation object
if ( navigator.geolocation ) {
navigator.geolocation.retrievePermission = function retrievePermission(succeed,failed,options,timeout) {
this.received = false; // reference for timeout callback
this.timedout = false; // reference for other callbacks
this.getCurrentPosition.apply(this,arguments); // actual request
// Trigger timeout with its function; default timeout offset 5000ms
if ( timeout ) {
setTimeout(timeout.callback,timeout.offset || 5000);
}
}
// New location request with timeout callback
navigator.geolocation.retrievePermission(succeed,failed,{},{
offset: 10000, // miliseconds
callback: timedout
});
// Awesome thingy is not implemented
} else {
alert('geolocation is not supported');
}
With that workaround we know if the request timedout, even when the succeess / failure callback get called afterwards.