-
Notifications
You must be signed in to change notification settings - Fork 0
/
cal_dav_interface.py
174 lines (154 loc) · 6.26 KB
/
cal_dav_interface.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""
The software class that handles connection to and requests from the Nextcloud calendar
"""
from datetime import datetime
import datetime as dt
import caldav
import icalendar
import pytz
Utc = pytz.UTC
class CalDavInterface:
"""
Interface to connect to a Nextcloud calendar using the CalDav protocol
"""
def __init__(self, url, username, password, local_timezone):
self.local_timezone = local_timezone
self.client = caldav.DAVClient(
url=url,
username=username,
password=password
)
self.calendar = self.get_calendar()
def get_calendar(self):
"""
Get calendar for existing Nextcloud calendar
:return: calendar instance
"""
my_principal = self.client.principal()
return my_principal.calendars()[0]
def get_event_details(self, event):
"""
Parses ical strings for event to dictionary containing title, start time and end time
:param event: ical string of event
:return: dictionary representation of event
"""
start = None
if "DTSTART" in event.keys():
start = event["DTSTART"].dt
if not isinstance(start, datetime):
start = datetime.combine(start, datetime.min.time())
start = start.replace(tzinfo=Utc)
else:
start = start.astimezone(self.local_timezone)
end = None
if "DTEND" in event.keys():
end = event["DTEND"].dt
if not isinstance(end, datetime):
end = datetime.combine(end, datetime.min.time())
end = end.replace(tzinfo=Utc)
else:
end = end.astimezone(self.local_timezone)
title = "untitled event"
if "SUMMARY" in event.keys():
title = str(event["SUMMARY"])
return {"title": title, "starttime": start, "endtime": end}
def parse_ics_events(self, events):
"""
Parses calendar events from ical format to python dictionary
:param events: list of events (ical strings) that should be parsed
:return: python list containing the pared events as dictionaries
"""
parsed_events = []
for event in events:
cal = icalendar.Calendar.from_ical(event.data, True)
url = event.url
for vevent in cal[0].walk("vevent"):
event_details = self.get_event_details(vevent)
event_details["event_url"] = url
parsed_events.append(event_details)
return parsed_events
def get_events_for_timeperiod(self, startdate, enddate):
"""
Returns list of parsed events in a specific time period
:param startdate: start date of search period
:param enddate: end date of search period
:return: list of parsed events (python dicts)
"""
events = self.calendar.date_search(start=startdate, end=enddate, expand=True)
return self.parse_ics_events(events)
def get_events_for_date(self, requested_date):
"""
Returns list of parsed events for a specific date
:param requested_date: date of the request
:return: list of parsed events (python dict)
"""
return self.get_events_for_timeperiod(
datetime.combine(requested_date, datetime.min.time()),
datetime.combine(requested_date, datetime.max.time())
)
def get_next_event(self):
"""
Returns next event planned in the calendar.
:return: next event as python dictionary. If there is no event planned in the future
"None" is returned
"""
all_events = self.calendar.events()
parsed_events = self.parse_ics_events(all_events)
sorted_events = sorted(parsed_events, key=lambda i: i["starttime"])
for event in sorted_events:
starttime = event["starttime"]
now = datetime.today()
current_time = now.replace(tzinfo=starttime.tzinfo)
if starttime > current_time:
return event
return None
def get_events_with_title(self, title):
"""
Get all events from the calendar, parse it to python dict representation
and filter them into a list only events matching the given title.
:param title: string that the event summary needs to contain
:return: list of matching events in python dictionary representation
"""
all_events = self.calendar.events()
parsed_events = self.parse_ics_events(all_events)
matching_events = [event for event in parsed_events
if title.lower() in event["title"].lower()]
return matching_events
def create_new_event(self, title, date, duration, fullday):
"""
Creates a new event in the Nextcloud calendar with the given details.
:param title: title used in summary of the new event
:param date: datetime used as dtstart of the event
:param duration: duration to calculate the dtend of the event
:param fullday: boolean that indicates if the event is a full-day event
"""
cal = icalendar.Calendar()
event = icalendar.Event()
event.add("summary", title)
if fullday:
start = date.date()
end = (date + dt.timedelta(duration)).date()
date = start
else:
end = date + duration
event.add("dtstart", date)
event.add("dtend", end)
cal.add_component(event)
self.calendar.add_event(cal)
def delete_event(self, event):
"""
Gets a CalDav event object from the event url and delete
the event form the calendar.
:param event: event in python dict representation
"""
caldav_event = self.calendar.event_by_url(event["event_url"])
caldav_event.delete()
def rename_event(self, event, new_title):
"""
Changes the summary field of a given event to a new title.
:param event: the event in python dict representation
:param new_title: the desired new title of the event
"""
caldav_event = self.calendar.event_by_url(event["event_url"])
caldav_event.vobject_instance.vevent.summary.value = new_title
caldav_event.save()