Rust daemon that skips rap songs in (not only) Spotify radios via MPRIS and MusicBrainz
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rapblock/src/brainz.rs

128 lines
4.3 KiB

5 years ago
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use failure::Error;
use std::io::Read;
use std::time::Duration;
use regex::Regex;
5 years ago
#[derive(Serialize, Deserialize, Debug)]
struct MBArtistQueryResult {
created : String,
count: i32,
offset: i32,
artists: Option<Vec<MBArtist>>,
}
#[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<Vec<Tag>>
}
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
5 years ago
pub fn check_artist(config : &crate::Config, artist : &str) -> Result<bool, Error> {
5 years ago
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);
5 years ago
reqwest::Client::builder()
.timeout(Duration::from_millis(config.api_timeout_ms))
5 years ago
.build()?
.get(&url)
.header(reqwest::header::USER_AGENT, ua.as_str())
5 years ago
.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);
5 years ago
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 config.whitelist.tag.contains(tag) {
info!("+ Whitelisted tag \"{}\"", tag);
continue 'tags;
}
let mut blacklisted = false;
if config.blacklist.tag.contains(&tag) {
info!("- Blacklisted tag \"{}\"", tag);
blacklisted = true;
} else {
'blacklist:
for t in config.blacklist.tag_partial.iter() {
let re = Regex::new(&format!(r"\b{}\b", regex::escape(t)));
let matches = match re {
Ok(re) => re.is_match(tag),
Err(_) => tag.contains(t)
};
if matches {
info!("- Blacklisted tag \"{}\" - due to substring \"{}\"", tag, t);
blacklisted = true;
break 'blacklist;
}
}
}
if blacklisted {
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);
5 years ago
}
}
}
if confidence {
return Ok(passed);
5 years ago
} else {
return Err(failure::err_msg("Artist found, but has no tags"));
}
}
}