From 9142e6941b5b827422324ccc35c08093c3dd70fd Mon Sep 17 00:00:00 2001 From: Paul Fariello Date: Wed, 22 Oct 2025 19:01:31 +0200 Subject: [PATCH 1/4] feat: Add pre-commit configuration (#1606) --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..77abd205c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/doublify/pre-commit-rust + rev: master + hooks: + - id: fmt + - id: clippy From 9a0c96bd8c97d716ded9088dbbe7b1d088f7bf80 Mon Sep 17 00:00:00 2001 From: Felix Prillwitz Date: Sat, 9 Aug 2025 23:55:25 +0200 Subject: [PATCH 2/4] fix: uses the extended-metadata endpoint to retrieve metadata --- core/src/spclient.rs | 92 +++++++++++++++++++++++++---------------- metadata/src/album.rs | 4 +- metadata/src/artist.rs | 4 +- metadata/src/episode.rs | 4 +- metadata/src/show.rs | 4 +- metadata/src/track.rs | 4 +- 6 files changed, 66 insertions(+), 46 deletions(-) diff --git a/core/src/spclient.rs b/core/src/spclient.rs index 7bc9d0b56..51a1336a5 100644 --- a/core/src/spclient.rs +++ b/core/src/spclient.rs @@ -18,6 +18,8 @@ use crate::{ connect::PutStateRequest, context::Context, extended_metadata::BatchedEntityRequest, + extended_metadata::{BatchedExtensionResponse, EntityRequest, ExtensionQuery}, + extension_kind::ExtensionKind, }, token::Token, util, @@ -32,7 +34,7 @@ use hyper::{ header::{ACCEPT, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderName, RANGE}, }; use hyper_util::client::legacy::ResponseFuture; -use protobuf::{Enum, Message, MessageFull}; +use protobuf::{Enum, EnumOrUnknown, Message, MessageFull}; use rand::RngCore; use sysinfo::System; use thiserror::Error; @@ -58,18 +60,14 @@ const NO_METRICS_AND_SALT: RequestOptions = RequestOptions { base_url: None, }; -const SPCLIENT_FALLBACK_ENDPOINT: RequestOptions = RequestOptions { - metrics: true, - salt: true, - base_url: Some("https://spclient.wg.spotify.com"), -}; - #[derive(Debug, Error)] pub enum SpClientError { #[error("missing attribute {0}")] Attribute(String), #[error("expected data but received none")] NoData, + #[error("expected an entry to exist in {0}")] + ExpectedEntry(&'static str), } impl From for Error { @@ -572,39 +570,67 @@ impl SpClient { .await } - pub async fn get_metadata(&self, scope: &str, id: &SpotifyId) -> SpClientResult { - let endpoint = format!("/metadata/4/{}/{}", scope, id.to_base16()?); - // For unknown reasons, metadata requests must now be sent through spclient.wg.spotify.com. - // Otherwise, the API will respond with 500 Internal Server Error responses. - // Context: https://github.com/librespot-org/librespot/issues/1527 - self.request_with_options( - &Method::GET, - &endpoint, - None, - None, - &SPCLIENT_FALLBACK_ENDPOINT, - ) - .await + pub async fn get_extended_metadata( + &self, + request: BatchedEntityRequest, + ) -> Result { + let endpoint = "/extended-metadata/v0/extended-metadata"; + let res = self + .request_with_protobuf(&Method::POST, endpoint, None, &request) + .await?; + Ok(BatchedExtensionResponse::parse_from_bytes(&res)?) + } + + pub async fn get_metadata(&self, kind: ExtensionKind, id: &SpotifyUri) -> SpClientResult { + let req = BatchedEntityRequest { + entity_request: vec![EntityRequest { + entity_uri: id.to_uri()?, + query: vec![ExtensionQuery { + extension_kind: EnumOrUnknown::new(kind), + ..Default::default() + }], + ..Default::default() + }], + ..Default::default() + }; + + let mut res = self.get_extended_metadata(req).await?; + let mut extended_metadata = res + .extended_metadata + .pop() + .ok_or(SpClientError::ExpectedEntry("extended_metadata"))?; + + let mut data = extended_metadata + .extension_data + .pop() + .ok_or(SpClientError::ExpectedEntry("extension_data"))?; + + match data.extension_data.take() { + None => Err(SpClientError::ExpectedEntry("data").into()), + Some(data) => Ok(Bytes::from(data.value)), + } } - pub async fn get_track_metadata(&self, track_id: &SpotifyId) -> SpClientResult { - self.get_metadata("track", track_id).await + pub async fn get_track_metadata(&self, track_uri: &SpotifyUri) -> SpClientResult { + self.get_metadata(ExtensionKind::TRACK_V4, track_uri).await } - pub async fn get_episode_metadata(&self, episode_id: &SpotifyId) -> SpClientResult { - self.get_metadata("episode", episode_id).await + pub async fn get_episode_metadata(&self, episode_uri: &SpotifyUri) -> SpClientResult { + self.get_metadata(ExtensionKind::EPISODE_V4, episode_uri) + .await } - pub async fn get_album_metadata(&self, album_id: &SpotifyId) -> SpClientResult { - self.get_metadata("album", album_id).await + pub async fn get_album_metadata(&self, album_uri: &SpotifyUri) -> SpClientResult { + self.get_metadata(ExtensionKind::ALBUM_V4, album_uri).await } - pub async fn get_artist_metadata(&self, artist_id: &SpotifyId) -> SpClientResult { - self.get_metadata("artist", artist_id).await + pub async fn get_artist_metadata(&self, artist_uri: &SpotifyUri) -> SpClientResult { + self.get_metadata(ExtensionKind::ARTIST_V4, artist_uri) + .await } - pub async fn get_show_metadata(&self, show_id: &SpotifyId) -> SpClientResult { - self.get_metadata("show", show_id).await + pub async fn get_show_metadata(&self, show_uri: &SpotifyUri) -> SpClientResult { + self.get_metadata(ExtensionKind::SHOW_V4, show_uri).await } pub async fn get_lyrics(&self, track_id: &SpotifyId) -> SpClientResult { @@ -733,12 +759,6 @@ impl SpClient { // TODO: Seen-in-the-wild but unimplemented endpoints // - /presence-view/v1/buddylist - pub async fn get_extended_metadata(&self, request: BatchedEntityRequest) -> SpClientResult { - let endpoint = "/extended-metadata/v0/extended-metadata"; - self.request_with_protobuf(&Method::POST, endpoint, None, &request) - .await - } - pub async fn get_audio_storage(&self, file_id: &FileId) -> SpClientResult { let endpoint = format!( "/storage-resolve/files/audio/interactive/{}", diff --git a/metadata/src/album.rs b/metadata/src/album.rs index b1b26468e..000eb10d2 100644 --- a/metadata/src/album.rs +++ b/metadata/src/album.rs @@ -75,11 +75,11 @@ impl Metadata for Album { type Message = protocol::metadata::Album; async fn request(session: &Session, album_uri: &SpotifyUri) -> RequestResult { - let SpotifyUri::Album { id: album_id } = album_uri else { + let SpotifyUri::Album { .. } = album_uri else { return Err(Error::invalid_argument("album_uri")); }; - session.spclient().get_album_metadata(album_id).await + session.spclient().get_album_metadata(album_uri).await } fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result { diff --git a/metadata/src/artist.rs b/metadata/src/artist.rs index 5f4437195..f14695806 100644 --- a/metadata/src/artist.rs +++ b/metadata/src/artist.rs @@ -172,11 +172,11 @@ impl Metadata for Artist { type Message = protocol::metadata::Artist; async fn request(session: &Session, artist_uri: &SpotifyUri) -> RequestResult { - let SpotifyUri::Artist { id: artist_id } = artist_uri else { + let SpotifyUri::Artist { .. } = artist_uri else { return Err(Error::invalid_argument("artist_uri")); }; - session.spclient().get_artist_metadata(artist_id).await + session.spclient().get_artist_metadata(artist_uri).await } fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result { diff --git a/metadata/src/episode.rs b/metadata/src/episode.rs index 847e89418..1bcf416cf 100644 --- a/metadata/src/episode.rs +++ b/metadata/src/episode.rs @@ -58,11 +58,11 @@ impl Metadata for Episode { type Message = protocol::metadata::Episode; async fn request(session: &Session, episode_uri: &SpotifyUri) -> RequestResult { - let SpotifyUri::Episode { id: episode_id } = episode_uri else { + let SpotifyUri::Episode { .. } = episode_uri else { return Err(Error::invalid_argument("episode_uri")); }; - session.spclient().get_episode_metadata(episode_id).await + session.spclient().get_episode_metadata(episode_uri).await } fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result { diff --git a/metadata/src/show.rs b/metadata/src/show.rs index 01a55c2d8..2bd7b9482 100644 --- a/metadata/src/show.rs +++ b/metadata/src/show.rs @@ -37,11 +37,11 @@ impl Metadata for Show { type Message = protocol::metadata::Show; async fn request(session: &Session, show_uri: &SpotifyUri) -> RequestResult { - let SpotifyUri::Show { id: show_id } = show_uri else { + let SpotifyUri::Show { .. } = show_uri else { return Err(Error::invalid_argument("show_uri")); }; - session.spclient().get_show_metadata(show_id).await + session.spclient().get_show_metadata(show_uri).await } fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result { diff --git a/metadata/src/track.rs b/metadata/src/track.rs index 5893ca155..c66adbfe7 100644 --- a/metadata/src/track.rs +++ b/metadata/src/track.rs @@ -59,11 +59,11 @@ impl Metadata for Track { type Message = protocol::metadata::Track; async fn request(session: &Session, track_uri: &SpotifyUri) -> RequestResult { - let SpotifyUri::Track { id: track_id } = track_uri else { + let SpotifyUri::Track { .. } = track_uri else { return Err(Error::invalid_argument("track_uri")); }; - session.spclient().get_track_metadata(track_id).await + session.spclient().get_track_metadata(track_uri).await } fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result { From 0e5574277b4339ad9f25aee374ae92f15758bda5 Mon Sep 17 00:00:00 2001 From: Felix Prillwitz Date: Sun, 10 Aug 2025 00:01:18 +0200 Subject: [PATCH 3/4] chore: update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a11d932e4..a35821bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [metadata] Changed arguments for `Metadata` trait from `&SpotifyId` to `&SpotifyUri` (breaking) - [player] `load` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking) - [player] `preload` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking) -- [spclient] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking) +- [core] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking) +- [core] Changed return type of `get_extended_metadata` to return `BatchedExtensionResponse` (breaking) +- [core] Changed parameter of `get__metadata` from `SpotifyId` to `SpotifyUri` (breaking) +### Fixed + +- [core] Fixed a problem where the metadata didn't include the audio file by switching to `get_extended_metadata` ### Removed From dd9c3a174e46078c0ecb83caa9c6bb48aea66cac Mon Sep 17 00:00:00 2001 From: Felix Prillwitz Date: Thu, 6 Nov 2025 19:12:27 +0100 Subject: [PATCH 4/4] chore: fix clippy warnings --- discovery/src/lib.rs | 2 +- playback/src/config.rs | 36 ++++++++---------------------------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/discovery/src/lib.rs b/discovery/src/lib.rs index e440c67f5..021473c9b 100644 --- a/discovery/src/lib.rs +++ b/discovery/src/lib.rs @@ -406,7 +406,7 @@ fn launch_libmdns( } .map_err(|e| DiscoveryError::DnsSdError(Box::new(e)))?; - let svc = responder.register(&DNS_SD_SERVICE_NAME, &name, port, &TXT_RECORD); + let svc = responder.register(DNS_SD_SERVICE_NAME, &name, port, &TXT_RECORD); let _ = shutdown_rx.blocking_recv(); diff --git a/playback/src/config.rs b/playback/src/config.rs index a747ce38d..f91c1a3c0 100644 --- a/playback/src/config.rs +++ b/playback/src/config.rs @@ -3,9 +3,10 @@ use std::{mem, str::FromStr, time::Duration}; pub use crate::dither::{DithererBuilder, TriangularDitherer, mk_ditherer}; use crate::{convert::i24, player::duration_to_coefficient}; -#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum Bitrate { Bitrate96, + #[default] Bitrate160, Bitrate320, } @@ -22,19 +23,14 @@ impl FromStr for Bitrate { } } -impl Default for Bitrate { - fn default() -> Self { - Self::Bitrate160 - } -} - -#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum AudioFormat { F64, F32, S32, S24, S24_3, + #[default] S16, } @@ -53,12 +49,6 @@ impl FromStr for AudioFormat { } } -impl Default for AudioFormat { - fn default() -> Self { - Self::S16 - } -} - impl AudioFormat { // not used by all backends #[allow(dead_code)] @@ -73,10 +63,11 @@ impl AudioFormat { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum NormalisationType { Album, Track, + #[default] Auto, } @@ -92,15 +83,10 @@ impl FromStr for NormalisationType { } } -impl Default for NormalisationType { - fn default() -> Self { - Self::Auto - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum NormalisationMethod { Basic, + #[default] Dynamic, } @@ -115,12 +101,6 @@ impl FromStr for NormalisationMethod { } } -impl Default for NormalisationMethod { - fn default() -> Self { - Self::Dynamic - } -} - #[derive(Clone)] pub struct PlayerConfig { pub bitrate: Bitrate,