1macro_rules! impl_collection {
2 (
3 $(#[$meta:meta])+
4 struct $name:ident($item:ty);
5 ) => {
6 $(#[$meta])+
7 pub struct $name(::std::vec::Vec<$item>);
8
9 impl $name {
10 #[doc = concat!("Creates a new, empty ", stringify!($name))]
11 pub fn new() -> Self {
12 Self(::std::vec::Vec::new())
13 }
14
15 #[doc = concat!("Returns true if the ", stringify!($name), " contains no ", stringify!($item), "s")]
16 pub fn is_empty(&self) -> bool {
17 self.0.is_empty()
18 }
19
20 #[doc = concat!("Returns the number of ", stringify!($item), "s in the ", stringify!($name))]
21 pub fn len(&self) -> usize {
22 self.0.len()
23 }
24
25 #[doc = concat!("Returns an iterator over the ", stringify!($name))]
26 pub fn iter<'a>(&'a self) -> ::std::slice::Iter<'a, $item> {
27 self.into_iter()
28 }
29 }
30
31 impl ::std::iter::FromIterator<$item> for $name {
32 fn from_iter<I: ::std::iter::IntoIterator<Item=$item>>(iter: I) -> Self {
33 Self(::std::vec::Vec::from_iter(iter))
34 }
35 }
36
37 impl ::std::convert::From<Vec<$item>> for $name {
38 fn from(value: ::std::vec::Vec<$item>) -> Self {
39 Self(value)
40 }
41 }
42
43 impl ::std::iter::IntoIterator for $name {
44 type Item = $item;
45 type IntoIter = ::std::vec::IntoIter<$item>;
46
47 fn into_iter(self) -> Self::IntoIter {
48 self.0.into_iter()
49 }
50 }
51
52 impl<'a> ::std::iter::IntoIterator for &'a $name {
53 type Item = &'a $item;
54 type IntoIter = ::std::slice::Iter<'a, $item>;
55
56 fn into_iter(self) -> Self::IntoIter {
57 self.0.iter()
58 }
59 }
60
61 impl ::std::iter::Extend<$item> for $name {
62 fn extend<I: ::std::iter::IntoIterator<Item=$item>>(&mut self, iter: I) {
63 self.0.extend(iter)
64 }
65 }
66 };
67}
68
69pub mod responses {
70
71 use std::fmt;
72
73 use serde::Deserialize;
74 use serde_json as json;
75
76 #[derive(Deserialize, Debug)]
77 pub struct AuthResponse {
78 pub session: SessionResponse,
79 }
80
81 #[derive(Deserialize, Debug, Clone)]
89 pub struct SessionResponse {
90 pub key: String,
91 pub subscriber: i64,
92 pub name: String,
93 }
94
95 #[derive(Deserialize)]
96 pub struct NowPlayingResponseWrapper {
97 pub nowplaying: NowPlayingResponse,
98 }
99
100 #[derive(Deserialize, Debug)]
108 pub struct NowPlayingResponse {
109 pub artist: CorrectableString,
110 pub album: CorrectableString,
111 #[serde(rename = "albumArtist")]
112 pub album_artist: CorrectableString,
113 pub track: CorrectableString,
114 }
115
116 #[derive(Deserialize)]
117 pub struct ScrobbleResponseWrapper {
118 pub scrobbles: SingleScrobble,
119 }
120
121 #[derive(Deserialize)]
122 pub struct SingleScrobble {
123 pub scrobble: ScrobbleResponse,
124 }
125
126 #[derive(Deserialize, Debug)]
133 pub struct ScrobbleResponse {
134 pub artist: CorrectableString,
135 pub album: CorrectableString,
136 #[serde(rename = "albumArtist")]
137 pub album_artist: CorrectableString,
138 pub track: CorrectableString,
139 pub timestamp: String,
140 }
141
142 impl_collection! {
143 #[derive(Debug, Deserialize)]
144 struct ScrobbleList(ScrobbleResponse);
145 }
146
147 #[derive(Debug)]
154 pub struct BatchScrobbleResponse {
155 pub scrobbles: ScrobbleList,
156 }
157
158 #[derive(Deserialize, Debug)]
159 pub struct BatchScrobbleResponseWrapper {
160 pub scrobbles: BatchScrobbles,
161 }
162
163 #[derive(Deserialize, Debug)]
164 pub struct BatchScrobbles {
165 #[serde(deserialize_with = "BatchScrobbles::deserialize_response_scrobbles")]
166 #[serde(rename = "scrobble")]
167 pub scrobbles: ScrobbleList,
168 }
169
170 impl BatchScrobbles {
171 fn deserialize_response_scrobbles<'de, D>(de: D) -> Result<ScrobbleList, D::Error>
172 where
173 D: serde::Deserializer<'de>,
174 {
175 let deser_result: json::Value = serde::Deserialize::deserialize(de)?;
176 let scrobbles = match deser_result {
177 obj @ json::Value::Object(_) => {
178 let scrobble: ScrobbleResponse =
179 serde_json::from_value(obj).expect("Parsing scrobble failed");
180 ScrobbleList::from(vec![scrobble])
181 }
182 arr @ json::Value::Array(_) => {
183 let scrobbles: ScrobbleList =
184 serde_json::from_value(arr).expect("Parsing scrobble list failed");
185 scrobbles
186 }
187 _ => ScrobbleList::from(vec![]),
188 };
189 Ok(scrobbles)
190 }
191 }
192
193 #[derive(Deserialize, Debug)]
203 pub struct CorrectableString {
204 #[serde(deserialize_with = "CorrectableString::deserialize_corrected_field")]
205 pub corrected: bool,
206 #[serde(rename = "#text", default)]
207 pub text: String,
208 }
209
210 impl CorrectableString {
211 fn deserialize_corrected_field<'de, D>(de: D) -> Result<bool, D::Error>
212 where
213 D: serde::Deserializer<'de>,
214 {
215 let deser_result: json::Value = serde::Deserialize::deserialize(de)?;
216 match deser_result {
217 json::Value::String(ref s) if &*s == "1" => Ok(true),
218 json::Value::String(ref s) if &*s == "0" => Ok(false),
219 _ => Err(serde::de::Error::custom("Unexpected value")),
220 }
221 }
222 }
223
224 impl fmt::Display for CorrectableString {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(f, "{}", self.text)
227 }
228 }
229}
230
231pub mod metadata {
232
233 use std::collections::HashMap;
234
235 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
246 pub struct Scrobble {
247 artist: String,
248 track: String,
249 album: Option<String>,
250
251 timestamp: Option<u64>,
252 }
253
254 impl_collection! {
255 #[derive(Clone, Debug)]
257 struct ScrobbleBatch(Scrobble);
258 }
259
260 impl Scrobble {
261 pub fn new(artist: &str, track: &str, album: Option<&str>) -> Self {
272 Self {
273 artist: artist.to_owned(),
274 track: track.to_owned(),
275 album: album.map(ToOwned::to_owned),
276 timestamp: None,
277 }
278 }
279
280 pub fn with_timestamp(&mut self, timestamp: u64) -> &mut Self {
297 self.timestamp = Some(timestamp);
298 self
299 }
300
301 pub fn as_map(&self) -> HashMap<String, String> {
312 let mut params = HashMap::new();
313 params.insert("track".to_string(), self.track.clone());
314 params.insert("artist".to_string(), self.artist.clone());
315
316 if let Some(ref album) = self.album {
317 params.insert("album".to_string(), album.clone());
318 }
319
320 if let Some(timestamp) = self.timestamp {
321 params.insert("timestamp".to_string(), timestamp.to_string());
322 }
323
324 params
325 }
326
327 pub fn artist(&self) -> &str {
329 &self.artist
330 }
331
332 pub fn track(&self) -> &str {
334 &self.track
335 }
336
337 pub fn album(&self) -> Option<&str> {
339 self.album.as_deref()
340 }
341 }
342
343 impl From<&(&str, &str, &str)> for Scrobble {
347 fn from((artist, track, album): &(&str, &str, &str)) -> Self {
348 Scrobble::new(artist, track, Some(album))
349 }
350 }
351
352 impl From<&(String, String, String)> for Scrobble {
356 fn from((artist, track, album): &(String, String, String)) -> Self {
357 Scrobble::new(artist, track, Some(album))
358 }
359 }
360
361 impl From<Vec<(&str, &str, &str)>> for ScrobbleBatch {
365 fn from(collection: Vec<(&str, &str, &str)>) -> Self {
366 let scrobbles: Vec<Scrobble> = collection.iter().map(Scrobble::from).collect();
367
368 ScrobbleBatch::from(scrobbles)
369 }
370 }
371
372 impl From<Vec<(String, String, String)>> for ScrobbleBatch {
376 fn from(collection: Vec<(String, String, String)>) -> Self {
377 let scrobbles: Vec<Scrobble> = collection.iter().map(Scrobble::from).collect();
378
379 ScrobbleBatch::from(scrobbles)
380 }
381 }
382
383 #[cfg(test)]
384 mod tests {
385 use super::*;
386
387 #[test]
388 fn make_scrobble() {
389 let mut scrobble = Scrobble::new(
390 "foo floyd and the fruit flies",
391 "old bananas",
392 Some("old bananas"),
393 );
394 scrobble.with_timestamp(1337);
395 assert_eq!(scrobble.artist(), "foo floyd and the fruit flies");
396 assert_eq!(scrobble.track(), "old bananas");
397 assert_eq!(scrobble.album(), Some("old bananas"));
398 assert_eq!(scrobble.timestamp, Some(1337));
399 }
400
401 #[test]
402 fn make_scrobble_check_map() {
403 let scrobble = Scrobble::new(
404 "foo floyd and the fruit flies",
405 "old bananas",
406 Some("old bananas"),
407 );
408
409 let params = scrobble.as_map();
410 assert_eq!(params["artist"], "foo floyd and the fruit flies");
411 assert_eq!(params["track"], "old bananas");
412 assert_eq!(params["album"], "old bananas");
413 }
414 }
415}