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> { 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> { 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) -> String { artists .into_iter() .map(|a| a.name) .collect::>() .join("&") }