Skip to content

Commit a9122dc

Browse files
authored
Fix: use extended-metadata endpoint to acquire metadata (#1622)
* fix: uses the extended-metadata endpoint to retrieve metadata * chore: update CHANGELOG.md * chore: fix clippy warnings * chore: replace let with const and inline endpoint
1 parent 8f79926 commit a9122dc

File tree

7 files changed

+82
-55
lines changed

7 files changed

+82
-55
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
- [playback] `load` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
2222
- [playback] `preload` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
2323
- [core] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
24+
- [core] Changed return type of `get_extended_metadata` to return `BatchedExtensionResponse` (breaking)
25+
- [core] Changed parameter of `get_<item>_metadata` from `SpotifyId` to `SpotifyUri` (breaking)
2426

2527
### Fixed
2628

2729
- [connect] Fixed failed transferring with transfer data that had an empty context uri and no tracks
2830
- [connect] Use the provided index or the first as fallback value to always play a track on loading
31+
- [core] Fixed a problem where the metadata didn't include the audio file by switching to `get_extended_metadata`
2932

3033
### Removed
3134

core/src/spclient.rs

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use crate::{
1919
connect::PutStateRequest,
2020
context::Context,
2121
extended_metadata::BatchedEntityRequest,
22+
extended_metadata::{BatchedExtensionResponse, EntityRequest, ExtensionQuery},
23+
extension_kind::ExtensionKind,
2224
},
2325
token::Token,
2426
util,
@@ -33,7 +35,7 @@ use hyper::{
3335
header::{ACCEPT, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, HeaderName, RANGE},
3436
};
3537
use hyper_util::client::legacy::ResponseFuture;
36-
use protobuf::{Enum, Message, MessageFull};
38+
use protobuf::{Enum, EnumOrUnknown, Message, MessageFull};
3739
use rand::RngCore;
3840
use serde::Serialize;
3941
use sysinfo::System;
@@ -60,18 +62,14 @@ const NO_METRICS_AND_SALT: RequestOptions = RequestOptions {
6062
base_url: None,
6163
};
6264

63-
const SPCLIENT_FALLBACK_ENDPOINT: RequestOptions = RequestOptions {
64-
metrics: true,
65-
salt: true,
66-
base_url: Some("https://spclient.wg.spotify.com"),
67-
};
68-
6965
#[derive(Debug, Error)]
7066
pub enum SpClientError {
7167
#[error("missing attribute {0}")]
7268
Attribute(String),
7369
#[error("expected data but received none")]
7470
NoData,
71+
#[error("expected an entry to exist in {0}")]
72+
ExpectedEntry(&'static str),
7573
}
7674

7775
impl From<SpClientError> for Error {
@@ -579,39 +577,71 @@ impl SpClient {
579577
.await
580578
}
581579

582-
pub async fn get_metadata(&self, scope: &str, id: &SpotifyId) -> SpClientResult {
583-
let endpoint = format!("/metadata/4/{}/{}", scope, id.to_base16()?);
584-
// For unknown reasons, metadata requests must now be sent through spclient.wg.spotify.com.
585-
// Otherwise, the API will respond with 500 Internal Server Error responses.
586-
// Context: https://github.com/librespot-org/librespot/issues/1527
587-
self.request_with_options(
588-
&Method::GET,
589-
&endpoint,
590-
None,
591-
None,
592-
&SPCLIENT_FALLBACK_ENDPOINT,
593-
)
594-
.await
580+
pub async fn get_extended_metadata(
581+
&self,
582+
request: BatchedEntityRequest,
583+
) -> Result<BatchedExtensionResponse, Error> {
584+
let res = self
585+
.request_with_protobuf(
586+
&Method::POST,
587+
"/extended-metadata/v0/extended-metadata",
588+
None,
589+
&request,
590+
)
591+
.await?;
592+
Ok(BatchedExtensionResponse::parse_from_bytes(&res)?)
593+
}
594+
595+
pub async fn get_metadata(&self, kind: ExtensionKind, id: &SpotifyUri) -> SpClientResult {
596+
let req = BatchedEntityRequest {
597+
entity_request: vec![EntityRequest {
598+
entity_uri: id.to_uri()?,
599+
query: vec![ExtensionQuery {
600+
extension_kind: EnumOrUnknown::new(kind),
601+
..Default::default()
602+
}],
603+
..Default::default()
604+
}],
605+
..Default::default()
606+
};
607+
608+
let mut res = self.get_extended_metadata(req).await?;
609+
let mut extended_metadata = res
610+
.extended_metadata
611+
.pop()
612+
.ok_or(SpClientError::ExpectedEntry("extended_metadata"))?;
613+
614+
let mut data = extended_metadata
615+
.extension_data
616+
.pop()
617+
.ok_or(SpClientError::ExpectedEntry("extension_data"))?;
618+
619+
match data.extension_data.take() {
620+
None => Err(SpClientError::ExpectedEntry("data").into()),
621+
Some(data) => Ok(Bytes::from(data.value)),
622+
}
595623
}
596624

597-
pub async fn get_track_metadata(&self, track_id: &SpotifyId) -> SpClientResult {
598-
self.get_metadata("track", track_id).await
625+
pub async fn get_track_metadata(&self, track_uri: &SpotifyUri) -> SpClientResult {
626+
self.get_metadata(ExtensionKind::TRACK_V4, track_uri).await
599627
}
600628

601-
pub async fn get_episode_metadata(&self, episode_id: &SpotifyId) -> SpClientResult {
602-
self.get_metadata("episode", episode_id).await
629+
pub async fn get_episode_metadata(&self, episode_uri: &SpotifyUri) -> SpClientResult {
630+
self.get_metadata(ExtensionKind::EPISODE_V4, episode_uri)
631+
.await
603632
}
604633

605-
pub async fn get_album_metadata(&self, album_id: &SpotifyId) -> SpClientResult {
606-
self.get_metadata("album", album_id).await
634+
pub async fn get_album_metadata(&self, album_uri: &SpotifyUri) -> SpClientResult {
635+
self.get_metadata(ExtensionKind::ALBUM_V4, album_uri).await
607636
}
608637

609-
pub async fn get_artist_metadata(&self, artist_id: &SpotifyId) -> SpClientResult {
610-
self.get_metadata("artist", artist_id).await
638+
pub async fn get_artist_metadata(&self, artist_uri: &SpotifyUri) -> SpClientResult {
639+
self.get_metadata(ExtensionKind::ARTIST_V4, artist_uri)
640+
.await
611641
}
612642

613-
pub async fn get_show_metadata(&self, show_id: &SpotifyId) -> SpClientResult {
614-
self.get_metadata("show", show_id).await
643+
pub async fn get_show_metadata(&self, show_uri: &SpotifyUri) -> SpClientResult {
644+
self.get_metadata(ExtensionKind::SHOW_V4, show_uri).await
615645
}
616646

617647
pub async fn get_lyrics(&self, track_id: &SpotifyId) -> SpClientResult {
@@ -740,12 +770,6 @@ impl SpClient {
740770
// TODO: Seen-in-the-wild but unimplemented endpoints
741771
// - /presence-view/v1/buddylist
742772

743-
pub async fn get_extended_metadata(&self, request: BatchedEntityRequest) -> SpClientResult {
744-
let endpoint = "/extended-metadata/v0/extended-metadata";
745-
self.request_with_protobuf(&Method::POST, endpoint, None, &request)
746-
.await
747-
}
748-
749773
pub async fn get_audio_storage(&self, file_id: &FileId) -> SpClientResult {
750774
let endpoint = format!(
751775
"/storage-resolve/files/audio/interactive/{}",
@@ -789,11 +813,11 @@ impl SpClient {
789813

790814
// Audio preview in 96 kbps MP3, unencrypted
791815
pub async fn get_audio_preview(&self, preview_id: &FileId) -> SpClientResult {
792-
let attribute = "audio-preview-url-template";
816+
const ATTRIBUTE: &str = "audio-preview-url-template";
793817
let template = self
794818
.session()
795-
.get_user_attribute(attribute)
796-
.ok_or_else(|| SpClientError::Attribute(attribute.to_string()))?;
819+
.get_user_attribute(ATTRIBUTE)
820+
.ok_or_else(|| SpClientError::Attribute(ATTRIBUTE.to_string()))?;
797821

798822
let mut url = template.replace("{id}", &preview_id.to_base16()?);
799823
let separator = match url.find('?') {
@@ -807,23 +831,23 @@ impl SpClient {
807831

808832
// The first 128 kB of a track, unencrypted
809833
pub async fn get_head_file(&self, file_id: &FileId) -> SpClientResult {
810-
let attribute = "head-files-url";
834+
const ATTRIBUTE: &str = "head-files-url";
811835
let template = self
812836
.session()
813-
.get_user_attribute(attribute)
814-
.ok_or_else(|| SpClientError::Attribute(attribute.to_string()))?;
837+
.get_user_attribute(ATTRIBUTE)
838+
.ok_or_else(|| SpClientError::Attribute(ATTRIBUTE.to_string()))?;
815839

816840
let url = template.replace("{file_id}", &file_id.to_base16()?);
817841

818842
self.request_url(&url).await
819843
}
820844

821845
pub async fn get_image(&self, image_id: &FileId) -> SpClientResult {
822-
let attribute = "image-url";
846+
const ATTRIBUTE: &str = "image-url";
823847
let template = self
824848
.session()
825-
.get_user_attribute(attribute)
826-
.ok_or_else(|| SpClientError::Attribute(attribute.to_string()))?;
849+
.get_user_attribute(ATTRIBUTE)
850+
.ok_or_else(|| SpClientError::Attribute(ATTRIBUTE.to_string()))?;
827851
let url = template.replace("{file_id}", &image_id.to_base16()?);
828852

829853
self.request_url(&url).await

metadata/src/album.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ impl Metadata for Album {
7575
type Message = protocol::metadata::Album;
7676

7777
async fn request(session: &Session, album_uri: &SpotifyUri) -> RequestResult {
78-
let SpotifyUri::Album { id: album_id } = album_uri else {
78+
let SpotifyUri::Album { .. } = album_uri else {
7979
return Err(Error::invalid_argument("album_uri"));
8080
};
8181

82-
session.spclient().get_album_metadata(album_id).await
82+
session.spclient().get_album_metadata(album_uri).await
8383
}
8484

8585
fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {

metadata/src/artist.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ impl Metadata for Artist {
172172
type Message = protocol::metadata::Artist;
173173

174174
async fn request(session: &Session, artist_uri: &SpotifyUri) -> RequestResult {
175-
let SpotifyUri::Artist { id: artist_id } = artist_uri else {
175+
let SpotifyUri::Artist { .. } = artist_uri else {
176176
return Err(Error::invalid_argument("artist_uri"));
177177
};
178178

179-
session.spclient().get_artist_metadata(artist_id).await
179+
session.spclient().get_artist_metadata(artist_uri).await
180180
}
181181

182182
fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {

metadata/src/episode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ impl Metadata for Episode {
5858
type Message = protocol::metadata::Episode;
5959

6060
async fn request(session: &Session, episode_uri: &SpotifyUri) -> RequestResult {
61-
let SpotifyUri::Episode { id: episode_id } = episode_uri else {
61+
let SpotifyUri::Episode { .. } = episode_uri else {
6262
return Err(Error::invalid_argument("episode_uri"));
6363
};
6464

65-
session.spclient().get_episode_metadata(episode_id).await
65+
session.spclient().get_episode_metadata(episode_uri).await
6666
}
6767

6868
fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {

metadata/src/show.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ impl Metadata for Show {
3737
type Message = protocol::metadata::Show;
3838

3939
async fn request(session: &Session, show_uri: &SpotifyUri) -> RequestResult {
40-
let SpotifyUri::Show { id: show_id } = show_uri else {
40+
let SpotifyUri::Show { .. } = show_uri else {
4141
return Err(Error::invalid_argument("show_uri"));
4242
};
4343

44-
session.spclient().get_show_metadata(show_id).await
44+
session.spclient().get_show_metadata(show_uri).await
4545
}
4646

4747
fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {

metadata/src/track.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ impl Metadata for Track {
5959
type Message = protocol::metadata::Track;
6060

6161
async fn request(session: &Session, track_uri: &SpotifyUri) -> RequestResult {
62-
let SpotifyUri::Track { id: track_id } = track_uri else {
62+
let SpotifyUri::Track { .. } = track_uri else {
6363
return Err(Error::invalid_argument("track_uri"));
6464
};
6565

66-
session.spclient().get_track_metadata(track_id).await
66+
session.spclient().get_track_metadata(track_uri).await
6767
}
6868

6969
fn parse(msg: &Self::Message, _: &SpotifyUri) -> Result<Self, Error> {

0 commit comments

Comments
 (0)