diff --git a/.env.default b/.env.default new file mode 100644 index 0000000..ad6bad5 --- /dev/null +++ b/.env.default @@ -0,0 +1,4 @@ +CALDAV_USERNAME= +CALDAV_PASSWORD= +CALDAV_URL= +NEXT_DAYS=10 \ No newline at end of file diff --git a/.gitignore b/.gitignore index ab3e8ce..fd5641a 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.env \ No newline at end of file diff --git a/caldav-reminder.py b/caldav-reminder.py new file mode 100644 index 0000000..52297ec --- /dev/null +++ b/caldav-reminder.py @@ -0,0 +1,80 @@ +#!/usr/bin/python3 + +"""calendar reminder + +this logs into a caldav server and get a digest of all of the +events scheduled for next days. Good candidate to run from cron +""" + +import sys +import os +from dotenv import load_dotenv + +from datetime import timedelta, date +from os import environ +from urllib.parse import quote_plus +import caldav + +load_dotenv() + +# read parameters from dot env +USERNAME = os.getenv('CALDAV_USERNAME') +PASSWORD = os.getenv('CALDAV_PASSWORD') +BASE_URL = os.getenv('CALDAV_URL') +NEXT_DAYS = int(os.getenv('NEXT_DAYS')) + + +def fill_event(component, calendar) -> dict[str, str]: + # quite some data is tossed away here - like, the recurring rule. + cur = {} + cur["calendar"] = f"{calendar}" + cur["summary"] = component.get("summary") + cur["description"] = component.get("description") + # month/day/year time? Never ever do that! + # It's one of the most confusing date formats ever! + # Use year-month-day time instead ... https://xkcd.com/1179/ + cur["start"] = component.start.strftime("%m/%d/%Y %H:%M") + endDate = component.end + if endDate: + cur["end"] = endDate.strftime("%m/%d/%Y %H:%M") + return cur + + +def fill_todo(component, calendar) -> dict[str, str]: + cur = {} + cur["calendar"] = f"{calendar}" + cur["summary"] = component.get("summary") + cur["description"] = component.get("description") + return cur + + +def display_event(event: dict[str, str]): + print(event["start"] + " " + event["calendar"] + "/" + event["summary"]) + + +def display_todo(todo: dict[str, str]): + print("Todo : " + todo["summary"]) + + +url = 'https://' + quote_plus(USERNAME) + ":" + quote_plus(PASSWORD) \ + + '@' + BASE_URL + quote_plus(USERNAME) + '/' +start = date.today() +end = start + timedelta(days=NEXT_DAYS) +client = caldav.DAVClient(url) +calendars = client.principal().calendars() + +if not calendars: + print("No calendars defined for " + USERNAME) + sys.exit(0) + +for cal in calendars: + events = cal.search(event=True, start=start, end=end, expand=True) \ + + cal.search(todo=True) + for event in events: + for component in event.icalendar_instance.walk(): + if component.name == "VEVENT": + display_event(fill_event(component, cal)) + elif component.name == "VTODO": + display_todo(fill_todo(component, cal)) + +print(".") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6a9698a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "caldav-reminder" +version = "1.0" +description = "CalDav Reminder" +readme = "README.md" +authors = [ + { name = "Yax" } +] +requires-python = ">=3.13.1" +dependencies = [ + "caldav>=2.0.1", + "python-dotenv>=1.1.1", +] + +[dependency-groups] +dev = [ +] + +[tool.setuptools] + +[tool.setuptools.package-data] + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta"