use failure::Error; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use regex::Regex; use std::io::Read; use std::time::Duration; #[derive(Serialize, Deserialize, Debug)] struct MBArtistQueryResult { created: String, count: i32, offset: i32, artists: Option>, } #[derive(Serialize, Deserialize, Debug)] struct Tag { count: i32, // negative doesn't make sense, but it sometimes occurs name: String, } #[derive(Serialize, Deserialize, Debug)] struct MBArtist { id: String, score: i32, name: String, #[serde(rename = "sort-name")] sort_name: String, tags: Option>, } const VERSION: &'static str = env!("CARGO_PKG_VERSION"); /// Check genre; returns true if it's OK pub fn check_genre(config: &crate::Config, genre: &String) -> bool { if config.whitelist.tag.contains(genre) { info!("+ Whitelisted tag \"{}\"", genre); return true; } if config.blacklist.tag.contains(genre) { info!("- Blacklisted tag \"{}\"", genre); return false; } else { for substr in config.blacklist.tag_partial.iter() { let re = Regex::new(&format!(r"\b{}\b", regex::escape(substr))); let matches = match re { Ok(re) => re.is_match(genre), Err(_) => genre.contains(substr), }; if matches { info!( "- Blacklisted tag \"{}\" - due to substring \"{}\"", genre, substr ); return false; } } } true } pub fn check_artist(config: &crate::Config, artist: &str) -> Result { let query = utf8_percent_encode(&format!("\"{}\"", artist), DEFAULT_ENCODE_SET).to_string(); let url = format!( "https://musicbrainz.org/ws/2/artist?query={}&fmt=json&inc=tags", query ); info!( "Querying MusicBrainz for artist \"{}\", query URL: {}", artist, url ); let mut resp = String::new(); let ua = format!( "Bad Song Skipper / {v} ( https://git.ondrovo.com/MightyPork/rapblock )", v = VERSION ); debug!("Using UA: {}", ua); reqwest::Client::builder() .timeout(Duration::from_millis(config.api_timeout_ms)) .build()? .get(&url) .header(reqwest::header::USER_AGENT, ua.as_str()) .header(reqwest::header::ACCEPT, "text/json") .send()? .read_to_string(&mut resp)?; debug!("Resp: {}", resp); let result: MBArtistQueryResult = serde_json::from_str(&resp)?; info!("Response OK"); debug!("{:#?}", result); if result.count == 0 { // not found, let's hope it's OK warn!("No results!"); return Err(failure::err_msg("Artist not found")); } else { info!( "Got {} results, checking where score >= {}", result.count, config.artist_min_score ); let artists = result.artists.as_ref().unwrap(); let mut confidence = false; let mut passed = true; 'artists: for (an, a) in artists.iter().enumerate() { if a.score >= config.artist_min_score { if a.tags.is_some() { let tags = a.tags.as_ref().unwrap(); let as_vec: Vec<&String> = tags.iter().map(|t| &t.name).collect(); info!("Artist #{} - \"{}\" has tags: {:?}", an + 1, a.name, as_vec); 'tags: for tag in as_vec { if !check_genre(config, tag) { confidence = true; passed = false; break 'artists; } } info!("All tags OK"); confidence = true; } else { warn!( "Artist #{} - \"{}\" has no tags, can't determine genre", an + 1, a.name ); } } } if confidence { return Ok(passed); } else { return Err(failure::err_msg("Artist found, but has no tags")); } } }