Metadata-Version: 2.4
Name: truewire-bluesky
Version: 0.1.0
Summary: Typed, validated Python client for Bluesky's public API and the Jetstream firehose, generated by Truewire.
License-Expression: MIT
Project-URL: Homepage, https://github.com/truewire-dev/bluesky
Project-URL: Truewire, https://truewire.dev
Keywords: bluesky,atproto,at-protocol,jetstream,api-client,truewire
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: truewire-core<0.3,>=0.2.1
Provides-Extra: dev
Requires-Dist: truewire==0.9.1; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: pyright>=1.1.390; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"

# truewire-bluesky

A typed, validated, async Python client for [Bluesky](https://bsky.app)'s AT Protocol API:
profiles, posts, threads, feeds, the follow graph, handle resolution, the Jetstream
firehose, and writing posts.

Generated by [Truewire](https://truewire.dev) from a spec of what the API puts on the wire,
and checked against recordings of real responses — so the types are what the API sent, not
what someone remembered.

```sh
pip install truewire-bluesky
```

**Note:** PyPI has an unrelated `bluesky` package (NSLS-II's experiment orchestration
framework) that also imports as `bluesky`. The two cannot share an environment.

## Reading needs no account

```python
import asyncio

from bluesky import Bluesky


async def main() -> None:
  async with Bluesky.new() as client:
    profile = await client.actor.get_profile('bsky.app')
    print(profile['handle'], profile.get('followersCount'), 'followers')

    feed = await client.feed.get_author_feed(actor='bsky.app', limit=3)
    for entry in feed['feed']:
      print(entry['post']['record']['text'][:60])


asyncio.run(main())
```

## The firehose

Jetstream is a public WebSocket of everything happening on the network, and needs no
credentials either:

```python
async with Bluesky.new() as client:
  async with client.jetstream.events(wanted_collections=['app.bsky.feed.post']) as stream:
    async for event in stream:
      print(event['kind'], event['did'])
```

## Writing

Needs an app password (bsky.app → Settings → App Passwords), never the account password.
The client creates the session and refreshes it for you.

```python
from datetime import datetime, timezone

from bluesky import Bluesky
from bluesky.core.richtext import post as post_record

async with Bluesky.new(identifier='you.bsky.social', app_password='xxxx-xxxx-xxxx-xxxx') as client:
  session = await client.server.create_session()
  await client.repo.create_record(
    repo=session['did'],
    collection='app.bsky.feed.post',
    record=post_record(
      'Posted from a generated client — truewire.dev',
      created_at=datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
      langs=['en'],
    ),
  )
```

`post_record` computes the link facets Bluesky needs to make a URL clickable, with the
UTF-8 byte offsets it insists on.

## Everything else

Twelve read endpoints, four write endpoints, cursor pagination on the ones that page, and
TypeScript and Rust clients from the same spec.

Full documentation, the spec, and the recordings:
**https://github.com/truewire-dev/bluesky**

MIT.
