init
This commit is contained in:
commit
c9d77c745e
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM python:3
|
||||
|
||||
WORKDIR /
|
||||
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["python3", "webserver.py"]
|
||||
|
||||
|
||||
ENV AM_I_IN_A_DOCKER_CONTAINER Yes
|
||||
|
||||
RUN date >/build-date.txt
|
189
calendar_fetcher.py
Executable file
189
calendar_fetcher.py
Executable file
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from pytz import UTC # timezone
|
||||
import caldav
|
||||
import caldav.elements.ical # for calendarColor
|
||||
import icalendar
|
||||
import requests
|
||||
|
||||
import config as cfg
|
||||
|
||||
|
||||
from collections import OrderedDict
|
||||
from operator import getitem
|
||||
|
||||
import pprint as pp
|
||||
|
||||
|
||||
|
||||
def fetch_calendar():
|
||||
numberOfDaysToShow = 5
|
||||
|
||||
excludedCalendars = ['todo', 'todogemeinsam', 'contact_birthdays', 'geburtstage', 'infos']
|
||||
#excludedCalendars = ['todo', 'todogemeinsam', 'contact_birthdays', 'geburtstage', 'gemeinsam', 'evtermine_shared_by_anita', 'jael']
|
||||
#excludedCalendars = ['gemeinsam', 'contact_birthdays', 'geburtstage']
|
||||
|
||||
client = caldav.DAVClient(url=cfg.url, username=cfg.userN, password=cfg.passW)
|
||||
principal = client.principal()
|
||||
calendars = principal.calendars()
|
||||
|
||||
firstDay = datetime.date.today()
|
||||
lastDay = firstDay + datetime.timedelta(days=numberOfDaysToShow)
|
||||
#print("Showing all events between %s and %s" % (firstDay, lastDay))
|
||||
|
||||
days = []
|
||||
|
||||
day = firstDay
|
||||
while day < lastDay:
|
||||
days.append(day)
|
||||
day += datetime.timedelta(days=1)
|
||||
|
||||
#print("Days to list: %s" % days)
|
||||
|
||||
# Get offset from local time to UTC, see also https://stackoverflow.com/questions/3168096/getting-computers-utc-offset-in-python
|
||||
# TODO: This should be done for every event individually!
|
||||
ts = time.time()
|
||||
utcOffset = (datetime.datetime.fromtimestamp(ts) -
|
||||
datetime.datetime.utcfromtimestamp(ts)).total_seconds()
|
||||
utcOffset = int(utcOffset / 3600) # in hours
|
||||
#print("UTC offset:", utcOffset)
|
||||
|
||||
#print("All calendars:")
|
||||
#for calendar in calendars:
|
||||
#print(" %s" % str(calendar).split("/")[-2])
|
||||
|
||||
calendarsFiltered = []
|
||||
|
||||
# exclude some calendars:
|
||||
for calendar in calendars:
|
||||
#print(excludedCalendars)
|
||||
#print(calendar)
|
||||
#if str(calendar).split("/")[-2] in excludedCalendars:
|
||||
if str(calendar) in excludedCalendars:
|
||||
#print("Ignoring %s" % str(calendar).split("/")[-2])
|
||||
continue
|
||||
else:
|
||||
#print("Adding %s" % str(calendar).split("/")[-2])
|
||||
calendarsFiltered.append(calendar)
|
||||
|
||||
|
||||
#print("Filtered calendars:")
|
||||
#for calendar in calendarsFiltered:
|
||||
#print(" %s" % str(calendar).split("/")[-2])
|
||||
|
||||
eventsList = {}
|
||||
metaData = {}
|
||||
|
||||
for day in days:
|
||||
eventsList[day] = {}
|
||||
|
||||
#print("processing calendars")
|
||||
for calendar in calendarsFiltered:
|
||||
#calendarName = str(calendar).split("/")[-2]
|
||||
calendarName = str(calendar)
|
||||
metaData[calendarName] = {}
|
||||
metaData[calendarName]['displayname'] = list(calendar.get_properties([caldav.dav.DisplayName()]).values())[0]
|
||||
metaData[calendarName]['color'] = list(calendar.get_properties([caldav.elements.ical.CalendarColor()]).values())[0]
|
||||
#print(metaData)
|
||||
|
||||
for day in days:
|
||||
#print(" Looking for events between %s and %s" % (day, day + datetime.timedelta(days=1)))
|
||||
results = calendar.date_search(day, day + datetime.timedelta(days=1)) # get event for selected day (24h)
|
||||
|
||||
for eventraw in results:
|
||||
eventUuid = str(eventraw).split("/")[-1].split(".")[0]
|
||||
#print(" - %s" % eventUuid)
|
||||
|
||||
#print(str(eventraw))
|
||||
#event = icalendar.Calendar.from_ical(eventraw._data)
|
||||
|
||||
url = "https://" + cfg.userN + ":" + cfg.passW + "@" + str(eventraw).replace("Event: https://", "")
|
||||
#print(url)
|
||||
data = requests.get(url).text
|
||||
|
||||
event = icalendar.Calendar.from_ical(data)
|
||||
for component in event.walk():
|
||||
if component.name == "VEVENT":
|
||||
eventsList[day][eventUuid] = {}
|
||||
##eventsList[day][eventUuid]["calendar"] = str(calendar).split("/")[-2]
|
||||
eventsList[day][eventUuid]["calendar"] = str(calendar)
|
||||
|
||||
#print(" summary: %s" % component.get('summary'))
|
||||
eventsList[day][eventUuid]["summary"] = str(component.get('summary'))
|
||||
|
||||
#print(" description: %s" % component.get('description'))
|
||||
#eventsList[day][eventUuid]["description"] = str(component.get('description'))
|
||||
|
||||
startDate = component.get('dtstart')
|
||||
#print(" dtstart: %s (%s)" % (startDate.dt.strftime('%m/%d/%Y %H:%M'), day))
|
||||
if startDate.dt.strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event starts today
|
||||
#print(" single day event")
|
||||
eventsList[day][eventUuid]["startDate"] = startDate.dt.strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["startTime"] = (startDate.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M')
|
||||
else: # event started before today, set startdate to day and start time to midnight (for multi day events with start/end time)
|
||||
#print(" multi day event")
|
||||
eventsList[day][eventUuid]["startDate"] = day.strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["startTime"] = "00:00"
|
||||
|
||||
|
||||
try:
|
||||
endDate = component.get('dtend')
|
||||
#print(" dtend: %s" % endDate.dt.strftime('%m/%d/%Y %H:%M'))
|
||||
if endDate.dt.strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event ends today
|
||||
eventsList[day][eventUuid]["endDate"] = endDate.dt.strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["endTime"] = (endDate.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M')
|
||||
else: # event ends after today, set enddate to day and end time to midnight (for multi day events with start/end time)
|
||||
eventsList[day][eventUuid]["endDate"] = day.strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["endTime"] = "24:00"
|
||||
|
||||
except AttributeError as ae: # event has no endtime, use duration
|
||||
#print(" %s" % ae)
|
||||
duration = component.get('duration')
|
||||
#print(" Event misses end time, using duration")
|
||||
#print(" duration: %s" % duration.dt)
|
||||
if (startDate.dt + duration.dt).strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event ends today
|
||||
eventsList[day][eventUuid]["endDate"] = (startDate.dt + duration.dt).strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["endTime"] = (startDate.dt + duration.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M')
|
||||
else: # event ends after today, set enddate to day and end time to midnight (for multi day events with start/end time)
|
||||
eventsList[day][eventUuid]["endDate"] = day.strftime('%m/%d/%Y')
|
||||
eventsList[day][eventUuid]["endTime"] = "24:00"
|
||||
|
||||
#print('')
|
||||
#break
|
||||
|
||||
|
||||
# order by time per day, see https://www.geeksforgeeks.org/python-sort-nested-dictionary-by-key/
|
||||
for day in days:
|
||||
sortedbyTimePerDay = OrderedDict(sorted(eventsList[day].items(), key = lambda x: getitem(x[1], 'startTime')))
|
||||
eventsList[day] = sortedbyTimePerDay
|
||||
|
||||
# Sort days
|
||||
eventsList = OrderedDict(sorted(eventsList.items()))
|
||||
|
||||
|
||||
# reformat day key (datetime => string)
|
||||
for day in days:
|
||||
eventsList[day.strftime('%m/%d/%Y')] = eventsList.pop(day)
|
||||
|
||||
|
||||
#pp.pprint(eventsList)
|
||||
|
||||
data = {}
|
||||
data['metadata'] = metaData
|
||||
data['events'] = OrderedDict(eventsList)
|
||||
|
||||
#pp.pprint(data)
|
||||
|
||||
|
||||
# Converting to JSON
|
||||
dataJson = json.dumps(data, indent=4)
|
||||
return dataJson
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(fetch_calendar())
|
6
config.py
Normal file
6
config.py
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Configuration for the CalDAV server
|
||||
url = "https://cloud.ruinelli.ch/remote.php/dav/principals/users/"
|
||||
userN = "gruinelli"
|
||||
passW = "attgigoc1"
|
19
readme.md
Normal file
19
readme.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Docker
|
||||
|
||||
## Build it manually
|
||||
`docker build -t calendar-fetcher .`
|
||||
|
||||
## Run it in a Docker Container
|
||||
1. Adjust the parameters in `Dockerfile`
|
||||
1. Build the docker image with `docker build -t calendar-fetcher .`
|
||||
1. Run it with `docker run -it --rm -v /volume1/web/calendar-fetcher:/data --name my-calendar-fetcher -p 8014:8014 --label=com.centurylinklabs.watchtower.enable=false calendar-fetcher`
|
||||
1. Call it in a webbrowser: `http://localhost:8014`
|
||||
|
||||
## replace existing Docker Container (Update)
|
||||
```
|
||||
docker build -t calendar-fetcher .
|
||||
docker stop calendar-fetcher
|
||||
docker rm calendar-fetcher
|
||||
docker run -d -P --name calendar-fetcher -p 8014:8014 -v /volume1/web/smartmirror/data:/data --label=com.centurylinklabs.watchtower.enable=false calendar-fetcher
|
||||
```
|
||||
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
pytz
|
||||
caldav
|
70
webserver.py
Normal file
70
webserver.py
Normal file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from calendar_fetcher import fetch_calendar
|
||||
|
||||
|
||||
internalPort = 8014
|
||||
outfile = "data/calendar.json"
|
||||
|
||||
|
||||
# This class will handle any incoming request from
|
||||
# a browser
|
||||
class myHandler(BaseHTTPRequestHandler):
|
||||
# Handler for the GET requests
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
|
||||
parsed_url = urlparse(self.path)
|
||||
query = parse_qs(parsed_url.query)
|
||||
|
||||
print("File: %s, query: %s" % (parsed_url.path, query), flush=True)
|
||||
|
||||
if parsed_url.path == "/":
|
||||
self.send_header('Content-type','text/html')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(b"<h1>Calendar Fetcher</h1>\n")
|
||||
self.wfile.write(b"fetching...<br>\n")
|
||||
print("fetching...")
|
||||
self.wfile.flush()
|
||||
|
||||
data = fetch_calendar()
|
||||
|
||||
self.wfile.write(b"storing...<br>\n")
|
||||
print("storing...")
|
||||
self.wfile.flush()
|
||||
|
||||
f = open(outfile, "w")
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
|
||||
self.wfile.write(b"Done.\nData got written to %s}<br>\n" % bytes(outfile, "utf8"))
|
||||
print("Done.\nData got written to %s}" % outfile)
|
||||
self.wfile.flush()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run the service
|
||||
|
||||
try:
|
||||
print("Starting...")
|
||||
server = HTTPServer(('', internalPort), myHandler)
|
||||
|
||||
print("Listening on port %d" % internalPort)
|
||||
print("Ready to receive requests")
|
||||
|
||||
# Wait forever for incoming http requests
|
||||
server.serve_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print ('^C received, shutting down the web server')
|
||||
server.socket.close()
|
Loading…
Reference in New Issue
Block a user