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 statsto 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=jsonfor machine-readable output. - Fresh Sync: Use
--freshto bypass the local cache and fetch everything directly from the server. - CAR Export: Use
--carwithsyncanddedupecommands to fetch the entire repository viacom.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_*.jsonfiles - Single
Streaming_History_Audio_*.jsonfiles
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