use std::fmt::{self, Write};
use anyhow::{anyhow, Context, Result};
use listenbrainz::ListenBrainz;
use rustfm_scrobble_proxy::Scrobbler;
mod lastfm;
use crate::config::{Config, ListenBrainzConfig};
use crate::track::Track;
pub enum Service {
LastFM(Scrobbler),
ListenBrainz {
client: ListenBrainz,
is_default: bool,
},
}
impl Service {
fn lastfm(config: &Config) -> Result<Option<Self>> {
match (&config.lastfm_key, &config.lastfm_secret) {
(Some(key), Some(secret)) => {
let mut scrobbler = Scrobbler::new(key, secret);
lastfm::authenticate(&mut scrobbler)
.context("Failed to authenticate with Last.fm")?;
Ok(Some(Self::LastFM(scrobbler)))
}
(None, None) => Ok(None),
_ => Err(anyhow!("Last.fm API key or API secret are missing")),
}
}
fn listenbrainz(lb: &ListenBrainzConfig) -> Result<Self> {
let mut client = match lb.url {
Some(ref url) => ListenBrainz::new_with_url(url),
None => ListenBrainz::new(),
};
client.authenticate(&lb.token).with_context(|| {
let mut err = "Failed to authenticate with ListenBrainz".to_owned();
if let Some(ref url) = lb.url {
write!(err, " ({url})").unwrap();
}
err
})?;
Ok(Self::ListenBrainz {
is_default: lb.url.is_none(),
client,
})
}
pub fn initialize_all(config: &Config) -> Vec<Self> {
let mut services = Vec::new();
match Self::lastfm(config) {
Ok(Some(lastfm)) => {
println!("Authenticated with {} successfully!", lastfm);
services.push(lastfm);
}
Err(err) => eprintln!("{:?}", err),
_ => {}
}
for lb in config.listenbrainz.iter().flatten() {
match Self::listenbrainz(lb) {
Ok(service) => {
println!("Authenticated with {} successfully!", service);
services.push(service);
}
Err(err) => eprintln!("{:?}", err),
}
}
if services.is_empty() {
eprintln!("Warning: no scrobbling services defined");
}
services
}
pub fn now_playing(&self, track: &Track) -> Result<()> {
match self {
Self::LastFM(scrobbler) => {
scrobbler
.now_playing(&track.into())
.with_context(|| format!("Failed to update status on {}", self))?;
}
Self::ListenBrainz { client, .. } => {
client
.playing_now(track.artist(), track.title(), track.album())
.with_context(|| format!("Failed to update status on {}", self))?;
}
}
Ok(())
}
pub fn submit(&self, track: &Track) -> Result<()> {
match self {
Self::LastFM(scrobbler) => {
scrobbler
.scrobble(&track.into())
.with_context(|| format!("Failed to submit track to {}", self))?;
}
Self::ListenBrainz { client, .. } => {
client
.listen(track.artist(), track.title(), track.album())
.with_context(|| format!("Failed to submit track to {}", self))?;
}
}
Ok(())
}
}
impl fmt::Display for Service {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LastFM(_) => write!(f, "Last.fm"),
Self::ListenBrainz { client, is_default } => {
write!(f, "ListenBrainz")?;
if !is_default {
write!(f, " ({})", client.api_url())?;
}
Ok(())
}
}
}
}