secondbrain/connectors/webdav.py
Atlas eb5b67dee2 Add connectors + setup wizard (files / IMAP email / Nextcloud-WebDAV) + encrypted vault
Product layer on top of the pure engine: a wizard to connect the apps that feed
the brain, an encrypted credential vault (Fernet/PBKDF2; app logins never enter
brain.db), and 3 working connectors. Connector data flows through the same
credential-scrub path as the engine. Atlas Corporation proprietary.
2026-07-01 21:50:20 +02:00

49 lines
2.2 KiB
Python

# SecondBrain — (c) 2026 Atlas Corporation. All Rights Reserved. Proprietary; see LICENSE.
"""Connector: Nextcloud / any WebDAV — index text files from a cloud folder.
For Nextcloud the base URL looks like: https://cloud.example.com/remote.php/dav/files/USERNAME/
Use a Nextcloud *app password*, not your login password."""
import os
import requests
from xml.etree import ElementTree as ET
from .base import Connector, Field, register, feed
TEXT_EXT = (".md", ".txt", ".rst", ".py", ".sh", ".json", ".csv", ".tsv", ".yml", ".yaml",
".ini", ".cfg", ".conf", ".html", ".js", ".ts", ".sql", ".xml")
@register
class WebDavConnector(Connector):
id = "webdav"
name = "Nextcloud / WebDAV files"
blurb = "Index text files from a Nextcloud (or any WebDAV) folder."
fields = [
Field("base_url", "WebDAV base URL (…/remote.php/dav/files/USER/)"),
Field("user", "Username"),
Field("password", "App password", secret=True),
Field("folder", "Folder path under base", default="/"),
]
def _list(self, cfg):
url = cfg["base_url"].rstrip("/") + "/" + cfg.get("folder", "/").strip("/")
r = requests.request("PROPFIND", url, auth=(cfg["user"], cfg["password"]),
headers={"Depth": "infinity"}, timeout=30)
r.raise_for_status()
hrefs = [e.text for e in ET.fromstring(r.content).iter("{DAV:}href")]
return [h for h in hrefs if h and h.lower().endswith(TEXT_EXT)]
def test(self, cfg):
return f"{len(self._list(cfg))} text files visible"
def sync(self, cfg):
host = cfg["base_url"].split("/remote.php")[0]
items = []
for href in self._list(cfg):
u = href if href.startswith("http") else host + href
try:
rr = requests.get(u, auth=(cfg["user"], cfg["password"]), timeout=30)
if rr.status_code != 200 or len(rr.content) > 400000:
continue
items.append({"text": rr.text, "project": "nextcloud", "path": href,
"ref": os.path.basename(href.rstrip("/")), "role": "file"})
except Exception:
continue
return feed(items, "file")