feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"""Pipeline: anade UN contacto a la libreta CardDAV de Enmanuel en una llamada.
|
||||
|
||||
Compone funciones del registry: genera un UID determinista cuando el caller no
|
||||
da uno (contact_import_key) para que re-anadir el mismo contacto sobrescriba en
|
||||
vez de duplicar, serializa el dict de contacto a VCARD 3.0 (build_vcard),
|
||||
resuelve la contrasena CardDAV desde `pass` (pass_get_secret) y sube el VCARD
|
||||
via HTTP PUT (carddav_put_vcard). Impuro (red + lectura de `pass`). Solo stdlib.
|
||||
|
||||
La contrasena resuelta NUNCA se logea ni se incluye en el dict de retorno.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from core.build_vcard import build_vcard
|
||||
from core.contact_import_key import contact_import_key
|
||||
from infra.carddav_put_vcard import carddav_put_vcard
|
||||
from infra.pass_get_secret import pass_get_secret
|
||||
|
||||
# Config destino embebida (libreta CardDAV de Enmanuel en Xandikos self-hosted).
|
||||
DEFAULT_BASE_URL = "https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com"
|
||||
DEFAULT_USERNAME = "enmanuel"
|
||||
DEFAULT_COLLECTION = "/enmanuel/contacts/addressbook/"
|
||||
|
||||
|
||||
def _as_list(value) -> list:
|
||||
"""Normaliza None / string suelto / lista a lista de strings.
|
||||
|
||||
None -> []; string suelto -> [string]; lista|tupla -> lista. Cualquier otro
|
||||
valor escalar se envuelve en una lista de un elemento.
|
||||
"""
|
||||
if value is None:
|
||||
return []
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
if isinstance(value, (list, tuple)):
|
||||
return list(value)
|
||||
return [value]
|
||||
|
||||
|
||||
def add_contact_dav(
|
||||
name: str,
|
||||
*,
|
||||
tels=None,
|
||||
emails=None,
|
||||
adrs=None,
|
||||
org: str = "",
|
||||
note: str = "",
|
||||
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 contacto a la libreta CardDAV en una sola llamada (one-shot).
|
||||
|
||||
Args:
|
||||
name: nombre completo del contacto (FN del vCard). Obligatorio.
|
||||
tels: telefono(s). Acepta lista, string suelto o None.
|
||||
emails: email(s). Acepta lista, string suelto o None.
|
||||
adrs: direccion(es). Acepta lista, string suelto o None.
|
||||
org: organizacion (ORG). Vacio = se omite.
|
||||
note: nota libre (NOTE). Vacio = se omite.
|
||||
uid: UID del vCard. Si se deja vacio se calcula con contact_import_key
|
||||
(telefono > email > nombre), de modo que re-anadir el mismo contacto
|
||||
sobrescribe el recurso en vez de duplicarlo (idempotencia).
|
||||
base_url: URL base del servidor DAV. Default = libreta de Enmanuel.
|
||||
username: usuario HTTP Basic. Default = enmanuel.
|
||||
collection_path: ruta de la coleccion CardDAV destino.
|
||||
secret_path: ruta del secreto en `pass` con la contrasena (primera linea).
|
||||
timeout_s: timeout del PUT en segundos. Default 20.0.
|
||||
verify_tls: si True (default) verifica el certificado TLS.
|
||||
|
||||
Returns:
|
||||
dict. En exito reusa el dict de carddav_put_vcard mas el uid usado:
|
||||
{status:'ok', http_status:int, url:str, uid:str}. En error (sin lanzar):
|
||||
{status:'error', error:str, uid:str, http_status:int|None}. Si la
|
||||
contrasena no se encuentra en `pass`, devuelve {status:'error',
|
||||
error:..., uid:...} sin tocar la red.
|
||||
"""
|
||||
tels_list = _as_list(tels)
|
||||
emails_list = _as_list(emails)
|
||||
adrs_list = _as_list(adrs)
|
||||
|
||||
used_uid = uid.strip() if uid else ""
|
||||
if not used_uid:
|
||||
used_uid = contact_import_key(name, phones=tels_list, emails=emails_list)
|
||||
|
||||
contact = {"uid": used_uid, "fn": name}
|
||||
if tels_list:
|
||||
contact["tels"] = tels_list
|
||||
if emails_list:
|
||||
contact["emails"] = emails_list
|
||||
if adrs_list:
|
||||
contact["adrs"] = adrs_list
|
||||
if org:
|
||||
contact["org"] = org
|
||||
if note:
|
||||
contact["note"] = note
|
||||
|
||||
vcard_text = build_vcard(contact)
|
||||
|
||||
secret = pass_get_secret(secret_path)
|
||||
if secret.get("status") != "ok":
|
||||
return {
|
||||
"status": "error",
|
||||
"error": "pass: %s" % secret.get("error", "secret not found"),
|
||||
"uid": used_uid,
|
||||
"http_status": None,
|
||||
}
|
||||
password = secret["value"]
|
||||
|
||||
result = carddav_put_vcard(
|
||||
base_url,
|
||||
username,
|
||||
password,
|
||||
collection_path,
|
||||
used_uid,
|
||||
vcard_text,
|
||||
timeout_s=timeout_s,
|
||||
verify_tls=verify_tls,
|
||||
)
|
||||
# Reusar el dict de carddav_put_vcard + asegurar el uid usado.
|
||||
result["uid"] = used_uid
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import json
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Anade UN contacto a la libreta CardDAV de Enmanuel."
|
||||
)
|
||||
parser.add_argument("--name", required=True, help="Nombre completo (FN).")
|
||||
parser.add_argument(
|
||||
"--tel", action="append", default=[], help="Telefono (repetible)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--email", action="append", default=[], help="Email (repetible)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--adr", action="append", default=[], help="Direccion (repetible)."
|
||||
)
|
||||
parser.add_argument("--org", default="", help="Organizacion (ORG).")
|
||||
parser.add_argument("--note", default="", help="Nota libre (NOTE).")
|
||||
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 la verificacion TLS (solo pruebas).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
out = add_contact_dav(
|
||||
args.name,
|
||||
tels=args.tel,
|
||||
emails=args.email,
|
||||
adrs=args.adr,
|
||||
org=args.org,
|
||||
note=args.note,
|
||||
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(out, ensure_ascii=False))
|
||||
Reference in New Issue
Block a user