Flutter Google Calendar Api list Events - events

Flutter, Google Calendar API v3
https://pub.dartlang.org/packages/googleapis
Works:
Future<List<Event>> getEvents() =>
calendarApi.events.list("primary",
)
.then((Events events){
return events.items;
}).catchError((e){
print("error encountered");
print("${e.toString()}");
});
Doesn't work:
DateTime start = new DateTime.now().subtract(new Duration(days: 10));
DateTime end = new DateTime.now().add(new Duration(days: 10));
..
Future<List<Event>> getEvents() =>
calendarApi.events.list("primary",
timeMin: start,
timeMax: end,
)
.then((Events events){
return events.items;
}).catchError((e){
print("error encountered");
print("${e.toString()}");
});
Why?

According to the Google calendar API the timeMin and timeMax values must follow the RFC3339 date standard.
Internally the calendar applies .toIso8601String() on the DateTimes you pass in. However that does not make them valid RFC3339 dates.
Calling .toUtc() before passing them in will make them a valid RFC3339. You can try it in DartPad togheter with Googles Api explorer and you will see the different responses.
There is probably more ways to make DateTime RFC3339 compliant but this should point you to the error atleast.

Related

Google API is not returning the events created from UI

I am using googleapis in NodeJS to create & fetch the calendar events. I am using the following method to get the list of events.
const getEvents = async (dateTimeStart, dateTimeEnd,timeZone) => {
console.log("Date Start : " + dateTimeStart + " date end :" + dateTimeEnd + " time zone " + timeZone);
try {
let response = await calendar.events.list({
auth: auth,
calendarId: CALENDER_ID,
timeMin: (new Date(dateTimeStart)).toISOString(),
timeMax: (new Date(dateTimeEnd)).toISOString(),
timeZone: timeZone,
singleEvents: true,
maxResults: 9999,
orderBy: 'startTime
});
let items = response['data']['items'];
console.log(items);
return items;
} catch (error) {
console.log(`Error at getEvents --> ${error}`);
return 0;
}
};
The above method returns only events that are created programmatically via googleapis. If I create the events directly on the calendar from the browser this does not return those events.
Any idea how to fetch all events even if they are created from browser.
Based on what you were explaining about the behavior of the events being created by the service account instead of the actual users I think the problem is that the events created through API are being created under the Calendar ID of the service account, and the ones created by the users through API may have a different Calendar ID, therefore when you try to get the list of events, since you are probably using the Calendar ID from the service account you get only those events created using the API and not the ones created by the users through the web UI.
In this case it may be necessary to make sure that every event is being created under the exact same calendar ID through the web UI and the API so that all the events no matter if they were created through the API or web UI get listed as expected.
Let me know if this is useful, otherwise I can edit the response to add more clarification depending on your specific situation.

Override the timestamp format in the webchat

When setting the timestamp format to 'absolute', the webchat with locale set to fr-FR prints the time portion of the timestamp using the 'short' time format of globalizejs library as 8:36 AM. See: https://github.com/globalizejs/globalize/blob/master/doc/api/date/date-formatter.md.
Can I override the time format to display the time in 24-hour notation, i.e. instead of 8:36 AM to print 8h 36?
Web Chat is integrated into a webpage using JavaScript (not React):
v. 4.8.1, https://cdn.botframework.com/botframework-webchat/latest/webchat.js
If you are using the non-React version of Web Chat, then, no, there isn't an in-built method for updating or changing the timestamp.
However, you can use Web Chat's store for accessing the activity's timestamp to overwrite the HTML element, as shown below. My example is updating the element with only the time. You will want to add functionality to capture any other bits (date, day, time offsets, etc.).
Also, you will need to account for the in-built auto-updating of the time element by Web Chat. For instance, when a minute has passed following an activity's arrival, the time element changes to "1 minute ago", then to "2 minutes ago", and so on.
You may be able to utilize an event listener that looks for changes to the time element which, when triggered, continues to update the time element to fit your needs.
Please note: There are inherent risks in manipulating the DOM directly. The biggest risk is your bot becomes subject to breaking changes should the Web Chat team decide to update, remove, or otherwise alter some underlying component in the future. I would recommended you consider switching to the React version of Web Chat that, among many other features, allows this change while functioning within Web Chat's space.
Lastly, any refreshing of the page will reset the time elements back to Web Chat defaults (in case you have your bot setup for persistent chat across sessions).
<script>
( async function () {
const store = window.WebChat.createStore( {}, ({dispatch}) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { activity } = action.payload;
if (activity.type === 'message') {
console.log('MESSAGE ', activity);
setTimeout(() => {
const webChatRow = document.querySelectorAll('.webchat__row');
const rowLen = webChatRow.length;
const timeParent = webChatRow[ rowLen - 1 ].children;
let timeText = timeParent[ 0 ].children[ 1 ].innerText;
let time = new Date(activity.timestamp);
let hours = time.getHours();
let mins = time.getMinutes();
timeParent[ 0 ].children[ 1 ].innerText = `${hours}:${mins}`
console.log(timeText)
}, 300);
}
}
next(action);
} );
const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
const { token } = await res.json();
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine( {
token: token
} ),
store: store
},
document.getElementById( 'webchat' )
);
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>
Hope of help!
Take a look at the Customize activity status Web Chat sample. It shows how you can use the activityStatusMiddleware to customize the timestamp.

GoogleCalendarAPI accept/decline event

I am working on GoogleCalendar API and using node.js as a platform to build my application.
I am able to create events using authentication procedure and creating the calendar event using the access token generated while authenticating.
My question is that suppose if we have any attendee in the event and I want to accept/decline the event using the calendar API from the attendee's side, how can we do that?
I have tried fetching the calendar event of the attendee and matching it with the iCalUID of the event which was originally created and then modifying the event using update event on the attendee's calendar.
Event creator or owner cannot modify the response of attendees. Only attendees can modify their status.
To update the status on the side of the user, You may use the Event.update API and provide value for 'attendees.responseStatus'. Attendee's response status has 4 (four) possible value (described below).
'needsAction' - has not responded to the invitation.
'declined' - has declined the invitation.
'tentative' - has tentatively accepted the invitation
'accepted' - has accepted the invitation.
In addition to this, You can use the word "primary" as value for the calendar id to represent the currently logged in user
CalendarId: Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the "primary" keyword. (string).
For the id, you need to use the "id" returned by the Events.list API not the "iCalUID". Those two are different as described here.
Other fields that you need to provide are the email (of the attendee), startdate and enddate.
For more information, you may view the official documentation, link below:
https://developers.google.com/google-apps/calendar/v3/reference/events
Here is an example in java, using PATCH. Create an event object with the just the information you want to change, in this case the attendee and the response status. This code is running as the attendee.
final Event event = new Event()
.setAttendees(Arrays.asList(new EventAttendee().setEmail(email)
.setResponseStatus("declined")));
try
getCalendarService(googleAccountCredential).events()
.patch(CALENDAR_PRIMARY, calendarEventId, event)
.setSendNotifications(true)
.setOauthToken(googleAccountCredential.getToken()).execute();
return true;
} catch (final Exception ex) {
...
return false;
}
}
Like Android Enthusiast discussed, only the attendee can update his or her calendar from the attendee's side. You should also check the documentation as he suggested. The answer below is a working example for node.js and python
To update the event, you need to have the eventId and the user email. Get the event from the calendar(with the eventID),
loop through all the attendees, change responseStatus for
that particular attendee and then update the google calendar
For node js using the google api
const { google } = require('googleapis');
const calendar = google.calendar({ version: 'v3', auth: 'YOUR-API-KEY-HERE' });
#get the event to be updated
let theEvent = calendar.events.get({ calendarId: 'primary', eventId: eventId })
#loop through the whole attendee
for (let i = 0, i < theEvent['atendees'].length; i++){
if (theEvent['atendees'][i]['email'] === userEmail){
theEvent['atendees'][i]['responseStatus'] = 'accepted'
}
}
#update the google event
calendar.events.update({ calendarId: 'primary', eventId: theEventId, body: theEvent}, function(err, event) {
  if (err) {
    console.log('there was an error');
    return;
  }
  console.log('Event updated');
});
For python using googleapiclient
from googleapiclient.discovery import build
calendar = build('calendar', 'v3', credentials=credential)
event = calendar.events().get(calendarId='primary', eventId='eventId').execute()
For attendee in event['atendees']:
if atendee['email'] == user_email:
attendee['responseStatus'] = 'accepted'
break
#update the google event
udated_event = calendar.events().update(calendarId='primary', eventId=eventId, body=event).execute()
Lets suppose that you already have the event payload with the attendees key, then you need to get the ID for the created event:
created_event = gcal_service.events().insert(
calendarId='primary', body=event_payload
).execute()
then copy the attendees in a new object
accepted_attendees = {}
accepted_attendees['attendees'] = event_payload['attendees'].copy()
and now what you need to do is submit a patch to the attendees calendar based on the event_id, like this:
for attendee in event_payload['attendees']:
attendee_pos = accepted_attendees['attendees'].index(attendee)
accepted_attendees['attendees'].pop(attendee_pos)
accepted_attendees['attendees'].append({
'email': attendee['email'],
'self': True,
'responseStatus': 'accepted',
'additionalGuests': 0,
})
gcal_service.events().patch(
calendarId='primary',
eventId=created_event['id'],
body=accepted_attendees
)
And that's all, all the other attendees, now have accepted the event, hope it helps.
To respond, you need to get the event with the same event id from the attendee's calendar and then perform a patch or an update operation changing the response status of this attendee from needsAction to accepted / declined.
A bit of documentation on how events are copied between attendees and organizers:
https://developers.google.com/google-apps/calendar/concepts/sharing
Here is an example in python for Google Calendar Api v3. You can either use update or patch. Both of them are working.
all_attendees = event['attendees']
event['attendees'] = [{
'email': 'you#example.com',
'self': True,
'responseStatus': 'accepted',
'additionalGuests': 0,
}]
updated_event = service.events().patch(calendarId=calendar_id, eventId=event_id, body=event).execute()
Have fun

Updating an Appointment causes it to change to a Meeting in EWS 1.1

Here's what I'm trying to do:
get all items on a user's calendar between two dates
update the Location or Subject for some items
I get the items with:
FindItemsResults<Appointment> findResults = calendar.FindAppointments(new CalendarView(startDate, endDate));
This query works fine. But whenever I call Update to save the item I get an exception:
Microsoft.Exchange.WebServices.Data.ServiceResponseException: One or more recipients are invalid.
Even though I get an exception, the item is saved and gets changed to have IsMeeting set to true! Now the updated item is a meeting with an organizer etc... This is, effectively, data corruption for me.
Here's the code. It is no more complicated than this. I've tested it by just changing Location or Subject and both cause the problem.
Appointment a = Appointment.Bind(_service, new ItemId(id));
a.Location = newLocation
a.Update(ConflictResolutionMode.AlwaysOverwrite);
Am I missing some concept or something? This seems like a pretty egregious problem.
FWIW, this is EWS 1.1 against an Office 365 server.
I figured it out with help from this Question:
Exchange Appointment Types
The key is the Update method needs to be called with the SendInvitationsOrCancellationsMode.SendToNone flag set in the 2nd parameter.
Like this:
a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);
So tig's answer works when you never want to send out appointment updates to the other attendees. However to answer this properly you actually need to get the attendee state loaded.
By default it is trying to send appointment updates to the attendees, however your appointment object doesn't have the attendee state loaded and is hence blowing up. When you do the bind you should load the attendee properties. You should probably also load the organizer as well to cover another edge case:
AppointmentSchema.RequiredAttendees
AppointmentSchema.OptionalAttendees
AppointmentSchema.Resources
AppointmentSchema.Organizer
This will get the attendees populated if you want to do an update that sends out updates to the attendees.
However there is then another edge case that you have to worry about. If you have an appointment with no attendees added to it (just the organizer), then EWS may still complain and throw this error. It will actually work for appointments in some states, but fail in other states.
So the most complete solution is a combination of:
Loading the attendee state.
Inspecting the attendee state to see if there are any attendees other than the organizer (depending on how the appointment was created the organizer may or may not appear in the RequiredAttendees collection). If there are not then you must use SendInvitationsOrCancellationsMode.SendToNone.
So the full sample would look something like:
Appointment a = Appointment.Bind(_service, new ItemId(id), new PropertySet(AppointmentSchema.RequiredAttendees, AppointmentSchema.OptionalAttendees, AppointmentSchema.Resources, AppointmentSchema.Organizer));
a.Location = newLocation
// Check if the appointment has attendees other than the organizer. The organizer may
// or may not appear in the required attendees list.
if (HasNoOtherAttendee(a.Organizer.Address, a.RequiredAttendees) &&
(a.OptionalAttendees.Count == 0) && (a.Resources.Count == 0))
{
a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);
}
else
{
// We have other attendees in the appointment, so we can use SendToAllAndSaveCopy so
// they will see the update.
a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToAllAndSaveCopy);
}
bool HasNoOtherAttendee(string email, AttendeeCollection attendees)
{
bool emptyOrOnlyMe = true;
foreach (var a in attendees)
{
if (!string.Equals(email, a.Address, StringComparison.OrdinalIgnoreCase))
{
emptyOrOnlyMe = false;
break;
}
}
return emptyOrOnlyMe;
}
To answer this bit of the question
"Even though I get an exception, the item is saved and gets changed to
have IsMeeting set to true! Now the updated item is a meeting with an
organizer etc... This is, effectively, data corruption for me."
The Microsoft documentation states, in the small print, "A meeting request is just an appointment that has attendees. You can convert an appointment into a meeting request by adding required attendees, optional attendees, or resources to the appointment" - as seen here
http://msdn.microsoft.com/en-us/library/office/dd633641%28v=exchg.80%29.aspx
In other words, as soon as you have any attendees, Exchange converts it to a meeting automatically.
public static bool UpdateAppointment(ExchangeCredential credentials,
ItemId appointmentId, string newLocation, string newSubject,
DateTime startTime,
DateTime endTime)
{
ExchangeService service = GetExchangeService(credentials);
try
{
Appointment appt = Appointment.Bind(service, appointmentId,
new PropertySet(BasePropertySet.IdOnly, AppointmentSchema.Start,
AppointmentSchema.ReminderDueBy, AppointmentSchema.End, AppointmentSchema.StartTimeZone,
AppointmentSchema.TimeZone));
appt.Location = newLocation;
appt.Start = startTime;
appt.End = endTime;
appt.Subject = newSubject;
// very important! you must load the new timeZone
appt.StartTimeZone = TimeZoneInfo.Local;
//appt.Body.Text = newBody; //if needed
appt.Update(ConflictResolutionMode.AlwaysOverwrite);
}
catch (Exception ex)
{
throw ex;
}
return true;
}

Jquery datetime picker plugin?

I am using this plugin:
http://tedserbinski.com/jcalendar/index.html
The plugin is supposed to accept a user defined startdate, but I cant manage to get it to work.
This is the header of the js file:
var _drawCalendar = function(dateIn, a, day, month, year) {
var today = new Date();
if (dateIn == undefined) {
// start from this month.
d = new Date(today.getFullYear(), today.getMonth(), 1);
year.val(today.getFullYear());
month.val(today.getMonth()+1);
day.val(today.getDate());
}
else {
// start from the passed in date
d = dateIn;
d.setDate(1);
}
And i am calling the calendar plug with the following line, that actually is missing something. I have tried hundreds of diffrent code snippets, but I have gived up :(
$('fieldset.jcalendar').jcalendar();
Best regards, Joakim
Any particular reason you aren't using the DatePicker that comes with jQuery UI? The page you linked even says that the project has been superceded by jQuery UI.

Resources