contacts: support multiple address book collections
Migadu exposes "family" and "business" address books via CardDAV. The contacts script now searches all subdirs under contacts/ and adds new contacts to the "family" collection by default.
This commit is contained in:
@@ -39,7 +39,7 @@ $CONTACTS_DIR/scripts/contacts.sh list
|
||||
- [ ] `contact add` prints "Added contact: ..."
|
||||
- [ ] Second `contact add` prints "Updated contact: ... — added ..."
|
||||
- [ ] `contact list` shows all contacts with email types
|
||||
- [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/default/`
|
||||
- [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/family/`
|
||||
|
||||
## 2. Recipient Resolution (Send Validation)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ LLMs can hallucinate email addresses — inventing plausible-looking addresses f
|
||||
|
||||
- Python 3 (no external dependencies)
|
||||
- `vdirsyncer` configured with a CardDAV pair for contacts sync
|
||||
- Contacts directory: `~/.openclaw/workspace/contacts/default/`
|
||||
- Contacts directory: `~/.openclaw/workspace/contacts/` (subdirs per address book, e.g. `family/`, `business/`)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -64,4 +64,4 @@ Unknown addresses are **rejected** with the available contacts list shown.
|
||||
|
||||
## Data
|
||||
|
||||
Contacts are stored as vCard 3.0 `.vcf` files in `~/.openclaw/workspace/contacts/default/`, synced to Migadu CardDAV via vdirsyncer. Filenames are `{uid}.vcf`.
|
||||
Contacts are stored as vCard 3.0 `.vcf` files in `~/.openclaw/workspace/contacts/<collection>/`, synced to Migadu CardDAV via vdirsyncer. Collections match Migadu address books (e.g. `family/`, `business/`). New contacts are added to `family/` by default. The resolve command searches all collections.
|
||||
|
||||
@@ -22,7 +22,8 @@ from pathlib import Path
|
||||
# Config
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CONTACTS_DIR = Path.home() / ".openclaw" / "workspace" / "contacts" / "default"
|
||||
CONTACTS_ROOT = Path.home() / ".openclaw" / "workspace" / "contacts"
|
||||
DEFAULT_COLLECTION = "family" # default address book for new contacts
|
||||
PRODID = "-//OpenClaw//Contacts//EN"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -57,17 +58,20 @@ def _parse_vcf(path):
|
||||
|
||||
|
||||
def _load_contacts():
|
||||
"""Load all contacts from CONTACTS_DIR. Returns list of parsed contact dicts."""
|
||||
if not CONTACTS_DIR.is_dir():
|
||||
"""Load all contacts from all collections under CONTACTS_ROOT."""
|
||||
if not CONTACTS_ROOT.is_dir():
|
||||
return []
|
||||
contacts = []
|
||||
for vcf_path in sorted(CONTACTS_DIR.glob("*.vcf")):
|
||||
try:
|
||||
contact = _parse_vcf(vcf_path)
|
||||
if contact["fn"] or contact["emails"]:
|
||||
contacts.append(contact)
|
||||
except Exception:
|
||||
for subdir in sorted(CONTACTS_ROOT.iterdir()):
|
||||
if not subdir.is_dir():
|
||||
continue
|
||||
for vcf_path in sorted(subdir.glob("*.vcf")):
|
||||
try:
|
||||
contact = _parse_vcf(vcf_path)
|
||||
if contact["fn"] or contact["emails"]:
|
||||
contacts.append(contact)
|
||||
except Exception:
|
||||
continue
|
||||
return contacts
|
||||
|
||||
|
||||
@@ -225,7 +229,8 @@ def cmd_list(args):
|
||||
|
||||
def cmd_add(args):
|
||||
"""Add a new contact (creates or updates a .vcf file)."""
|
||||
CONTACTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
target_dir = CONTACTS_ROOT / DEFAULT_COLLECTION
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
emails = [{"address": args.email, "type": args.type or ""}]
|
||||
nickname = args.nickname or ""
|
||||
@@ -251,7 +256,7 @@ def cmd_add(args):
|
||||
# New contact
|
||||
uid = f"{uuid.uuid4()}@openclaw"
|
||||
vcf_str = _build_vcf(args.name, emails, nickname, uid)
|
||||
dest = CONTACTS_DIR / f"{uid}.vcf"
|
||||
dest = target_dir / f"{uid}.vcf"
|
||||
dest.write_text(vcf_str, encoding="utf-8")
|
||||
print(f"Added contact: {args.name} <{args.email}>")
|
||||
_sync_contacts()
|
||||
|
||||
Reference in New Issue
Block a user