use crate::client::LastFm;
use crate::error::ScrobblerError;
use crate::models::metadata::{Scrobble, ScrobbleBatch};
use crate::models::responses::{
BatchScrobbleResponse, NowPlayingResponse, ScrobbleResponse, SessionResponse,
};
use std::collections::HashMap;
use std::result;
use std::time::UNIX_EPOCH;
type Result<T> = result::Result<T, ScrobblerError>;
pub struct Scrobbler {
client: LastFm,
}
impl Scrobbler {
pub fn new(api_key: &str, api_secret: &str) -> Self {
let client = LastFm::new(api_key, api_secret);
Self { client }
}
pub fn authenticate_with_password(
&mut self,
username: &str,
password: &str,
) -> Result<SessionResponse> {
self.client.set_user_credentials(username, password);
Ok(self.client.authenticate_with_password()?)
}
pub fn authenticate_with_token(&mut self, token: &str) -> Result<SessionResponse> {
self.client.set_user_token(token);
Ok(self.client.authenticate_with_token()?)
}
pub fn authenticate_with_session_key(&mut self, session_key: &str) {
self.client.authenticate_with_session_key(session_key)
}
pub fn now_playing(&self, scrobble: &Scrobble) -> Result<NowPlayingResponse> {
let params = scrobble.as_map();
Ok(self.client.send_now_playing(¶ms)?)
}
pub fn scrobble(&self, scrobble: &Scrobble) -> Result<ScrobbleResponse> {
let mut params = scrobble.as_map();
let current_time = UNIX_EPOCH.elapsed()?;
params
.entry("timestamp".to_string())
.or_insert_with(|| format!("{}", current_time.as_secs()));
Ok(self.client.send_scrobble(¶ms)?)
}
pub fn scrobble_batch(&self, batch: &ScrobbleBatch) -> Result<BatchScrobbleResponse> {
let mut params = HashMap::new();
let batch_count = batch.len();
if batch_count > 50 {
return Err(ScrobblerError::new(
"Scrobble batch too large (must be 50 or fewer scrobbles)".to_owned(),
));
} else if batch_count == 0 {
return Err(ScrobblerError::new("Scrobble batch is empty".to_owned()));
}
for (i, scrobble) in batch.iter().enumerate() {
let mut scrobble_params = scrobble.as_map();
let current_time = UNIX_EPOCH.elapsed()?;
scrobble_params
.entry("timestamp".to_string())
.or_insert_with(|| format!("{}", current_time.as_secs()));
for (key, val) in &scrobble_params {
params.insert(format!("{}[{}]", key, i), val.clone());
}
}
Ok(self.client.send_batch_scrobbles(¶ms)?)
}
pub fn session_key(&self) -> Option<&str> {
self.client.session_key()
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockito::mock;
use std::error::Error;
#[test]
fn make_scrobbler_pass_auth() {
let _m = mock("POST", mockito::Matcher::Any).create();
let mut scrobbler = Scrobbler::new("api_key", "api_secret");
let resp = scrobbler.authenticate_with_password("user", "pass");
assert!(resp.is_err());
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"session": {
"key": "key",
"subscriber": 1337,
"name": "foo floyd"
}
}
"#,
)
.create();
let resp = scrobbler.authenticate_with_password("user", "pass");
assert!(resp.is_ok());
}
#[test]
fn make_scrobbler_token_auth() {
let _m = mock("POST", mockito::Matcher::Any).create();
let mut scrobbler = Scrobbler::new("api_key", "api_secret");
let resp = scrobbler.authenticate_with_token("some_token");
assert!(resp.is_err());
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"session": {
"key": "key",
"subscriber": 1337,
"name": "foo floyd"
}
}
"#,
)
.create();
let resp = scrobbler.authenticate_with_token("some_token");
assert!(resp.is_ok());
}
#[test]
fn check_scrobbler_error() {
let err = ScrobblerError::new("test_error".into());
let fmt = format!("{}", err);
assert_eq!("test_error", fmt);
let desc = err.to_string();
assert_eq!("test_error", &desc);
assert!(err.source().is_none());
}
#[test]
fn check_scrobbler_now_playing() {
let mut scrobbler = Scrobbler::new("api_key", "api_secret");
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"session": {
"key": "key",
"subscriber": 1337,
"name": "foo floyd"
}
}
"#,
)
.create();
let resp = scrobbler.authenticate_with_token("some_token");
assert!(resp.is_ok());
let mut scrobble = crate::models::metadata::Scrobble::new(
"foo floyd and the fruit flies",
"old bananas",
"old bananas",
);
scrobble.with_timestamp(1337);
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"nowplaying": {
"artist": [ "0", "foo floyd and the fruit flies" ],
"album": [ "1", "old bananas" ],
"albumArtist": [ "0", "foo floyd"],
"track": [ "1", "old bananas"],
"timestamp": "2019-10-04 13:23:40"
}
}
"#,
)
.create();
let resp = scrobbler.now_playing(&scrobble);
assert!(resp.is_ok());
}
#[test]
fn check_scrobbler_scrobble() {
let mut scrobbler = Scrobbler::new("api_key", "api_secret");
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"session": {
"key": "key",
"subscriber": 1337,
"name": "foo floyd"
}
}
"#,
)
.create();
let resp = scrobbler.authenticate_with_token("some_token");
assert!(resp.is_ok());
let mut scrobble = crate::models::metadata::Scrobble::new(
"foo floyd and the fruit flies",
"old bananas",
"old bananas",
);
scrobble.with_timestamp(1337);
let _m = mock("POST", mockito::Matcher::Any)
.with_body(
r#"
{
"scrobbles": [{
"artist": [ "0", "foo floyd and the fruit flies" ],
"album": [ "1", "old bananas" ],
"albumArtist": [ "0", "foo floyd"],
"track": [ "1", "old bananas"],
"timestamp": "2019-10-04 13:23:40"
}]
}
"#,
)
.create();
let resp = scrobbler.scrobble(&scrobble);
assert!(resp.is_ok());
}
}