# backend/app/connectors/knowledge/clipper.py — 웹 클리퍼 / Markdown 노트 임포트(로컬 우선) import hashlib from datetime import UTC, datetime from sqlmodel import Session from ..base import BaseConnector, NormalizedRecord, RawRecord from .normalize import normalize_knowledge, upsert_knowledge class ClipperConnector(BaseConnector): domain = "knowledge" entity_type = "knowledge_item" def fetch(self, session: Session, *, full: bool = False): for p in getattr(self, "_items", []): yield RawRecord( external_id=p["id"], payload=p, etag=p.get("title", ""), external_updated_at=datetime.now(UTC), ) def normalize(self, raw: RawRecord) -> NormalizedRecord: return normalize_knowledge(self.account, raw, provider="clipper") def write(self, session: Session, norm: NormalizedRecord): return upsert_knowledge(session, norm) def event_for(self, norm, entity_id): return "knowledge.ingested" def import_bytes(self, session: Session, content: bytes, filename: str = "") -> dict: text = content.decode("utf-8", "ignore") title = next((ln.lstrip("# ").strip() for ln in text.split("\n") if ln.strip()), filename) self._items = [ { "id": "clip-" + hashlib.sha1(text.encode()).hexdigest()[:12], "type": "article", "title": title or "스크랩", "src": filename or "웹 클리퍼", "excerpt": text[:280], } ] res = self.sync(session, full=True) return { "entity_type": "knowledge_item", "imported": res.upserted, "skipped": res.skipped, "errors": res.errors, "detail": f"{filename or '스크랩'} · 지식 {res.upserted}건 적재", }