use json::{self, JsonValue};
use reqwest::header::{ORIGIN, REFERER, USER_AGENT};
use reqwest::{self, Client};
use std::io::Read;
use std::net::TcpListener;
use std::sync::Mutex;
const HEADER_UA: &str = "Mozilla/5.0 (Windows; rv:50.0) Gecko/20100101 Firefox/50.0";
const HEADER_ORIGIN_SCHEME: &str = "https";
const HEADER_ORIGIN_HOST: &str = "embed.spotify.com";
const URL_EMBED: &str = "https://embed.spotify.com";
const URL_TOKEN: &str = "https://open.spotify.com/token";
const URL_LOCAL: &str = "http://spotifyrs.spotilocal.com";
const PORT_START: u16 = 4370;
const PORT_END: u16 = 4399;
const REQUEST_CSRF: &str = "simplecsrf/token.json";
const REQUEST_STATUS: &str = "remote/status.json";
const REQUEST_PLAY: &str = "remote/play.json";
const REQUEST_OPEN: &str = "remote/open.json";
const REQUEST_PAUSE: &str = "remote/pause.json";
const REFERAL_TRACK: &str = "track/4uLU6hMCjMI75M1A2tKUQC";
type Result<T> = ::std::result::Result<T, InternalSpotifyError>;
#[derive(Debug)]
pub enum InternalSpotifyError {
ReqwestError(reqwest::Error),
JSONParseError(json::Error),
InvalidOAuthToken,
InvalidCSRFToken,
IOError(::std::io::Error),
}
pub struct SpotifyConnector {
client: Mutex<Client>,
oauth_token: String,
csrf_token: String,
port: i32,
}
impl SpotifyConnector {
pub fn connect_new() -> Result<SpotifyConnector> {
let client = Client::new();
let mut connector = SpotifyConnector {
client: Mutex::new(client),
oauth_token: String::default(),
csrf_token: String::default(),
port: 0, };
connector.update_port();
connector.start_spotify()?;
connector.oauth_token = match connector.fetch_oauth_token() {
Ok(result) => result,
Err(error) => return Err(error),
};
connector.csrf_token = match connector.fetch_csrf_token() {
Ok(result) => result,
Err(error) => return Err(error),
};
Ok(connector)
}
fn update_port(&mut self) {
for port in PORT_START..PORT_END {
if TcpListener::bind(("127.0.0.1", port)).is_err() {
self.port = port as i32;
return;
}
}
}
fn get_local_url(&self) -> String {
format!("{}:{}", URL_LOCAL, self.port)
}
fn start_spotify(&self) -> Result<bool> {
match self.query(&self.get_local_url(), REQUEST_OPEN, false, false, None) {
Ok(result) => Ok(result["running"] == true),
Err(error) => Err(error),
}
}
fn fetch_oauth_token(&self) -> Result<String> {
let json = match self.query(URL_TOKEN, "", false, false, None) {
Ok(result) => result,
Err(error) => return Err(error),
};
match json["t"].as_str() {
Some(token) => Ok(token.to_owned()),
None => Err(InternalSpotifyError::InvalidOAuthToken),
}
}
fn fetch_csrf_token(&self) -> Result<String> {
let json = match self.query(&self.get_local_url(), REQUEST_CSRF, false, false, None) {
Ok(result) => result,
Err(error) => return Err(error),
};
match json["token"].as_str() {
Some(token) => Ok(token.to_owned()),
None => Err(InternalSpotifyError::InvalidCSRFToken),
}
}
pub fn fetch_status_json(&self) -> Result<JsonValue> {
self.query(&self.get_local_url(), REQUEST_STATUS, true, true, None)
}
pub fn request_play(&self, track: String) -> bool {
let params = vec![format!("uri={0}", track)];
self.query(
&self.get_local_url(),
REQUEST_PLAY,
true,
true,
Some(params),
)
.is_ok()
}
pub fn request_pause(&self, pause: bool) -> bool {
let params = vec![format!("pause={}", pause)];
self.query(
&self.get_local_url(),
REQUEST_PAUSE,
true,
true,
Some(params),
)
.is_ok()
}
fn query(
&self,
base: &str,
query: &str,
with_oauth: bool,
with_csrf: bool,
params: Option<Vec<String>>,
) -> Result<JsonValue> {
let timestamp = time::now_utc().to_timespec().sec;
let arguments = {
let mut arguments = String::new();
if !query.contains('?') {
arguments.push('?');
}
arguments.push_str("&ref=&cors=");
arguments.push_str(format!("&_={}", timestamp).as_ref());
if with_oauth {
arguments.push_str(format!("&oauth={}", self.oauth_token).as_ref());
}
if with_csrf {
arguments.push_str(format!("&csrf={}", self.csrf_token).as_ref());
}
if let Some(params) = params {
for elem in params {
arguments.push_str(format!("&{}", elem).as_ref());
}
}
arguments
};
let url = format!("{}/{}{}", base, query, arguments);
let response = {
let mut content = String::new();
let mut resp = match self
.client
.lock()
.unwrap()
.get::<&str>(url.as_ref())
.header(USER_AGENT, HEADER_UA)
.header(
ORIGIN,
format!("{}://{}", HEADER_ORIGIN_SCHEME, HEADER_ORIGIN_HOST),
)
.header(REFERER, format!("{}/{}", URL_EMBED, REFERAL_TRACK))
.send()
{
Ok(result) => result,
Err(error) => return Err(InternalSpotifyError::ReqwestError(error)),
};
match resp.read_to_string(&mut content) {
Ok(_) => content,
Err(error) => return Err(InternalSpotifyError::IOError(error)),
}
};
match json::parse(response.as_ref()) {
Ok(result) => Ok(result),
Err(error) => Err(InternalSpotifyError::JSONParseError(error)),
}
}
}