like malachite (atproto-lastfm-importer) but in go and bluer
go spotify tealfm lastfm atproto
Go 99.5%
Nix 0.5%
23 1 11

Clone this repository

https://tangled.org/karitham.dev/lazuli https://tangled.org/did:plc:kcgwlowulc3rac43lregdawo/lazuli
git@knot.tangled.wizardry.systems:karitham.dev/lazuli git@knot.tangled.wizardry.systems:did:plc:kcgwlowulc3rac43lregdawo/lazuli

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

Lazuli#

Import Last.fm and Spotify listening history to teal.fm.

Overview#

Lazuli is a command-line tool that parses listening history exports from Last.fm and Spotify, merges them to remove duplicates, and publishes them to teal.fm as fm.teal.alpha.feed.play records.

Re-written from https://tangled.org/ewancroft.uk/atproto-lastfm-importer.

Usage#

Authentication#

Set your Bluesky credentials via environment variables or flags:

export LAZULI_HANDLE="your-handle.bsky.social"
export LAZULI_PASSWORD="your-app-password"

Commands#

Command Usage
export Parse and merge Last.fm/Spotify exports into a JSON file
import Import new records to Bluesky (auto-skips existing)
sync Refresh the local cache with records from Bluesky
stats Show database status and daily rate limit consumption
failed List records that failed to import
retry Attempt to re-import failed records
dedupe Remove duplicate records from your Bluesky profile
debug Dump raw records from Bluesky for troubleshooting

Advanced Options#

  • Rate Limiting: Lazuli automatically respects Bluesky/ATProto rate limits (default 9,000 writes/day). Use lazuli stats to see your remaining quota.
  • Automatic Resume: The local cache tracks which records were successfully imported. If a process is interrupted, re-running the same command will skip already-published entries.
  • Output Formats: Most commands support --output-format=json for machine-readable output.
  • Fresh Sync: Use --fresh to bypass the local cache and fetch everything directly from the server.
  • CAR Export: Use --car with sync and dedupe commands to fetch the entire repository via com.atproto.sync.getRepo (much faster for large repos, uses a single API call).

Environment Variables#

Variable Description
LAZULI_HANDLE Bluesky handle (e.g., user.bsky.social)
LAZULI_PASSWORD Bluesky app password
LAZULI_LASTFM Path to Last.fm CSV file
LAZULI_SPOTIFY Path to Spotify JSON file/directory/zip
LAZULI_DRY_RUN Preview without publishing
LAZULI_VERBOSE Enable verbose logging
LAZULI_REVERSE Process records in reverse order
LAZULI_USE_CAR Use CAR export for sync/dedupe (1/true)

Input Formats#

Last.fm#

Export your listening history from Last.fm. The CSV file should have columns:

  • UTC timestamp
  • Artist name
  • Album name
  • Track name
  • MusicBrainz IDs (optional)

Spotify#

Lazuli is designed to work with your Extended Streaming History from Spotify. You can request this from your Spotify Privacy Settings.

The recommended way to use Spotify data is by passing the ZIP archive you receive from Spotify directly. Lazuli will automatically find and parse all streaming history files within it.

Lazuli accepts:

  • ZIP archives containing extended history (Recommended)
  • Directories containing Streaming_History_Audio_*.json files
  • Single Streaming_History_Audio_*.json files
IMPORTANT

Make sure to request "Extended streaming history", as the standard "Account data" export does not contain your full listening history.

Features#

  • Cross-source deduplication: Merges Last.fm and Spotify data, removing duplicates within a configurable time tolerance
  • Rate limiting: Respects ATProto rate limits with configurable batch sizes and delays
  • Automatic resume: Cache tracks imported records - re-running skips already-imported entries
  • Dry-run mode: Preview imports without publishing
  • Cache management: Caches teal records for faster subsequent operations