Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

general improvements #2

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions src/scrobbler/last_fm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ struct LastfmSessionData {
}

impl LastfmScrobbler {
pub fn new(username: String, password: String, api_key: String, api_secret: String) -> Self {
/// create a new LastfmScrobbler instance.
pub fn new(username: String, password: String, api_key: String, api_secret: String) -> Result<Self, String> {
let client = Client::new();

let session_key = match client
let session_key = client
.post(API_BASE_URL)
.header("content-length", "0")
.query(
Expand All @@ -43,21 +44,29 @@ impl LastfmScrobbler {
.sign(&api_secret),
)
.send()
.unwrap()
.map_err(|err| format!("Failed to authenticate with Last.fm: {}", err))?
.json::<LastfmSession>()
{
Ok(session) => {
println!("{} Successfully authenticated with username {}.", "[Last.fm]".bright_green(), session.session.name.bright_blue());
.map_err(|err| format!("Failed to parse Last.fm session: {}", err))
.map(|session| {
println!(
"{} Successfully authenticated with username {}.",
"[Last.fm]".bright_green(),
session.session.name.bright_blue()
);
session.session.key
},
Err(_) => panic!("{} Invalid credentials provided.", "[Last.fm]".bright_red()),
};
})?;

Self { client, api_key, api_secret, session_key }
Ok(Self { client, api_key, api_secret, session_key })
}

/// scrobble a track to Last.fm.
pub fn scrobble(&self, title: &str, artist: &str, total_length: u32) -> Result<(), String> {
match self
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|err| format!("Failed to get timestamp: {}", err))?
.as_secs();

let response = self
.client
.post(API_BASE_URL)
.header("content-length", "0")
Expand All @@ -68,17 +77,16 @@ impl LastfmScrobbler {
.insert("duration[0]", total_length)
.insert("method", "track.scrobble")
.insert("sk", &self.session_key)
.insert("timestamp[0]", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs())
.insert("timestamp[0]", timestamp)
.insert("track[0]", title)
.sign(&self.api_secret),
)
.send()
{
Ok(response) => match response.status() {
StatusCode::OK => Ok(()),
status_code => Err(format!("Received status code {status_code}.")),
},
Err(error) => Err(error.to_string()),
.map_err(|err| format!("Failed to send scrobble request: {}", err))?;

match response.status() {
StatusCode::OK => Ok(()),
status_code => Err(format!("Received status code {}.", status_code)),
}
}
}
Expand All @@ -88,20 +96,26 @@ struct LastfmQuery {
}

impl LastfmQuery {
/// create a new LastfmQuery instance.
pub fn new() -> Self {
Self { query: BTreeMap::new() }
}

/// insert a key-value pair into the query parameters.
pub fn insert<T: ToString, U: ToString>(mut self, key: T, value: U) -> Self {
self.query.insert(key.to_string(), value.to_string());
self
}

/// sign the query parameters using the API secret.
pub fn sign<T: ToString>(self, api_secret: T) -> BTreeMap<String, String> {
let api_sig = format!(
"{:x}",
compute(self.query.iter().map(|(key, value)| format!("{key}{value}")).collect::<String>() + &api_secret.to_string()),
);
let api_sig = format!("{:x}", compute(
self.query
.iter()
.map(|(key, value)| format!("{}{}", key, value))
.collect::<String>()
+ &api_secret.to_string(),
));

self.insert("api_sig", api_sig).insert("format", "json").query
}
Expand Down
84 changes: 46 additions & 38 deletions src/scrobbler/listenbrainz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,61 @@ struct ListenBrainzToken {
}

impl ListenBrainzScrobbler {
pub fn new(user_token: String) -> Self {
pub fn new(user_token: String) -> Result<Self, String> {
let client = Client::new();

match client
.get(format!("{API_BASE_URL}/validate-token"))
.header("authorization", format!("Token {user_token}"))
let response = client
.get(format!("{}/validate-token", API_BASE_URL))
.header("authorization", format!("Token {}", user_token))
.send()
.unwrap()
.json::<ListenBrainzToken>()
{
Ok(token) => {
println!("{} Successfully authenticated with username {}.", "[ListenBrainz]".bright_green(), token.user_name.bright_blue())
},
Err(_) => panic!("{} Invalid user token provided.", "[ListenBrainz]".bright_red()),
};
.map_err(|error| format!("Error validating user token: {}", error))?;

Self { client, user_token }
if response.status().is_success() {
let token = response
.json::<ListenBrainzToken>()
.map_err(|error| format!("Error parsing token response: {}", error))?;
println!(
"{} Successfully authenticated with username {}.",
"[ListenBrainz]".bright_green(),
token.user_name.bright_blue()
);
Ok(Self { client, user_token })
} else {
Err(format!("Invalid user token provided."))
}
}

pub fn scrobble(&self, title: &str, artist: &str, total_length: u32) -> Result<(), String> {
match self
let listen_data = json!({
"listen_type": "single",
"payload": [{
"listened_at": SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| format!("Error getting current time: {}", error))?
.as_secs(),
"track_metadata": {
"artist_name": artist,
"track_name": title,
"additional_info": {
"media_player": "osu!",
"submission_client": "osu-scrobbler (github.com/flazepe/osu-scrobbler)",
"submission_client_version": env!("CARGO_PKG_VERSION"),
"duration_ms": total_length * 1000,
},
},
}],
});

let response = self
.client
.post(format!("{API_BASE_URL}/submit-listens"))
.post(format!("{}/submit-listens", API_BASE_URL))
.header("authorization", format!("Token {}", self.user_token))
.json(&json!({
"listen_type": "single",
"payload": [{
"listened_at": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
"track_metadata": {
"artist_name": artist,
"track_name": title,
"additional_info": {
"media_player": "osu!",
"submission_client": "osu-scrobbler (github.com/flazepe/osu-scrobbler)",
"submission_client_version": env!("CARGO_PKG_VERSION"),
"duration_ms": total_length * 1000,
},
},
}],
}))
.json(&listen_data)
.send()
{
Ok(response) => match response.status() {
StatusCode::OK => Ok(()),
status_code => Err(format!("Received status code {status_code}.")),
},
Err(error) => Err(error.to_string()),
.map_err(|error| format!("Error sending scrobble request: {}", error))?;

match response.status() {
StatusCode::OK => Ok(()),
status_code => Err(format!("Received status code {}.", status_code)),
}
}
}
51 changes: 28 additions & 23 deletions src/scrobbler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ impl Scrobbler {
}

fn poll(&mut self) {
let Some(score) = self.get_recent_score() else { return; };

if self.recent_score.as_ref().map_or(true, |recent_score| recent_score.ended_at != score.ended_at) {
self.scrobble(score);
if let Some(score) = self.get_recent_score() {
if self.recent_score.as_ref().map_or(true, |recent_score| recent_score.ended_at != score.ended_at) {
self.scrobble(score);
}
}
}

Expand All @@ -76,30 +76,32 @@ impl Scrobbler {
return;
}

let title = match self.config.use_original_metadata {
true => &score.beatmapset.title_unicode,
false => &score.beatmapset.title,
let title = if self.config.use_original_metadata {
&score.beatmapset.title_unicode
} else {
&score.beatmapset.title
};

let artist = match self.config.use_original_metadata {
true => &score.beatmapset.artist_unicode,
false => &score.beatmapset.artist,
let artist = if self.config.use_original_metadata {
&score.beatmapset.artist_unicode
} else {
&score.beatmapset.artist
};

println!("{} New score found: {}", "[Scrobbler]".bright_green(), format!("{artist} - {title}").bright_blue());

if let Some(last_fm) = self.last_fm.as_ref() {
match last_fm.scrobble(title, artist, score.beatmap.total_length) {
Ok(_) => println!("\t{} Successfully scrobbled score.", "[Last.fm]".bright_green()),
Err(error) => println!("\t{} {error}", "[Last.fm]".bright_red()),
};
Err(error) => println!("\t{} Error while scrobbling score: {}", "[Last.fm]".bright_red(), error),
}
}

if let Some(listenbrainz) = self.listenbrainz.as_ref() {
match listenbrainz.scrobble(title, artist, score.beatmap.total_length) {
Ok(_) => println!("\t{} Successfully scrobbled score.", "[ListenBrainz]".bright_green()),
Err(error) => println!("\t{} {error}", "[ListenBrainz]".bright_red()),
};
Err(error) => println!("\t{} Error while scrobbling score: {}", "[ListenBrainz]".bright_red(), error),
}
}

self.recent_score = Some(score);
Expand All @@ -115,9 +117,9 @@ impl Scrobbler {
let response = match request.send() {
Ok(response) => response,
Err(error) => {
println!("{} Could not get user's recent score: {error}", "[Scrobbler]".bright_red());
println!("{} Error while getting user's recent score: {}", "[Scrobbler]".bright_red(), error);
return None;
},
}
};

let status_code = response.status();
Expand All @@ -126,17 +128,20 @@ impl Scrobbler {
match status_code {
StatusCode::NOT_FOUND => panic!("{} Invalid osu! user ID given.", "[Scrobbler]".bright_red()),
_ => {
println!("{} Could not get user's recent score: Received status code {status_code}.", "[Scrobbler]".bright_red());
println!("{} Error while getting user's recent score: Received status code {}.", "[Scrobbler]".bright_red(), status_code);
return None;
},
}
}
}

let Ok(mut scores) = response.json::<Vec<Score>>() else { return None; };
let scores = match response.json::<Vec<Score>>() {
Ok(scores) => scores,
Err(error) => {
println!("{} Error while parsing scores: {}", "[Scrobbler]".bright_red(), error);
return None;
}
};

match scores.is_empty() {
true => None,
false => Some(scores.remove(0)),
}
scores.into_iter().next()
}
}