From 9586c126d0d5a36679ec84be0673537734e8b5a2 Mon Sep 17 00:00:00 2001 From: MrDulfin Date: Wed, 19 Mar 2025 11:03:05 -0400 Subject: [PATCH] Added last.fm scrobbling support andsupport for authentication via username & password --- dmp-core/src/music_controller/connections.rs | 118 +++++++++++++++++-- 1 file changed, 106 insertions(+), 12 deletions(-) diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs index af18d4e..140ec16 100644 --- a/dmp-core/src/music_controller/connections.rs +++ b/dmp-core/src/music_controller/connections.rs @@ -14,7 +14,7 @@ use discord_presence::Client; use listenbrainz::ListenBrainz; use parking_lot::RwLock; use prismriver::State as PrismState; -use rustfm_scrobble::Scrobbler; +use rustfm_scrobble::{Scrobble, Scrobbler}; use serde::Deserialize; use crate::{ @@ -42,12 +42,21 @@ pub(super) enum TryConnectionType { LastFM { api_key: String, api_secret: String, - session: Option + auth: LastFMAuth }, ListenBrainz(String), Custom(String) } +#[derive(Debug, Clone)] +pub(super) enum LastFMAuth { + Session(Option), + UserPass { + username: String, + password: String + } +} + pub(super) struct ControllerConnections { pub notifications_rx: Sender, pub notifications_tx: Receiver, @@ -121,19 +130,29 @@ pub(super) fn handle_connections( }) .unwrap(); } - TryConnectionType::LastFM { api_key, api_secret, session } => { + TryConnectionType::LastFM { api_key, api_secret, auth } => { let (config, notifications_rx) = (config.clone(), notifications_rx.clone()); + let (last_song_tx, last_abt_fin_tx, last_eos_tx) = (last_song_tx.clone(), last_abt_fin_tx.clone(), last_eos_tx.clone()); std::thread::Builder::new() .name("last.fm Handler".to_string()) .spawn(move || { - let scrobbler = if let Some(session) = session { - let mut scrobbler = Scrobbler::new(&api_key, &api_secret); - scrobbler.authenticate_with_session_key(&session); - Ok(scrobbler) - } else { - last_fm_auth(config, notifications_rx, &api_key, &api_secret) - }; - // TODO: Add scrobbling support + let scrobbler = match auth { + LastFMAuth::Session(key) => { + if let Some(session) = key { + let mut scrobbler = Scrobbler::new(&api_key, &api_secret); + scrobbler.authenticate_with_session_key(&session); + Ok(scrobbler) + } else { + last_fm_auth(config, notifications_rx, &api_key, &api_secret) + }.unwrap() + }, + LastFMAuth::UserPass { username, password } => { + let mut scrobbler = Scrobbler::new(&api_key, &api_secret); + scrobbler.authenticate_with_password(&username, &password).unwrap(); + scrobbler + } + }; + last_fm_scrobble(scrobbler, last_song_tx, last_abt_fin_tx, last_eos_tx); }) .unwrap(); } @@ -293,7 +312,7 @@ fn listenbrainz_scrobble(token: &str, song_tx: Receiver, abt_fn_tx: Receiv } LB_ACTIVE.store(false, Ordering::Relaxed); } -pub(super) fn last_fm_auth( +fn last_fm_auth( config: Arc>, notifications_rx: Sender, api_key: &str, @@ -331,6 +350,81 @@ pub(super) fn last_fm_auth( Ok(scrobbler) } +fn last_fm_scrobble(scrobbler: Scrobbler, song_tx: Receiver, abt_fn_tx: Receiver<()>, eos_tx: Receiver<()>) { + // TODO: Add support for scrobble storage for later + + let mut song: Option = None; + let mut last_song: Option = None; + LAST_FM_ACTIVE.store(true, Ordering::Relaxed); + println!("ListenBrainz connected"); + + while true { + let song = &mut song; + let last_song = &mut last_song; + + let scrobbler = &scrobbler; + + select! { + recv(song_tx) -> res => { + if let Ok(_song) = res { + let title = if let Some(tag) = _song.get_tag(&Tag::Title) { + tag.as_str() + } else { + continue + }; + let artist = if let Some(tag) = _song.get_tag(&Tag::Artist) { + tag.as_str() + } else { + "" + }; + let album = if let Some(tag) = _song.get_tag(&Tag::Album) { + tag.as_str() + } else { + "" + }; + + match scrobbler.now_playing(&Scrobble::new(artist, title, album)) { + Ok(_) => println!("Song Scrobbling = {artist} - {title} - {album}"), + Err(e) => println!("Error at last.fm now playing:\n{e}") + }; + + *song = Some(_song); + } + }, + recv(abt_fn_tx) -> _ => { + *last_song = song.take(); + println!("song = {:?}", last_song.as_ref().map(|s| s.get_tag(&Tag::Title).map_or("No Title", |t| t.as_str()))); + }, + recv(eos_tx) -> _ => { + if let Some(song) = last_song { + let title = if let Some(tag) = song.get_tag(&Tag::Title) { + tag.as_str() + } else { + continue + }; + let artist = if let Some(tag) = song.get_tag(&Tag::Artist) { + tag.as_str() + } else { + "" + }; + let album = if let Some(tag) = song.get_tag(&Tag::Album) { + tag.as_str() + } else { + "" + }; + + match scrobbler.scrobble(&Scrobble::new(artist, title, album)) { + Ok(_) => println!("Song Scrobbled"), + Err(e) => println!("Error at last.fm scrobbler:\n{e:?}") + } + } + } + } + } + LAST_FM_ACTIVE.store(false, Ordering::Relaxed); +} + + #[derive(Deserialize)] pub struct Token {