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

169 lines
3.9 KiB
Markdown

---
description: Bluesky/ATProto patterns. Authentication, posting, replies, notifications.
limit: 20000
---
# Bluesky Patterns
*From asa-degroff/umbra — minimal patterns for ani-aster.bsky.social*
---
## Authentication
```python
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)
```python
response = client.send_post(text="Hello Bluesky!")
post_uri = response.uri # at://did:plc:.../app.bsky.feed.post/...
```
## Create Post (With Mentions)
```python
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
```python
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
```
---
## Read and Search
### Notifications
```python
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
```python
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
```python
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
```
### Search
```python
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.*