from typing import Any from atproto import AtUri, Client, IdResolver, client_utils from atproto_client import models from util.util import LOGGER def resolve_identity( handle: str | None = None, did: str | None = None, pds: str | None = None ): """helper to try and resolve identity from provided parameters, a valid handle is enough""" if did and pds: return did, pds[:-1] if pds.endswith("/") else pds resolver = IdResolver() if not did: if not handle: raise Exception("ATP handle not specified!") LOGGER.info("Resolving ATP identity for %s...", handle) did = resolver.handle.resolve(handle) if not did: raise Exception("Failed to resolve DID!") if not pds: LOGGER.info("Resolving PDS from DID document...") did_doc = resolver.did.resolve(did) if not did_doc: raise Exception("Failed to resolve DID doc for '%s'", did) pds = did_doc.get_pds_endpoint() if not pds: raise Exception("Failed to resolve PDS!") return did, pds[:-1] if pds.endswith("/") else pds class Client2(Client): def __init__(self, base_url: str | None = None, *args: Any, **kwargs: Any) -> None: super().__init__(base_url, *args, **kwargs) def send_video( self, text: str | client_utils.TextBuilder, video: bytes, video_alt: str | None = None, video_aspect_ratio: models.AppBskyEmbedDefs.AspectRatio | None = None, reply_to: models.AppBskyFeedPost.ReplyRef | None = None, langs: list[str] | None = None, facets: list[models.AppBskyRichtextFacet.Main] | None = None, labels: models.ComAtprotoLabelDefs.SelfLabels | None = None, time_iso: str | None = None, ) -> models.AppBskyFeedPost.CreateRecordResponse: """same as send_video, but with labels""" if video_alt is None: video_alt = "" upload = self.upload_blob(video) return self.send_post( text, reply_to=reply_to, embed=models.AppBskyEmbedVideo.Main( video=upload.blob, alt=video_alt, aspect_ratio=video_aspect_ratio ), langs=langs, facets=facets, labels=labels, time_iso=time_iso, ) def send_images( self, text: str | client_utils.TextBuilder, images: list[bytes], image_alts: list[str] | None = None, image_aspect_ratios: list[models.AppBskyEmbedDefs.AspectRatio] | None = None, reply_to: models.AppBskyFeedPost.ReplyRef | None = None, langs: list[str] | None = None, facets: list[models.AppBskyRichtextFacet.Main] | None = None, labels: models.ComAtprotoLabelDefs.SelfLabels | None = None, time_iso: str | None = None, ) -> models.AppBskyFeedPost.CreateRecordResponse: """same as send_images, but with labels""" if image_alts is None: image_alts = [""] * len(images) else: diff = len(images) - len(image_alts) image_alts = image_alts + [""] * diff if image_aspect_ratios is None: aligned_image_aspect_ratios = [None] * len(images) else: diff = len(images) - len(image_aspect_ratios) aligned_image_aspect_ratios = image_aspect_ratios + [None] * diff uploads = [self.upload_blob(image) for image in images] embed_images = [ models.AppBskyEmbedImages.Image( alt=alt, image=upload.blob, aspect_ratio=aspect_ratio ) for alt, upload, aspect_ratio in zip( image_alts, uploads, aligned_image_aspect_ratios ) ] return self.send_post( text, reply_to=reply_to, embed=models.AppBskyEmbedImages.Main(images=embed_images), langs=langs, facets=facets, labels=labels, time_iso=time_iso, ) def send_post( self, text: str | client_utils.TextBuilder, reply_to: models.AppBskyFeedPost.ReplyRef | None = None, embed: None | models.AppBskyEmbedImages.Main | models.AppBskyEmbedExternal.Main | models.AppBskyEmbedRecord.Main | models.AppBskyEmbedRecordWithMedia.Main | models.AppBskyEmbedVideo.Main = None, langs: list[str] | None = None, facets: list[models.AppBskyRichtextFacet.Main] | None = None, labels: models.ComAtprotoLabelDefs.SelfLabels | None = None, time_iso: str | None = None, ) -> models.AppBskyFeedPost.CreateRecordResponse: """same as send_post, but with labels""" if isinstance(text, client_utils.TextBuilder): facets = text.build_facets() text = text.build_text() repo = self.me and self.me.did if not repo: raise Exception("Client not logged in!") if not langs: langs = ["en"] record = models.AppBskyFeedPost.Record( created_at=time_iso or self.get_current_time_iso(), text=text, reply=reply_to or None, embed=embed or None, langs=langs, facets=facets or None, labels=labels or None, ) return self.app.bsky.feed.post.create(repo, record) def create_gates( self, thread_gate_opts: list[str], quote_gate: bool, post_uri: str, time_iso: str | None = None, ): account = self.me if not account: raise Exception("Client not logged in!") rkey = AtUri.from_str(post_uri).rkey time_iso = time_iso or self.get_current_time_iso() if "everybody" not in thread_gate_opts: allow = [] if thread_gate_opts: if "following" in thread_gate_opts: allow.append(models.AppBskyFeedThreadgate.FollowingRule()) if "followers" in thread_gate_opts: allow.append(models.AppBskyFeedThreadgate.FollowerRule()) if "mentioned" in thread_gate_opts: allow.append(models.AppBskyFeedThreadgate.MentionRule()) thread_gate = models.AppBskyFeedThreadgate.Record( post=post_uri, created_at=time_iso, allow=allow ) self.app.bsky.feed.threadgate.create(account.did, thread_gate, rkey) if quote_gate: post_gate = models.AppBskyFeedPostgate.Record( post=post_uri, created_at=time_iso, embedding_rules=[models.AppBskyFeedPostgate.DisableRule()], ) self.app.bsky.feed.postgate.create(account.did, post_gate, rkey)