You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2b-rs/src/providers/music/mod.rs

125 lines
4.0 KiB
Rust

use crate::providers::music::queue::{Song, SongSource};
use crate::utils::context_data::StoreData;
use crate::utils::error::BotResult;
use aspotify::{ArtistSimplified, Track};
use bot_database::Database;
use regex::Regex;
use responses::VideoInformation;
use youtube_dl::search_video_information;
pub mod inspirobot;
pub mod lyrics;
pub mod player;
pub mod player_events;
pub mod queue;
pub mod responses;
pub mod spotify;
pub mod youtube_dl;
/// Searches for a youtube video for the specified song
pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult<Option<VideoInformation>> {
let artist = song.author().clone();
let title = song.title().clone();
let match_query = format!("{} - {}", artist, title);
let queries = vec![
format! {"{} - {} topic", artist, title},
format!("{} - {} lyrics", artist, title),
format!("{} - {} audio only", artist, title),
format!("{} by {}", title, artist),
match_query.clone(),
];
let mut last_result = None;
for query in queries {
let result = search_video_information(query).await?;
if let Some(video) = result {
if trigram::similarity(&video.title, &match_query) >= 0.4
|| (trigram::similarity(&video.title, &title) >= 0.3
&& trigram::similarity(&video.uploader, &artist) >= 0.3)
{
return Ok(Some(video));
}
tracing::debug!("Video title is not similar enough to song name.");
last_result = Some(video);
}
}
tracing::debug!("Returning last result");
Ok(last_result)
}
/// Adds a youtube song to the database of songs
pub async fn add_youtube_song_to_database(
store: &StoreData,
database: &Database,
song: &mut Song,
) -> BotResult<()> {
let track = match song.source() {
SongSource::Spotify(track) => track.clone(),
SongSource::YouTube(_) => match search_for_song_variations(store, song).await {
Ok(Some(track)) => track,
Err(e) => {
tracing::error!("Failed to search for song on spotify {:?}", e);
return Ok(());
}
_ => return Ok(()),
},
};
tracing::debug!("Song found on spotify. Inserting metadata");
let artists = artists_to_string(track.artists);
let url = song.url().await.unwrap();
if let Some(id) = track.id {
database
.add_song(id, artists, track.name, track.album.name, url)
.await?;
}
Ok(())
}
/// Searches for multiple queries on spotify
async fn search_for_song_variations(
store: &StoreData,
song: &mut Song,
) -> BotResult<Option<Track>> {
static COMMON_AFFIXES: &str =
r"feat\.(\s\w+)|official(\svideo)?|remastered|revisited|(with\s)?lyrics";
lazy_static::lazy_static! {
static ref COMMON_ADDITIONS: Regex = Regex::new(format!(r"(?i)\[.*\]|#\w+|\(?[^\w\s]*\s?({})[^\w\s]*\s?\)?", COMMON_AFFIXES).as_str()).unwrap();
}
let mut query = COMMON_ADDITIONS.replace_all(song.title(), " ").to_string();
query = query.replace(|c| c != ' ' && !char::is_alphanumeric(c), "");
tracing::debug!("Searching for youtube song");
if let Some(track) = store.spotify_api.search_for_song(&query).await? {
let similarity = trigram::similarity(
&format!(
"{} {}",
artists_to_string(track.artists.clone()),
track.name
),
&query,
);
if similarity > 0.3 {
tracing::debug!("Result is similar enough ({}). Returning track", similarity);
return Ok(Some(track));
}
tracing::debug!("Result is not similar enough");
}
tracing::debug!("No result found");
Ok(None)
}
/// Creates a string from a vector of artists
pub fn artists_to_string(artists: Vec<ArtistSimplified>) -> String {
artists
.into_iter()
.map(|a| a.name)
.collect::<Vec<String>>()
.join("&")
}