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

148 lines
4.2 KiB

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"));
}
}
}