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}