from abc import ABC, abstractmethod from typing import Any from atproto.store import AtprotoStore from atproto.xrpc import resolve_identity from cross.service import Service from util.util import normalize_service_url SERVICE = "https://bsky.app" def validate_and_transform(data: dict[str, Any]) -> None: if not data.get("handle") and not data.get("did"): raise KeyError("no 'handle' or 'did' specified for bluesky!") if "did" in data: did = str(data["did"]) # only did:web and did:plc are supported if not did.startswith("did:plc:") and not did.startswith("did:web:"): raise ValueError( f"Invalid DID format: {did}! Only did:plc: and did:web: are supported." ) if "pds" in data: data["pds"] = normalize_service_url(data["pds"]) class BlueskyService(ABC, Service): pds: str did: str _store: AtprotoStore def _init_identity(self) -> None: handle, did, pds = self.get_identity_options() if did: self.did = did if pds: self.pds = pds if not did: if not handle: raise KeyError("No did: or atproto handle provided!") self.log.info("Resolving ATP identity for '%s'...", handle) identity = resolve_identity(handle, self._store) self.did = identity.did if not pds: self.log.info("Resolving PDS for '%s'...", self.did) identity = resolve_identity(self.did, self._store) self.pds = identity.pds @abstractmethod def get_identity_options(self) -> tuple[str | None, str | None, str | None]: pass