[PR #4] [MERGED] Add Spotify Web API (OAuth PKCE) support: auth, token caching, resilient client, playlist/liked-songs downloads #6

Closed
opened 2026-02-28 15:19:04 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Ssenseii/harmoni/pull/4
Author: @TheRealAlexV
Created: 12/17/2025
Status: Merged
Merged: 12/18/2025
Merged by: @Ssenseii

Base: mainHead: TRAV_spotify_api


📝 Commits (7)

  • 89936ad chore(config): remove unused config file
  • 5b862c9 feat(config): add spotify web api config options
  • 465cc49 feat(config): add example configuration file
  • f0c0277 feat(spotify): enhance spotify authentication flow
  • 419a191 feat(spotify): enhance spotify authentication flow
  • b50c634 fix(spotify): refresh token handling and error details
  • a385960 chore(config): remove spotify token from git

📊 Changes

16 files changed (+2137 additions, -348 deletions)

View changed files

📝 .gitignore (+3 -1)
📝 config.json.example (+11 -1)
📝 config.py (+29 -0)
📝 docker-compose.yml (+7 -10)
📝 downloader/metadata.py (+1 -1)
📝 menus/downloads_menu.py (+688 -215)
📝 readme.md (+231 -118)
📝 requirements.txt (+1 -0)
spotify_api/__init__.py (+20 -0)
spotify_api/auth.py (+270 -0)
spotify_api/client.py (+264 -0)
spotify_api/data_loader.py (+279 -0)
spotify_api/token_manager.py (+114 -0)
📝 start.sh (+16 -2)
test_spotify_api_basic.py (+92 -0)
test_spotify_data_loader_limits.py (+111 -0)

📄 Description

Summary

This PR introduces a complete Spotify Web API integration (Authorization Code + PKCE) so HARMONI can load tracks directly from a user’s Spotify account (Playlists + Liked Songs) and reuse the existing per-playlist selection + download pipeline.

Key deliverables:

  • New spotify_api/ package (PKCE auth, token cache, API client with retries/paging, data normalization)
  • Downloads menu integration with a dedicated Spotify submenu and guided OAuth flow
  • New config keys + README docs for Spotify setup
  • Basic tests for PKCE helpers, token manager, and data-loader paging limits

Motivation

File-based workflows (Exportify CSV / Spotify export JSON) remain supported, but direct Web API access is the most reliable and user-friendly source of up-to-date playlist + liked-song data.

What’s Included

1) OAuth PKCE implementation (no client secret)

Dependency note:

2) Token caching + expiry

Config controls:

3) Resilient Spotify Web API client (stdlib networking)

Tunable retry keys (optional):

4) High-level data loader + normalization compatible with existing workflows

5) CLI menu integration (auth + download UI)

  • Adds Spotify submenu entry in Downloads menu: downloads_menu()
  • Spotify submenu loop: _spotify_api_menu()
    • Authenticate
    • Download from my playlists
    • Download from liked songs
    • Setup help
    • Log out (clear token cache)

OAuth UX improvements:

Download workflow integration:

6) Configuration + docs

  • Adds Spotify Web API defaults + validation schema keys in DEFAULT_CONFIG and CONFIG_SCHEMA
    • spotify_client_id, spotify_redirect_uri, spotify_scopes, spotify_cache_tokens, spotify_auto_refresh
  • README includes end-to-end setup and usage for Spotify Web API workflow: readme.md

Client ID behavior:

Testing

Security / Privacy notes

User-facing workflow

  1. Configure redirect + client id (recommended) per docs in readme.md
  2. Authenticate via the Downloads → Spotify menu (auth auto-captures loopback redirect when possible): _spotify_authenticate()
  3. Choose playlists / liked songs to load and download, then select tracks per playlist via select_songs_for_playlist()

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Ssenseii/harmoni/pull/4 **Author:** [@TheRealAlexV](https://github.com/TheRealAlexV) **Created:** 12/17/2025 **Status:** ✅ Merged **Merged:** 12/18/2025 **Merged by:** [@Ssenseii](https://github.com/Ssenseii) **Base:** `main` ← **Head:** `TRAV_spotify_api` --- ### 📝 Commits (7) - [`89936ad`](https://github.com/Ssenseii/harmoni/commit/89936adbc12ee9d7243099bba0767817aa31b2d4) chore(config): remove unused config file - [`5b862c9`](https://github.com/Ssenseii/harmoni/commit/5b862c99d044133f880404fb90bf5f66ef6c8de4) feat(config): add spotify web api config options - [`465cc49`](https://github.com/Ssenseii/harmoni/commit/465cc49bea6a42815d91eae5f6acc0f4ff5a31bc) feat(config): add example configuration file - [`f0c0277`](https://github.com/Ssenseii/harmoni/commit/f0c0277b3afb32d9b4d94797e521bf66744746ae) feat(spotify): enhance spotify authentication flow - [`419a191`](https://github.com/Ssenseii/harmoni/commit/419a1919dc7576029ec6ed8379c1997521acfcca) feat(spotify): enhance spotify authentication flow - [`b50c634`](https://github.com/Ssenseii/harmoni/commit/b50c6348a5a3194c108d539b640a4f36bb594173) fix(spotify): refresh token handling and error details - [`a385960`](https://github.com/Ssenseii/harmoni/commit/a38596092d10e7d75fe7248e236bd8e3e8a4c54f) chore(config): remove spotify token from git ### 📊 Changes **16 files changed** (+2137 additions, -348 deletions) <details> <summary>View changed files</summary> 📝 `.gitignore` (+3 -1) 📝 `config.json.example` (+11 -1) 📝 `config.py` (+29 -0) 📝 `docker-compose.yml` (+7 -10) 📝 `downloader/metadata.py` (+1 -1) 📝 `menus/downloads_menu.py` (+688 -215) 📝 `readme.md` (+231 -118) 📝 `requirements.txt` (+1 -0) ➕ `spotify_api/__init__.py` (+20 -0) ➕ `spotify_api/auth.py` (+270 -0) ➕ `spotify_api/client.py` (+264 -0) ➕ `spotify_api/data_loader.py` (+279 -0) ➕ `spotify_api/token_manager.py` (+114 -0) 📝 `start.sh` (+16 -2) ➕ `test_spotify_api_basic.py` (+92 -0) ➕ `test_spotify_data_loader_limits.py` (+111 -0) </details> ### 📄 Description ## Summary This PR introduces a complete **Spotify Web API integration (Authorization Code + PKCE)** so HARMONI can load tracks directly from a user’s Spotify account (Playlists + Liked Songs) and reuse the existing per-playlist selection + download pipeline. Key deliverables: - New `spotify_api/` package (PKCE auth, token cache, API client with retries/paging, data normalization) - Downloads menu integration with a dedicated Spotify submenu and guided OAuth flow - New config keys + README docs for Spotify setup - Basic tests for PKCE helpers, token manager, and data-loader paging limits ## Motivation File-based workflows (Exportify CSV / Spotify export JSON) remain supported, but direct Web API access is the most reliable and user-friendly source of up-to-date playlist + liked-song data. ## What’s Included ### 1) OAuth PKCE implementation (no client secret) - Implements the Authorization Code + PKCE flow in [`SpotifyPKCEAuth`](spotify-yt-dlp-downloader/spotify_api/auth.py:133) - PKCE verifier/challenge generation via [`SpotifyPKCEAuth.generate_pkce_pair()`](spotify-yt-dlp-downloader/spotify_api/auth.py:141) and [`code_challenge_from_verifier()`](spotify-yt-dlp-downloader/spotify_api/auth.py:30) - Redirect parsing helper: [`extract_code_from_redirect_url()`](spotify-yt-dlp-downloader/spotify_api/auth.py:112) - Token exchange + refresh via [`SpotifyPKCEAuth.exchange_code_for_token()`](spotify-yt-dlp-downloader/spotify_api/auth.py:184) and [`SpotifyPKCEAuth.refresh_access_token()`](spotify-yt-dlp-downloader/spotify_api/auth.py:202) Dependency note: - Token exchange/refresh uses `httpx` (import guarded with a clear runtime error when missing) in [`spotify_api/auth.py`](spotify-yt-dlp-downloader/spotify_api/auth.py:10) - `httpx` is included in [`requirements.txt`](spotify-yt-dlp-downloader/requirements.txt:8) ### 2) Token caching + expiry - Token persistence and expiry handling implemented in [`TokenManager`](spotify-yt-dlp-downloader/spotify_api/token_manager.py:54) - Default cache file: [`DEFAULT_TOKEN_CACHE_PATH`](spotify-yt-dlp-downloader/spotify_api/token_manager.py:8) (`data/spotify_tokens.json`) - Expiry check with skew buffer: [`TokenManager.is_expired()`](spotify-yt-dlp-downloader/spotify_api/token_manager.py:112) Config controls: - `spotify_cache_tokens`: enable/disable disk cache (checked in [`TokenManager.should_cache()`](spotify-yt-dlp-downloader/spotify_api/token_manager.py:60)) - `spotify_auto_refresh`: enable/disable refresh-token flow (enforced in [`SpotifyClient.get_token()`](spotify-yt-dlp-downloader/spotify_api/client.py:39)) ### 3) Resilient Spotify Web API client (stdlib networking) - API wrapper in [`SpotifyClient`](spotify-yt-dlp-downloader/spotify_api/client.py:16) - Core request method with retry logic: [`SpotifyClient.request_json()`](spotify-yt-dlp-downloader/spotify_api/client.py:70) - 429 rate-limit handling honoring `Retry-After` - exponential backoff retries on 5xx - one refresh attempt on 401 if possible - actionable diagnostics for 401/403 including scope mismatch hints - Fully paged helpers: - [`SpotifyClient.get_user_playlists()`](spotify-yt-dlp-downloader/spotify_api/client.py:251) - [`SpotifyClient.get_playlist_tracks()`](spotify-yt-dlp-downloader/spotify_api/client.py:254) - [`SpotifyClient.get_liked_songs()`](spotify-yt-dlp-downloader/spotify_api/client.py:262) Tunable retry keys (optional): - `spotify_max_retries`, `spotify_backoff_base`, `spotify_retry_jitter` (read in [`SpotifyClient.request_json()`](spotify-yt-dlp-downloader/spotify_api/client.py:70) and [`SpotifyClient._sleep_with_jitter()`](spotify-yt-dlp-downloader/spotify_api/client.py:64)) ### 4) High-level data loader + normalization compatible with existing workflows - High-level extraction helpers: [`SpotifyDataLoader`](spotify-yt-dlp-downloader/spotify_api/data_loader.py:6) - playlists: [`SpotifyDataLoader.list_all_playlists()`](spotify-yt-dlp-downloader/spotify_api/data_loader.py:20) - playlist tracks: [`SpotifyDataLoader.load_playlist_tracks()`](spotify-yt-dlp-downloader/spotify_api/data_loader.py:59) - liked songs: [`SpotifyDataLoader.load_liked_songs()`](spotify-yt-dlp-downloader/spotify_api/data_loader.py:114) - Track normalization produces dicts compatible with HARMONI’s existing download pipeline (must include `artist` + `track`): [`SpotifyDataLoader._normalize_track()`](spotify-yt-dlp-downloader/spotify_api/data_loader.py:227) - filters local tracks - preserves optional fields like album, release_date, uri, isrc, etc. ### 5) CLI menu integration (auth + download UI) - Adds Spotify submenu entry in Downloads menu: [`downloads_menu()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:505) - Spotify submenu loop: [`_spotify_api_menu()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:460) - Authenticate - Download from my playlists - Download from liked songs - Setup help - Log out (clear token cache) OAuth UX improvements: - Interactive auth flow with loopback callback server (auto-capture) + paste fallback: [`_spotify_authenticate()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:81) - Docker-aware callback binding (bind `0.0.0.0` inside container when redirect is `127.0.0.1`): [`_spotify_authenticate()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:137) Download workflow integration: - Playlist selection via checkbox UI + guidance: [`_spotify_download_from_playlists()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:265) - Liked songs loading + selection: [`_spotify_download_liked_songs()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:391) - Reuses existing per-playlist song selection UI: [`select_songs_for_playlist()`](spotify-yt-dlp-downloader/menus/song_selection_menu.py:1) - Final download path reuses playlist downloader: [`download_playlist()`](spotify-yt-dlp-downloader/downloader/playlist_download.py:1) ### 6) Configuration + docs - Adds Spotify Web API defaults + validation schema keys in [`DEFAULT_CONFIG`](spotify-yt-dlp-downloader/config.py:8) and [`CONFIG_SCHEMA`](spotify-yt-dlp-downloader/config.py:104) - `spotify_client_id`, `spotify_redirect_uri`, `spotify_scopes`, `spotify_cache_tokens`, `spotify_auto_refresh` - README includes end-to-end setup and usage for Spotify Web API workflow: [`readme.md`](spotify-yt-dlp-downloader/readme.md:42) Client ID behavior: - If `spotify_client_id` is not set, falls back to Exportify’s public client id via [`get_effective_spotify_client_id()`](spotify-yt-dlp-downloader/spotify_api/auth.py:37) (with warnings + guidance from [`check_spotify_credentials()`](spotify-yt-dlp-downloader/spotify_api/auth.py:45)) ## Testing - PKCE + token manager + normalization tests: [`test_spotify_api_basic.py`](spotify-yt-dlp-downloader/test_spotify_api_basic.py:1) - Data loader max-track/max-playlist limit tests with a fake client: [`test_spotify_data_loader_limits.py`](spotify-yt-dlp-downloader/test_spotify_data_loader_limits.py:1) ## Security / Privacy notes - Uses PKCE (no client secret stored). - Tokens are cached only when `spotify_cache_tokens` is enabled (see [`TokenManager.should_cache()`](spotify-yt-dlp-downloader/spotify_api/token_manager.py:60)). - Client avoids leaking secrets in diagnostics (see logging logic in [`SpotifyClient.request_json()`](spotify-yt-dlp-downloader/spotify_api/client.py:70)). ## User-facing workflow 1. Configure redirect + client id (recommended) per docs in [`readme.md`](spotify-yt-dlp-downloader/readme.md:54) 2. Authenticate via the Downloads → Spotify menu (auth auto-captures loopback redirect when possible): [`_spotify_authenticate()`](spotify-yt-dlp-downloader/menus/downloads_menu.py:81) 3. Choose playlists / liked songs to load and download, then select tracks per playlist via [`select_songs_for_playlist()`](spotify-yt-dlp-downloader/menus/song_selection_menu.py:1) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-02-28 15:19:04 +03:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/harmoni#6
No description provided.