social media crossposting tool. 3rd time's the charm
mastodon misskey crossposting bluesky
at next 54 lines 1.7 kB view raw
1from abc import ABC, abstractmethod 2from typing import Any 3 4from atproto.store import AtprotoStore 5from atproto.xrpc import resolve_identity 6from cross.service import Service 7from util.util import normalize_service_url 8 9 10SERVICE = "https://bsky.app" 11 12 13def validate_and_transform(data: dict[str, Any]) -> None: 14 if not data.get("handle") and not data.get("did"): 15 raise KeyError("no 'handle' or 'did' specified for bluesky!") 16 17 if "did" in data: 18 did = str(data["did"]) # only did:web and did:plc are supported 19 if not did.startswith("did:plc:") and not did.startswith("did:web:"): 20 raise ValueError( 21 f"Invalid DID format: {did}! Only did:plc: and did:web: are supported." 22 ) 23 24 if "pds" in data: 25 data["pds"] = normalize_service_url(data["pds"]) 26 27 28class BlueskyService(ABC, Service): 29 pds: str 30 did: str 31 _store: AtprotoStore 32 33 def _init_identity(self) -> None: 34 handle, did, pds = self.get_identity_options() 35 if did: 36 self.did = did 37 if pds: 38 self.pds = pds 39 40 if not did: 41 if not handle: 42 raise KeyError("No did: or atproto handle provided!") 43 self.log.info("Resolving ATP identity for '%s'...", handle) 44 identity = resolve_identity(handle, self._store) 45 self.did = identity.did 46 47 if not pds: 48 self.log.info("Resolving PDS for '%s'...", self.did) 49 identity = resolve_identity(self.did, self._store) 50 self.pds = identity.pds 51 52 @abstractmethod 53 def get_identity_options(self) -> tuple[str | None, str | None, str | None]: 54 pass