diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index 9ff23cf3..bcb7bfe1 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -17,7 +17,11 @@ pub struct APResolveData { ap_list: Vec, } -fn apresolve(handle: &Handle, proxy: &Option) -> Box> { +fn apresolve( + handle: &Handle, + proxy: &Option, + ap_port: &Option, +) -> Box> { let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL"); let use_proxy = proxy.is_some(); @@ -53,9 +57,15 @@ fn apresolve(handle: &Handle, proxy: &Option) -> Box(&body).chain_err(|| "invalid JSON")); + let p = ap_port.clone(); + let ap = data.and_then(move |data| { let mut aps = data.ap_list.iter().filter(|ap| { - if use_proxy { + if p.is_some() { + Uri::from_str(ap) + .ok() + .map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap())) + } else if use_proxy { // It is unlikely that the proxy will accept CONNECT on anything other than 443. Uri::from_str(ap) .ok() @@ -75,11 +85,12 @@ fn apresolve(handle: &Handle, proxy: &Option) -> Box( handle: &Handle, proxy: &Option, + ap_port: &Option, ) -> Box> where E: 'static, { - let ap = apresolve(handle, proxy).or_else(|e| { + let ap = apresolve(handle, proxy, ap_port).or_else(|e| { warn!("Failed to resolve Access Point: {}", e.description()); warn!("Using fallback \"{}\"", AP_FALLBACK); Ok(AP_FALLBACK.into()) diff --git a/core/src/config.rs b/core/src/config.rs index 7baff40b..283b7c83 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -10,6 +10,7 @@ pub struct SessionConfig { pub user_agent: String, pub device_id: String, pub proxy: Option, + pub ap_port: Option, } impl Default for SessionConfig { @@ -19,6 +20,7 @@ impl Default for SessionConfig { user_agent: version::version_string(), device_id: device_id, proxy: None, + ap_port: None, } } } diff --git a/core/src/session.rs b/core/src/session.rs index 335cf0e3..931b60c7 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -51,7 +51,7 @@ impl Session { cache: Option, handle: Handle, ) -> Box> { - let access_point = apresolve_or_fallback::(&handle, &config.proxy); + let access_point = apresolve_or_fallback::(&handle, &config.proxy, &config.ap_port); let handle_ = handle.clone(); let proxy = config.proxy.clone(); diff --git a/docs/connection.md b/docs/connection.md index 8bc8b480..e64fac7f 100644 --- a/docs/connection.md +++ b/docs/connection.md @@ -6,7 +6,7 @@ An AP is randomly picked from that list to connect to. The connection is done using a bare TCP socket. Despite many APs using ports 80 and 443, neither HTTP nor TLS are used to connect. -If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:80` is used as a fallback. +If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:443` is used as a fallback. ## Connection Hello The first 3 packets exchanged are unencrypted, and have the following format : diff --git a/examples/play.rs b/examples/play.rs index ac261978..87f68825 100644 --- a/examples/play.rs +++ b/examples/play.rs @@ -33,7 +33,8 @@ fn main() { let backend = audio_backend::find(None).unwrap(); println!("Connecting .."); - let session = core.run(Session::connect(session_config, credentials, None, handle)) + let session = core + .run(Session::connect(session_config, credentials, None, handle)) .unwrap(); let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None)); diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index a18a6940..a077f37c 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -110,13 +110,15 @@ impl Metadata for Track { fn parse(msg: &Self::Message, session: &Session) -> Self { let country = session.country(); - let artists = msg.get_artist() + let artists = msg + .get_artist() .iter() .filter(|artist| artist.has_gid()) .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap()) .collect::>(); - let files = msg.get_file() + let files = msg + .get_file() .iter() .filter(|file| file.has_file_id()) .map(|file| { @@ -133,7 +135,8 @@ impl Metadata for Track { album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(), artists: artists, files: files, - alternatives: msg.get_alternative() + alternatives: msg + .get_alternative() .iter() .map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap()) .collect(), @@ -150,20 +153,23 @@ impl Metadata for Album { } fn parse(msg: &Self::Message, _: &Session) -> Self { - let artists = msg.get_artist() + let artists = msg + .get_artist() .iter() .filter(|artist| artist.has_gid()) .map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap()) .collect::>(); - let tracks = msg.get_disc() + let tracks = msg + .get_disc() .iter() .flat_map(|disc| disc.get_track()) .filter(|track| track.has_gid()) .map(|track| SpotifyId::from_raw(track.get_gid()).unwrap()) .collect::>(); - let covers = msg.get_cover_group() + let covers = msg + .get_cover_group() .get_image() .iter() .filter(|image| image.has_file_id()) @@ -194,7 +200,8 @@ impl Metadata for Artist { fn parse(msg: &Self::Message, session: &Session) -> Self { let country = session.country(); - let top_tracks: Vec = match msg.get_top_track() + let top_tracks: Vec = match msg + .get_top_track() .iter() .find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country)) { diff --git a/playback/src/player.rs b/playback/src/player.rs index dd994235..ab1a8abe 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs @@ -557,7 +557,8 @@ impl PlayerInternal { } }; - let key = self.session + let key = self + .session .audio_key() .request(track.id, file_id) .wait() @@ -599,7 +600,8 @@ impl Drop for PlayerInternal { impl ::std::fmt::Debug for PlayerCommand { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match *self { - PlayerCommand::Load(track, play, position, _) => f.debug_tuple("Load") + PlayerCommand::Load(track, play, position, _) => f + .debug_tuple("Load") .field(&track) .field(&play) .field(&position) diff --git a/protocol/files.rs b/protocol/files.rs index fe1c8fec..39b6b5ee 100644 --- a/protocol/files.rs +++ b/protocol/files.rs @@ -1,6 +1,6 @@ // Autogenerated by build.sh -pub const FILES : &'static [(&'static str, u32)] = &[ +pub const FILES: &'static [(&'static str, u32)] = &[ ("proto/authentication.proto", 2098196376), ("proto/keyexchange.proto", 451735664), ("proto/mercury.proto", 709993906), diff --git a/src/main.rs b/src/main.rs index 3665e615..a18d7b5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,6 +129,7 @@ fn setup(args: &[String]) -> Setup { .optopt("u", "username", "Username to sign in with", "USERNAME") .optopt("p", "password", "Password", "PASSWORD") .optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY") + .optopt("", "ap-port", "Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070", "AP_PORT") .optflag("", "disable-discovery", "Disable discovery mode") .optopt( "", @@ -255,20 +256,23 @@ fn setup(args: &[String]) -> Setup { proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map( |s| { match Url::parse(&s) { - Ok(url) => { - if url.host().is_none() || url.port().is_none() { - panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed"); + Ok(url) => { + if url.host().is_none() || url.port().is_none() { + panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed"); + } + + if url.scheme() != "http" { + panic!("Only unsecure http:// proxies are supported"); + } + url + }, + Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err) } - - if url.scheme() != "http" { - panic!("Only unsecure http:// proxies are supported"); - } - url - }, - Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err) - } }, ), + ap_port: matches + .opt_str("ap-port") + .map(|port| port.parse::().expect("Invalid port")), } };