rustfm_scrobble/
models.rs

1pub mod responses {
2
3    use std::fmt;
4
5    use serde::Deserialize;
6    use serde_json as json;
7
8    #[derive(Deserialize, Debug)]
9    pub struct AuthResponse {
10        pub session: SessionResponse,
11    }
12
13    /// Response to an Authentication request. 
14    /// 
15    /// Contains a Session Key and the username of the authenticated Last.fm user and a subscriber ID.
16    /// Only the Session Key is used internally by the crate; the other values are exposed as they may have some value
17    /// for clients.
18    /// 
19    /// [Authentication API Requests Documentation](https://www.last.fm/api/authspec)
20    #[derive(Deserialize, Debug, Clone)]
21    pub struct SessionResponse {
22        pub key: String,
23        pub subscriber: i64,
24        pub name: String,
25    }
26
27    #[derive(Deserialize)]
28    pub struct NowPlayingResponseWrapper {
29        pub nowplaying: NowPlayingResponse,
30    }
31
32    /// Response to a Now Playing request.
33    ///
34    /// Represents a response to a Now Playing API request. This type can often be ignored by clients. All of the 
35    /// fields are [`CorrectableString`] types, which can be used to see if Last.fm applied any metadata correction
36    /// to your artist, song or album. 
37    /// 
38    /// [Now Playing Request API Documentation](https://www.last.fm/api/show/track.updateNowPlaying)
39    #[derive(Deserialize, Debug)]
40    pub struct NowPlayingResponse {
41        pub artist: CorrectableString,
42        pub album: CorrectableString,
43        #[serde(rename = "albumArtist")]
44        pub album_artist: CorrectableString,
45        pub track: CorrectableString,
46    }
47
48    #[derive(Deserialize)]
49    pub struct ScrobbleResponseWrapper {
50        pub scrobbles: SingleScrobble,
51    }
52
53    #[derive(Deserialize)]
54    pub struct SingleScrobble {
55        pub scrobble: ScrobbleResponse,
56    }
57
58    /// Response to a Scrobble request
59    /// 
60    /// Represents a response to a Scrobble API request. Contains the results of the Scrobble call, including any 
61    /// metadata corrections the Last.fm API made to the arist/track/album submitted.
62    /// 
63    /// [Scrobble Request API Documentation](https://www.last.fm/api/show/track.scrobble)
64    #[derive(Deserialize, Debug, WrappedVec)]
65    #[CollectionName = "ScrobbleList"]
66    #[CollectionDerives = "Debug, Deserialize"]
67    pub struct ScrobbleResponse {
68        pub artist: CorrectableString,
69        pub album: CorrectableString,
70        #[serde(rename = "albumArtist")]
71        pub album_artist: CorrectableString,
72        pub track: CorrectableString,
73        pub timestamp: String,
74    }
75
76    /// Response to a Batch Scrobble request
77    /// 
78    /// Represents a response to a batched Scrobble request. Contains the results of the Scrobble call, including
79    /// any metadata corrections the Last.fm API made to the arist/track/album submitted.
80    /// 
81    /// [Scrobble Request API Documentation](https://www.last.fm/api/show/track.scrobble)
82    #[derive(Debug)]
83    pub struct BatchScrobbleResponse {
84        pub scrobbles: ScrobbleList,
85    }
86
87    #[derive(Deserialize, Debug)]
88    pub struct BatchScrobbleResponseWrapper {
89        pub scrobbles: BatchScrobbles,
90    }
91
92    #[derive(Deserialize, Debug)]
93    pub struct BatchScrobbles {
94        #[serde(deserialize_with = "BatchScrobbles::deserialize_response_scrobbles")]
95        #[serde(rename = "scrobble")]
96        pub scrobbles: ScrobbleList,
97    }
98
99    impl BatchScrobbles {
100        fn deserialize_response_scrobbles<'de, D>(de: D) -> Result<ScrobbleList, D::Error>
101        where
102            D: serde::Deserializer<'de>,
103        {
104
105            let deser_result: json::Value = serde::Deserialize::deserialize(de)?;
106            let scrobbles = match deser_result {
107                obj@json::Value::Object(_) => {
108                    let scrobble: ScrobbleResponse = serde_json::from_value(obj).expect("Parsing scrobble failed");
109                    ScrobbleList::from(vec!(scrobble))
110                }
111                arr@json::Value::Array(_) => {
112                    let scrobbles: ScrobbleList = serde_json::from_value(arr).expect("Parsing scrobble list failed");
113                    scrobbles
114                },
115                _ => ScrobbleList::from(vec!())
116            };
117            Ok(scrobbles)
118        }
119    }
120
121    /// Represents a string that can be marked as 'corrected' by the Last.fm API. 
122    /// 
123    /// All Scrobble/NowPlaying responses have their fields as `CorrectableString`'s. The API will sometimes change
124    /// the artist/song name/album name data that you have submitted. For example - it is common for Bjork to be turned
125    /// into Björk by the API; the modified artist field would be marked `corrected = true`, `text = "Björk". 
126    /// 
127    /// Most clients can ignore these corrections, but the information is exposed for clients that require it.
128    /// 
129    /// [Meta-Data Correction Documentation](https://www.last.fm/api/scrobbling#meta-data-corrections)
130    #[derive(Deserialize, Debug)]
131    pub struct CorrectableString {
132        #[serde(deserialize_with = "CorrectableString::deserialize_corrected_field")]
133        pub corrected: bool,
134        #[serde(rename = "#text", default)]
135        pub text: String,
136    }
137
138    impl CorrectableString {
139        fn deserialize_corrected_field<'de, D>(de: D) -> Result<bool, D::Error>
140        where
141            D: serde::Deserializer<'de>,
142        {
143            let deser_result: json::Value = serde::Deserialize::deserialize(de)?;
144            match deser_result {
145                json::Value::String(ref s) if &*s == "1" => Ok(true),
146                json::Value::String(ref s) if &*s == "0" => Ok(false),
147                _ => Err(serde::de::Error::custom("Unexpected value")),
148            }
149        }
150    }
151
152    impl fmt::Display for CorrectableString {
153        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154            write!(f, "{}", self.text)
155        }
156    }
157}
158
159pub mod metadata {
160
161    use std::collections::HashMap;
162
163    /// Repesents a single music track played at a point in time. In the Last.fm universe, this is known as a 
164    /// "scrobble".
165    /// 
166    /// Takes an artist, track and album name. Can hold a timestamp indicating when the track was listened to.
167    /// `Scrobble` objects are submitted via [`Scrobbler::now_playing`], [`Scrobbler::scrobble`] and batches of
168    /// Scrobbles are sent via [`Scrobbler::scrobble_batch`].
169    /// 
170    /// [`Scrobbler::now_playing`]: struct.Scrobbler.html#method.now_playing
171    /// [`Scrobbler::scrobble`]: struct.Scrobbler.html#method.scrobble
172    /// [`Scrobbler::scrobble_batch`]: struct.Scrobbler.html#method.scrobble_batch
173    #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, WrappedVec)]
174    #[CollectionName = "ScrobbleBatch"]
175    #[CollectionDoc = "A batch of Scrobbles to be submitted to Last.fm together."]
176    #[CollectionDerives = "Clone, Debug"]
177    pub struct Scrobble {
178        artist: String,
179        track: String,
180        album: String,
181
182        timestamp: Option<u64>,
183    }
184
185    impl Scrobble {
186    
187        /// Constructs a new Scrobble instance, representing a single playthrough of a music track. `Scrobble`s are 
188        /// submitted to Last.fm via an instance of [`Scrobbler`]. A new `Scrobble` requires an artist name, song/track
189        /// name, and an album name.
190        /// 
191        /// # Example
192        /// ```ignore
193        /// let scrobble = Scrobble::new("Example Artist", "Example Track", "Example Album")
194        /// ```
195        /// 
196        /// [`Scrobbler`]: struct.Scrobbler.html
197        pub fn new(artist: &str, track: &str, album: &str) -> Self {
198            Self {
199                artist: artist.to_owned(),
200                track: track.to_owned(),
201                album: album.to_owned(),
202                timestamp: None,
203            }
204        }
205
206        /// Sets the timestamp (date/time of play) of a Scrobble. Used in a builder-style pattern, typically after 
207        /// [`Scrobble::new`].
208        /// 
209        /// # Example
210        /// ```ignore
211        /// let mut scrobble = Scrobble::new(...).with_timestamp(12345);
212        /// ```
213        ///
214        /// # Note on Timestamps
215        /// Scrobbles without timestamps are automatically assigned a timestamp of the current time when
216        /// submitted via [`Scrobbler::scrobble`] or [`Scrobbler::scrobble_batch`]. Timestamps only need to be 
217        /// explicitly set when you are submitting a Scrobble at a point in the past, or in the future.
218        /// 
219        /// [`Scrobble::new`]: struct.Scrobble.html#method.new
220        /// [`Scrobbler::scrobble`]: struct.Scrobbler.html#method.scrobble
221        /// [`Scrobbler::scrobble_batch`]: struct.Scrobbler.html#method.scrobble_batch
222        pub fn with_timestamp(&mut self, timestamp: u64) -> &mut Self {
223            self.timestamp = Some(timestamp);
224            self
225        }
226
227        /// Converts the Scrobble metadata (track name, artist & album name) into a `HashMap`. Map keys are 
228        /// `"track"`, `"artist"` and `"album"`. If a timestamp is set, it will be present in the map under key 
229        /// `"timestamp"`.
230        /// 
231        /// # Example
232        /// ```ignore
233        /// let scrobble = Scrobble::new("Example Artist", ...);
234        /// let scrobble_map = scrobble.as_map();
235        /// assert_eq!(scrobble_map.get("artist"), "Example Artist");
236        /// ```
237        pub fn as_map(&self) -> HashMap<String, String> {
238            let mut params = HashMap::new();
239            params.insert("track".to_string(), self.track.clone());
240            params.insert("artist".to_string(), self.artist.clone());
241            params.insert("album".to_string(), self.album.clone());
242
243            if let Some(timestamp) = self.timestamp {
244                params.insert("timestamp".to_string(), timestamp.to_string());
245            }
246
247            params
248        }
249
250        /// Returns the `Scrobble`'s artist name
251        pub fn artist(&self) -> &str {
252            &self.artist
253        }
254
255        /// Returns the `Scrobble`'s track name
256        pub fn track(&self) -> &str {
257            &self.track
258        }
259
260        /// Returns the `Scrobble`'s album name
261        pub fn album(&self) -> &str {
262            &self.album
263        }
264    
265    }
266
267    /// Converts from tuple of `&str`s in the form `(artist, track, album)`
268    /// 
269    /// Designed to make it easier to cooperate with other track info types.
270    impl From<&(&str, &str, &str)> for Scrobble {
271        fn from((artist, track, album): &(&str, &str, &str)) -> Self {
272            Scrobble::new(artist, track, album)
273        }
274    }
275
276    /// Converts from tuple of `String`s in the form `(artist, track, album)`
277    /// 
278    /// Designed to make it easier to cooperate with other track info types.
279    impl From<&(String, String, String)> for Scrobble {
280        fn from((artist, track, album): &(String, String, String)) -> Self {
281            Scrobble::new(artist, track, album)
282        }
283    }
284
285    /// Converts from vector of `&str` tuples, in the form `(artist, track, album)`.
286    /// 
287    /// Designed to make it easier to cooperate with other track info types.
288    impl From<Vec<(&str, &str, &str)>> for ScrobbleBatch {
289        fn from(collection: Vec<(&str, &str, &str)>) -> Self {
290            let scrobbles: Vec<Scrobble> = collection
291                                .iter()
292                                .map(Scrobble::from)
293                                .collect();
294
295            ScrobbleBatch::from(scrobbles)
296        }
297    }
298
299    /// Converts from vector of `String` tuples, in the form `(artist, track, album)`.
300    /// 
301    /// Designed to make it easier to cooperate with other track info types.
302    impl From<Vec<(String, String, String)>> for ScrobbleBatch {
303        fn from(collection: Vec<(String, String, String)>) -> Self {
304            let scrobbles: Vec<Scrobble> = collection
305                                .iter()
306                                .map(Scrobble::from)
307                                .collect();
308
309            ScrobbleBatch::from(scrobbles)
310        }
311    }
312
313    #[cfg(test)]
314    mod tests {
315        use super::*;
316
317        #[test]
318        fn make_scrobble() {
319            let mut scrobble = Scrobble::new(
320                "foo floyd and the fruit flies",
321                "old bananas",
322                "old bananas",
323            );
324            scrobble.with_timestamp(1337);
325            assert_eq!(scrobble.artist(), "foo floyd and the fruit flies");
326            assert_eq!(scrobble.track(), "old bananas");
327            assert_eq!(scrobble.album(), "old bananas");
328            assert_eq!(scrobble.timestamp, Some(1337));
329        }
330
331        #[test]
332        fn make_scrobble_check_map() {
333            let scrobble = Scrobble::new(
334                "foo floyd and the fruit flies",
335                "old bananas",
336                "old bananas",
337            );
338
339            let params = scrobble.as_map();
340            assert_eq!(params["artist"], "foo floyd and the fruit flies");
341            assert_eq!(params["track"], "old bananas");
342            assert_eq!(params["album"], "old bananas");
343        }
344    }
345}