Skip to main content

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

bsky_saves-0.2.2.tar.gz (42.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bsky_saves-0.2.2-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file bsky_saves-0.2.2.tar.gz.

File metadata

  • Download URL: bsky_saves-0.2.2.tar.gz
  • Upload date:
  • Size: 42.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bsky_saves-0.2.2.tar.gz
Algorithm Hash digest
SHA256 5484813347b6ce43d3eabf954ea2fe95ca139da0df6828bf458337ab1381d3f3
MD5 848aee2f39eeba625f87e3fa19ad77c9
BLAKE2b-256 5f45a0c4747119d0edd41b65109686f536d8501cd1650935f7403c0eabde8a7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for bsky_saves-0.2.2.tar.gz:

Publisher: release.yml on tenorune/bsky-saves

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bsky_saves-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: bsky_saves-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bsky_saves-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0e007f82960d0fc54ddc3b6833914529fd0be690d349502b6b3820e46f31e2c6
MD5 d0413ac4ec9bb4de53a2fa720e1244bc
BLAKE2b-256 20ae714a7e9c295509fbdbf4a5c97ecd36805ee0a7d679bcd8962843ac29abf5

See more details on using hashes here.

Provenance

The following attestation bundles were made for bsky_saves-0.2.2-py3-none-any.whl:

Publisher: release.yml on tenorune/bsky-saves

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page