- 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)
169 lines
3.9 KiB
Markdown
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.*
|