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}