1pub mod accessory;
11pub mod ad;
12pub mod auth;
13pub mod bookmark;
14mod crypt;
15pub mod device;
16pub mod errors;
17pub mod music;
18pub mod station;
19pub mod test;
20pub mod track;
21pub mod user;
22
23use std::collections::HashMap;
24use std::fmt::Debug;
25
26use serde::{Deserialize, Serialize};
27use serde_json;
28
29use crate::errors::Error;
30use crate::json::auth::{PartnerLogin, PartnerLoginResponse};
31use crate::json::errors::{JsonError, JsonErrorKind};
32
33#[derive(Debug, Clone)]
35pub struct PandoraSession {
36 client: reqwest::Client,
37 endpoint_url: url::Url,
38 tokens: SessionTokens,
39 json: serde_json::value::Value,
40 args: std::collections::BTreeMap<String, String>,
41 encrypted: bool,
42}
43
44impl PandoraSession {
45 pub fn new<T: ToEncryptionTokens, E: ToEndpoint>(
47 client: Option<reqwest::Client>,
48 to_encryption_tokens: &T,
49 to_endpoint: &E,
50 ) -> Self {
51 Self {
52 client: client.unwrap_or_default(),
53 endpoint_url: to_endpoint.to_endpoint_url(),
54 tokens: SessionTokens::new(to_encryption_tokens),
55 json: serde_json::value::Value::Object(serde_json::map::Map::new()),
56 args: std::collections::BTreeMap::new(),
57 encrypted: false,
58 }
59 }
60
61 pub fn copy_session(&self) -> Self {
64 Self {
65 client: self.client.clone(),
66 endpoint_url: self.endpoint_url.clone(),
67 tokens: self.tokens.clone(),
68 json: serde_json::value::Value::Object(serde_json::map::Map::new()),
69 args: std::collections::BTreeMap::new(),
70 encrypted: false,
71 }
72 }
73
74 pub fn http_client(&self) -> &reqwest::Client {
76 &self.client
77 }
78
79 pub fn endpoint<E: ToEndpoint>(&mut self, to_endpoint: E) -> &mut Self {
81 self.endpoint_url = to_endpoint.to_endpoint_url();
82 self
83 }
84
85 pub fn endpoint_mut<E: ToEndpoint>(&mut self) -> &mut url::Url {
87 &mut self.endpoint_url
88 }
89
90 pub fn update_partner_tokens<T: ToPartnerTokens>(&mut self, to_partner_tokens: &T) {
92 self.tokens.update_partner_tokens(to_partner_tokens);
93 }
94
95 pub fn update_user_tokens<T: ToUserTokens>(&mut self, to_user_tokens: &T) {
97 self.tokens.update_user_tokens(to_user_tokens);
98 }
99
100 pub fn session_tokens(&self) -> &SessionTokens {
103 &self.tokens
104 }
105
106 pub fn session_tokens_mut(&mut self) -> &mut SessionTokens {
109 &mut self.tokens
110 }
111
112 pub fn json(&mut self, json: serde_json::value::Value) -> &mut Self {
117 self.json = json;
118 self
119 }
120
121 pub fn json_mut(&mut self) -> &mut serde_json::value::Value {
123 &mut self.json
124 }
125
126 pub fn arg(&mut self, key: &str, value: &str) -> &mut Self {
128 self.args.insert(key.to_string(), value.to_string());
129 self
130 }
131
132 pub fn encrypted(&mut self) -> &mut Self {
135 self.encrypted = true;
136 self
137 }
138
139 fn add_session_tokens_to_args(&mut self) {
141 if let Some(auth_token) = self
143 .tokens
144 .user_token
145 .clone()
146 .or_else(|| self.tokens.partner_token.clone())
147 {
148 self.arg("auth_token", &auth_token);
149 }
150 if let Some(partner_id) = self.tokens.partner_id.clone() {
151 self.arg("partner_id", &partner_id);
152 }
153 if let Some(user_id) = self.tokens.user_id.clone() {
154 self.arg("user_id", &user_id);
155 }
156 }
157
158 fn add_session_tokens_to_json(&mut self) {
160 let json_obj = self
161 .json
162 .as_object_mut()
163 .expect("Programming Error accessing API request json for modification.");
164 if let Some(partner_auth_token) = self.tokens.partner_token.clone() {
165 json_obj.insert(
166 "partnerAuthToken".to_string(),
167 serde_json::Value::String(partner_auth_token),
168 );
169 }
170 if let Some(user_auth_token) = self.tokens.user_token.clone() {
171 json_obj.insert(
172 "userAuthToken".to_string(),
173 serde_json::Value::String(user_auth_token),
174 );
175 }
176
177 if let Some(sync_time) = self.tokens.sync_time {
178 json_obj.insert("syncTime".to_string(), serde_json::Value::from(sync_time));
179 }
180 }
181
182 pub fn build_request(
191 &self,
192 method: &str,
193 mut json: serde_json::value::Value,
194 encrypted: bool,
195 ) -> reqwest::RequestBuilder {
196 let mut args = std::collections::BTreeMap::new();
197 args.insert("method".to_string(), method.to_string());
198 if let Some(auth_token) = self
199 .tokens
200 .user_token
201 .as_ref()
202 .or(self.tokens.partner_token.as_ref())
203 {
204 args.insert("auth_token".to_string(), auth_token.clone());
205 }
206 if let Some(partner_id) = self.tokens.partner_id.as_ref() {
207 args.insert("partner_id".to_string(), partner_id.clone());
208 }
209 if let Some(user_id) = self.tokens.user_id.as_ref() {
210 args.insert("user_id".to_string(), user_id.clone());
211 }
212
213 let mut url = self.endpoint_url.clone();
214 url.query_pairs_mut().extend_pairs(&args);
215
216 let json_obj = json
217 .as_object_mut()
218 .expect("Programming Error: API request json must be an object.");
219 if let Some(partner_auth_token) = self.tokens.partner_token.as_ref() {
220 json_obj.insert(
221 "partnerAuthToken".to_string(),
222 serde_json::Value::String(partner_auth_token.clone()),
223 );
224 }
225 if let Some(user_auth_token) = self.tokens.user_token.as_ref() {
226 json_obj.insert(
227 "userAuthToken".to_string(),
228 serde_json::Value::String(user_auth_token.clone()),
229 );
230 }
231 if let Some(sync_time) = self.tokens.sync_time {
232 json_obj.insert("syncTime".to_string(), serde_json::Value::from(sync_time));
233 }
234
235 let mut body = json.to_string();
236 if cfg!(test) {
237 log::debug!("Request body: {:?}", body);
238 }
239 if encrypted {
240 body = self.tokens.encrypt(&body);
241 if cfg!(test) {
242 log::debug!("Encrypted body: {:?}", body);
243 }
244 }
245
246 self.client.post(url).body(body)
247 }
248
249 pub fn build(&mut self) -> reqwest::RequestBuilder {
252 self.add_session_tokens_to_args();
253 let mut url: url::Url = self.endpoint_url.clone();
254 url.query_pairs_mut().extend_pairs(&self.args);
255
256 self.add_session_tokens_to_json();
257 let mut body: String = self.json.to_string();
258 if cfg!(test) {
259 log::debug!("Request body: {:?}", body);
260 }
261 if self.encrypted {
262 body = self.tokens.encrypt(&body);
263 if cfg!(test) {
264 log::debug!("Encrypted body: {:?}", body);
265 }
266 }
267
268 self.client.post(url).body(body)
269 }
270}
271
272#[derive(Debug, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct PandoraResponse<T> {
278 pub stat: PandoraStatus,
280 pub result: Option<T>,
282 pub message: Option<String>,
284 pub code: Option<u32>,
286}
287
288impl<T: serde::de::DeserializeOwned> From<PandoraResponse<T>>
289 for std::result::Result<T, JsonError>
290{
291 fn from(pandora_resp: PandoraResponse<T>) -> Self {
292 match pandora_resp {
293 PandoraResponse {
294 stat: PandoraStatus::Ok,
295 result: Some(result),
296 ..
297 } => Ok(result),
298 PandoraResponse {
299 stat: PandoraStatus::Ok,
300 result: None,
301 ..
302 } => {
303 let default_value = serde_json::json!({});
304 let deser = serde_json::from_value(default_value);
305 deser.map_err(|_| JsonError::new(None, Some(String::from("Invalid JSON content."))))
306 }
307 PandoraResponse { code, message, .. } => Err(JsonError::new(code, message)),
308 }
309 }
310}
311
312#[derive(Debug, Deserialize)]
314#[serde(rename_all = "camelCase")]
315pub enum PandoraStatus {
316 Ok,
318 Fail,
320}
321
322#[async_trait::async_trait]
328pub trait PandoraJsonApiRequest: serde::ser::Serialize {
329 type Response: Debug + serde::de::DeserializeOwned;
331 type Error: Debug
333 + From<serde_json::error::Error>
334 + From<reqwest::Error>
335 + From<JsonError>
336 + Send;
337
338 fn get_method(&self) -> String;
341
342 fn get_json(&self) -> std::result::Result<serde_json::value::Value, Self::Error> {
345 serde_json::to_value(self).map_err(Self::Error::from)
346 }
347
348 fn encrypt_request(&self) -> bool {
351 false
352 }
353
354 fn request(
357 &self,
358 session: &PandoraSession,
359 ) -> std::result::Result<reqwest::RequestBuilder, Self::Error> {
360 let method = self.get_method();
361 let json = self.get_json()?;
362 Ok(session.build_request(&method, json, self.encrypt_request()))
363 }
364
365 async fn response(
368 &self,
369 session: &mut PandoraSession,
370 ) -> std::result::Result<Self::Response, Self::Error> {
371 let response = self
372 .request(session)?
373 .send()
374 .await
375 .map_err(Self::Error::from)?;
376
377 let response_obj: PandoraResponse<Self::Response> = if cfg!(test) {
378 let response_body = response.text().await?;
381 if cfg!(test) {
382 log::debug!("Full response: {:?}", response_body);
383 }
384 serde_json::from_slice(response_body.as_bytes())?
385 } else {
386 response.json().await?
388 };
389
390 if cfg!(test) {
391 log::debug!("Json response: {:?}", response_obj);
392 }
393
394 let result: std::result::Result<Self::Response, JsonError> = response_obj.into();
395 match result {
397 Err(JsonError {
398 kind: JsonErrorKind::InvalidAuthToken,
399 message,
400 }) => {
401 session.session_tokens_mut().clear_partner_tokens();
402 session.session_tokens_mut().clear_user_tokens();
403 Err(JsonError {
404 kind: JsonErrorKind::InvalidAuthToken,
405 message,
406 })
407 }
408 Err(JsonError {
409 kind: JsonErrorKind::InsufficientConnectivity,
410 message,
411 }) => {
412 session.session_tokens_mut().clear_partner_tokens();
413 session.session_tokens_mut().clear_user_tokens();
414 Err(JsonError {
415 kind: JsonErrorKind::InsufficientConnectivity,
416 message,
417 })
418 }
419 res => res,
420 }
421 .map_err(Self::Error::from)
422 }
423}
424
425pub trait ToEndpoint: serde::ser::Serialize {
428 fn to_endpoint(&self) -> String;
430 fn to_endpoint_url(&self) -> url::Url {
432 url::Url::parse(&self.to_endpoint()).expect("Error parsing Pandora endpoint url.")
433 }
434}
435
436impl ToEndpoint for String {
437 fn to_endpoint(&self) -> String {
439 self.clone()
440 }
441}
442
443pub trait ToEncryptionTokens {
446 fn to_encrypt_key(&self) -> String;
448 fn encrypt(&self, data: &str) -> String {
450 crypt::encrypt(&self.to_encrypt_key(), data)
451 }
452 fn to_decrypt_key(&self) -> String;
454 fn decrypt(&self, hex_data: &str) -> Vec<u8> {
456 crypt::decrypt(&self.to_decrypt_key(), hex_data)
457 }
458}
459
460pub trait ToPartnerTokens {
463 fn to_partner_id(&self) -> Option<String>;
467
468 fn to_partner_token(&self) -> Option<String>;
472
473 fn to_sync_time(&self) -> Option<String>;
477}
478
479pub trait ToUserTokens {
482 fn to_user_id(&self) -> Option<String>;
486
487 fn to_user_token(&self) -> Option<String>;
491}
492
493pub trait ToStationToken: serde::ser::Serialize {
495 fn to_station_token(&self) -> String;
497}
498
499impl ToStationToken for String {
500 fn to_station_token(&self) -> String {
502 self.clone()
503 }
504}
505
506impl ToStationToken for &str {
507 fn to_station_token(&self) -> String {
509 (*self).to_string()
512 }
513}
514
515pub trait ToTrackingToken: serde::ser::Serialize {
517 fn to_ad_tracking_tokens(&self) -> String;
519}
520
521impl ToTrackingToken for String {
522 fn to_ad_tracking_tokens(&self) -> String {
524 self.clone()
525 }
526}
527
528impl ToTrackingToken for &str {
529 fn to_ad_tracking_tokens(&self) -> String {
531 (*self).to_string()
534 }
535}
536
537#[derive(Debug, Clone, Serialize)]
540#[serde(rename_all = "camelCase")]
541pub struct Partner {
542 pub username: String,
545 pub password: String,
548 pub device_model: String,
550 pub version: String,
552 #[serde(skip)]
554 pub encrypt_password: String,
555 #[serde(skip)]
557 pub decrypt_password: String,
558 #[serde(skip)]
562 pub endpoint_host: String,
563}
564
565impl Partner {
566 pub fn new_android() -> Self {
568 Self {
569 username: "android".to_string(),
570 password: "AC7IBG09A3DTSYM4R41UJWL07VLN8JI7".to_string(),
571 device_model: "android-generic".to_string(),
572 version: "5".to_string(),
573 decrypt_password: "R=U!LH$O2B#".to_string(),
574 encrypt_password: "6#26FRL$ZWD".to_string(),
575 endpoint_host: "tuner.pandora.com".to_string(),
576 }
577 }
578
579 pub fn new_ios() -> Self {
581 Self {
582 username: "iphone".to_string(),
583 password: "P2E4FC0EAD3*878N92B2CDp34I0B1@388137C".to_string(),
584 device_model: "IP01".to_string(),
585 version: "5".to_string(),
586 decrypt_password: "20zE1E47BE57$51".to_string(),
587 encrypt_password: "721^26xE22776".to_string(),
588 endpoint_host: "tuner.pandora.com".to_string(),
589 }
590 }
591
592 pub fn new_palm() -> Self {
594 Self {
595 username: "palm".to_string(),
596 password: "IUC7IBG09A3JTSYM4N11UJWL07VLH8JP0".to_string(),
597 device_model: "pre".to_string(),
598 version: "5".to_string(),
599 decrypt_password: "E#U$MY$O2B=".to_string(),
600 encrypt_password: "%526CBL$ZU3".to_string(),
601 endpoint_host: "tuner.pandora.com".to_string(),
602 }
603 }
604
605 pub fn new_windows_mobile() -> Self {
607 Self {
608 username: "winmo".to_string(),
609 password: "ED227E10a628EB0E8Pm825Dw7114AC39".to_string(),
610 device_model: "VERIZON_MOTOQ9C".to_string(),
611 version: "5".to_string(),
612 decrypt_password: "7D671jt0C5E5d251".to_string(),
613 encrypt_password: "v93C8C2s12E0EBD".to_string(),
614 endpoint_host: "tuner.pandora.com".to_string(),
615 }
616 }
617
618 pub fn new_desktop_air() -> Self {
620 Self {
621 username: "pandora one".to_string(),
622 password: "TVCKIBGS9AO9TSYLNNFUML0743LH82D".to_string(),
623 device_model: "D01".to_string(),
624 version: "5".to_string(),
625 decrypt_password: "U#IO$RZPAB%VX2".to_string(),
626 encrypt_password: "2%3WCL*JU$MP]4".to_string(),
627 endpoint_host: "internal-tuner.pandora.com".to_string(),
628 }
629 }
630
631 pub fn new_vista_widget() -> Self {
633 Self {
634 username: "windowsgadget".to_string(),
635 password: "EVCCIBGS9AOJTSYMNNFUML07VLH8JYP0".to_string(),
636 device_model: "WG01".to_string(),
637 version: "5".to_string(),
638 decrypt_password: "E#IO$MYZOAB%FVR2".to_string(),
639 encrypt_password: "%22CML*ZU$8YXP[1".to_string(),
640 endpoint_host: "internal-tuner.pandora.com".to_string(),
641 }
642 }
643
644 pub fn init_session(&self) -> PandoraSession {
647 PandoraSession::new(None, self, self)
648 }
649
650 pub fn to_partner_login(&self) -> PartnerLogin {
653 PartnerLogin {
654 username: self.username.clone(),
655 password: self.password.clone(),
656 device_model: self.device_model.clone(),
657 version: self.version.clone(),
658 optional: HashMap::new(),
659 }
660 }
661
662 pub async fn login(&self, session: &mut PandoraSession) -> Result<PartnerLoginResponse, Error> {
665 let response = self.to_partner_login().response(session).await?;
666 session.update_partner_tokens(&response);
667 Ok(response)
668 }
669}
670
671impl Default for Partner {
672 fn default() -> Self {
674 Self::new_android()
675 }
676}
677
678impl ToEncryptionTokens for Partner {
679 fn to_encrypt_key(&self) -> String {
680 self.encrypt_password.clone()
681 }
682
683 fn to_decrypt_key(&self) -> String {
684 self.decrypt_password.clone()
685 }
686}
687
688impl ToEndpoint for Partner {
689 fn to_endpoint(&self) -> String {
691 format!("https://{}/services/json", self.endpoint_host)
692 }
693}
694
695#[derive(Debug, Clone)]
698pub struct SessionTokens {
699 pub encrypt_key: String,
701 pub decrypt_key: String,
704 pub partner_id: Option<String>,
706 pub partner_token: Option<String>,
708 sync_time: Option<u64>,
715 local_time_base: Option<std::time::Instant>,
719 pub user_id: Option<String>,
721 pub user_token: Option<String>,
723}
724
725impl SessionTokens {
726 pub fn new<T: ToEncryptionTokens>(to_encryption_tokens: &T) -> Self {
729 Self {
730 encrypt_key: to_encryption_tokens.to_encrypt_key(),
731 decrypt_key: to_encryption_tokens.to_decrypt_key(),
732 partner_id: None,
733 partner_token: None,
734 sync_time: None,
735 local_time_base: None,
736 user_id: None,
737 user_token: None,
738 }
739 }
740
741 pub fn update_partner_tokens<T: ToPartnerTokens>(&mut self, to_partner_tokens: &T) {
744 self.partner_id = to_partner_tokens.to_partner_id();
745 self.partner_token = to_partner_tokens.to_partner_token();
746 if let Some(sync_time) = to_partner_tokens.to_sync_time() {
750 let sync_time_bytes: Vec<u8> =
751 self.decrypt(&sync_time).iter().skip(4).cloned().collect();
752 let sync_time_str = std::str::from_utf8(&sync_time_bytes).unwrap_or("0");
753 self.set_sync_time(sync_time_str.parse::<u64>().unwrap_or(0));
754 }
755 }
756
757 pub fn update_user_tokens<T: ToUserTokens>(&mut self, to_user_tokens: &T) {
760 self.user_id = to_user_tokens.to_user_id();
761 self.user_token = to_user_tokens.to_user_token();
762 }
763
764 pub fn set_sync_time(&mut self, sync_time: u64) {
768 self.local_time_base = Some(std::time::Instant::now());
769 self.sync_time = Some(sync_time);
770 }
771
772 pub fn clear_sync_time(&mut self) {
774 self.local_time_base = None;
775 self.sync_time = None;
776 }
777
778 pub fn get_sync_time(&self) -> Option<u64> {
781 self.sync_time
782 .and_then(|st| self.local_time_base.map(|ltb| ltb.elapsed().as_secs() + st))
783 }
784
785 pub fn clear_partner_tokens(&mut self) {
787 self.partner_id = None;
788 self.partner_token = None;
789 self.clear_sync_time();
790 }
791
792 pub fn clear_user_tokens(&mut self) {
794 self.user_id = None;
795 self.user_token = None;
796 }
797}
798
799impl ToEncryptionTokens for SessionTokens {
800 fn to_encrypt_key(&self) -> String {
802 self.encrypt_key.clone()
803 }
804 fn to_decrypt_key(&self) -> String {
806 self.decrypt_key.clone()
807 }
808}
809
810impl<T: ToEncryptionTokens> From<&T> for SessionTokens {
811 fn from(tokens: &T) -> Self {
812 Self::new(tokens)
813 }
814}
815
816#[derive(Debug, Clone, Deserialize)]
818#[serde(rename_all = "camelCase")]
819pub struct Timestamp {
820 _timezone_offset: u32,
822 time: i64,
824 _year: u32,
826 _month: u8,
828 _day: u8,
830 _hours: u8,
832 _minutes: u8,
834 _seconds: u8,
836 _date: u8,
838}
839
840impl From<Timestamp> for chrono::DateTime<chrono::Utc> {
841 fn from(ts: Timestamp) -> chrono::DateTime<chrono::Utc> {
842 let dt = chrono::DateTime::from_timestamp(ts.time, 0).expect("Invalid date/time timestamp");
848 let naive_utc = dt.naive_utc();
849 let offset = *dt.offset();
850 chrono::DateTime::from_naive_utc_and_offset(naive_utc, offset)
851 }
852}
853
854#[cfg(test)]
855mod tests {
856 use super::*;
857
858 use crate::errors::Error;
859 use crate::json::auth::user_login;
860
861 pub async fn session_login(partner: &Partner) -> Result<PandoraSession, Error> {
867 let mut session = partner.init_session();
868 let _partner_login = partner.login(&mut session).await?;
869
870 let test_username_raw = include_str!("../../test_username.txt");
871 let test_username = test_username_raw.trim();
872 let test_password_raw = include_str!("../../test_password.txt");
873 let test_password = test_password_raw.trim();
874
875 let user_login = user_login(&mut session, &test_username, &test_password).await?;
876 session.update_user_tokens(&user_login);
877 Ok(session)
878 }
879
880 #[tokio::test]
881 async fn partner_test() {
882 let partner = Partner::default();
883 let mut session = partner.init_session();
884 let partner_login = partner
885 .login(&mut session)
886 .await
887 .expect("Failed while performing partner login");
888 session.update_partner_tokens(&partner_login);
889 }
890}