@@ -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} ;
3537use hyper_util:: client:: legacy:: ResponseFuture ;
36- use protobuf:: { Enum , Message , MessageFull } ;
38+ use protobuf:: { Enum , EnumOrUnknown , Message , MessageFull } ;
3739use rand:: RngCore ;
3840use serde:: Serialize ;
3941use 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 ) ]
7066pub 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
7775impl 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
0 commit comments