|
|
|
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<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");
|
|
|
|
|
|
|
|
/// 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<bool, Error> {
|
|
|
|
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"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|