# QTransfer

Spotify -> Qobuz playlist transfer tool in Go.

## Features

- Spotify OAuth login via Authorization Code + PKCE (client secret not required).
- Manual Spotify callback code entry (paste callback URL/code) enabled by default.
- Session cache for Spotify/Qobuz credentials and tokens (so you do not need to re-enter each run).
- Fetches all Spotify playlists and liked songs.
- Playlist URL mode (`--playlist-url`) for direct targeted transfers.
- Monitor mode to detect playlist updates (`--monitor`) with optional sync-to-Qobuz (`--monitor-transfer`) and sync modes (`--sync-mode`).
- Interactive selection prompt (or non-interactive flags).
- Creates Qobuz playlists and fills them with best-effort track matches.
- Transfers liked songs into a dedicated playlist (not favorites).
- Writes a JSON transfer report with unmatched tracks and errors.

## Requirements

- Go 1.22+
- Spotify app client ID with redirect URI configured (default: `http://127.0.0.1:8888/callback`)
- Qobuz account username/password

## Build

```bash
go build ./cmd/qtransfer
```

## Usage

```bash
./qtransfer login
```

`qtransfer login` runs an interactive setup and stores Spotify/Qobuz credentials/tokens in the session file.

After login, run transfers without re-entering credentials:

```bash
./qtransfer
```

For first-time non-interactive usage (without `login`), you can still pass flags:

```bash
./qtransfer \
  --spotify-client-id "<spotify-client-id>" \
  --qobuz-username "<qobuz-user>" \
  --qobuz-password "<qobuz-password>"
```

Credentials/tokens are cached in `~/.config/qtransfer/session.json` by default.

### Useful flags

- `--all`: transfer all Spotify playlists
- `--liked`: include liked songs as a generated Qobuz playlist
- `--playlist "Name"` (repeatable): transfer specific playlists by exact name
- `--playlist-url "..."` (repeatable): transfer specific Spotify playlists by URL/URI/ID
- `--config sync.toml`: TOML config with global options and per-playlist monitor sync jobs
- `--spotify-manual-code=true|false`: paste callback URL/code manually or use local callback server
- `--remember-creds=true|false`: persist/reuse tokens and credentials in session file
- `--session-file path`: custom session file path (default `~/.config/qtransfer/session.json`)
- `--monitor`: monitor selected playlists for updates
- `--monitor-interval 5m`: monitor polling interval
- `--monitor-once`: run one monitor check and exit
- `--monitor-transfer`: in monitor mode, sync changed playlists to Qobuz
- `--sync-mode append|mirror`: append only, or mirror source by recreating target playlist
- `--target-playlist-id 123456`: bind single monitored source playlist to an existing Qobuz playlist ID
- `--qobuz-self-test`: run Qobuz login/verify/search checks and exit (skips Spotify)
- `--qobuz-self-test-write`: when self-test is enabled, also create a test playlist and add one track
- `--qobuz-self-test-query "..."`: search query used during self-test
- `--non-interactive`: fail instead of prompting when no explicit selection is given
- `--dry-run`: resolve matches only, do not create playlists or add tracks
- `--report transfer-report.json`: report output path
- `--public-playlists`: create public Qobuz playlists

Quick auth check without waiting for Spotify:

```bash
./qtransfer \
  --qobuz-self-test \
  --qobuz-username "<qobuz-user>" \
  --qobuz-password "<qobuz-password>"
```

Transfer from direct Spotify playlist URLs:

```bash
./qtransfer \
  --spotify-client-id "<spotify-client-id>" \
  --qobuz-username "<qobuz-user>" \
  --qobuz-password "<qobuz-password>" \
  --playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
  --playlist-url "spotify:playlist:37i9dQZF1DWY4xHQp97fN6"
```

Monitor selected playlists for changes:

```bash
./qtransfer \
  --spotify-client-id "<spotify-client-id>" \
  --playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
  --monitor --monitor-interval 2m
```

Monitor with append-only sync (never removes from Qobuz):

```bash
./qtransfer \
  --playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
  --monitor --monitor-transfer --sync-mode append --monitor-interval 2m
```

Monitor with mirror sync (recreates playlist on change):

```bash
./qtransfer \
  --playlist-url "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd" \
  --monitor --monitor-transfer --sync-mode mirror --monitor-interval 2m
```

Config-file mode:

```bash
./qtransfer --config sync.toml
```

Example `sync.toml`:

```toml
[global]
monitor = true
monitor_transfer = true
monitor_interval = "10m"
sync_mode = "append"
include_liked = true

[[playlist]]
url = "https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd"
sync_mode = "append"
target_playlist_id = 61646089

[[playlist]]
url = "spotify:playlist:37i9dQZF1DWY4xHQp97fN6"
sync_mode = "mirror"
enabled = true
```

Login command:

```bash
./qtransfer login
```

Logout command (removes cached session):

```bash
./qtransfer logout
```

### Environment variables

- `SPOTIFY_CLIENT_ID`
- `SPOTIFY_REDIRECT_URI` (optional)
- `SPOTIFY_SCOPES` (optional)
- `QTRANSFER_SPOTIFY_MANUAL_CODE` (optional, defaults to true)
- `QTRANSFER_SESSION_FILE` (optional)
- `QTRANSFER_REMEMBER_CREDS` (optional, defaults to true)
- `QTRANSFER_CONFIG` (optional)
- `QTRANSFER_MONITOR` (optional)
- `QTRANSFER_MONITOR_ONCE` (optional)
- `QTRANSFER_MONITOR_TRANSFER` (optional)
- `QTRANSFER_MONITOR_INTERVAL` (optional)
- `QTRANSFER_SYNC_MODE` (optional: append|mirror)
- `QTRANSFER_TARGET_PLAYLIST_ID` (optional, monitor mode only)
- `QTRANSFER_QOBUZ_SELF_TEST` (optional)
- `QTRANSFER_QOBUZ_SELF_TEST_WRITE` (optional)
- `QTRANSFER_QOBUZ_SELF_TEST_QUERY` (optional)
- `QOBUZ_USERNAME`
- `QOBUZ_PASSWORD`
- `QOBUZ_APP_ID` (optional, defaults to reverse-engineered app id)
- `QOBUZ_APP_SECRET` (optional, defaults to reverse-engineered app secret)
