The connect flows, same methodology as `qbzd login`: - `scrobble login lastfm`: LastFmClient::get_token prints an authorize URL, the user approves on last.fm, get_session exchanges it for a session key. - `scrobble login listenbrainz --token <T>`: ListenBrainzClient::set_token validates a pasted user token (like `login --token`). - `scrobble status` · `scrobble enable|disable <provider>`. Credentials land in the CANONICAL scrobbler store (qbz_app::settings::scrobblers::ScrobblerSettingsStore — SQLite at the daemon data root), the SAME store the desktop uses and the one the settings bundle export/import already carries, so scrobbler creds travel with `settings export`. Secrets registered with qbz_log; a running daemon is nudged to reload. - Cargo.toml: qbz-integrations path dep (already in-tree via qbz-core). - cli/scrobble.rs: the verbs. main.rs: Scrobble subcommand group. Verified: cargo test -p qbzd --bin qbzd -> 276 passed, 0 failed. NOTE: lands CREDENTIALS + auth only. Scrobble-on-play (a daemon task on the CoreEvent bus: update_now_playing on TrackStarted, scrobble past scrobble_delay_secs, ListenBrainz submit + offline queue) is the next slice — it wires the playback driver. The setup-TUI Scrobbler screen is also pending. cc
57 lines
2.5 KiB
TOML
57 lines
2.5 KiB
TOML
[package]
|
|
name = "qbzd"
|
|
version.workspace = true
|
|
edition = "2021"
|
|
description = "QBZ headless Qobuz playback daemon (daemon + CLI + setup TUI in one binary)"
|
|
license = "MIT"
|
|
|
|
[[bin]]
|
|
name = "qbzd"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
# workspace crates — the slint-free column ONLY (01-architecture.md §1.2)
|
|
qbz-models = { path = "../qbz-models" }
|
|
qbz-app = { path = "../qbz-app" }
|
|
qbz-core = { path = "../qbz-core" }
|
|
qbz-player = { path = "../qbz-player" }
|
|
qbz-audio = { path = "../qbz-audio" } # PROTECTED — read-only consumer
|
|
qbz-qobuz = { path = "../qbz-qobuz" }
|
|
qbz-reco = { path = "../qbz-reco" } # playlist recommendations (ArtistVectorStore); already in-tree via qbz-core
|
|
qbz-integrations = { path = "../qbz-integrations" } # Last.fm / ListenBrainz scrobbling; already in-tree via qbz-core
|
|
qbz-credentials = { path = "../qbz-credentials" }
|
|
qbz-log = { path = "../qbz-log" }
|
|
qconnect-core = { path = "../qconnect-core" }
|
|
qconnect-protocol = { path = "../qconnect-protocol" }
|
|
qconnect-transport-ws = { path = "../qconnect-transport-ws" }
|
|
qconnect-app = { path = "../qconnect-app" }
|
|
# runtime / wire
|
|
tokio = { workspace = true }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
reqwest = { workspace = true }
|
|
dirs = "6"
|
|
rand = { workspace = true }
|
|
log = { workspace = true }
|
|
async-trait = { workspace = true }
|
|
# QConnect glue copies (T9) — mirror the desktop copies' direct imports
|
|
uuid = { version = "1", features = ["v4"] } # renderer-report action uuids
|
|
rusqlite = { version = "0.31", features = ["bundled"] } # daemon-root qconnect_settings.db KV
|
|
futures-util = { workspace = true } # remote_stream.rs feeder (byte-exact)
|
|
# new direct deps (versions already in lock where noted)
|
|
tiny_http = "0.12" # in lock via qbz-cast
|
|
toml = "0.9" # in lock transitively
|
|
libc = "0.2" # flock
|
|
urlencoding = "2" # OAuth redirect_url encode + callback decode (in lock via qbz)
|
|
open = "5" # system-browser launch for `qbzd login` (in lock via qbz)
|
|
clap = { version = "4", features = ["derive"] }
|
|
clap_complete = "4"
|
|
# T13 setup TUI. ratatui 0.30 (NEVER 0.29 — 0.29 pins unicode-width "=0.2.0",
|
|
# unresolvable against Slint's annotate-snippets 0.12.16 which needs >=0.2.2;
|
|
# ratatui-core 0.30 accepts >=0.2.0,<=0.2.2 → unifies on the lock's 0.2.2, IV5).
|
|
# The `crossterm` backend is a DEFAULT feature; re-exported as ratatui::crossterm.
|
|
ratatui = "0.30"
|
|
|
|
[dev-dependencies]
|
|
tempfile = "3"
|