1use json::{self, JsonValue};
2use reqwest::header::{ORIGIN, REFERER, USER_AGENT};
3use reqwest::{self, Client};
4use std::io::Read;
5use std::net::TcpListener;
6use std::sync::Mutex;
7
8const HEADER_UA: &str = "Mozilla/5.0 (Windows; rv:50.0) Gecko/20100101 Firefox/50.0";
10const HEADER_ORIGIN_SCHEME: &str = "https";
11const HEADER_ORIGIN_HOST: &str = "embed.spotify.com";
12
13const URL_EMBED: &str = "https://embed.spotify.com";
15const URL_TOKEN: &str = "https://open.spotify.com/token";
16const URL_LOCAL: &str = "http://spotifyrs.spotilocal.com";
17
18const PORT_START: u16 = 4370;
20const PORT_END: u16 = 4399;
21
22const REQUEST_CSRF: &str = "simplecsrf/token.json";
24const REQUEST_STATUS: &str = "remote/status.json";
25const REQUEST_PLAY: &str = "remote/play.json";
26const REQUEST_OPEN: &str = "remote/open.json";
27const REQUEST_PAUSE: &str = "remote/pause.json";
28
29const REFERAL_TRACK: &str = "track/4uLU6hMCjMI75M1A2tKUQC";
31
32type Result<T> = ::std::result::Result<T, InternalSpotifyError>;
34
35#[derive(Debug)]
37pub enum InternalSpotifyError {
38 ReqwestError(reqwest::Error),
40 JSONParseError(json::Error),
42 InvalidOAuthToken,
44 InvalidCSRFToken,
46 IOError(::std::io::Error),
48}
49
50pub struct SpotifyConnector {
52 client: Mutex<Client>,
54 oauth_token: String,
56 csrf_token: String,
58 port: i32,
60}
61
62impl SpotifyConnector {
64 pub fn connect_new() -> Result<SpotifyConnector> {
67 let client = Client::new();
69 let mut connector = SpotifyConnector {
71 client: Mutex::new(client),
72 oauth_token: String::default(),
73 csrf_token: String::default(),
74 port: 0, };
76 connector.update_port();
77 connector.start_spotify()?;
79 connector.oauth_token = match connector.fetch_oauth_token() {
81 Ok(result) => result,
82 Err(error) => return Err(error),
83 };
84 connector.csrf_token = match connector.fetch_csrf_token() {
86 Ok(result) => result,
87 Err(error) => return Err(error),
88 };
89 Ok(connector)
91 }
92 fn update_port(&mut self) {
94 for port in PORT_START..PORT_END {
95 if TcpListener::bind(("127.0.0.1", port)).is_err() {
96 self.port = port as i32;
97 return;
98 }
99 }
100 }
101 fn get_local_url(&self) -> String {
103 format!("{}:{}", URL_LOCAL, self.port)
104 }
105 fn start_spotify(&self) -> Result<bool> {
107 match self.query(&self.get_local_url(), REQUEST_OPEN, false, false, None) {
108 Ok(result) => Ok(result["running"] == true),
109 Err(error) => Err(error),
110 }
111 }
112 fn fetch_oauth_token(&self) -> Result<String> {
114 let json = match self.query(URL_TOKEN, "", false, false, None) {
115 Ok(result) => result,
116 Err(error) => return Err(error),
117 };
118 match json["t"].as_str() {
119 Some(token) => Ok(token.to_owned()),
120 None => Err(InternalSpotifyError::InvalidOAuthToken),
121 }
122 }
123 fn fetch_csrf_token(&self) -> Result<String> {
125 let json = match self.query(&self.get_local_url(), REQUEST_CSRF, false, false, None) {
126 Ok(result) => result,
127 Err(error) => return Err(error),
128 };
129 match json["token"].as_str() {
130 Some(token) => Ok(token.to_owned()),
131 None => Err(InternalSpotifyError::InvalidCSRFToken),
132 }
133 }
134 pub fn fetch_status_json(&self) -> Result<JsonValue> {
136 self.query(&self.get_local_url(), REQUEST_STATUS, true, true, None)
137 }
138 pub fn request_play(&self, track: String) -> bool {
140 let params = vec![format!("uri={0}", track)];
141 self.query(
142 &self.get_local_url(),
143 REQUEST_PLAY,
144 true,
145 true,
146 Some(params),
147 )
148 .is_ok()
149 }
150 pub fn request_pause(&self, pause: bool) -> bool {
152 let params = vec![format!("pause={}", pause)];
153 self.query(
154 &self.get_local_url(),
155 REQUEST_PAUSE,
156 true,
157 true,
158 Some(params),
159 )
160 .is_ok()
161 }
162 fn query(
165 &self,
166 base: &str,
167 query: &str,
168 with_oauth: bool,
169 with_csrf: bool,
170 params: Option<Vec<String>>,
171 ) -> Result<JsonValue> {
172 let timestamp = time::now_utc().to_timespec().sec;
173 let arguments = {
174 let mut arguments = String::new();
175 if !query.contains('?') {
176 arguments.push('?');
177 }
178 arguments.push_str("&ref=&cors=");
179 arguments.push_str(format!("&_={}", timestamp).as_ref());
180 if with_oauth {
181 arguments.push_str(format!("&oauth={}", self.oauth_token).as_ref());
182 }
183 if with_csrf {
184 arguments.push_str(format!("&csrf={}", self.csrf_token).as_ref());
185 }
186 if let Some(params) = params {
187 for elem in params {
188 arguments.push_str(format!("&{}", elem).as_ref());
189 }
190 }
191 arguments
192 };
193 let url = format!("{}/{}{}", base, query, arguments);
194 let response = {
195 let mut content = String::new();
196 let mut resp = match self
197 .client
198 .lock()
199 .unwrap()
200 .get::<&str>(url.as_ref())
201 .header(USER_AGENT, HEADER_UA)
202 .header(
203 ORIGIN,
204 format!("{}://{}", HEADER_ORIGIN_SCHEME, HEADER_ORIGIN_HOST),
205 )
206 .header(REFERER, format!("{}/{}", URL_EMBED, REFERAL_TRACK))
207 .send()
208 {
209 Ok(result) => result,
210 Err(error) => return Err(InternalSpotifyError::ReqwestError(error)),
211 };
212 match resp.read_to_string(&mut content) {
213 Ok(_) => content,
214 Err(error) => return Err(InternalSpotifyError::IOError(error)),
215 }
216 };
217 match json::parse(response.as_ref()) {
218 Ok(result) => Ok(result),
219 Err(error) => Err(InternalSpotifyError::JSONParseError(error)),
220 }
221 }
222}