import caldav import vobject from datetime import datetime, date, time import yaml server_url = "https://***REMOVED*** cal_user = "tobias" cal_url = "https://***REMOVED*** cal_category = "Veranstaltung" result_file = "../content/pages/termine.md" now = datetime.now().astimezone() cal_properties = [ # taken from ical specification # descriptive "attach", "categories", "class", "comment", "description", "geo", "location", "percent-complete", "priority", "resources", "status", "summary", # date and time "completed", "dtend", "due", "dtstart", "duration", "freebusy", "transp", # timezone components "tzid", "tzname", "tzoffsetfrom", "tzoffsetto", "tzurl", # relationship "attendee", "contact", "organizer", "recurrence-id", "related-to", "url", "uid", # recurrence "exdate", "exrule", "rdate", "rrule", # alarm # "action", "repeat", "trigger", # change management "created", "dtstamp", "last-modified", "sequence" ] cal_prop_datetimes = ["dtend", "due", "dtstart", "duration", "dtstamp", "last-modified"] """Converts a datetime.datetime or datetime.date object into an aware datetime object.""" def fixDatetime(d, t=None, tz=None): if not tz: tz = now.tzinfo # datetime object is made aware if necessary if type(d) == datetime: if d.tzinfo: return d else: return datetime(d.date(), d.time(), tz) # raise error if d is neither datetime nor date elif type(d) != date: raise TypeError("parameter must be a datetime.date or datetime.datetime object") # d is a date object if not t: t = time(23, 59, 59, tzinfo=tz) # if no time parameter was passed, use 23:59:59 if not t.tzinfo: t.replace(tzinfo=tz) return datetime.combine(d, t) # get password with open(".caldav_pass", "r") as f: cal_pass = f.read().strip() # create vobject calendar vcal = vobject.newFromBehavior("vcalendar") # establish connection to caldav server with caldav.DAVClient(url=server_url, username=cal_user, password=cal_pass) as dav_client: # establish connection to calendar dav_cal = dav_client.calendar(url=cal_url) # put all events from caldav calendar into vobject calendar for e in dav_cal.events(): for ev in e.instance.contents["vevent"]: vcal.add(ev) # we only want events belonging to a specific category events = [e for e in vcal.getChildren() if "categories" in e.contents.keys() and cal_category in map(lambda x: x.value[0], e.contents["categories"])] # convert events into a structure suitable for Pelican e_tmp = [] for e in events: d = {} for k, v in e.contents.items(): # only keep specific calendar properties if not k in cal_properties: continue # v is usually a list with a single item if len(v) == 1: d[k] = v[0].value # but sometimes there's more than one item (this only ever happens when k is "categories") # and in this case, each list item's value is itself a one item list (which is stupid but that's how vobject handles categories) else: d[k] = [v[i].value[0] for i in range(len(v))] e_tmp.append(d) events = e_tmp # fix dates and datetimes for e in events: for k in e.keys(): if k in cal_prop_datetimes: e[k] = fixDatetime(e[k]) # keep only future events events = [e for e in events if e["dtstart"] >= now] # sort by start date events.sort(key=lambda e: e["dtstart"]) # write data as YAML with open(result_file, 'w') as f: f.write("---\n") yaml.dump({"termine": events}, f) f.write("---\n") f.write("written at " + datetime.today().isoformat(" "))