A decentralized music tracking and discovery platform built on AT Protocol ๐ต
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879---title: "Python"description: "Python SDK for Rocksky โ native bindings to the shared Rust core."icon: "python"---
`rocksky` is native bindings to the shared Rocksky Rust core (via UniFFI):AppView reads, AT Protocol PDS writes, a local dedup index, and the identityhashes โ the same engine behind every Rocksky SDK.
## Install
<CodeGroup>```bash uvuv add rocksky==0.6.0```
```bash pippip install rocksky==0.6.0```</CodeGroup>
The wheel is pure-Python; the native library is fetched from the GitHub releaseon first import and cached.
## Quickstart
```pythonfrom rocksky import AppView, Agent, ScrobbleInput, song_hash
# Reads โ unauthenticated. AppView() uses https://api.rocksky.app.av = AppView()print(av.global_stats().scrobbles)for t in av.top_tracks(10, 0): print(t.artist, "โ", t.title)
# Writes โ log in with an app password.agent = Agent.login_password("session.json", "alice.bsky.social", "app-password", None, None)out = agent.scrobble(ScrobbleInput( title="Chaser", artist="Calibro 35", album="Jazzploitation", album_artist="Calibro 35", duration_ms=182320,))print(out.scrobble_uri)```
## API
**Reads โ `AppView(base=None, token=None)`** (pass `token` to send`Authorization: Bearer` on auth-gated queries). Typed views:`profile`, `scrobbles`, `songs`, `albums`, `artists`, `loved_songs`,`catalog_albums` / `catalog_artists` / `catalog_songs`, `album_tracks`,`artist_albums` / `artist_tracks`, `scrobble_feed`, `scrobble` (single),`follows` / `followers` / `known_followers`, `top_tracks`, `top_artists`,`global_stats`. Date-window charts `top_tracks_interval(limit, offset, interval)`and `top_artists_interval(...)` take a `DateInterval` (`ALL_TIME`,`LAST_DAYS(days=7)`, `LAST_WEEKS`, `LAST_MONTHS`, `LAST_YEARS`,`RANGE(start=..., end=...)`). `match_song(title, artist, mb_id, isrc)` resolves abare title+artist to canonical metadata. A raw-JSON long tail (`album`, `artist`,`song`, `playlists`, `playlist`, `stats`, `wrapped`, `scrobbles_chart`,`recommendations`, `neighbours`, shouts, โฆ) returns JSON strings, and`av.get(nsid, params)` (a `dict[str, str]`) calls **any** read query by NSID.
**Writes โ `Agent`**: `login_password(...)`, then `scrobble` (full metadata) or`scrobble_match(ScrobbleMatchInput(title, artist, album=None, mb_id=None,isrc=None, timestamp=None))` (resolve via `match_song`, then fan out โ `title`/`artist` required, `timestamp` is scrobbled-at Unix seconds; `ScrobbleMatchInput`imports from `rocksky`), `create_song` / `create_album` / `create_artist`, `like` /`unlike`, `follow` / `unfollow`, `shout` / `reply_shout`, `set_now_playing` /`clear_now_playing`, `sync_repo` (dedup backfill, returns per-collection countsas JSON), `hydrate_from_jetstream` (live dedup off the firehose).
**Identity hashes**: `song_hash`, `album_hash`, `artist_hash`.
## Duplicate prevention
Pass a `dedup_path` to `Agent.login_password` to enable the local index, then`agent.sync_repo()` to backfill it from the repo. With it, write verbs skiprecords that already exist.