{ pkgs, ... }@all: with all; let username = "lennart"; in { sops.secrets = { "nx2site/nextcloud/lennart_pass" = { }; }; home.packages = [ (pkgs.writers.writePython3Bin "caldav_event" { libraries = with pkgs.python3Packages; [ caldav ics pytz ]; flakeIgnore = [ "E302" "E305" "E501" "E261" ]; } /* python */ '' import json from caldav import DAVClient from datetime import datetime, timezone, timedelta from ics import Calendar from pytz import UTC def get_password(password_file): with open(password_file, "r") as file: return file.read().strip() def datetime_converter(obj): if isinstance(obj, datetime): return obj.isoformat() return obj def datetime_parser(dct): for key, value in dct.items(): if isinstance(value, str): try: dct[key] = datetime.fromisoformat(value) except ValueError: pass return dct def load_cache(cache_file): try: with open(cache_file, "r") as file: return json.load(file, object_hook=datetime_parser) except (json.JSONDecodeError, FileNotFoundError): return None def save_cache(cache_file, data): with open(cache_file, "w") as file: data['last_checked'] = datetime.now(timezone.utc) json.dump(data, file, default=datetime_converter, indent=4) def get_ongoing_or_next_event(url, username, password): now = datetime.now(timezone.utc) try: client = DAVClient(url, username=username, password=password) principal = client.principal() calendars = principal.calendars() next_event_dict = { 'event_name': "fake", 'event_begin': datetime(9000, 1, 1, tzinfo=UTC), # in the year 9000 'event_end': datetime(9000, 1, 1, 8, tzinfo=UTC), } for calendar in calendars: for event in calendar.search(start=now): if "VEVENT" not in event.data: continue calendar_parsed = Calendar(event.data) for ics_event in calendar_parsed.events: event_dict = {} event_dict['event_name'] = ics_event.name or "(No Title)" event_dict['event_begin'] = ics_event.begin.astimezone(timezone.utc) event_dict['event_end'] = ics_event.end.astimezone(timezone.utc) if event_dict['event_begin'] <= now and now <= event_dict['event_end']: return event_dict elif event_dict['event_begin'] >= now and next_event_dict['event_begin'] > event_dict['event_begin']: next_event_dict = event_dict return next_event_dict except Exception as e: print(f"Error accessing {url}: {str(e)}".splitlines()[0]) return None def is_expired(event_dict: dict): now = datetime.now(timezone.utc).timestamp() event_end = event_dict['event_end'].timestamp() return not (now <= event_end) def is_too_old(event_dict: dict) -> bool: last_checked = event_dict['last_checked'] now = datetime.now(timezone.utc) return now - last_checked >= timedelta(minutes=10) if __name__ == "__main__": password_file = "${config.sops.secrets."nx2site/nextcloud/lennart_pass".path}" # Path to password file cache_file = "/tmp/caldav_event_cache.json" # Path to cache file url = "https://n.${hyper.domain}/remote.php/dav/calendars/${username}/" username = "${username}" password = get_password(password_file) now = datetime.now(timezone.utc).timestamp() event_dict = load_cache(cache_file) if (event_dict is None) or is_expired(event_dict) or is_too_old(event_dict): event_dict = get_ongoing_or_next_event(url, username, password) save_cache(cache_file, event_dict) if event_dict is None: # none were found print("* zen *") exit(0) event_start = event_dict['event_begin'].timestamp() event_end = event_dict['event_end'].timestamp() if event_start <= now <= event_end: # is currently ongoing action_string = "ends" t = event_end - now # time_remaining else: # is in the future action_string = "starts" t = event_start - now # time_remaining hours, rem = divmod(int(t), 3600) minutes, _ = divmod(rem, 60) hour_string = f"{hours} hour{'s ' if hours != 1 else ' '}" if hours > 0 else "" minu_string = f"{minutes} minute{'s ' if minutes != 1 else ' '}" if minutes > 0 else "" if hour_string == "" and minu_string == "": time_string = "now" elif hour_string == "" or minu_string == "": time_string = "in " + hour_string + minu_string else: time_string = "in " + hour_string + "and " + minu_string print(f"\'{event_dict['event_name']}\' {action_string} {time_string}".splitlines()[0]) '') ]; }