BlueSky bookmarks ingestion toolkit: fetch, hydrate (article text, self-thread context, images), and merge into a JSON inventory.
Project description
bsky-saves
A toolkit for ingesting your own BlueSky bookmarks ("saves") into a portable JSON inventory, with optional hydration of linked article text, self-thread context, and CDN image downloads.
Why
The BlueSky web client lets you bookmark posts, but the saves are siloed inside the app. This tool pulls them out into a single JSON file you can read, archive, mirror, or build on top of.
It works for accounts hosted on bsky.social and on third-party AT
Protocol PDSes (e.g. eurosky.social), because the bookmark fetch goes
PDS-direct rather than through the AppView.
Install
pip install bsky-saves
Authenticate
Set two env vars from a BlueSky app password:
export BSKY_HANDLE=alice.bsky.social
export BSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
# Required only for accounts hosted on a third-party PDS:
export BSKY_PDS=https://eurosky.social
The default BSKY_PDS is https://bsky.social.
Use
# Pull all bookmarks → ./saves_inventory.json
bsky-saves fetch --inventory ./saves_inventory.json
# Hydrate every external-link bookmark with the linked article's text.
bsky-saves hydrate articles --inventory ./saves_inventory.json
# Hydrate every bookmark with same-author self-thread descendants.
bsky-saves hydrate threads --inventory ./saves_inventory.json
# Decode each save's post-creation timestamp from its rkey (offline).
bsky-saves enrich --inventory ./saves_inventory.json
# Download cdn.bsky.app images referenced by the inventory into ./images/
# (flat layout). Records url→path mappings as `local_images` on each entry.
# Use --uris FILE (newline-delimited at:// URIs) to limit to a subset.
bsky-saves hydrate images --inventory ./saves_inventory.json --out ./images
All commands are idempotent: running them again skips already-hydrated
entries and adds only what's new. Failures are recorded inline (e.g.
article_fetch_error) so subsequent runs don't pointlessly re-hit them.
Inventory schema
{
"fetched_at": "2026-04-30T14:00:00Z",
"saves": [
{
"uri": "at://did:plc:.../app.bsky.feed.post/abc123",
"saved_at": "2026-04-29T22:11:00Z",
"post_created_at": "2026-04-29T17:43:51Z", // decoded from rkey
"post_text": "...",
"embed": {
"type": "external",
"url": "https://example.org/article",
"title": "...",
"description": "..."
},
"author": { "handle": "...", "display_name": "...", "did": "..." },
"images": [
{ "kind": "image", "url": "https://cdn.bsky.app/...", "alt": "..." }
],
"quoted_post": { /* optional, when the save quote-posts another post */ },
// Added by `hydrate articles`:
"article_text": "...",
"article_published_at": "2025-09-13",
"article_fetched_at": "...",
// Added by `hydrate threads`:
"thread_replies": [
{ "uri": "...", "indexedAt": "...", "text": "...", "images": [...] }
],
"thread_schema_version": 3,
"thread_fetched_at": "...",
// Added by `hydrate images`:
"local_images": [
{ "url": "https://cdn.bsky.app/...", "path": "img-9f2c8e1b....jpg" }
]
}
]
}
What about OAuth?
bsky-saves only supports the app-password authentication path. The
OAuth + DPoP machinery for third-party PDSes lives in a separate package,
atproto-oauth-py, and exists primarily for AppView-targeted resource calls
that aren't reachable via PDS-direct auth. For BlueSky bookmarks the
PDS-direct path (which bsky-saves uses) works regardless of where your
account is hosted.
License
MIT. See LICENSE.
Provenance
Extracted from https://github.com/tenorune/tenorune.github.io's scripts/
directory, where it powered the Stories of 47 archive's BlueSky save
ingestion. The Jekyll site itself stays in that repo; this is the reusable
ingestion layer.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bsky_saves-0.2.3.tar.gz.
File metadata
- Download URL: bsky_saves-0.2.3.tar.gz
- Upload date:
- Size: 43.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e83a900fb22f6206f33861bbf4101aeac08e214c7b0ea9b57587017afea32f1
|
|
| MD5 |
76129ed105a8e284dfd199a1be78674a
|
|
| BLAKE2b-256 |
ae9f1b0c13ee09c3adf40457e4419a9682fe2f40ebfc0bb1ae60a9ef33eba653
|
Provenance
The following attestation bundles were made for bsky_saves-0.2.3.tar.gz:
Publisher:
release.yml on tenorune/bsky-saves
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bsky_saves-0.2.3.tar.gz -
Subject digest:
4e83a900fb22f6206f33861bbf4101aeac08e214c7b0ea9b57587017afea32f1 - Sigstore transparency entry: 1427342582
- Sigstore integration time:
-
Permalink:
tenorune/bsky-saves@00797151505eeb9c3e446df4beae173f7edab0a5 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/tenorune
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@00797151505eeb9c3e446df4beae173f7edab0a5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bsky_saves-0.2.3-py3-none-any.whl.
File metadata
- Download URL: bsky_saves-0.2.3-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39f3db8e1b551be6e008e5c3a0c0533a58afa0fdf77dd7786f528c116da14c01
|
|
| MD5 |
e4f8e569fda3e33e20a60eb975a34023
|
|
| BLAKE2b-256 |
98ca99488dfd8754346b77d5b53eda0e962addfd5d67c2f0fcc81fd78b42f1b3
|
Provenance
The following attestation bundles were made for bsky_saves-0.2.3-py3-none-any.whl:
Publisher:
release.yml on tenorune/bsky-saves
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bsky_saves-0.2.3-py3-none-any.whl -
Subject digest:
39f3db8e1b551be6e008e5c3a0c0533a58afa0fdf77dd7786f528c116da14c01 - Sigstore transparency entry: 1427342742
- Sigstore integration time:
-
Permalink:
tenorune/bsky-saves@00797151505eeb9c3e446df4beae173f7edab0a5 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/tenorune
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@00797151505eeb9c3e446df4beae173f7edab0a5 -
Trigger Event:
push
-
Statement type: