pandora_api/json/user.rs
1/*!
2User support methods.
3
4User Settings
5The following settings are currently read/writeable:
6
7| Name | Type | Description |
8| gender | string | Male or Female |
9| birthYear | int | |
10| zipCode | string | |
11| isProfilePrivate | boolean | |
12| enableComments | boolean | |
13| emailOptIn | boolean | |
14| emailComments | boolean | |
15| emailNewFollowers | boolean | |
16| isExplicitContentFilterEnabled | boolean | |
17| isExplicitContentFilterPINProtected | boolean | |
18| newUsername | string | |
19| newPassword | string | |
20| facebookAutoShareEnabled | boolean | |
21| autoShareTrackPlay | boolean | |
22| autoShareLikes | boolean | |
23| autoShareFollows | boolean | |
24| facebookSettingChecksum | boolean | |
25*/
26// SPDX-License-Identifier: MIT AND WTFPL
27use std::collections::HashMap;
28use std::convert::TryFrom;
29
30use pandora_api_derive::PandoraJsonRequest;
31use serde::{Deserialize, Serialize};
32
33use crate::errors::Error;
34use crate::json::{PandoraJsonApiRequest, PandoraSession, Timestamp};
35
36/// Valid values for the gender is user account settings. The documentation
37/// suggests that the only valid values are "Male", "Female".
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub enum UserGender {
40 /// User gender is male.
41 Male,
42 /// User gender is female.
43 Female,
44}
45
46impl ToString for UserGender {
47 fn to_string(&self) -> String {
48 match self {
49 UserGender::Male => "Male".to_string(),
50 UserGender::Female => "Female".to_string(),
51 }
52 }
53}
54
55impl TryFrom<&str> for UserGender {
56 type Error = Error;
57 fn try_from(fmt: &str) -> std::result::Result<Self, Self::Error> {
58 match fmt {
59 "Male" => Ok(UserGender::Male),
60 "Female" => Ok(UserGender::Female),
61 x => Err(Self::Error::InvalidUserGender(x.to_string())),
62 }
63 }
64}
65
66impl TryFrom<String> for UserGender {
67 type Error = Error;
68 fn try_from(fmt: String) -> std::result::Result<Self, Self::Error> {
69 Self::try_from(fmt.as_str())
70 }
71}
72
73/// **Unsupported!**
74/// Undocumented method
75/// [user.accountMessageDismissed()](https://6xq.net/pandora-apidoc/json/methods/)
76pub struct AccountMessageDismissedUnsupported {}
77
78/// **Unsupported!**
79/// Undocumented method
80/// [user.acknowledgeSubscriptionExpiration()](https://6xq.net/pandora-apidoc/json/methods/)
81pub struct AcknowledgeSubscriptionExpirationUnsupported {}
82
83/// **Unsupported!**
84/// Undocumented method
85/// [user.associateDevice()](https://6xq.net/pandora-apidoc/json/methods/)
86pub struct AssociateDeviceUnsupported {}
87
88/// **Unsupported!**
89/// Undocumented method
90/// [user.authorizeFacebook()](https://6xq.net/pandora-apidoc/json/methods/)
91pub struct AuthorizeFacebookUnsupported {}
92
93/// Returns whether a user is subscribed or if they can subscribe to Pandora One. Can be useful to determine which Partner password to use.
94///
95/// | Name | Type | Description |
96/// | iapVendor | string | (optional) |
97#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
98#[pandora_request(encrypted = true)]
99#[serde(rename_all = "camelCase")]
100pub struct CanSubscribe {
101 /// Optional parameters on the call
102 #[serde(flatten)]
103 pub optional: HashMap<String, serde_json::value::Value>,
104}
105
106impl CanSubscribe {
107 /// Create a new CanSubscribe with some values. All Optional fields are
108 /// set to None.
109 pub fn new() -> Self {
110 Self::default()
111 }
112
113 /// Convenience function for setting string flags in the request. (Chaining call)
114 pub fn and_string_option(mut self, option: &str, value: &str) -> Self {
115 self.optional
116 .insert(option.to_string(), serde_json::value::Value::from(value));
117 self
118 }
119
120 /// Set the name of the in-app purchases vendor. (Chaining call)
121 pub fn iap_vendor(self, value: &str) -> Self {
122 self.and_string_option("iapVendor", value)
123 }
124}
125
126/// | Name | Type | Description |
127/// | canSubscribe | boolean | false if user is a Pandora One subscriber |
128/// | isSubscriber | boolean | true if user is a Pandora One Subscriber |
129/// ``` json
130/// {
131/// "stat": "ok",
132/// "result": {
133/// "canSubscribe": false,
134/// "isSubscriber": true
135/// }
136/// }
137/// ```
138#[derive(Debug, Clone, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct CanSubscribeResponse {
141 /// Whether it is valid for this account to subscribe to Pandora One.
142 pub can_subscribe: bool,
143 /// Whether this account has a valid subscription to Pandora One.
144 pub is_subscriber: bool,
145}
146
147/// Convenience function to do a basic canSubscribe call.
148pub async fn can_subscribe(session: &mut PandoraSession) -> Result<CanSubscribeResponse, Error> {
149 CanSubscribe::new().response(session).await
150}
151
152/// | Name | Type Description |
153/// | currentUsername | string | |
154/// | currentPassword | string | |
155/// | userInitiatedChange | boolean | optional |
156/// | includeFacebook | boolean | optional |
157/// Additionally keys listed in Settings are permitted in the request body.
158#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
159#[pandora_request(encrypted = true)]
160#[serde(rename_all = "camelCase")]
161pub struct ChangeSettings {
162 /// Current credentials must be provided with the request.
163 pub current_username: String,
164 /// Current credentials must be provided with the request.
165 pub current_password: String,
166 /// Optional parameters on the call
167 #[serde(flatten)]
168 pub optional: HashMap<String, serde_json::value::Value>,
169}
170
171impl ChangeSettings {
172 /// Create a new ChangeSettings with some values. All Optional fields are
173 /// set to None.
174 pub fn new(current_username: &str, current_password: &str) -> Self {
175 Self {
176 current_username: current_username.to_string(),
177 current_password: current_password.to_string(),
178 optional: HashMap::new(),
179 }
180 }
181
182 /// Convenience function for setting boolean flags in the request. (Chaining call)
183 pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
184 self.optional
185 .insert(option.to_string(), serde_json::value::Value::from(value));
186 self
187 }
188
189 /// Convenience function for setting boolean flags in the request. (Chaining call)
190 pub fn and_string_option(mut self, option: &str, value: &str) -> Self {
191 self.optional
192 .insert(option.to_string(), serde_json::value::Value::from(value));
193 self
194 }
195
196 /// Convenience function for setting boolean flags in the request. (Chaining call)
197 pub fn and_number_option(mut self, option: &str, value: u32) -> Self {
198 self.optional
199 .insert(option.to_string(), serde_json::value::Value::from(value));
200 self
201 }
202
203 /// Whether request was initiated at the user request. (Chaining call)
204 pub fn user_initiated_change(self, value: bool) -> Self {
205 self.and_boolean_option("userInitiatedChange", value)
206 }
207
208 /// Unknown. (Chaining call)
209 pub fn include_facebook(self, value: bool) -> Self {
210 self.and_boolean_option("includeFacebook", value)
211 }
212
213 /// Set account-holder gender, Male or Female. (Chaining call)
214 pub fn gender(self, value: UserGender) -> Self {
215 self.and_string_option("gender", &value.to_string())
216 }
217
218 /// Set account-holder birth year. (Chaining call)
219 pub fn birth_year(self, value: u32) -> Self {
220 self.and_number_option("birthYear", value)
221 }
222
223 /// Set account-holder zip code. (Chaining call)
224 pub fn zip_code(self, value: &str) -> Self {
225 self.and_string_option("zipCode", value)
226 }
227
228 /// Whether the user profile is private or publicly visible. (Chaining call)
229 pub fn is_profile_private(self, value: bool) -> Self {
230 self.and_boolean_option("isProfilePrivate", value)
231 }
232
233 /// Whether account comments are enabled. (Chaining call)
234 pub fn enable_comments(self, value: bool) -> Self {
235 self.and_boolean_option("enableComments", value)
236 }
237
238 /// Whether email communications from Pandora are permitted. (Chaining call)
239 pub fn email_opt_in(self, value: bool) -> Self {
240 self.and_boolean_option("emailOptIn", value)
241 }
242
243 /// Whether to receive email notifications for comments. (Chaining call)
244 pub fn email_comments(self, value: bool) -> Self {
245 self.and_boolean_option("emailComments", value)
246 }
247
248 /// Whether to receive email notifications of new followers. (Chaining call)
249 pub fn email_new_followers(self, value: bool) -> Self {
250 self.and_boolean_option("emailNewFollowers", value)
251 }
252
253 /// Whether the explicit content filter should be enabled. (Chaining call)
254 pub fn is_explicit_content_filter_enabled(self, value: bool) -> Self {
255 self.and_boolean_option("isExplicitContentFilterEnabled", value)
256 }
257
258 /// Whether the explicit content filter is protected by a PIN code. (Chaining call)
259 pub fn is_explicit_content_filter_pin_protected(self, value: bool) -> Self {
260 self.and_boolean_option("isExplicitContentFilterPINProtected", value)
261 }
262
263 /// New account username. (Chaining call)
264 pub fn new_username(self, value: &str) -> Self {
265 self.and_string_option("newUsername", value)
266 }
267
268 /// New account password. (Chaining call)
269 pub fn new_password(self, value: &str) -> Self {
270 self.and_string_option("newPassword", value)
271 }
272
273 /// Whether to auto-share on facebook. (Chaining call)
274 pub fn facebook_auto_share_enabled(self, value: bool) -> Self {
275 self.and_boolean_option("facebookAutoShareEnabled", value)
276 }
277
278 /// Whether to auto-share tracks played. (Chaining call)
279 pub fn auto_share_track_play(self, value: bool) -> Self {
280 self.and_boolean_option("autoShareTrackPlay", value)
281 }
282
283 /// Whether to auto-share liked tracks. (Chaining call)
284 pub fn auto_share_track_likes(self, value: bool) -> Self {
285 self.and_boolean_option("autoShareTrackLikes", value)
286 }
287
288 /// Whether to auto-share user follows. (Chaining call)
289 pub fn auto_share_follows(self, value: bool) -> Self {
290 self.and_boolean_option("autoShareFollows", value)
291 }
292
293 /// Unknown. (Chaining call)
294 pub fn facebook_setting_checksum(self, value: bool) -> Self {
295 self.and_boolean_option("facebookSettingChecksum", value)
296 }
297}
298
299/// There's no known response to data to this request.
300#[derive(Debug, Clone, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ChangeSettingsResponse {
303 /// The fields of the changeSettings response are unknown.
304 #[serde(flatten)]
305 pub optional: HashMap<String, serde_json::value::Value>,
306}
307
308/// Convenience function to do a basic canSubscribe call. This function
309/// is basically useless for actually changing settings, but is useful
310/// to return the current values for user account settings.
311pub async fn change_settings(
312 session: &mut PandoraSession,
313 username: &str,
314 password: &str,
315) -> Result<ChangeSettingsResponse, Error> {
316 ChangeSettings::new(username, password)
317 .response(session)
318 .await
319}
320
321/// | Name | Type | Description |
322/// | username | string | |
323/// | password | string | |
324/// | gender | string | |
325/// | birthYear | int | |
326/// | zipCode | int | |
327/// | emailOptIn | boolean | |
328/// | countryCode | string | |
329/// | accountType| string registered | |
330/// | registeredType | string user | |
331/// | includePandoraOneInfo | boolean | |
332/// | includeAccountMessage | boolean | |
333/// | returnCollectTrackLifetimeStats | boolean | |
334/// | returnIsSubscriber | boolean | |
335/// | xplatformAdCapable | boolean | |
336/// | includeFacebook | boolean | |
337/// | includeGoogleplay | boolean | |
338/// | includeShowUserRecommendations | boolean | |
339/// | includeAdvertiserAttributes | boolean | |
340#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
341#[pandora_request(encrypted = true)]
342#[serde(rename_all = "camelCase")]
343pub struct CreateUser {
344 /// Username to associate with the account.
345 pub username: String,
346 /// Password to set for the account.
347 pub password: String,
348 /// Account-holder gender, Male or Female.
349 pub gender: UserGender,
350 /// Account-holder birth year.
351 pub birth_year: u32,
352 /// Account-holder zip code.
353 pub zip_code: String,
354 /// Whether the user opts in to e-mail communciations.
355 pub email_opt_in: bool,
356 /// Account-holder country code.
357 pub country_code: String,
358 /// Unknown: "registered"?
359 pub account_type: String,
360 /// Unknown: "user"?
361 pub registered_type: String,
362 /// Unknown.
363 pub include_pandora_one_info: bool,
364 /// Unknown.
365 pub include_account_message: bool,
366 /// Unknown.
367 pub return_collect_track_lifetime_stats: bool,
368 /// Unknown.
369 pub return_is_subscriber: bool,
370 /// Unknown.
371 pub xplatform_ad_capable: bool,
372 /// Unknown.
373 pub include_facebook: bool,
374 /// Unknown.
375 pub include_googleplay: bool,
376 /// Unknown.
377 pub include_show_user_recommendations: bool,
378 /// Unknown.
379 pub include_advertiser_attributes: bool,
380}
381
382impl CreateUser {
383 /// Create a new CreateUser with some values. All Optional fields are
384 /// set to None.
385 pub fn new(
386 username: &str,
387 password: &str,
388 gender: UserGender,
389 birth_year: u32,
390 zip_code: &str,
391 country_code: &str,
392 ) -> Self {
393 Self {
394 username: username.to_string(),
395 password: password.to_string(),
396 gender,
397 birth_year,
398 zip_code: zip_code.to_string(),
399 country_code: country_code.to_string(),
400 email_opt_in: false,
401 account_type: "registered".to_string(),
402 registered_type: "user".to_string(),
403 include_pandora_one_info: false,
404 include_account_message: false,
405 return_collect_track_lifetime_stats: false,
406 return_is_subscriber: false,
407 xplatform_ad_capable: false,
408 include_facebook: false,
409 include_googleplay: false,
410 include_show_user_recommendations: false,
411 include_advertiser_attributes: false,
412 }
413 }
414}
415
416/// There's no known response to data to this request.
417#[derive(Debug, Clone, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct CreateUserResponse {
420 /// The fields of the createUser response are unknown.
421 #[serde(flatten)]
422 pub optional: HashMap<String, serde_json::value::Value>,
423}
424
425/// Convenience function to do a basic emailPassword call.
426pub async fn create_user(
427 session: &mut PandoraSession,
428 username: &str,
429 password: &str,
430 gender: UserGender,
431 birth_year: u32,
432 zip_code: &str,
433 country_code: &str,
434) -> Result<CreateUserResponse, Error> {
435 CreateUser::new(
436 username,
437 password,
438 gender,
439 birth_year,
440 zip_code,
441 country_code,
442 )
443 .response(session)
444 .await
445}
446
447/// **Unsupported!**
448/// Undocumented method
449/// [user.disconnectFacebook()](https://6xq.net/pandora-apidoc/json/methods/)
450pub struct DisconnectFacebookUnsupported {}
451
452/// | Name | Type | Description |
453/// | username | string | |
454#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
455#[serde(rename_all = "camelCase")]
456pub struct EmailPassword {
457 /// The e-mail password recovery information to the e-mail associated with
458 /// the this username.
459 pub username: String,
460}
461
462impl<TS: ToString> From<&TS> for EmailPassword {
463 fn from(username: &TS) -> Self {
464 Self {
465 username: username.to_string(),
466 }
467 }
468}
469
470/// There's no known response to data to this request.
471#[derive(Debug, Clone, Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct EmailPasswordResponse {
474 /// The fields of the emailPassword response are unknown.
475 #[serde(flatten)]
476 pub optional: HashMap<String, serde_json::value::Value>,
477}
478
479/// Convenience function to do a basic emailPassword call.
480pub async fn email_password(
481 session: &mut PandoraSession,
482 username: &str,
483) -> Result<EmailPasswordResponse, Error> {
484 EmailPassword::from(&username).response(session).await
485}
486
487/// **Unsupported!**
488/// Undocumented method
489/// [user.facebookAuthFailed()](https://6xq.net/pandora-apidoc/json/methods/)
490pub struct FacebookAuthFailedUnsupported {}
491
492/// The request has no parameters.
493#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
494#[pandora_request(encrypted = true)]
495#[serde(rename_all = "camelCase")]
496pub struct GetBookmarks {}
497
498impl GetBookmarks {
499 /// Create a new GetBookmarks.
500 pub fn new() -> Self {
501 Self::default()
502 }
503}
504
505/// ``` json
506/// {
507/// "stat":"ok",
508/// "result": {
509/// "artists": [
510/// {
511/// "musicToken": "R130360",
512/// "artistName": "Cannabich, Christian",
513/// "artUrl": "http://cont-sv5-2.pandora.com/images/public/amz/5/2/9/7/095115137925_500W_488H.jpg",
514/// "bookmarkToken": "80982345262345234",
515/// "dateCreated": {
516/// "nanos": 300000000,
517/// "seconds": 22,
518/// "year": 112,
519/// "month": 4,
520/// "hours": 11,
521/// "time": 1350566223422,
522/// "date": 23,
523/// "minutes": 01,
524/// "day": 2,
525/// "timezoneOffset": 720
526/// }
527/// }
528/// ],
529/// "songs": [
530/// {
531/// "sampleUrl": "http://www.pandora.com/favorites/getSample.jsp?token=32458973245b90287345d0234fc34f8b&allowExplicit=true",
532/// "sampleGain": "-7.87",
533/// "albumName": "Symphony In G Major",
534/// "artistName": "Cannabich, Christian",
535/// "musicToken": "S2894329",
536/// "dateCreated": {
537/// "nanos": 300000000,
538/// "seconds": 22,
539/// "year": 112,
540/// "month": 4,
541/// "hours": 11,
542/// "time": 1350566223422,
543/// "date": 23,
544/// "minutes": 01,
545/// "day": 2,
546/// "timezoneOffset": 720
547/// },
548/// "artUrl": "http://cont-sv5-2.pandora.com/images/public/amz/5/2/9/7/095115137925_500W_488H.jpg",
549/// "bookmarkToken": "290832123432459854",
550/// "songName": "London Mozart Players, Christian Cannabich: Symphonies"
551/// }
552/// ]
553/// }
554///
555/// }
556/// ```
557#[derive(Debug, Clone, Deserialize)]
558#[serde(rename_all = "camelCase")]
559pub struct GetBookmarksResponse {
560 /// A list of bookmarked artists.
561 pub artists: Vec<ArtistBookmark>,
562 /// A list of bookmarked songs.
563 pub songs: Vec<SongBookmark>,
564}
565
566/// "artists": [
567/// {
568/// "musicToken": "R130360",
569/// "artistName": "Cannabich, Christian",
570/// "artUrl": "http://cont-sv5-2.pandora.com/images/public/amz/5/2/9/7/095115137925_500W_488H.jpg",
571/// "bookmarkToken": "80982345262345234",
572/// "dateCreated": {
573/// "nanos": 300000000,
574/// "seconds": 22,
575/// "year": 112,
576/// "month": 4,
577/// "hours": 11,
578/// "time": 1350566223422,
579/// "date": 23,
580/// "minutes": 01,
581/// "day": 2,
582/// "timezoneOffset": 720
583/// }
584/// }
585/// ]
586#[derive(Debug, Clone, Deserialize)]
587#[serde(rename_all = "camelCase")]
588pub struct ArtistBookmark {
589 /// Unique identifier (token) associated with this bookmark.
590 pub bookmark_token: String,
591 /// Unique identifier (token) for the music item that was bookmarked.
592 pub music_token: String,
593 /// The name of the artist bookmarked.
594 pub artist_name: String,
595 /// Art url for the bookmark.
596 pub art_url: String,
597 /// Timestamp for when the bookmark was created.
598 pub date_created: Timestamp,
599}
600
601/// "songs": [
602/// {
603/// "sampleUrl": "http://www.pandora.com/favorites/getSample.jsp?token=32458973245b90287345d0234fc34f8b&allowExplicit=true",
604/// "sampleGain": "-7.87",
605/// "albumName": "Symphony In G Major",
606/// "artistName": "Cannabich, Christian",
607/// "musicToken": "S2894329",
608/// "dateCreated": {
609/// "nanos": 300000000,
610/// "seconds": 22,
611/// "year": 112,
612/// "month": 4,
613/// "hours": 11,
614/// "time": 1350566223422,
615/// "date": 23,
616/// "minutes": 01,
617/// "day": 2,
618/// "timezoneOffset": 720
619/// },
620/// "artUrl": "http://cont-sv5-2.pandora.com/images/public/amz/5/2/9/7/095115137925_500W_488H.jpg",
621/// "bookmarkToken": "290832123432459854",
622/// "songName": "London Mozart Players, Christian Cannabich: Symphonies"
623/// }
624/// ]
625#[derive(Debug, Clone, Deserialize)]
626#[serde(rename_all = "camelCase")]
627pub struct SongBookmark {
628 /// Unique identifier (token) associated with this bookmark.
629 pub bookmark_token: String,
630 /// Unique identifier (token) for the music item that was bookmarked.
631 pub music_token: String,
632 /// The name of the song bookmarked.
633 pub song_name: String,
634 /// The name of the artist for the bookmarked song.
635 pub artist_name: String,
636 /// The name of the album for the bookmarked song.
637 pub album_name: String,
638 /// Art url for the bookmark.
639 pub art_url: String,
640 /// Url for a sample of the bookmarked song.
641 pub sample_url: String,
642 /// Playback gain for the song sample.
643 pub sample_gain: String,
644 /// Timestamp for when the bookmark was created.
645 pub date_created: Timestamp,
646}
647
648/// Convenience function to do a basic getBookmarks call.
649pub async fn get_bookmarks(session: &mut PandoraSession) -> Result<GetBookmarksResponse, Error> {
650 GetBookmarks::new().response(session).await
651}
652
653/// **Unsupported!**
654/// Undocumented method
655/// [user.getFacebookInfo()](https://6xq.net/pandora-apidoc/json/methods/)
656pub struct GetFacebookInfoUnsupported {}
657
658/// | Name | Type | Description |
659/// | includeFacebook | boolean | |
660#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
661#[pandora_request(encrypted = true)]
662#[serde(rename_all = "camelCase")]
663pub struct GetSettings {
664 /// Optional parameters on the call
665 #[serde(flatten)]
666 pub optional: HashMap<String, serde_json::value::Value>,
667}
668
669impl GetSettings {
670 /// Create a new GetSettings, omitting facebook from the response.
671 pub fn new() -> Self {
672 Self::default()
673 }
674
675 /// Convenience function for setting boolean flags in the request. (Chaining call)
676 pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
677 self.optional
678 .insert(option.to_string(), serde_json::value::Value::from(value));
679 self
680 }
681
682 /// Whether to include Facebook settings in the response. (Chaining call)
683 pub fn include_facebook(self, value: bool) -> Self {
684 self.and_boolean_option("includeFacebook", value)
685 }
686}
687
688/// See Settings for return values.
689#[derive(Debug, Clone, Deserialize)]
690#[serde(rename_all = "camelCase")]
691pub struct GetSettingsResponse {
692 /// The fields of the setQuickMix response are unknown.
693 #[serde(flatten)]
694 pub optional: HashMap<String, serde_json::value::Value>,
695}
696
697/// Convenience function to do a basic getSettings call.
698pub async fn get_settings(session: &mut PandoraSession) -> Result<GetSettingsResponse, Error> {
699 GetSettings::new()
700 .include_facebook(false)
701 .response(session)
702 .await
703}
704
705/// To check if the station list was modified by another client the checksum
706/// can be fetched. No parameters are required for this request.
707#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
708#[serde(rename_all = "camelCase")]
709pub struct GetStationListChecksum {}
710
711impl GetStationListChecksum {
712 /// Create a new GetStationListChecksum.
713 pub fn new() -> Self {
714 Self::default()
715 }
716}
717
718/// The response contains the new checksum.
719///
720/// | Name | Type | Description |
721/// | checksum | string | |
722/// ``` json
723/// {
724/// "stat":"ok",
725/// "result":{
726/// "checksum":"99776ddd31ad798895578593e78e3691"
727/// }
728/// }
729/// ```
730#[derive(Debug, Clone, Deserialize)]
731#[serde(rename_all = "camelCase")]
732pub struct GetStationListChecksumResponse {
733 /// Checksum for station list
734 pub checksum: String,
735}
736
737/// | Name | Type | Description |
738/// | includeStationArtUrl | boolean | Includes “artUrl” field in result (optional) |
739/// | stationArtSize | string | “W130H130” (optional) |
740/// | includeAdAttributes | boolean | (optional) |
741/// | includeStationSeeds | boolean | (optional) |
742/// | includeShuffleInsteadOfQuickMix | boolean | (optional) |
743/// | includeRecommendations | boolean | (optional) |
744/// | includeExplanations | boolean | (optional) |
745/// ``` json
746/// {
747/// "userAuthToken": "XXX",
748/// "syncTime": XXX
749/// }
750/// ```
751#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
752#[pandora_request(encrypted = true)]
753#[serde(rename_all = "camelCase")]
754pub struct GetStationList {
755 /// Optional parameters on the call
756 #[serde(flatten)]
757 pub optional: HashMap<String, serde_json::value::Value>,
758}
759
760impl GetStationList {
761 /// Create a new GetStationList with some values. All Optional fields are
762 /// set to None.
763 pub fn new() -> Self {
764 Self::default()
765 }
766
767 /// Convenience function for setting boolean flags in the request. (Chaining call)
768 pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
769 self.optional
770 .insert(option.to_string(), serde_json::value::Value::from(value));
771 self
772 }
773
774 /// Convenience function for setting boolean flags in the request. (Chaining call)
775 pub fn and_string_option(mut self, option: &str, value: &str) -> Self {
776 self.optional
777 .insert(option.to_string(), serde_json::value::Value::from(value));
778 self
779 }
780
781 /// Whether to include station art url in the response. (Chaining call)
782 pub fn include_station_art_url(self, value: bool) -> Self {
783 self.and_boolean_option("includeStationArtUrl", value)
784 }
785
786 /// The size of the station art image to include in the response. (Chaining call)
787 pub fn station_art_size(self, value: &str) -> Self {
788 self.and_string_option("stationArtSize", value)
789 }
790
791 /// Whether to include ad attributes in the response. (Chaining call)
792 pub fn include_ad_attributes(self, value: bool) -> Self {
793 self.and_boolean_option("includeAdAttributes", value)
794 }
795
796 /// Whether to include station seeds in the response. (Chaining call)
797 pub fn include_station_seeds(self, value: bool) -> Self {
798 self.and_boolean_option("includeStationSeeds", value)
799 }
800
801 /// Whether to include shuffle stations instead of quickmix in the response. (Chaining call)
802 pub fn include_shuffle_instead_of_quick_mix(self, value: bool) -> Self {
803 self.and_boolean_option("includeShuffleInsteadOfQuickMix", value)
804 }
805
806 /// Whether to include recommendations in the response. (Chaining call)
807 pub fn include_recommendations(self, value: bool) -> Self {
808 self.and_boolean_option("includeRecommendations", value)
809 }
810
811 /// Whether to include explanations in the response. (Chaining call)
812 pub fn include_explanations(self, value: bool) -> Self {
813 self.and_boolean_option("includeExplanations", value)
814 }
815}
816
817/// Currently stationId and stationToken are the same.
818///
819/// QuickMix stations additionally include a list of station ids
820/// (quickMixStationIds) that are currently selected for the mix.
821///
822/// | Name | Type | Description |
823/// | stations.stationId | string | |
824/// | stations.stationName | string | |
825/// | stations.dateCreated.time | int | Number of milliseconds since Unix epoch |
826/// | checksum | string | |
827/// ``` json
828/// {
829/// "stat":"ok",
830/// "result":{
831/// "stations":[
832/// {
833/// "suppressVideoAds":true,
834/// "isQuickMix":true,
835/// "stationId":"3914377363925265",
836/// "stationDetailUrl":"https://www.pandora.com/login?target=%2Fstations%2Fa61985110ea3d6c6c8d8a9c038588b26425ba2910f7abf8b",
837/// "isShared":false,
838/// "dateCreated":{
839/// "date":8,
840/// "day":4,
841/// "hours":22,
842/// "minutes":44,
843/// "month":10,
844/// "nanos":241000000,
845/// "seconds":46,
846/// "time":1194590686241,
847/// "timezoneOffset":480,
848/// "year":107
849/// },
850/// "stationToken":"3914377363925265",
851/// "stationName":"QuickMix",
852/// "stationSharingUrl":"https://www.pandora.com/login?target=%2Fshare%2Fstation%2Fa61985110ea3d6c6c8d8a9c038588b26425ba2910f7abf8b",
853/// "requiresCleanAds":true,
854/// "allowRename":false,
855/// "allowAddMusic":false,
856/// "quickMixStationIds":[
857/// "339646069607180561",
858/// "339644480469281041"
859/// ],
860/// "allowDelete":false,
861/// "allowEditDescription":false
862/// }
863/// ],
864/// "checksum":"99776ddd31ad798895578593e78e3691"
865/// }
866/// }
867/// ```
868#[derive(Debug, Clone, Deserialize)]
869#[serde(rename_all = "camelCase")]
870pub struct GetStationListResponse {
871 /// List of user-defined stations.
872 pub stations: Vec<Station>,
873 /// Checksum over the list of stations. This can be used later to determine
874 /// whether the station list has changed and cached station list information
875 /// can be re-used.
876 pub checksum: String,
877}
878
879/// "stations":[
880/// {
881/// "suppressVideoAds":true,
882/// "isQuickMix":true,
883/// "stationId":"3914377363925265",
884/// "stationDetailUrl":"https://www.pandora.com/login?target=%2Fstations%2Fa61985110ea3d6c6c8d8a9c038588b26425ba2910f7abf8b",
885/// "isShared":false,
886/// "dateCreated":{
887/// "date":8,
888/// "day":4,
889/// "hours":22,
890/// "minutes":44,
891/// "month":10,
892/// "nanos":241000000,
893/// "seconds":46,
894/// "time":1194590686241,
895/// "timezoneOffset":480,
896/// "year":107
897/// },
898/// "stationToken":"3914377363925265",
899/// "stationName":"QuickMix",
900/// "stationSharingUrl":"https://www.pandora.com/login?target=%2Fshare%2Fstation%2Fa61985110ea3d6c6c8d8a9c038588b26425ba2910f7abf8b",
901/// "requiresCleanAds":true,
902/// "allowRename":false,
903/// "allowAddMusic":false,
904/// "quickMixStationIds":[
905/// "339646069607180561",
906/// "339644480469281041"
907/// ],
908/// "allowDelete":false,
909/// "allowEditDescription":false
910/// }
911/// ],
912#[derive(Debug, Clone, Deserialize)]
913#[serde(rename_all = "camelCase")]
914pub struct Station {
915 /// Unique identifier (token) for this station. Currently stationId and
916 /// stationToken are the same.
917 pub station_id: String,
918 /// Unique identifier (token) for this station. Currently stationId and
919 /// stationToken are the same.
920 pub station_token: String,
921 /// User-defined name for this station.
922 pub station_name: String,
923 /// Url for additional information about station.
924 pub station_detail_url: String,
925 /// Ids for stations included in this quickmix.
926 #[serde(default)]
927 pub quick_mix_station_ids: Vec<String>,
928 /// Is this station a quickmix.
929 pub is_quick_mix: bool,
930 /// Unknown.
931 pub suppress_video_ads: bool,
932 /// Wether this station is shared.
933 pub is_shared: bool,
934 /// Unknown.
935 pub requires_clean_ads: bool,
936 /// Whether station may be renamed.
937 pub allow_rename: bool,
938 /// Whether station allows adding music.
939 pub allow_add_music: bool,
940 /// Whether station can be deleted.
941 pub allow_delete: bool,
942 /// Whether station description can be edited.
943 pub allow_edit_description: bool,
944 /// Timestamp for when the station was created.
945 pub date_created: Timestamp,
946 /// Additional, optional fields of the response.
947 #[serde(flatten)]
948 pub optional: HashMap<String, serde_json::value::Value>,
949}
950
951/// Convenience function to do a basic getStationList call.
952pub async fn get_station_list(
953 session: &mut PandoraSession,
954) -> Result<GetStationListResponse, Error> {
955 GetStationList::new()
956 .include_station_art_url(false)
957 .include_ad_attributes(false)
958 .include_station_seeds(false)
959 .include_shuffle_instead_of_quick_mix(false)
960 .include_recommendations(false)
961 .include_explanations(false)
962 .response(session)
963 .await
964}
965
966/// The request has no parameters.
967#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
968#[pandora_request(encrypted = true)]
969#[serde(rename_all = "camelCase")]
970pub struct GetUsageInfo {}
971
972impl GetUsageInfo {
973 /// Create a new GetUsageInfo.
974 pub fn new() -> Self {
975 Self::default()
976 }
977}
978
979///
980/// | Name | Type | Description |
981/// | accountMonthlyListening | int | |
982/// | deviceMonthlyListening | int | |
983/// | monthlyCapHours | int | |
984/// | monthlyCapWarningPercent | int | |
985/// | monthlyCapWarningRepeatPercent | int | |
986/// | isMonthlyPayer | bool | |
987/// | isCapped | bool | |
988/// | listeningTimestamp | int | |
989/// ``` json
990/// {
991/// "stat": "ok",
992/// "result": {
993/// "monthlyCapWarningRepeatPercent": 10,
994/// "monthlyCapHours": 320,
995/// "deviceMonthlyListening": 0,
996/// "isMonthlyPayer": false,
997/// "isCapped": false,
998/// "monthlyCapWarningPercent": 85,
999/// "accountMonthlyListening": 0
1000/// }
1001/// }
1002/// ```
1003#[derive(Debug, Clone, Deserialize)]
1004#[serde(rename_all = "camelCase")]
1005pub struct GetUsageInfoResponse {
1006 /// Number of hours spent listening this month on this account.
1007 pub account_monthly_listening: u32,
1008 /// Number of hours spent listening from this device this month.
1009 pub device_monthly_listening: u32,
1010 /// Maximum number of allowed hours per month.
1011 pub monthly_cap_hours: u32,
1012 /// At what percentage of monthly allowed hours the user should be warned.
1013 pub monthly_cap_warning_percent: u32,
1014 /// At what percentage of monthly allowed hours the user should get a second
1015 /// warning.
1016 pub monthly_cap_warning_repeat_percent: u32,
1017 /// Whether the account is billed monthly.
1018 pub is_monthly_payer: bool,
1019 /// Whether the account has a usage cap.
1020 pub is_capped: bool,
1021 /// Unknown.
1022 pub listening_timestamp: Option<u32>,
1023}
1024
1025/// Convenience function to get account usage info.
1026pub async fn get_usage_info(session: &mut PandoraSession) -> Result<GetUsageInfoResponse, Error> {
1027 GetUsageInfo {}.response(session).await
1028}
1029
1030/// **Unsupported!**
1031/// Undocumented method
1032/// [user.purchaseAmazonPayToPlay()](https://6xq.net/pandora-apidoc/json/methods/)
1033pub struct PurchaseAmazonPayToPlayUnsupported {}
1034
1035/// **Unsupported!**
1036/// Undocumented method
1037/// [user.purchaseAmazonSubscription()](https://6xq.net/pandora-apidoc/json/methods/)
1038pub struct PurchaseAmazonSubscriptionUnsupported {}
1039
1040/// **Unsupported!**
1041/// Undocumented method
1042/// [user.purchaseGooglePayToPlay()](https://6xq.net/pandora-apidoc/json/methods/)
1043pub struct PurchaseGooglePayToPlayUnsupported {}
1044
1045/// **Unsupported!**
1046/// Undocumented method
1047/// [user.purchaseGoogleSubscription()](https://6xq.net/pandora-apidoc/json/methods/)
1048pub struct PurchaseGoogleSubscriptionUnsupported {}
1049
1050/// **Unsupported!**
1051/// Undocumented method
1052/// [user.purchaseItunesSubscription()](https://6xq.net/pandora-apidoc/json/methods/)
1053pub struct PurchaseItunesSubscriptionUnsupported {}
1054
1055/// **Unsupported!**
1056/// Undocumented method
1057/// [user.setAwareOfProfile()](https://6xq.net/pandora-apidoc/json/methods/)
1058pub struct SetAwareOfProfileUnsupported {}
1059
1060/// **Unsupported!**
1061/// Undocumented method
1062/// [user.setExplicitContentFilter()](https://6xq.net/pandora-apidoc/json/methods/)
1063pub struct SetExplicitContentFilterUnsupported {}
1064
1065/// | Name | Type | Description |
1066/// | quickMixStationIds | array | List of station id’s (strings) (see Retrieve station list) |
1067/// ``` json
1068/// {
1069/// "quickMixStationIds": ["404958383414849005", "403387202773593581"],
1070/// "userAuthToken": "XXX",
1071/// "syncTime": 1338211186
1072/// }
1073/// ```
1074#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
1075#[serde(rename_all = "camelCase")]
1076pub struct SetQuickMix {
1077 /// The identifiers for stations that should be included in the quickmix.
1078 pub quick_mix_station_ids: Vec<String>,
1079}
1080
1081impl SetQuickMix {
1082 /// Create a new SetQuickMix. Call add_station() to add a station to the
1083 /// mix.
1084 pub fn new() -> Self {
1085 Self::default()
1086 }
1087
1088 /// Add a station to this quickmix.
1089 pub fn add_station(&mut self, station_id: &str) {
1090 self.quick_mix_station_ids.push(station_id.to_string());
1091 }
1092}
1093
1094/// The response contains no data.
1095#[derive(Debug, Clone, Deserialize)]
1096#[serde(rename_all = "camelCase")]
1097pub struct SetQuickMixResponse {
1098 /// The fields of the setQuickMix response are unknown.
1099 #[serde(flatten)]
1100 pub optional: HashMap<String, serde_json::value::Value>,
1101}
1102
1103/// A song can be banned from all stations temporarily (one month).
1104///
1105/// | Name | Type | Description |
1106/// | trackToken | string | See Retrieve playlist |
1107/// ``` json
1108/// {
1109/// "trackToken":
1110/// "d6aa37c60833f12150c4e2ba172c46f24590ebc49df948b6fb7117314c41c8e7d4faee3568884468d9509db2ab998dafdbc4093baf8c38ef",
1111/// "userAuthToken": "XXX",
1112/// "syncTime": 1336386838
1113/// }
1114/// ```
1115#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
1116#[serde(rename_all = "camelCase")]
1117pub struct SleepSong {
1118 /// Temporarily ban the specified track from all stations for one month.
1119 pub track_token: String,
1120}
1121
1122impl<TS: ToString> From<&TS> for SleepSong {
1123 fn from(track_token: &TS) -> Self {
1124 Self {
1125 track_token: track_token.to_string(),
1126 }
1127 }
1128}
1129
1130/// The response contains no data.
1131#[derive(Debug, Clone, Deserialize)]
1132#[serde(rename_all = "camelCase")]
1133pub struct SleepSongResponse {
1134 /// The fields of the sleepSong response are unknown.
1135 #[serde(flatten)]
1136 pub optional: HashMap<String, serde_json::value::Value>,
1137}
1138
1139/// Starts a complimentary pandora one trial. It is unknown what constitutes a
1140/// valid sponsor at this time, and as such this method will always fail.
1141///
1142/// | Name | Type | Description |
1143/// | complimentarySponsor | string | The ID of the sponsor providing the complimentary trial. |
1144#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
1145#[serde(rename_all = "camelCase")]
1146pub struct StartComplimentaryTrial {
1147 /// The ID of the sponsor providing the complimentary trial. There are no
1148 /// known valid values for this field.
1149 pub complimentary_sponsor: String,
1150}
1151
1152impl<TS: ToString> From<&TS> for StartComplimentaryTrial {
1153 fn from(complimentary_sponsor: &TS) -> Self {
1154 Self {
1155 complimentary_sponsor: complimentary_sponsor.to_string(),
1156 }
1157 }
1158}
1159
1160/// The response contains no data.
1161#[derive(Debug, Clone, Deserialize)]
1162#[serde(rename_all = "camelCase")]
1163pub struct StartComplimentaryTrialResponse {
1164 /// The fields of the startComplimentaryTrial response are unknown.
1165 #[serde(flatten)]
1166 pub optional: HashMap<String, serde_json::value::Value>,
1167}
1168
1169/// This method can be used before authenticating a user with User login, but
1170/// requires a valid Partner login.
1171///
1172/// | Name | Type | Description |
1173/// | username | string | |
1174#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
1175#[pandora_request(encrypted = true)]
1176#[serde(rename_all = "camelCase")]
1177pub struct ValidateUsername {
1178 /// The username to validate.
1179 pub username: String,
1180}
1181
1182impl<TS: ToString> From<&TS> for ValidateUsername {
1183 fn from(username: &TS) -> Self {
1184 Self {
1185 username: username.to_string(),
1186 }
1187 }
1188}
1189
1190/// | Name | Type | Description |
1191/// | isValid | boolean | |
1192/// | isUnique | boolean | |
1193#[derive(Debug, Clone, Deserialize)]
1194#[serde(rename_all = "camelCase")]
1195pub struct ValidateUsernameResponse {
1196 /// Whether the username is valid (registered).
1197 pub is_valid: bool,
1198 /// Whether the username is unique (already used).
1199 pub is_unique: Option<bool>,
1200}
1201
1202/// Convenience function to verify that a username is either valid or unique.
1203pub async fn validate_username(
1204 session: &mut PandoraSession,
1205 username: &str,
1206) -> Result<ValidateUsernameResponse, Error> {
1207 ValidateUsername {
1208 username: username.to_string(),
1209 }
1210 .response(session)
1211 .await
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216 use super::*;
1217 use crate::errors;
1218 use crate::json::{errors::JsonErrorKind, tests::session_login, Partner};
1219
1220 #[tokio::test]
1221 async fn user_test() {
1222 let partner = Partner::default();
1223 let mut session = session_login(&partner)
1224 .await
1225 .expect("Failed initializing login session");
1226
1227 let _can_subscribe = can_subscribe(&mut session)
1228 .await
1229 .expect("Failed submitting subscription information request");
1230
1231 let _get_settings = get_settings(&mut session)
1232 .await
1233 .expect("Failed submitting settings info request");
1234
1235 let test_username_raw = include_str!("../../test_username.txt");
1236 let test_username = test_username_raw.trim();
1237 let test_password_raw = include_str!("../../test_password.txt");
1238 let test_password = test_password_raw.trim();
1239
1240 let _change_settings = change_settings(&mut session, &test_username, &test_password)
1241 .await
1242 .expect("Failed submitting settings change request");
1243 }
1244
1245 /* This test might trigger e-mail-based account recovery, which we probably
1246 * don't want to do automatically as a test.
1247 #[tokio::test]
1248 async fn email_password_test() {
1249 let partner = Partner::default();
1250 let mut session = session_login(&partner).await.expect("Failed initializing login session");
1251
1252 let email_password = email_password(&mut session).await.expect("Failed submitting settings change request");
1253 }
1254 */
1255
1256 #[tokio::test]
1257 #[should_panic(expected = "Invalid country code.")]
1258 async fn create_user_test() {
1259 /*
1260 flexi_logger::Logger::try_with_str("info, pandora_api=debug")
1261 .expect("Failed to set logging configuration")
1262 .start()
1263 .expect("Failed to start logger");
1264 */
1265
1266 let partner = Partner::default();
1267 let mut session = partner.init_session();
1268 let partner_login = partner
1269 .login(&mut session)
1270 .await
1271 .expect("Failed completing partner login");
1272 session.update_partner_tokens(&partner_login);
1273
1274 let test_username_raw = include_str!("../../test_username.txt");
1275 let test_username = test_username_raw.trim();
1276 let test_password_raw = include_str!("../../test_password.txt");
1277 let test_password = test_password_raw.trim();
1278
1279 let test_gender = UserGender::Male;
1280 let test_birth = 1970u32;
1281 let test_zip = "90210";
1282 // I can't figure out a valid country code to use, so we'll write this
1283 // test in the negative and check for the correct error for invalid
1284 // country code.
1285 let test_cc = "US";
1286
1287 // Theory is that the above credentials are for an existing account,
1288 // so this should fail as a duplicate account.
1289 match create_user(
1290 &mut session,
1291 &test_username,
1292 &test_password,
1293 test_gender,
1294 test_birth,
1295 test_zip,
1296 test_cc,
1297 )
1298 .await
1299 {
1300 Ok(cu) => log::debug!("User successfully created? {:?}", cu),
1301 Err(errors::Error::PandoraJsonRequestError(e))
1302 if e.kind() == JsonErrorKind::InvalidCountryCode =>
1303 {
1304 panic!("Invalid country code.")
1305 }
1306 Err(e) => panic!("Unexpected request error: {:?}", e),
1307 }
1308 }
1309}