Hardened the lookups

This commit is contained in:
Connor Johnstone
2026-03-02 23:54:28 -05:00
parent 34977ea54b
commit 09d562fabb
7 changed files with 222 additions and 66 deletions

View File

@@ -70,20 +70,59 @@ impl LastfmClient {
Self { api_key }
}
/// Fetch a URL and return the body. Returns `None` if Last.fm returns an API error.
fn fetch_or_none(&self, url: &str) -> Result<Option<String>, Box<dyn std::error::Error>> {
let body: String = ureq::get(url).call()?.body_mut().read_to_string()?;
if serde_json::from_str::<ApiError>(&body).is_ok() {
return Ok(None);
}
Ok(Some(body))
}
/// Try fetching by MBID first, fall back to artist name.
fn fetch_with_fallback(
&self,
method: &str,
artist_mbid: &str,
artist_name: Option<&str>,
extra_params: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
let url = format!(
"{}?method={}&mbid={}&api_key={}{}&format=json",
BASE_URL, method, artist_mbid, self.api_key, extra_params
);
if let Some(body) = self.fetch_or_none(&url)? {
return Ok(Some(body));
}
// Fall back to artist name
if let Some(name) = artist_name {
let encoded = urlencoding::encode(name);
let url = format!(
"{}?method={}&artist={}&api_key={}{}&format=json",
BASE_URL, method, encoded, self.api_key, extra_params
);
if let Some(body) = self.fetch_or_none(&url)? {
return Ok(Some(body));
}
}
Ok(None)
}
pub fn get_similar_artists(
&self,
artist_mbid: &str,
artist_name: Option<&str>,
) -> Result<Vec<SimilarArtist>, Box<dyn std::error::Error>> {
let url = format!(
"{}?method=artist.getSimilar&mbid={}&api_key={}&limit=500&format=json",
BASE_URL, artist_mbid, self.api_key
);
let body: String = ureq::get(&url).call()?.body_mut().read_to_string()?;
if let Ok(err) = serde_json::from_str::<ApiError>(&body) {
eprintln!(" Last.fm: {}", err.message);
let Some(body) = self.fetch_with_fallback(
"artist.getSimilar",
artist_mbid,
artist_name,
"&limit=500",
)? else {
return Ok(Vec::new());
}
};
let resp: SimilarArtistsResponse = serde_json::from_str(&body)?;
Ok(resp
@@ -104,17 +143,16 @@ impl LastfmClient {
pub fn get_top_tracks(
&self,
artist_mbid: &str,
artist_name: Option<&str>,
) -> Result<Vec<TopTrack>, Box<dyn std::error::Error>> {
let url = format!(
"{}?method=artist.getTopTracks&mbid={}&api_key={}&limit=1000&format=json",
BASE_URL, artist_mbid, self.api_key
);
let body: String = ureq::get(&url).call()?.body_mut().read_to_string()?;
if let Ok(err) = serde_json::from_str::<ApiError>(&body) {
eprintln!(" Last.fm: {}", err.message);
let Some(body) = self.fetch_with_fallback(
"artist.getTopTracks",
artist_mbid,
artist_name,
"&limit=1000",
)? else {
return Ok(Vec::new());
}
};
let resp: TopTracksResponse = serde_json::from_str(&body)?;
Ok(resp