Fix lyrics retrieval

This commit is contained in:
Roderick van Domburg 2021-11-27 08:30:51 +01:00
parent d19fd24074
commit e1b273b8a1
No known key found for this signature in database
GPG key ID: FE2585E713F9F30A
2 changed files with 45 additions and 21 deletions

View file

@ -1,3 +1,7 @@
use bytes::Bytes;
use http::header::HeaderValue;
use http::uri::InvalidUri;
use hyper::header::InvalidHeaderValue;
use hyper::{Body, Client, Request, Response, StatusCode}; use hyper::{Body, Client, Request, Response, StatusCode};
use hyper_proxy::{Intercept, Proxy, ProxyConnector}; use hyper_proxy::{Intercept, Proxy, ProxyConnector};
use hyper_rustls::HttpsConnector; use hyper_rustls::HttpsConnector;
@ -11,7 +15,7 @@ pub struct HttpClient {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum HttpClientError { pub enum HttpClientError {
#[error("could not parse request: {0}")] #[error("could not parse request: {0}")]
Parsing(#[from] http::uri::InvalidUri), Parsing(#[from] http::Error),
#[error("could not send request: {0}")] #[error("could not send request: {0}")]
Request(hyper::Error), Request(hyper::Error),
#[error("could not read response: {0}")] #[error("could not read response: {0}")]
@ -20,6 +24,18 @@ pub enum HttpClientError {
ProxyBuilder(#[from] std::io::Error), ProxyBuilder(#[from] std::io::Error),
} }
impl From<InvalidHeaderValue> for HttpClientError {
fn from(err: InvalidHeaderValue) -> Self {
Self::Parsing(err.into())
}
}
impl From<InvalidUri> for HttpClientError {
fn from(err: InvalidUri) -> Self {
Self::Parsing(err.into())
}
}
impl HttpClient { impl HttpClient {
pub fn new(proxy: Option<&Url>) -> Self { pub fn new(proxy: Option<&Url>) -> Self {
Self { Self {
@ -27,10 +43,17 @@ impl HttpClient {
} }
} }
pub async fn request(&self, req: Request<Body>) -> Result<Response<Body>, HttpClientError> { pub async fn request(&self, mut req: Request<Body>) -> Result<Response<Body>, HttpClientError> {
let connector = HttpsConnector::with_native_roots(); let connector = HttpsConnector::with_native_roots();
let uri = req.uri().clone(); let uri = req.uri().clone();
let headers_mut = req.headers_mut();
headers_mut.insert(
"User-Agent",
// Some features like lyrics are version-gated and require a "real" version string.
HeaderValue::from_str("Spotify/8.6.80 iOS/13.5 (iPhone11,2)")?,
);
let response = if let Some(url) = &self.proxy { let response = if let Some(url) = &self.proxy {
let uri = url.to_string().parse()?; let uri = url.to_string().parse()?;
let proxy = Proxy::new(Intercept::All, uri); let proxy = Proxy::new(Intercept::All, uri);
@ -58,7 +81,7 @@ impl HttpClient {
response response
} }
pub async fn request_body(&self, req: Request<Body>) -> Result<bytes::Bytes, HttpClientError> { pub async fn request_body(&self, req: Request<Body>) -> Result<Bytes, HttpClientError> {
let response = self.request(req).await?; let response = self.request(req).await?;
hyper::body::to_bytes(response.into_body()) hyper::body::to_bytes(response.into_body())
.await .await

View file

@ -1,11 +1,16 @@
use crate::apresolve::SocketAddress; use crate::apresolve::SocketAddress;
use crate::http_client::HttpClientError; use crate::http_client::HttpClientError;
use crate::mercury::MercuryError; use crate::mercury::MercuryError;
use crate::protocol; use crate::protocol::canvaz::EntityCanvazRequest;
use crate::spotify_id::SpotifyId; use crate::protocol::connect::PutStateRequest;
use crate::protocol::extended_metadata::BatchedEntityRequest;
use crate::spotify_id::{FileId, SpotifyId};
use bytes::Bytes;
use http::header::HeaderValue;
use hyper::header::InvalidHeaderValue; use hyper::header::InvalidHeaderValue;
use hyper::{Body, HeaderMap, Request}; use hyper::{Body, HeaderMap, Request};
use protobuf::Message;
use rand::Rng; use rand::Rng;
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
@ -17,7 +22,7 @@ component! {
} }
} }
pub type SpClientResult = Result<bytes::Bytes, SpClientError>; pub type SpClientResult = Result<Bytes, SpClientError>;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SpClientError { pub enum SpClientError {
@ -83,7 +88,7 @@ impl SpClient {
method: &str, method: &str,
endpoint: &str, endpoint: &str,
headers: Option<HeaderMap>, headers: Option<HeaderMap>,
message: &dyn protobuf::Message, message: &dyn Message,
) -> SpClientResult { ) -> SpClientResult {
let body = protobuf::text_format::print_to_string(message); let body = protobuf::text_format::print_to_string(message);
@ -126,7 +131,7 @@ impl SpClient {
} }
headers_mut.insert( headers_mut.insert(
"Authorization", "Authorization",
http::header::HeaderValue::from_str(&format!( HeaderValue::from_str(&format!(
"Bearer {}", "Bearer {}",
self.session() self.session()
.token_provider() .token_provider()
@ -186,7 +191,7 @@ impl SpClient {
pub async fn put_connect_state( pub async fn put_connect_state(
&self, &self,
connection_id: String, connection_id: String,
state: protocol::connect::PutStateRequest, state: PutStateRequest,
) -> SpClientResult { ) -> SpClientResult {
let endpoint = format!("/connect-state/v1/devices/{}", self.session().device_id()); let endpoint = format!("/connect-state/v1/devices/{}", self.session().device_id());
@ -223,10 +228,12 @@ impl SpClient {
} }
// TODO: Not working at the moment, always returns 400. // TODO: Not working at the moment, always returns 400.
pub async fn get_lyrics(&self, track_id: SpotifyId) -> SpClientResult { pub async fn get_lyrics(&self, track_id: SpotifyId, image_id: FileId) -> SpClientResult {
// /color-lyrics/v2/track/22L7bfCiAkJo5xGSQgmiIO/image/spotify:image:ab67616d0000b273d9194aa18fa4c9362b47464f?clientLanguage=en let endpoint = format!(
// https://spclient.wg.spotify.com/color-lyrics/v2/track/{track_id}/image/spotify:image:{image_id}?clientLanguage=en "/color-lyrics/v2/track/{}/image/spotify:image:{}",
let endpoint = format!("/color-lyrics/v2/track/{}", track_id.to_base16()); track_id.to_base16(),
image_id
);
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/json".parse()?); headers.insert("Content-Type", "application/json".parse()?);
@ -235,19 +242,13 @@ impl SpClient {
} }
// TODO: Find endpoint for newer canvas.proto and upgrade to that. // TODO: Find endpoint for newer canvas.proto and upgrade to that.
pub async fn get_canvases( pub async fn get_canvases(&self, request: EntityCanvazRequest) -> SpClientResult {
&self,
request: protocol::canvaz::EntityCanvazRequest,
) -> SpClientResult {
let endpoint = "/canvaz-cache/v0/canvases"; let endpoint = "/canvaz-cache/v0/canvases";
self.protobuf_request("POST", endpoint, None, &request) self.protobuf_request("POST", endpoint, None, &request)
.await .await
} }
pub async fn get_extended_metadata( pub async fn get_extended_metadata(&self, request: BatchedEntityRequest) -> SpClientResult {
&self,
request: protocol::extended_metadata::BatchedEntityRequest,
) -> SpClientResult {
let endpoint = "/extended-metadata/v0/extended-metadata"; let endpoint = "/extended-metadata/v0/extended-metadata";
self.protobuf_request("POST", endpoint, None, &request) self.protobuf_request("POST", endpoint, None, &request)
.await .await