Skip to content

Commit c1a8d08

Browse files
updating model and fixing issues when using multiple calendars
1 parent a81a9b7 commit c1a8d08

12 files changed

+333
-244
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/.project

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Your service will now be available via https://yourdomain/calexa/
3737

3838
## Configuration via Alexa Skills Kit <i class="icon-cog"></i>
3939

40-
The configuration process is straigt forward, as known by other skills.
40+
The configuration process is straight forward, as known by other skills.
4141

4242

4343
#### Intent Schema
@@ -62,7 +62,7 @@ Agenda
6262
```
6363

6464
#### Sample Utterances:
65-
Copy the content of the [Sample Utterance file](https://github.com/martin-riedl/CALexa/blob/master/speech_assets/SampleUtterances_DE.txt) to the Sample Utterances field of the Interaction Model tab while using the Alexa Skills Kit for configuration.
65+
Copy the content of the [Interaction Model definition](https://github.com/nikolauskrismer/CALexa/blob/master/interactionModel.json) to the Interaction Model while using the [Alexa Developer Console](https://developer.amazon.com/alexa/console/ask) for configuration.
6666

6767

6868

calexa.py

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import pprint
2+
import json
3+
from datetime import datetime
4+
import caldav
5+
import urllib3
6+
from caldav.elements import dav, cdav
7+
8+
from ics import Calendar
9+
10+
from flask import Flask
11+
from flask_ask import Ask, statement
12+
13+
from datetime import timedelta
14+
app = Flask(__name__)
15+
ask = Ask(app, '/')
16+
17+
# open log files
18+
#f = open('./log/calexa.log', 'a')
19+
#fe = open('./log/calexa_error.log', 'a')
20+
21+
# read configuration
22+
with open('./config.json') as json_data_file:
23+
config = json.load(json_data_file)
24+
25+
def connectCalendar():
26+
global config
27+
28+
client = caldav.DAVClient(config["url"], username=config["username"], password=config["password"])
29+
principal = client.principal()
30+
return principal.calendars()
31+
32+
def getCalDavEvents(begin, end):
33+
# global f, fe
34+
calendars = connectCalendar()
35+
speech_text = ""
36+
37+
if (len(calendars) <= 0):
38+
speech_text = " ich konnte mich leider nicht mit dem Kalender verbinden\n"
39+
# fe.write(speech_text)
40+
# fe.flush()
41+
else:
42+
eventList = []
43+
flatten = lambda l: [item for sublist in l for item in sublist]
44+
45+
# f.write(" gefundene Kalender: " + str(len(calendars)) + "\n")
46+
i = 0
47+
for calendar in calendars:
48+
# f.write(" [" + str(i + 1) + "]: " + str(calendar))
49+
results = calendar.date_search(begin, end)
50+
51+
# f.write(" -> " + str(len(results)) + " Termine \n")
52+
if len(results) > 0:
53+
eventList = eventList + flatten([Calendar(event._data).events for event in results])
54+
i = i + 1
55+
56+
if (len(eventList) <= 0):
57+
speech_text = "Es sind keine Termine eingetragen"
58+
else:
59+
sortedEventList = sorted(eventList,key=lambda icsEvent: icsEvent.begin)
60+
61+
# pp = pprint.PrettyPrinter(indent=4)
62+
# pp.pprint(sortedEventList)
63+
64+
speech_text = "<speak>\n"
65+
speech_text += ' Es sind folgende Termine auf dem Kalender:\n'
66+
for icsEvent in sortedEventList:
67+
speech_text += ' <break time="1s"/> ' + icsEvent.begin.humanize(locale='de') + " ist " + icsEvent.name + '.\n'
68+
speech_text += "</speak>"
69+
70+
return speech_text
71+
72+
#@ask.intent('GetTodayEventsIntent')
73+
#def getTodayEvents():
74+
# speech_text = getCalDavEvents(datetime.now(), datetime.now() + timedelta(days=1))
75+
# print(speech_text)
76+
# return statement(speech_text).simple_card('Kalendertermine', speech_text)
77+
78+
@ask.intent('GetEventsIntent', convert={ 'date': 'date', 'enddate': 'date' })
79+
def getDateEvents(date, enddate):
80+
# global f, fe
81+
82+
# f.write("Reading events!\n")
83+
# f.write(" date (from user): " + str(date) + " " + str(type(date)) + "\n")
84+
# f.write(" enddate (from user): " + str(enddate) + " " + str(type(enddate)) + "\n")
85+
86+
# in case that default "enddate" does not comply to "date",
87+
# the enddate is set to end of the day of "date"
88+
if date==None:
89+
date=datetime.now()
90+
91+
if enddate==None or date>=enddate:
92+
enddate = datetime(date.year, date.month, date.day+1)
93+
94+
# f.write(" date: " + str(date) + "\n")
95+
# f.write(" endDate: " + str(enddate) + "\n")
96+
97+
speech_text = getCalDavEvents(date, enddate)
98+
# f.write(" text: " + speech_text + "\n")
99+
# f.flush()
100+
101+
return statement(speech_text).simple_card('Kalendertermine', speech_text)
102+
103+
@ask.intent('SetEventIntent', convert={'date': 'date', 'time':'time', 'duration' : 'timedelta'})
104+
def setEvent(date, time, duration, eventtype, location):
105+
# global f, fe
106+
107+
# f.write("Creating net event!\n");
108+
# f.write(" date: " + date + "\n")
109+
# f.write(" time: " + time + "\n")
110+
# f.write(" duration: " + duration + "\n")
111+
speech_text = "Termin konnte nicht eingetragen werden!"
112+
113+
try:
114+
if date==None:
115+
date = datetime.today()
116+
117+
if duration==None:
118+
duration = timedelta(hours=1)
119+
120+
d = datetime.combine(date,time)
121+
122+
creationDate = datetime.now().strftime("%Y%m%dT%H%M%SZ")
123+
startDate = d.strftime("%Y%m%dT%H%M%SZ")
124+
endDate = (d + duration).strftime("%Y%m%dT%H%M%SZ")
125+
126+
vcal = "BEGIN:VCALENDAR"+"\n"
127+
vcal += "VERSION:2.0"+"\n"
128+
vcal += "PRODID:-//Example Corp.//CalDAV Client//EN"+"\n"
129+
vcal += "BEGIN:VEVENT"+"\n"
130+
vcal += "UID:1234567890"+"\n"
131+
vcal += "DTSTAMP:" + creationDate +"\n"
132+
vcal += "DTSTART:" + startDate +"\n"
133+
vcal += "DTEND:" + endDate +"\n"
134+
vcal += "SUMMARY:" + eventtype + "\n"
135+
vcal += "END:VEVENT"+"\n"
136+
vcal += "END:VCALENDAR"
137+
138+
# f.write(" entry: " + vcal + "\n")
139+
140+
calendars = connectCalendar()
141+
142+
if len(calendars) > 0:
143+
calendar = calendars[0]
144+
event = calendar.add_event(vcal)
145+
speech_text = "Termin wurde eingetragen!"
146+
147+
except TypeError as te:
148+
# fe.write(" error: " + te + "\n")
149+
# fe.flush()
150+
pass
151+
152+
# f.write(" text: " + speech_text + "\n")
153+
# f.flush()
154+
155+
return statement(speech_text).simple_card('Kalendertermine', speech_text)
156+
157+
#print getTodayEvents()
158+
if __name__ == '__main__':
159+
app.run(host="0.0.0.0", port=config["calexaPort"])

conf/config.json

-5
This file was deleted.

config.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"calexaPort": 5000,
3+
"url" : "https://yourcaldavurl",
4+
"username" : "yourusername",
5+
"password" : "yourpassword"
6+
}

interactionModel.json

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
{
2+
"interactionModel": {
3+
"languageModel": {
4+
"invocationName": "calexa",
5+
"intents": [
6+
{
7+
"name": "GetEventsIntent",
8+
"slots": [
9+
{
10+
"name": "date",
11+
"type": "AMAZON.DATE"
12+
},
13+
{
14+
"name": "endDate",
15+
"type": "AMAZON.DATE"
16+
},
17+
{
18+
"name": "eventSynonyms",
19+
"type": "LIST_OF_EVENT_SYNONYMS"
20+
},
21+
{
22+
"name": "calendarSynonyms",
23+
"type": "LIST_OF_CALENDAR_SYNONYMS"
24+
}
25+
],
26+
"samples": [
27+
"GetEventsIntent Welche Termine habe ich zwischen dem {date} und dem {endDate}",
28+
"GetEventsIntent Was habe ich für {eventSynonyms}",
29+
"GetEventsIntent Was habe ich am {date} für {eventSynonyms} im {calendarSynonyms}",
30+
"GetEventsIntent Was habe ich am {date} für {eventSynonyms}",
31+
"GetEventsIntent Was habe ich für {eventSynonyms} {date}",
32+
"GetEventsIntent Was habe ich {date} für {eventSynonyms}",
33+
"GetEventsIntent Gib mir die Ereignisse für {date}",
34+
"GetEventsIntent Gib mir die {eventSynonyms} für {date}",
35+
"GetEventsIntent Gib mir die {eventSynonyms} {date}",
36+
"GetEventsIntent Was ist {date} auf dem {calendarSynonyms}",
37+
"GetEventsIntent Was ist auf dem {calendarSynonyms}",
38+
"GetEventsIntent Welche {eventSynonyms} stehen im {calendarSynonyms} zwischen dem {date} und dem {endDate} ",
39+
"GetEventsIntent Welche {eventSynonyms} stehen im {calendarSynonyms} zwischen dem {date} und dem {endDate} an",
40+
"GetEventsIntent Welche {eventSynonyms} stehen zwischen dem {date} und dem {endDate} an",
41+
"GetEventsIntent Welche {eventSynonyms} stehen {date} an",
42+
"GetEventsIntent Welche {eventSynonyms} ich vom {date} bis zum {endDate} auf dem {calendarSynonyms} habe",
43+
"GetEventsIntent Welche {eventSynonyms} ich habe ",
44+
"GetEventsIntent Welche {eventSynonyms} ich am {date} habe ",
45+
"GetEventsIntent Welche {eventSynonyms} ich am {date} auf dem {calendarSynonyms} habe ",
46+
"GetEventsIntent nach meinen {eventSynonyms} vom {date} zum {endDate}",
47+
"GetEventsIntent nach meinen {eventSynonyms} vom {date} bis zum {endDate} ",
48+
"GetEventsIntent nach meinen {eventSynonyms} zwischen {date} und {endDate}",
49+
"GetEventsIntent nach meinen {eventSynonyms}",
50+
"GetEventsIntent Welche {eventSynonyms} habe ich {date}",
51+
"GetEventsIntent Welche {eventSynonyms} habe ich am {date} ",
52+
"GetEventsIntent Welche {eventSynonyms} habe ich am {date} auf dem {calendarSynonyms}"
53+
]
54+
},
55+
{
56+
"name": "SetEventIntent",
57+
"slots": [
58+
{
59+
"name": "date",
60+
"type": "AMAZON.DATE"
61+
},
62+
{
63+
"name": "time",
64+
"type": "AMAZON.TIME"
65+
},
66+
{
67+
"name": "duration",
68+
"type": "AMAZON.DURATION"
69+
},
70+
{
71+
"name": "endDate",
72+
"type": "AMAZON.DATE"
73+
},
74+
{
75+
"name": "eventSynonyms",
76+
"type": "LIST_OF_EVENT_SYNONYMS"
77+
},
78+
{
79+
"name": "calendarSynonyms",
80+
"type": "LIST_OF_CALENDAR_SYNONYMS"
81+
},
82+
{
83+
"name": "eventtype",
84+
"type": "LIST_OF_EVENT_TYPES"
85+
}
86+
],
87+
"samples": [
88+
"SetEventIntent einen {eventtype} um {time} einzutragen",
89+
"SetEventIntent ein {eventtype} für den {date} um {time} anzusetzen",
90+
"SetEventIntent ein {eventtype} für den {date} um {time} über {duration} einzurichten",
91+
"SetEventIntent einen {eventtype} für den {date} um {time} von {duration} anzusetzen",
92+
"SetEventIntent ein {eventtype} für den {date} um {time} für {duration} einzurichten",
93+
"SetEventIntent einen neuen Termin {eventtype} für den {date} um {time} für {duration} hinzuzufügen",
94+
"SetEventIntent einen neuen Termin {eventtype} für den {date} um {time} für {duration} anzulegen"
95+
]
96+
},
97+
{
98+
"name": "AMAZON.HelpIntent",
99+
"samples": []
100+
},
101+
{
102+
"name": "AMAZON.StopIntent",
103+
"samples": []
104+
},
105+
{
106+
"name": "AMAZON.CancelIntent",
107+
"samples": []
108+
}
109+
],
110+
"types": [
111+
{
112+
"name": "LIST_OF_EVENT_SYNONYMS",
113+
"values": [
114+
{
115+
"name": {
116+
"value": "Termin",
117+
"synonyms": [
118+
"Abmachung",
119+
"Verabredung",
120+
"Event",
121+
"Ereignis"
122+
]
123+
}
124+
}
125+
]
126+
},
127+
{
128+
"name": "LIST_OF_CALENDAR_SYNONYMS",
129+
"values": [
130+
{
131+
"name": {
132+
"value": "Kalender",
133+
"synonyms": [
134+
"Agenda",
135+
"Terminplaner"
136+
]
137+
}
138+
}
139+
]
140+
},
141+
{
142+
"name": "LIST_OF_EVENT_TYPES",
143+
"values": [
144+
{
145+
"name": {
146+
"value": "Besprechung",
147+
"synonyms": [
148+
"Party",
149+
"Festival",
150+
"Veranstaltung",
151+
"Vortrag",
152+
"Treffen",
153+
"Meeting",
154+
"Lehre",
155+
"Vorlesung",
156+
"Arzttermin"
157+
]
158+
}
159+
}
160+
]
161+
}
162+
]
163+
}
164+
}
165+
}

speech_assets/IntentSchema_DE.json

-33
This file was deleted.

0 commit comments

Comments
 (0)