[READ-ONLY] Mirror of https://github.com/wojtekmaj/odsluchane-to-spotify. Sync songs scraped from odsluchane.eu to Spotify playlists
TypeScript 53%
JavaScript 47%
<1%

README.md

odsluchane-to-spotify #

TypeScript CLI that scrapes public playlists from odsluchane.eu and syncs them to Spotify.

Prerequisites #

Usage #

Add .env file:

cp .env.example .env

Fill in:

  • SPOTIFY_CLIENT_ID
  • SPOTIFY_CLIENT_SECRET
  • SPOTIFY_REDIRECT_URI (recommended: http://127.0.0.1:8888/callback)

In Spotify Dashboard, add the exact same Redirect URI:

  • http://127.0.0.1:8888/callback

Install:

yarn

Auth #

Get Spotify refresh token:

yarn auth

This starts a local callback server, auto-opens the authorization URL in your default browser, waits for Spotify redirect, exchanges code automatically, and writes SPOTIFY_REFRESH_TOKEN to .env.

If browser does not open automatically, open the URL printed in the terminal while yarn auth is still running.

Mapping #

Map station to playlist:

yarn map

Interactive yarn map flow:

  • fetches stations from odsluchane.eu
  • lets you pick station group first (groupName)
  • then lets you pick station in that group
  • fetches playlists from your Spotify library
  • lets you pick playlist and saves mapping
  • picker controls: type to search, use arrows to navigate, Enter to select

Non-interactive flow with CLI args:

yarn map --station 40 --playlist 37i9dQZF1DX…

You can also pass full Spotify playlist URL in --playlist.

Filter-by-name mapping:

yarn map --station-name chill --playlist-name chill

If each filter matches exactly one result, mapping is saved automatically without prompts.

Accepted options (yarn map) #

Option Type Required Default Description
--station string no none Station ID. When used together with --playlist, mapping is saved non-interactively.
--playlist string no none Spotify playlist ID or full playlist URL.
--station-name string no none Filters station list by name. Auto-selects when exactly one result matches.
--playlist-name string no none Filters playlist list by name. Auto-selects when exactly one result matches.
--verbose boolean flag no false Prints detailed request/debug logs.

Syncing #

Run sync:

Default run for full day (00-24) with 2-hour windows and date set to today in Europe/Warsaw:

yarn sync --station 40

You can also select station by name filter (must match exactly one station):

yarn sync --station-name chill --date 24-02-2026

Use remembered mapping, or override and save mapping in one go:

yarn sync --station 40 --playlist 37i9dQZF1DX… --date 24-02-2026

Accepted options (yarn sync) #

Option Type Required Default Description
--station string conditional none Station ID. Required unless --station-name is provided.
--station-name string conditional none Filters station list by name. Auto-selects when exactly one result matches.
--playlist string no mapped from state Optional Spotify playlist ID or URL override. Also saves mapping for the station.
--date DD-MM-YYYY no today in Europe/Warsaw Date to scrape. Must be a real calendar date.
--from integer no 0 Start hour (0-23).
--to integer no 24 End hour (1-24). Must be greater than --from.
--window integer no 2 Window size in hours. Allowed values: 1 or 2.
--source-delay-ms integer no 2500 Delay between source requests (respectful pacing).
--spotify-delay-ms integer no 120 Delay between Spotify search requests in a window.
--dry-run boolean flag no false Runs matching logic without modifying Spotify playlists or state.
--force boolean flag no false Ignores processed-hour memory and scrapes windows again.
--verbose boolean flag no false Prints detailed request/debug logs, including Spotify rate-limit headers when available.

What it does #

  • Scrapes song history windows from https://www.odsluchane.eu/szukaj.php.
  • Searches tracks on Spotify and prefers album versions over singles when the match is close.
  • Adds matched tracks to your chosen playlist.
  • Inserts newly added tracks at the top of the playlist (newest first).
  • Remembers station-to-playlist mapping.
  • Remembers already-scraped hours per station/date.
  • Avoids adding duplicate songs already present in the playlist.
  • Uses respectful request pacing (--source-delay-ms, retries with backoff).

Cache and state #

By default, runtime files are saved in .cache/:

  • .cache/state.json
  • .cache/stations.json
  • .cache/playlist-indexes.json
  • .cache/search-cache.json

state.json stores:

  • station -> playlist mapping
  • processed hours per station/date (hour: true), so reruns skip ranges covered by already scraped hours

stations.json stores cached station catalog from odsluchane.eu to avoid unnecessary repeated fetches.

playlist-indexes.json stores playlist duplicate-detection index with Spotify snapshot_id, so unchanged playlists can skip full playlist pagination on subsequent runs.

search-cache.json stores song-key -> Spotify match/unmatch results to avoid repeating identical /v1/search requests across sync runs.