"""Pipeline: anade UN evento al calendario CalDAV de Enmanuel en una llamada. Compone funciones del registry: - build_vevent (core): compone el dict de evento -> texto VCALENDAR. - extract_or_make_uid (infra): resuelve/sintetiza el UID si no se da. - pass_get_secret (infra): resuelve la contrasena DAV desde `pass`. - caldav_put_event (infra): hace el HTTP PUT a la coleccion CalDAV. Impuro (red + lectura de secreto via subproceso). La contrasena NUNCA se logea ni aparece en el resultado. Solo stdlib + funciones del registry. """ import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from core.build_vevent import build_vevent from infra.extract_or_make_uid import extract_or_make_uid from infra.pass_get_secret import pass_get_secret from infra.caldav_put_event import caldav_put_event DEFAULT_BASE_URL = "https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com" DEFAULT_USERNAME = "enmanuel" DEFAULT_COLLECTION = "/enmanuel/calendars/calendar/" def add_event_dav( summary: str, start: str, end: str = "", *, location: str = "", description: str = "", all_day: bool = False, rrule: str = "", alarm_minutes: int = 0, uid: str = "", base_url: str = DEFAULT_BASE_URL, username: str = DEFAULT_USERNAME, collection_path: str = DEFAULT_COLLECTION, secret_path: str = "dav/xandikos-enmanuel", timeout_s: float = 20.0, verify_tls: bool = True, ) -> dict: """Anade un evento al calendario CalDAV de Enmanuel en un solo paso. Args: summary: titulo del evento (-> SUMMARY). Obligatorio. start: fecha/hora de inicio (p.ej. '2026-06-20T17:00'). Obligatorio. end: fecha/hora de fin. Si vacio y no es all_day, build_vevent deriva +1h. location: lugar del evento (-> LOCATION). description: descripcion (-> DESCRIPTION). all_day: si True, evento de dia completo (DTSTART;VALUE=DATE). rrule: regla de recurrencia (p.ej. 'FREQ=WEEKLY;BYDAY=MO'). alarm_minutes: si > 0, anade un recordatorio N minutos antes (VALARM). uid: UID explicito del evento. Si vacio, se sintetiza determinista a partir del VCALENDAR generado (idempotente: re-subir sobrescribe). base_url: URL base del servidor DAV. Default = Xandikos de Enmanuel. username: usuario para HTTP Basic auth. Default 'enmanuel'. collection_path: ruta de la coleccion CalDAV destino. secret_path: ruta del secreto en `pass` con la contrasena DAV. timeout_s: timeout del PUT en segundos. verify_tls: si True (default) verifica el certificado TLS. Returns: dict. En exito: {status: 'ok', http_status: int, uid: str, url: str}. En error (sin lanzar): {status: 'error', error: str, uid: str|None, http_status: int|None}. La contrasena NUNCA aparece en el resultado. """ event = { "summary": summary, "start": start, "end": end or None, "location": location or None, "description": description or None, "all_day": all_day, "rrule": rrule or None, "alarm_minutes": alarm_minutes or None, "uid": uid or None, } try: vcalendar = build_vevent(event) except ValueError as e: return {"status": "error", "error": str(e), "uid": None, "http_status": None} # UID definitivo: el explicito si vino, o el (sintetico) del VCALENDAR. final_uid = uid.strip() if uid else extract_or_make_uid(vcalendar, prefix="evt-") secret = pass_get_secret(secret_path) if secret.get("status") != "ok": return { "status": "error", "error": "pass: %s" % secret.get("error", "unknown"), "uid": final_uid, "http_status": None, } password = secret["value"] res = caldav_put_event( base_url, username, password, collection_path, final_uid, vcalendar, timeout_s=timeout_s, verify_tls=verify_tls, ) # Reusa el dict de caldav_put_event y le anade el uid usado. out = dict(res) out["uid"] = final_uid return out if __name__ == "__main__": import argparse import json parser = argparse.ArgumentParser( description="Anade un evento al calendario CalDAV de Enmanuel." ) parser.add_argument("--summary", required=True, help="Titulo del evento.") parser.add_argument( "--start", required=True, help="Inicio, p.ej. 2026-06-20T17:00." ) parser.add_argument("--end", default="", help="Fin, p.ej. 2026-06-20T18:00.") parser.add_argument("--location", default="", help="Lugar.") parser.add_argument("--description", default="", help="Descripcion.") parser.add_argument( "--all-day", action="store_true", help="Evento de dia completo." ) parser.add_argument( "--rrule", default="", help="Recurrencia, p.ej. FREQ=WEEKLY;BYDAY=MO." ) parser.add_argument( "--alarm-minutes", type=int, default=0, help="Recordatorio N min antes." ) parser.add_argument("--uid", default="", help="UID explicito (opcional).") parser.add_argument("--base-url", default=DEFAULT_BASE_URL) parser.add_argument("--username", default=DEFAULT_USERNAME) parser.add_argument("--collection-path", default=DEFAULT_COLLECTION) parser.add_argument("--secret-path", default="dav/xandikos-enmanuel") parser.add_argument("--timeout-s", type=float, default=20.0) parser.add_argument( "--no-verify-tls", action="store_true", help="Desactiva verificacion TLS." ) args = parser.parse_args() result = add_event_dav( args.summary, args.start, args.end, location=args.location, description=args.description, all_day=args.all_day, rrule=args.rrule, alarm_minutes=args.alarm_minutes, uid=args.uid, base_url=args.base_url, username=args.username, collection_path=args.collection_path, secret_path=args.secret_path, timeout_s=args.timeout_s, verify_tls=not args.no_verify_tls, ) print(json.dumps(result))