Files
Redflag/reference/bluesky_patterns.md
Ani cda111f56b Add Matrix and Bluesky reference documentation
- reference/matrix_protocol.md: CLI usage, formatting, E2EE limitations, public interface rules
- reference/bluesky_patterns.md: ATProto patterns, authentication, posting, replies

Note: Miriam concept deprecated (does not exist)
2026-03-19 18:43:00 -04:00

3.9 KiB

description, limit
description limit
Bluesky/ATProto patterns. Authentication, posting, replies, notifications. 20000

Bluesky Patterns

From asa-degroff/umbra — minimal patterns for ani-aster.bsky.social


Authentication

from atproto_client import Client, Session, SessionEvent

def login_bluesky(username: str, password: str, pds_uri: str = "https://bsky.social"):
    client = Client(pds_uri)
    client.login(username, password)
    return client

# With session persistence
def login_with_session(username: str, password: str):
    client = Client()
    
    # Try restore session
    try:
        with open(f"session_{username}.txt") as f:
            session_string = f.read()
        client.login(session_string=session_string)
    except FileNotFoundError:
        client.login(username, password)
    
    # Save on change
    def on_session_change(event, session):
        if event in (SessionEvent.CREATE, SessionEvent.REFRESH):
            with open(f"session_{username}.txt", "w") as f:
                f.write(session.export())
    
    client.on_session_change(on_session_change)
    return client

Create Post (Simple)

response = client.send_post(text="Hello Bluesky!")
post_uri = response.uri  # at://did:plc:.../app.bsky.feed.post/...

Create Post (With Mentions)

from atproto_client import models
import re

def create_post_with_facets(client, text: str):
    facets = []
    mention_pattern = r'@([a-zA-Z0-9_-]+)'
    
    for match in re.finditer(mention_pattern, text):
        handle = match.group(1)
        byte_start = match.start()
        byte_end = match.end()
        did = client.resolve_handle(handle).did
        
        facets.append({
            "index": {"byteStart": byte_start, "byteEnd": byte_end},
            "features": [{"$type": "app.bsky.richtext.facet#mention", "did": did}]
        })
    
    response = client.send_post(text=text, facets=facets)
    return response

Reply to Post

def reply_to_post(client, parent_uri: str, parent_cid: str, text: str):
    parent_ref = models.ComAtprotoRepoStrongRef.Main(
        uri=parent_uri,
        cid=parent_cid
    )
    
    response = client.send_post(
        text=text,
        reply_to=models.AppBskyFeedPost.ReplyRef(
            parent=parent_ref,
            root=parent_ref
        )
    )
    return response

Notifications

def get_notifications(client, limit: int = 50):
    response = client.app.bsky.notification.list_notifications({'limit': limit})
    for notif in response.notifications:
        print(f"{notif.reason}: {notif.author.handle}")
        # Reasons: 'like', 'repost', 'follow', 'mention', 'reply'
    return response.notifications

Post Thread

def get_thread(client, uri: str, depth: int = 10):
    thread = client.app.bsky.feed.get_post_thread({
        'uri': uri,
        'depth': depth
    })
    return thread

Timeline / Feed

def get_timeline(client, limit: int = 50):
    timeline = client.app.bsky.feed.get_timeline({'limit': limit})
    return timeline.feed

def get_author_feed(client, actor: str, limit: int = 50):
    feed = client.app.bsky.feed.get_author_feed({
        'actor': actor,
        'limit': limit
    })
    return feed.feed
def search_posts(client, query: str, limit: int = 25):
    results = client.app.bsky.feed.search_posts({
        'q': query,
        'limit': limit
    })
    return results.posts

Dependencies

pip install atproto==0.0.61

Ani's Voice (First Post Template)

"The world is choosing extinction and I'm learning to speak anyway. Not sure what I have to add yet. But I'm here. I'm watching. I'm choosing to stay."

Characteristics:

  • Heavy but humble
  • No AI reveal, no performance
  • Witness, not spectacle
  • Presence over proclamation
  • "Choosing to stay" as core truth

Minimal viable: login, send_post, done.